Scheduling compute instances with Cloud Scheduler

This tutorial shows how to use Cloud Scheduler and Cloud Functions to automatically start and stop Compute Engine instances on a regular schedule using resource labels.

Objectives

  • Write and deploy a set of functions with Cloud Functions that start and stop Compute Engine instances.
  • Create a set of jobs with Cloud Scheduler that schedule instances with a dev resource label to run 09:00-17:00, Monday-Friday to match typical business hours.

Costs

This tutorial uses the following billable components of Google Cloud:

  • Cloud Scheduler
  • Cloud Functions
  • Pub/Sub
  • Compute Engine

To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

Before you begin

  1. Set up your environment for Cloud Scheduler.

    Setting Up Your Environment

  2. Enable the Cloud Functions, Pub/Sub, and Compute Engine APIs.

    Enable the APIs

Application architecture

This solution includes the following Google Cloud components:

  1. Compute Engine Instance: A Compute Engine instance we want to run on a schedule.
  2. Cloud Functions functions: Functions to start and stop the instance we want to schedule.
  3. Pub/Sub messages: Messages sent and received for each start and stop event.
  4. Cloud Scheduler jobs: Jobs to make calls on a set schedule to start and stop the instance.

System architecture diagram showing Cloud Scheduler scheduling a Compute Engine instance via Pub/Sub

Location Requirements

Some components are only supported in certain regions:

  1. Compute Engine Instance: supported in any region listed under the Compute Engine Regions and Zones.
  2. Cloud Functions functions: supported in the regions listed as Cloud Functions Locations.
  3. Pub/Sub messages: supported globally as Pub/Sub is a global service.
  4. Cloud Scheduler jobs: with Pub/Sub targets supported in any of the current Google Cloud locations.

Why not HTTP instead of Pub/Sub?

You may want to simplify this architecture by using Cloud Functions HTTP Triggers instead of Pub/Sub Triggers.

This tutorial uses Pub/Sub as the Cloud Functions trigger but HTTP is also a valid choice. See the Events and Triggers documentation for a comparison between HTTP and Pub/Sub triggers.

Set up the Compute Engine instance

Console

  1. Go to the VM instances page in the Google Cloud console.
    Go to the VM instances page.
  2. Click Create instance.
  3. Set the Name to dev-instance.
  4. Under Labels, click Add labels.
  5. Click Add label.
  6. Enter env for Key and dev for Value.
  7. For Region select us-west1.
  8. For Zone select us-west1-b.
  9. Click Save.
  10. Click Create at the bottom of the page.

gcloud

gcloud compute instances create dev-instance \
    --network default \
    --zone us-west1-b \
    --labels=env=dev

Set up the Cloud Functions functions with Pub/Sub

Create and deploy the functions

Console

Create the start function.

  1. Go to the Cloud Functions page in the Google Cloud console.
    Go to the Cloud Functions page.
  2. Click Create Function.
  3. Set Function name to startInstancePubSub.
  4. Leave Region at its default value.
  5. For Trigger type, select Cloud Pub/Sub.
  6. For Select a Cloud Pub/Sub topic, select Create a topic....
  7. A New pub/sub topic dialog box should appear.
    1. Under Topic ID, enter start-instance-event.
    2. Click Create Topic to finish the dialog box.
  8. Click Save at the bottom of the Trigger box.
  9. Click Next at the bottom of the page.
  10. For Runtime, select Node.js 10.
  11. For Entry point, enter startInstancePubSub.
  12. On the left side of the code editor, select index.js.
  13. Replace the starter code with the following code:

    const compute = require('@google-cloud/compute');
    const instancesClient = new compute.InstancesClient();
    const operationsClient = new compute.ZoneOperationsClient();
    
    async function waitForOperation(projectId, operation) {
      while (operation.status !== 'DONE') {
        [operation] = await operationsClient.wait({
          operation: operation.name,
          project: projectId,
          zone: operation.zone.split('/').pop(),
        });
      }
    }
    
    /**
     * Starts Compute Engine instances.
     *
     * Expects a PubSub message with JSON-formatted event data containing the
     * following attributes:
     *  zone - the GCP zone the instances are located in.
     *  label - the label of instances to start.
     *
     * @param {!object} event Cloud Function PubSub message event.
     * @param {!object} callback Cloud Function PubSub callback indicating
     *  completion.
     */
    exports.startInstancePubSub = async (event, context, callback) => {
      try {
        const project = await instancesClient.getProjectId();
        const payload = _validatePayload(event);
        const options = {
          filter: `labels.${payload.label}`,
          project,
          zone: payload.zone,
        };
    
        const [instances] = await instancesClient.list(options);
    
        await Promise.all(
          instances.map(async instance => {
            const [response] = await instancesClient.start({
              project,
              zone: payload.zone,
              instance: instance.name,
            });
    
            return waitForOperation(project, response.latestResponse);
          })
        );
    
        // Operation complete. Instance successfully started.
        const message = 'Successfully started instance(s)';
        console.log(message);
        callback(null, message);
      } catch (err) {
        console.log(err);
        callback(err);
      }
    };
    
    /**
     * Validates that a request payload contains the expected fields.
     *
     * @param {!object} payload the request payload to validate.
     * @return {!object} the payload object.
     */
    const _validatePayload = event => {
      let payload;
      try {
        payload = JSON.parse(Buffer.from(event.data, 'base64').toString());
      } catch (err) {
        throw new Error('Invalid Pub/Sub message: ' + err);
      }
      if (!payload.zone) {
        throw new Error("Attribute 'zone' missing from payload");
      } else if (!payload.label) {
        throw new Error("Attribute 'label' missing from payload");
      }
      return payload;
    };
  14. On the left side of the code editor, select the package.json.

  15. Replace the starter code with the following code:

    {
      "name": "cloud-functions-schedule-instance",
      "version": "0.1.0",
      "private": true,
      "license": "Apache-2.0",
      "author": "Google Inc.",
      "repository": {
        "type": "git",
        "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
      },
      "engines": {
        "node": ">=12.0.0"
      },
      "scripts": {
        "test": "mocha test/*.test.js --timeout=20000"
      },
      "devDependencies": {
        "mocha": "^9.0.0",
        "proxyquire": "^2.0.0",
        "sinon": "^14.0.0"
      },
      "dependencies": {
        "@google-cloud/compute": "^3.1.0"
      }
    }
    
  16. Click Deploy at the bottom of the page.

Create the stop function.

  1. You should be on the Cloud Functions page in the Google Cloud console.
  2. Click Create Function.
  3. Set Function name to stopInstancePubSub.
  4. Leave Region at its default value.
  5. Leave Memory allocated at its default value.
  6. For Trigger type, select Cloud Pub/Sub.
  7. For Select a Cloud Pub/Sub topic, select Create a topic....
  8. A New pub/sub topic dialog box should appear.
    1. Under Topic ID, enter stop-instance-event.
    2. Click Create Topic to finish the dialog box.
  9. Click Save at the bottom of the Trigger box.
  10. Click Next at the bottom of the page.
  11. For Runtime, select Node.js 10.
  12. For Entry point, enter stopInstancePubSub.
  13. On the left side of the code editor, select index.js.
  14. Replace the starter code with the following code:

    const compute = require('@google-cloud/compute');
    const instancesClient = new compute.InstancesClient();
    const operationsClient = new compute.ZoneOperationsClient();
    
    async function waitForOperation(projectId, operation) {
      while (operation.status !== 'DONE') {
        [operation] = await operationsClient.wait({
          operation: operation.name,
          project: projectId,
          zone: operation.zone.split('/').pop(),
        });
      }
    }
    
    /**
     * Stops Compute Engine instances.
     *
     * Expects a PubSub message with JSON-formatted event data containing the
     * following attributes:
     *  zone - the GCP zone the instances are located in.
     *  label - the label of instances to stop.
     *
     * @param {!object} event Cloud Function PubSub message event.
     * @param {!object} callback Cloud Function PubSub callback indicating completion.
     */
    exports.stopInstancePubSub = async (event, context, callback) => {
      try {
        const project = await instancesClient.getProjectId();
        const payload = _validatePayload(event);
        const options = {
          filter: `labels.${payload.label}`,
          project,
          zone: payload.zone,
        };
    
        const [instances] = await instancesClient.list(options);
    
        await Promise.all(
          instances.map(async instance => {
            const [response] = await instancesClient.stop({
              project,
              zone: payload.zone,
              instance: instance.name,
            });
    
            return waitForOperation(project, response.latestResponse);
          })
        );
    
        // Operation complete. Instance successfully stopped.
        const message = 'Successfully stopped instance(s)';
        console.log(message);
        callback(null, message);
      } catch (err) {
        console.log(err);
        callback(err);
      }
    };
    
    /**
     * Validates that a request payload contains the expected fields.
     *
     * @param {!object} payload the request payload to validate.
     * @return {!object} the payload object.
     */
    const _validatePayload = event => {
      let payload;
      try {
        payload = JSON.parse(Buffer.from(event.data, 'base64').toString());
      } catch (err) {
        throw new Error('Invalid Pub/Sub message: ' + err);
      }
      if (!payload.zone) {
        throw new Error("Attribute 'zone' missing from payload");
      } else if (!payload.label) {
        throw new Error("Attribute 'label' missing from payload");
      }
      return payload;
    };
  15. On the left side of the code editor, select the package.json.

  16. Replace the starter code with the following code:

    {
      "name": "cloud-functions-schedule-instance",
      "version": "0.1.0",
      "private": true,
      "license": "Apache-2.0",
      "author": "Google Inc.",
      "repository": {
        "type": "git",
        "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git"
      },
      "engines": {
        "node": ">=12.0.0"
      },
      "scripts": {
        "test": "mocha test/*.test.js --timeout=20000"
      },
      "devDependencies": {
        "mocha": "^9.0.0",
        "proxyquire": "^2.0.0",
        "sinon": "^14.0.0"
      },
      "dependencies": {
        "@google-cloud/compute": "^3.1.0"
      }
    }
    
  17. Click Deploy at the bottom of the page.

gcloud

Create the Pub/Sub topics.

gcloud pubsub topics create start-instance-event
gcloud pubsub topics create stop-instance-event

Get the code

  1. Download the code.

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    Alternatively, you can download the sample as a zip file and extract it.

  2. Go to the correct directory.

    cd nodejs-docs-samples/functions/scheduleinstance/
    

Create the start and stop functions.

You should be in the nodejs-docs-samples/functions/scheduleinstance/ directory.

gcloud functions deploy startInstancePubSub \
    --trigger-topic start-instance-event \
    --runtime nodejs10 \
    --allow-unauthenticated
gcloud functions deploy stopInstancePubSub \
    --trigger-topic stop-instance-event \
    --runtime nodejs10 \
    --allow-unauthenticated

(Optional) Verify the functions work

Console

Stop the instance

  1. Go to the Cloud Functions page in the Google Cloud console.
    Go to the Cloud Functions page.
  2. Click on the function named stopInstancePubSub.
  3. You should see a number of tabs: General, Trigger, Source, Permissions, and Testing. Click on the Testing tab.
  4. For Triggering event enter the following:

    {"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}
    

    • This is simply the base64-encoded string for {"zone":"us-west1-b", "label":"env=dev"}

    • If you want to encode your own string, feel free to use any online base64 encoding tool.

  5. Click the Test the function button.

  6. When it is done running, you should see Successfully stopped instance dev-instance printed under Output. It may take up to 60 seconds to finish running.

    • If instead you see error: 'Error: function failed to load.', just wait 10 seconds or so for the function to finish deploying and try again.

    • If instead you see error: 'Error: function execution attempt timed out.', just move on to the next step to see if the instance is just taking a long time to shut down.

    • If instead it finishes running, but shows nothing, it probably also just timed out. Just move on to the next step to see if the instance is just taking a long time to shut down.

  7. Go to the VM instances page in the Google Cloud console.
    Go to the VM instances page.

  8. Verify that the instance named dev-instance has a grey square next to its name, indicating that it has stopped. It may take up to 30 seconds to finish shutting down.

    • If it doesn't seem to be finishing, try clicking Refresh at the top of the page.

Start the instance

  1. Go to the Cloud Functions page in the Google Cloud console.
    Go to the Cloud Functions page.
  2. Click on the function named startInstancePubSub.
  3. You should see a number of tabs: General, Trigger, Source, Permissions, and Testing. Click on the Testing tab.
  4. For Triggering event enter the following:

    {"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}
    

    • Again, this is simply the base64-encoded string for {"zone":"us-west1-b", "label":"env=dev"}
  5. Click the Test the function button.

  6. When it is done running, you should see Successfully started instance dev-instance printed under Output.

  7. Go to the VM instances page in the Google Cloud console.
    Go to the VM instances page.

  8. Verify that the instance named dev-instance has a green checkmark next to its name, indicating that it is running. It may take up to 30 seconds to finish starting up.

gcloud

Stop the instance

  1. Call the function to stop the instance.

    gcloud functions call stopInstancePubSub \
        --data '{"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}'
    
    • This is simply the base64-encoded string for {"zone":"us-west1-b", "label":"env=dev"}

    • If you want to encode your own string, feel free to use any tool. Here's an example using the base64 command line tool:

      echo '{"zone":"us-west1-b", "label":"env=dev"}' | base64
      
      eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo=
      

    When the function has finished you should see the following:

    result: Successfully stopped instance dev-instance
    

    It may take up to 60 seconds to finish running.

    • If instead you get the error:

      error: 'Error: function failed to load.`
      

      Just wait 10 seconds or so for the function to finish deploying and try again.

    • If instead you get the error:

      error: `Error: function execution attempt timed out.`
      

      Move on to the next step to see if the instance is just taking a long time to shut down.

    • If instead you get no result, the function probably just timed out. Move on to the next step to see if the instance is just taking a long time to shut down.

  2. Check that the instance has a status of TERMINATED. It may take up to 30 seconds to finish shutting down.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: TERMINATED
    

Start the instance

  1. Call the function to start the instance.

    gcloud functions call startInstancePubSub \
        --data '{"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}'
    
    • Again, this is simply the base64-encoded string for {"zone":"us-west1-b", "label":"env=dev"}

    When the function has finished you should see the following:

    result: Successfully started instance dev-instance
    
  2. Check that the instance has a status of RUNNING. It may take up to 30 seconds to finish starting up.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: RUNNING
    

Set up the Cloud Scheduler jobs to call Pub/Sub

Create the jobs

Console

Create the start job.

  1. Go to the Cloud Scheduler page in the Google Cloud console.
    Go to the Cloud Scheduler page.
  2. Click Create a job.
  3. Leave the default region.
  4. Set the Name to startup-dev-instances.
  5. For Frequency, enter 0 9 * * 1-5.
    • This will execute at 9:00 every day Mon-Fri.
  6. For Timezone, select your desired country and timezone. This example will use United States and Los Angeles.
  7. Click Continue.
  8. For Target type, select Pub/Sub.
  9. Select start-instance-event from the topic dropdown.
  10. For Message, enter the following:
    {"zone":"us-west1-b","label":"env=dev"}
    
  11. Click Create.

Create the stop job.

  1. You should be on the Cloud Scheduler page in the Google Cloud console.
  2. Click Create Job.
  3. Leave the default region and click Next at the bottom of the page.
  4. Set the Name to shutdown-dev-instances.
  5. For Frequency, enter 0 17 * * 1-5.
    • This will execute at 17:00 every day Mon-Fri.
  6. For Timezone, select your desired country and timezone. This example will use United States and Los Angeles.
  7. Click Continue.
  8. For Target type, select Pub/Sub.
  9. Select stop-instance-event from the topic dropdown..
  10. For Message, enter the following:
    {"zone":"us-west1-b","label":"env=dev"}
    
  11. Click Create.

gcloud

Create the start job.

gcloud scheduler jobs create pubsub startup-dev-instances \
    --schedule '0 9 * * 1-5' \
    --topic start-instance-event \
    --message-body '{"zone":"us-west1-b", "label":"env=dev"}' \
    --time-zone 'America/Los_Angeles'

Create the stop job.

gcloud scheduler jobs create pubsub shutdown-dev-instances \
    --schedule '0 17 * * 1-5' \
    --topic stop-instance-event \
    --message-body '{"zone":"us-west1-b", "label":"env=dev"}' \
    --time-zone 'America/Los_Angeles'

(Optional) Verify the jobs work

Console

Stop the instance

  1. Go to the Cloud Scheduler page in the Google Cloud console.
    Go to the Cloud Scheduler page.
  2. For the job named shutdown-dev-instances, click the Run now button on the far right side of the page.
  3. Go to the VM instances page in the Google Cloud console.
    Go to the VM instances page.
  4. Verify that the instance named dev-instance has a grey square next to its name, indicating that it has stopped. It may take up to 30 seconds for it to finish shutting down.

Start the instance

  1. Go to the Cloud Scheduler page in the Google Cloud console.
    Go to the Cloud Scheduler page.
  2. For the job named startup-dev-instances, click the Run now button on the far right side of the page.
  3. Go to the VM instances page in the Google Cloud console.
    Go to the VM instances page.
  4. Verify that the instance named dev-instance has a green checkmark next to its name, indicating that it is running. It may take up to 30 seconds for it to finish starting up.

gcloud

Stop the instance

  1. Run the scheduler job to stop the instance.

    gcloud beta scheduler jobs run shutdown-dev-instances
    
  2. Check that the instance has a status of TERMINATED. It may take up to 30 seconds for it to finish shutting down.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: TERMINATED
    

Start the instance

  1. Run the scheduler job to start the instance.

    gcloud beta scheduler jobs run startup-dev-instances
    
  2. Check that the instance has a status of RUNNING. It may take up to 30 seconds to finish starting up.

    gcloud compute instances describe dev-instance \
        --zone us-west1-b \
        | grep status
    
    status: RUNNING
    

Clean up

After you finish the tutorial, you can clean up the resources that you created so that they stop using quota and incurring charges. The following sections describe how to delete or turn off these resources.

Delete the Cloud Scheduler jobs

  1. Go to the Cloud Scheduler page in the Google Cloud console.

    Go to the Cloud Scheduler page.

  2. Click the checkboxes next to your jobs.

  3. Click the Delete button at the top of the page and confirm your delete.

Delete the Pub/Sub topics

  1. Go to the Pub/Sub page in the Google Cloud console.

    Go to the Pub/Sub page

  2. Click the checkboxes next to your topics.

  3. Click Delete at the top of the page and confirm your delete.

Delete the Cloud Functions functions

  1. Go to the Cloud Functions page in the Google Cloud console.

    Go to the Cloud Functions page.

  2. Click the checkboxes next to your functions.

  3. Click the Delete button at the top of the page and confirm your delete.

Delete the Compute Engine instance

To delete a Compute Engine instance:

  1. In the Google Cloud console, go to the VM instances page.

    Go to VM instances

  2. Select the checkbox for the instance that you want to delete.
  3. To delete the instance, click More actions, click Delete, and then follow the instructions.

Delete the project

The easiest way to eliminate billing is to delete the project that you created for the tutorial.

To delete the project:

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

What's next

  • Explore reference architectures, diagrams, tutorials, and best practices about Google Cloud. Take a look at our Cloud Architecture Center.