使用 Cloud Scheduler 调度计算实例

本教程介绍了如何使用 Cloud Scheduler 和 Cloud Functions 通过资源标签定期自动启动和停止 Compute Engine 实例。

目标

  • 使用 Cloud Functions 编写和部署一组启用和停止 Compute Engine 实例的函数。
  • 使用 Cloud Scheduler 创建一组作业,调度带 dev 资源标签的实例在周一至周五的 09:00-17:00 运行,以匹配典型的工作时间。

费用

本教程使用 Google Cloud 的以下收费组件:

  • Cloud Scheduler
  • 云端函数
  • Pub/Sub
  • Compute Engine

您可使用价格计算器根据您的预计使用情况来估算费用。Google Cloud 新用户可能有资格申请免费试用

准备工作

  1. 为 Cloud Scheduler 设置环境。

    设置您的环境

  2. 启用 Cloud Functions, Pub/Sub, and Compute Engine API。

    启用 API

应用架构

此解决方案包含以下 Google Cloud 组件:

  1. Compute Engine 实例:我们希望按时间表运行的 Compute Engine 实例。
  2. Cloud Functions 函数:用于启动和停止我们要调度的实例的函数。
  3. Pub/Sub 消息:针对每个启动和停止事件发送和接收的消息。
  4. Cloud Scheduler 作业:根据具体时间表进行调用以启动和停止实例的作业。

显示 Cloud Scheduler 通过 Pub/Sub 调度 Compute Engine 实例的系统架构图

位置要求

某些组件仅在特定区域受支持:

  1. Compute Engine 实例:在 Compute Engine 区域和地区下列出的任何区域中皆受支持。
  2. Cloud Functions 函数:在列为 Cloud Functions 位置的区域中皆受支持。
  3. Pub/Sub 消息:Pub/Sub 是一项全球服务,因此在全球皆受支持。
  4. Cloud Scheduler 作业:在当前的任何 App Engine 位置皆受支持。

最佳做法:为什么不用 HTTP 取代 Pub/Sub?

您可能希望使用 Cloud Functions HTTP 触发器取代 Pub/Sub 触发器以简化此架构。

显示 Cloud Scheduler 通过 HTTP 调度 Compute Engine 实例的系统架构图

显示通过 HTTP 进行调度会允许任何人调度 Compute Engine 实例的系统架构图

为了创建更安全的设置,我们建议您使用 Pub/Sub 函数。

设置 Compute Engine 实例

Console

  1. 转到 Cloud Console 中的虚拟机实例页面。
    转到“虚拟机实例”页面
  2. 点击创建实例
  3. 名称设置为 dev-instance
  4. 对于区域,请选择 us-west1
  5. 对于地区,请选择 us-west1-b
  6. 展开管理、安全、磁盘、网络、单独租用部分。
  7. 在管理下,点击添加标签。为 Key 输入 env,为 Value 输入 dev
  8. 点击该页面底部的创建

gcloud

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

使用 Pub/Sub 设置 Cloud Functions 函数

创建和部署函数

Console

创建启动函数。

  1. 转到 Cloud Functions 网页。
    转到 Cloud Functions 页面
  2. 点击创建函数
  3. 名称设置为 startInstancePubSub
  4. 使用分配的内存的默认值。
  5. 对于触发器,选择 Cloud Pub/Sub
  6. 对于主题,选择 Create new topic...
  7. 将出现一个新的 pub/sub 主题对话框。
    1. 名称下,输入 start-instance-event
    2. 点击创建以完成对话框。
  8. 对于运行时,选择 Node.js 8
  9. 在代码文本块上方,选择 index.js 标签。
  10. 使用以下代码替换入门代码:

    const Compute = require('@google-cloud/compute');
    const compute = new Compute();
    
    /**
     * 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 payload = _validatePayload(
          JSON.parse(Buffer.from(event.data, 'base64').toString())
        );
        const options = {filter: `labels.${payload.label}`};
        const [vms] = await compute.getVMs(options);
        await Promise.all(
          vms.map(async (instance) => {
            if (payload.zone === instance.zone.id) {
              const [operation] = await compute
                .zone(payload.zone)
                .vm(instance.name)
                .start();
    
              // Operation pending
              return operation.promise();
            }
          })
        );
    
        // 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 = (payload) => {
      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;
    };
  11. 在代码文本块上方,选择 package.json 标签。

  12. 使用以下代码替换入门代码:

    {
      "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": ">=8.0.0"
      },
      "scripts": {
        "test": "mocha test/*.test.js --timeout=20000"
      },
      "devDependencies": {
        "mocha": "^7.0.0",
        "proxyquire": "^2.0.0",
        "sinon": "^9.0.0"
      },
      "dependencies": {
        "@google-cloud/compute": "^1.0.0"
      }
    }
    
  13. 对于要执行的函数,输入 startInstancePubSub

  14. 点击创建

创建停止函数。

  1. 转到 Cloud Console 中的 Cloud Functions 页面。
  2. 点击创建函数
  3. 名称设置为 stopInstancePubSub
  4. 使用分配的内存的默认值。
  5. 对于触发器,选择 Cloud Pub/Sub
  6. 对于主题,选择 Create new topic...
  7. 将出现一个新的 pub/sub 主题对话框。
    1. 名称下,输入 stop-instance-event
    2. 点击创建以完成对话框。
  8. 对于运行时,选择 Node.js 8
  9. 在代码文本块上方,选择 index.js 标签。
  10. 使用以下代码替换入门代码:

    const Compute = require('@google-cloud/compute');
    const compute = new Compute();
    
    /**
     * 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 payload = _validatePayload(
          JSON.parse(Buffer.from(event.data, 'base64').toString())
        );
        const options = {filter: `labels.${payload.label}`};
        const [vms] = await compute.getVMs(options);
        await Promise.all(
          vms.map(async (instance) => {
            if (payload.zone === instance.zone.id) {
              const [operation] = await compute
                .zone(payload.zone)
                .vm(instance.name)
                .stop();
    
              // Operation pending
              return operation.promise();
            } else {
              return Promise.resolve();
            }
          })
        );
    
        // 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 = (payload) => {
      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;
    };
  11. 在代码文本块上方,选择 package.json 标签。

  12. 使用以下代码替换入门代码:

    {
      "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": ">=8.0.0"
      },
      "scripts": {
        "test": "mocha test/*.test.js --timeout=20000"
      },
      "devDependencies": {
        "mocha": "^7.0.0",
        "proxyquire": "^2.0.0",
        "sinon": "^9.0.0"
      },
      "dependencies": {
        "@google-cloud/compute": "^1.0.0"
      }
    }
    
  13. 对于要执行的函数,输入 stopInstancePubSub

  14. 点击创建

gcloud

创建 Pub/Sub 主题。

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

获取代码

  1. 下载代码。

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

    或者,您也可以下载该示例的 zip 文件并将其解压缩。

  2. 转到正确的目录。

    cd nodejs-docs-samples/functions/scheduleinstance/
    

创建启动和停止函数。

您应该已位于 nodejs-docs-samples/functions/scheduleinstance/ 目录中。

gcloud functions deploy startInstancePubSub \
    --trigger-topic start-instance-event \
    --runtime nodejs8
gcloud functions deploy stopInstancePubSub \
    --trigger-topic stop-instance-event \
    --runtime nodejs8

(可选)验证函数能否正常运行

Console

停止实例

  1. 转到 Cloud Functions 网页。
    转到 Cloud Functions 页面
  2. 点击名为 stopInstancePubSub 的函数。
  3. 您应该看到多个标签:常规触发器测试。点击测试标签。
  4. 对于触发事件,输入以下内容:

    {"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}
    

    • 这只是 {"zone":"us-west1-b", "label":"env=dev"} 的 base64 编码的字符串

    • 如果您想要编码生成自己的字符串,则可以随意使用任何在线 base64 编码工具

  5. 点击测试函数按钮。

  6. 当函数完成运行时,您应该会看到输出下面显示了 Successfully stopped instance dev-instance。完成运行可能最多需要 60 秒。

    • 如果您看到的是 error: 'Error: function failed to load.',只需等待 10 秒左右以便函数完成部署,然后重试。

    • 如果您看到的是 error: 'Error: function execution attempt timed out.',直接继续下一步,以确定实例是否只是需要较长时间完成关闭。

    • 如果函数完成了运行,但没有显示任何内容,则可能也只是超时。直接继续下一步,以确定实例是否只是需要较长时间完成关闭。

  7. 转到 Cloud Console 中的虚拟机实例页面。
    转到“虚拟机实例”页面

  8. 验证名为 dev-instance 的实例的名称旁边是否有灰色方块,该方块表示实例已停止。完成关闭可能最多需要 30 秒。

    • 如果似乎未完成关闭,尝试点击页面顶部的刷新

启动实例

  1. 转到 Cloud Functions 网页。
    转到 Cloud Functions 页面
  2. 点击名为 startInstancePubSub 的函数。
  3. 您应该看到多个标签:常规触发器测试。点击测试标签。
  4. 对于触发事件,输入以下内容:

    {"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}
    

    • 同样,这只是 {"zone":"us-west1-b", "label":"env=dev"} 的 base64 编码的字符串
  5. 点击测试函数按钮。

  6. 当函数完成运行时,您应该会看到输出下面显示了 Successfully started instance dev-instance

  7. 转到 Cloud Console 中的虚拟机实例页面。
    转到“虚拟机实例”页面

  8. 验证名为 dev-instance 的实例的名称旁边是否有绿色对勾标记,该标记表示实例正在运行。完成启动可能最多需要 30 秒。

gcloud

停止实例

  1. 调用以下函数来停止实例。

    gcloud functions call stopInstancePubSub \
        --data '{"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}'
    
    • 这只是 {"zone":"us-west1-b", "label":"env=dev"} 的 base64 编码的字符串

    • 如果您想编码生成自己的字符串,可以使用任何工具。 以下示例中使用了 base64 命令行工具:

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

    函数完成后,您将看到以下内容:

    result: Successfully stopped instance dev-instance
    

    完成运行可能最多需要 60 秒。

    • 如果您看到以下错误:

      error: 'Error: function failed to load.`
      

      只需等待 10 秒左右以便函数完成部署,然后重试。

    • 如果您看到以下错误:

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

      直接继续下一步,以确定实例是否只是需要较长时间完成关闭。

    • 如果您未看到任何结果,该函数可能只是超时。 直接继续下一步,以确定实例是否只是需要较长时间完成关闭。

  2. 检查实例的状态是否为 TERMINATED。完成关闭可能最多需要 30 秒。

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

启动实例

  1. 调用以下函数来启动实例。

    gcloud functions call startInstancePubSub \
        --data '{"data":"eyJ6b25lIjoidXMtd2VzdDEtYiIsICJsYWJlbCI6ImVudj1kZXYifQo="}'
    
    • 同样,这只是 {"zone":"us-west1-b", "label":"env=dev"} 的 base64 编码的字符串

    函数完成后,您将看到以下内容:

    result: Successfully started instance dev-instance
    
  2. 检查实例的状态是否为 RUNNING。完成启动可能最多需要 30 秒。

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

设置 Cloud Scheduler 作业以调用 Pub/Sub

创建作业

Console

创建启动作业。

  1. 转到 Cloud Console 中的 Cloud Scheduler 页面。
    转到 Cloud Scheduler 页面
  2. 点击创建作业
  3. 名称设置为 startup-dev-instances
  4. 对于频率,输入 0 9 * * 1-5
  5. 对于时区,请选择相应的国家/地区和时区。此示例将使用 United StatesLos Angeles
  6. 对于目标,请选择 Pub/Sub
  7. 对于主题,请输入 start-instance-event
  8. 对于负载,键入以下内容:
    {"zone":"us-west1-b","label":"env=dev"}
    
  9. 点击创建

创建停止作业。

  1. 转到 Cloud Console 中的 Cloud Scheduler 页面。
  2. 点击创建作业
  3. 名称设置为 shutdown-dev-instances
  4. 对于频率,输入 0 17 * * 1-5
  5. 对于时区,请选择相应的国家/地区和时区。此示例将使用 United StatesLos Angeles
  6. 对于目标,请选择 Pub/Sub
  7. 对于主题,请输入 stop-instance-event
  8. 对于负载,键入以下内容:
    {"zone":"us-west1-b","label":"env=dev"}
    
  9. 点击创建

gcloud

创建启动作业。

gcloud beta 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'

创建停止作业。

gcloud beta 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'

(可选)验证作业能否正常运行

Console

停止实例

  1. 转到 Cloud Console 中的 Cloud Scheduler 页面。
    转到 Cloud Scheduler 页面
  2. 对于名为 shutdown-dev-instances 的作业,点击页面最右侧的立即运行按钮。
  3. 转到 Cloud Console 中的虚拟机实例页面。
    转到“虚拟机实例”页面
  4. 验证名为 dev-instance 的实例的名称旁边是否有灰色方块,该方块表示实例已停止。完成关闭可能最多需要 30 秒。

启动实例

  1. 转到 Cloud Console 中的 Cloud Scheduler 页面。
    转到 Cloud Scheduler 页面
  2. 对于名为 startup-dev-instances 的作业,点击页面最右侧的立即运行按钮。
  3. 转到 Cloud Console 中的虚拟机实例页面。
    转到“虚拟机实例”页面
  4. 验证名为 dev-instance 的实例的名称旁边是否有绿色对勾标记,该标记表示实例正在运行。完成启动可能最多需要 30 秒。

gcloud

停止实例

  1. 运行调度程序作业以停止实例。

    gcloud beta scheduler jobs run shutdown-dev-instances
    
  2. 检查实例的状态是否为 TERMINATED。完成关闭可能最多需要 30 秒。

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

启动实例

  1. 运行调度程序作业以启动实例。

    gcloud beta scheduler jobs run startup-dev-instances
    
  2. 检查实例的状态是否为 RUNNING。完成启动可能最多需要 30 秒。

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

清理

完成本实例调度教程后,您可以清理在 Google Cloud 上创建的资源,以避免这些资源占用配额,日后产生费用。以下部分介绍如何删除或关闭这些资源。

删除 Cloud Scheduler 作业

  1. 转到 Cloud Console 中的 Cloud Scheduler 页面。

    转到 Cloud Scheduler 页面

  2. 点击作业旁边的复选框。

  3. 点击页面顶部的删除作业按钮并确认删除操作。

删除 Pub/Sub 主题

  1. 转到 Cloud Console 中的 Pub/Sub 页面:

    转到 Pub/Sub 页面

  2. 点击主题旁边的复选框。

  3. 在页面顶部,点击删除并确认删除操作。

删除 Cloud Functions 函数

  1. 转到 Cloud Functions 网页。

    转到 Cloud Functions 页面

  2. 点击函数旁边的复选框。

  3. 点击页面顶部的删除按钮并确认删除操作。

删除 Compute Engine 实例

要删除 Compute Engine 实例,请运行以下命令:

  1. 在 Cloud Console 中,转到虚拟机实例页面。

    转到“虚拟机实例”页面

  2. 点击您要删除的实例。
  3. 点击删除 以删除实例。

删除项目

为了避免产生费用,最简单的方法是删除您为本教程创建的项目。

要删除项目,请执行以下操作:

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”页面

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤

  • 试用其他 Google Cloud 功能。查阅我们的教程