Cloud Run functions 使用入门

1. 简介

概览

Cloud Run functions 是 Google Cloud 的函数即服务产品,由 Cloud RunEventarc 提供支持,可让您对性能和可伸缩性进行更高级的控制,并更好地控制函数运行时和来自 90 多个事件来源的触发器。

此 Codelab 将引导您创建可响应 HTTP 调用并由 Pub/Sub 消息和 Cloud 审核日志触发的 Cloud Run 函数。

此 Codelab 还通过使用 --base-image 标志指定基础映像,为函数部署使用自动基础映像更新。通过为 Cloud Run 配置自动基础映像更新,使 Google 能够自动为基础映像的操作系统和语言运行时组件进行安全补丁。您无需重新构建或重新部署服务即可更新基础映像。如需了解详情,请参阅自动基础映像更新

如果您想使用自动基础映像更新,可以从本 Codelab 中显示的示例中移除 --base-image 标志。

学习内容

  • Cloud Run functions 概览以及如何使用自动基础映像更新。
  • 如何编写响应 HTTP 调用请求的函数。
  • 如何编写响应 Pub/Sub 消息的函数。
  • 如何编写响应 Cloud Storage 事件的函数。
  • 如何在两个修订版本之间拆分流量。
  • 如何通过设置实例数下限来避免冷启动。

2. 设置和要求

创建根文件夹

为所有示例创建一个根文件夹。

mkdir crf-codelab
cd crf-codelab

设置环境变量

设置将在整个 Codelab 中使用的环境变量

gcloud config set project <YOUR-PROJECT-ID>
REGION=<YOUR_REGION>

PROJECT_ID=$(gcloud config get-value project)

启用 API

启用所有必要的服务:

gcloud services enable \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  eventarc.googleapis.com \
  run.googleapis.com \
  logging.googleapis.com \
  pubsub.googleapis.com

3. HTTP 函数

对于第一个函数,我们来创建一个响应 HTTP 请求的 经过身份验证的 Node.js 函数。我们还将使用 10 分钟的超时时间,以展示函数如何有更多时间来响应 HTTP 请求。

创建

为应用创建一个文件夹,然后前往该文件夹:

mkdir hello-http
cd hello-http

创建可响应 HTTP 请求的 index.js 文件:

const functions = require('@google-cloud/functions-framework');

functions.http('helloWorld', (req, res) => {
  res.status(200).send('HTTP with Node.js in Cloud Run functions!');
});

创建一个 package.json 文件以指定依赖项:

{
  "name": "nodejs-run-functions-codelab",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^2.0.0"
  }
}

部署

部署函数的方法如下:

gcloud run deploy nodejs-run-function \
      --source . \
      --function helloWorld \
      --base-image nodejs22 \
      --region $REGION \
      --timeout 600 \
      --no-allow-unauthenticated

此命令使用 buildpack 将函数源代码转换为可用于生产用途的容器映像。

请注意以下事项:

  • --source 标志用于告知 Cloud Run 将函数构建为可运行的基于容器的服务
  • --function 标志(新)用于将新服务的入口点设置为您希望调用的函数签名
  • --base-image 标志(新增)用于指定函数的基础映像环境,例如 nodejs22python312go123java21dotnet8ruby33php83。如需详细了解基础映像以及每个映像中包含的软件包,请参阅运行时基础映像
  • (可选)--timeout 标志允许函数具有更长的超时时间来响应 HTTP 请求。在此示例中,我们使用 600 秒来演示 10 分钟的响应时间。
  • (可选)--no-allow-unauthenticated,以防止您的函数被公开调用

测试

使用以下命令测试函数:

# get the Service URL
SERVICE_URL="$(gcloud run services describe nodejs-run-function --region $REGION --format 'value(status.url)')"

# invoke the service
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

您应该会看到 HTTP with Node.js in Cloud Run functions! 消息作为响应。

4. Pub/Sub 函数

对于第二个函数,我们来创建一个由发布到特定主题的 Pub/Sub 消息触发的 Python 函数。

设置 Pub/Sub 身份验证令牌

如果您在 2021 年 4 月 8 日或之前启用了 Pub/Sub 服务账号,请将 iam.serviceAccountTokenCreator 角色授予 Pub/Sub 服务账号:

PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member  serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
  --role roles/iam.serviceAccountTokenCreator

创建

创建要用于示例的 Pub/Sub 主题:

TOPIC=cloud-run-functions-pubsub-topic
gcloud pubsub topics create $TOPIC

为应用创建一个文件夹,然后前往该文件夹:

mkdir ../hello-pubsub
cd ../hello-pubsub

创建一个 main.py 文件,用于记录包含 CloudEvent ID 的消息:

import functions_framework

@functions_framework.cloud_event
def hello_pubsub(cloud_event):
   print('Pub/Sub with Python in Cloud Run functions! Id: ' + cloud_event['id'])

创建一个包含以下内容的 requirements.txt 文件,以指定依赖项:

functions-framework==3.*

部署

部署函数的方法如下:

gcloud run deploy python-pubsub-function \
       --source . \
       --function hello_pubsub \
       --base-image python313 \
       --region $REGION \
       --no-allow-unauthenticated

检索将用于服务账号身份的项目编号。

PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')

创建触发器

gcloud eventarc triggers create python-pubsub-function-trigger  \
    --location=$REGION \
    --destination-run-service=python-pubsub-function  \
    --destination-run-region=$REGION \
    --event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
    --transport-topic=projects/$PROJECT_ID/topics/$TOPIC \
    --service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com

测试

通过向主题发送消息来测试函数:

gcloud pubsub topics publish $TOPIC --message="Hello World"

您应该会在日志中看到已接收到的 CloudEvent:

gcloud run services logs read python-pubsub-function --region $REGION --limit=10

5. Cloud Storage 函数

对于下一个函数,我们来创建一个 Node.js 函数,该函数可响应来自 Cloud Storage 存储分区的事件。

设置

如需使用 Cloud Storage 函数,请将 pubsub.publisher IAM 角色授予 Cloud Storage 服务账号:

SERVICE_ACCOUNT=$(gsutil kms serviceaccount -p $PROJECT_NUMBER)

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT \
  --role roles/pubsub.publisher

创建

为应用创建一个文件夹,然后前往该文件夹:

mkdir ../hello-storage
cd ../hello-storage

创建一个仅响应 Cloud Storage 事件的 index.js 文件:

const functions = require('@google-cloud/functions-framework');

functions.cloudEvent('helloStorage', (cloudevent) => {
  console.log('Cloud Storage event with Node.js in Cloud Run functions!');
  console.log(cloudevent);
});

创建一个 package.json 文件以指定依赖项:

{
  "name": "nodejs-crf-cloud-storage",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^2.0.0"
  }
}

部署

首先,创建 Cloud Storage 存储分区(或使用您已有的现有存储分区):

export BUCKET_NAME="gcf-storage-$PROJECT_ID"
​​export BUCKET="gs://gcf-storage-$PROJECT_ID"
gsutil mb -l $REGION $BUCKET

部署函数的方法如下:

gcloud run deploy nodejs-crf-cloud-storage \
 --source . \
 --base-image nodejs22 \
 --function helloStorage \
 --region $REGION \
 --no-allow-unauthenticated

部署函数后,您可以在 Cloud 控制台的 Cloud Run 部分下看到该函数。

现在,创建 Eventarc 触发器。

BUCKET_REGION=$REGION

gcloud eventarc triggers create nodejs-crf-cloud-storage-trigger \
  --location=$BUCKET_REGION \
  --destination-run-service=nodejs-crf-cloud-storage \
  --destination-run-region=$REGION \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=$BUCKET_NAME" \
  --service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com

测试

通过将文件上传到存储分区来测试函数:

echo "Hello World" > random.txt
gsutil cp random.txt $BUCKET/random.txt

您应该会在日志中看到已接收到的 CloudEvent:

gcloud run services logs read nodejs-crf-cloud-storage --region $REGION --limit=10

6. Cloud Audit Logs

对于下一个函数,我们来创建一个 Node.js 函数,该函数会在创建 Compute Engine 虚拟机实例时接收 Cloud Audit Log 事件。作为响应,它会向新创建的虚拟机添加一个标签,用于指定虚拟机的创建者。

确定新创建的 Compute Engine 虚拟机

创建虚拟机时,Compute Engine 会发出 2 个审核日志。

第一个是在虚拟机创建开始时发出的。第二个是在创建虚拟机后发出的。

在审核日志中,操作字段不同,包含 first: truelast: true 值。第二个审核日志包含标记实例所需的所有信息,因此我们将使用 last: true 标志在 Cloud Run 函数中检测到它。

设置

如需使用 Cloud Audit Log 功能,您必须为 Eventarc 启用审核日志。您还需要使用具有 eventarc.eventReceiver 角色的服务账号。

  1. 为 Compute Engine API 启用 Cloud Audit Logs 管理员读取数据读取数据写入日志类型。
  2. 向默认 Compute Engine 服务账号授予 eventarc.eventReceiver IAM 角色:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

创建函数

此 Codelab 使用 Node.js,但您可以在 https://github.com/GoogleCloudPlatform/eventarc-samples 找到其他示例

创建 package.json 文件

{
  "dependencies": {
    "googleapis": "^84.0.0"
  }
}

创建 node.js 文件

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const { google } = require("googleapis");
var compute = google.compute("v1");

exports.labelVmCreation = async (cloudevent) => {
  const data = cloudevent.body;

  // in case an event has >1 audit log
  // make sure we respond to the last event
  if (!data.operation || !data.operation.last) {
    console.log("Operation is not last, skipping event");
    return;
  }

  // projects/dogfood-gcf-saraford/zones/us-central1-a/instances/instance-1
  var resourceName = data.protoPayload.resourceName;
  var resourceParts = resourceName.split("/");
  var project = resourceParts[1];
  var zone = resourceParts[3];
  var instanceName = resourceParts[5];
  var username = data.protoPayload.authenticationInfo.principalEmail.split("@")[0];

  console.log(`Setting label username: ${username} to instance ${instanceName} for zone ${zone}`);

  var authClient = await google.auth.getClient({
    scopes: ["https://www.googleapis.com/auth/cloud-platform"]
  });

  // per docs: When updating or adding labels in the API,
  // you need to provide the latest labels fingerprint with your request,
  // to prevent any conflicts with other requests.
  var labelFingerprint = await getInstanceLabelFingerprint(authClient, project, zone, instanceName);

  var responseStatus = await setVmLabel(
    authClient,
    labelFingerprint,
    username,
    project,
    zone,
    instanceName
  );

  // log results of setting VM label
  console.log(JSON.stringify(responseStatus, null, 2));
};

async function getInstanceLabelFingerprint(authClient, project, zone, instanceName) {
  var request = {
    project: project,
    zone: zone,
    instance: instanceName,
    auth: authClient
  };

  var response = await compute.instances.get(request);
  var labelFingerprint = response.data.labelFingerprint;
  return labelFingerprint;
}

async function setVmLabel(authClient, labelFingerprint, username, project, zone, instanceName) {
  var request = {
    project: project,
    zone: zone,
    instance: instanceName,

    resource: {
      labels: { "creator": username },
      labelFingerprint: labelFingerprint
    },

    auth: authClient
  };

  var response = await compute.instances.setLabels(request);
  return response.statusText;
}

部署

部署函数的方法如下:

gcloud run deploy gce-vm-labeler \
  --source . \
  --function labelVmCreation \
  --region $REGION \
  --no-allow-unauthenticated

现在,创建触发器。请注意,该函数如何使用 --trigger-event-filters 标志过滤 Compute Engine 插入的审核日志。

gcloud eventarc triggers create gce-vm-labeler-trigger \
  --location=$REGION \
  --destination-run-service=gce-vm-labeler \
  --destination-run-region=$REGION \
  --event-filters="type=google.cloud.audit.log.v1.written,serviceName=compute.googleapis.com,methodName=v1.compute.instances.insert" \
  --service-account=$ROJECT_NUMBER-compute@developer.gserviceaccount.com

测试

设置环境变量:

# if you're using europe-west1 as your region
ZONE=europe-west1-d
VM_NAME=codelab-crf-auditlog

运行以下命令可创建虚拟机:

gcloud compute instances create $VM_NAME --zone=$ZONE --machine-type=e2-medium --image-family=debian-11  --image-project=debian-cloud

虚拟机创建完成后,您应该会在 Cloud 控制台的基本信息部分或使用以下命令看到添加的 creator 标签:

gcloud compute instances describe $VM_NAME --zone=$ZONE

您应该会在输出中看到标签,如以下示例所示:

...
labelFingerprint: ULU6pAy2C7s=
labels:
  creator: atameldev
...

清理

请务必删除虚拟机实例。在本实验中,您将不会再使用该变量。

gcloud compute instances delete $VM_NAME --zone=$ZONE

7. 流量分配

Cloud Run functions 支持函数的多个修订版本,让您可以在不同的修订版本之间分配流量,还可以将函数回滚到先前版本。

在此步骤中,您将部署函数的 2 个修订版本,然后在它们之间按 50-50 的比例拆分流量。

创建

为应用创建一个文件夹,然后前往该文件夹:

mkdir ../traffic-splitting
cd ../traffic-splitting

创建一个 main.py 文件,其中包含一个 Python 函数,该函数可读取颜色环境变量并以该背景颜色回复 Hello World

import os

color = os.environ.get('COLOR')

def hello_world(request):
    return f'<body style="background-color:{color}"><h1>Hello World!</h1></body>'

创建一个包含以下内容的 requirements.txt 文件,以指定依赖项:

functions-framework==3.*

部署

部署第一个修订版本的函数(采用橙色背景):

COLOR=orange
gcloud run deploy hello-world-colors \
 --source . \
 --base-image python313 \
 --function hello_world \
 --region $REGION \
 --allow-unauthenticated \
 --update-env-vars COLOR=$COLOR

此时,如果您在浏览器中查看 HTTP 触发器(上述部署命令的 URI 输出),测试该函数,则应会看到带有橙色背景的 Hello World

36ca0c5f39cc89cf.png

部署具有黄色背景的第二个修订版本:

COLOR=yellow
gcloud run deploy hello-world-colors \
 --source . \
 --base-image python313 \
 --function hello_world \
 --region $REGION \
 --allow-unauthenticated \
 --update-env-vars COLOR=$COLOR

由于这是最新修订版本,因此如果您测试该函数,应该会看到带有黄色背景的 Hello World

391286a08ad3cdde.png

将流量平均分配

如需在橙色修订版本和黄色修订版本之间拆分流量,您需要找到 Cloud Run 服务的修订版本 ID。以下是查看修订版本 ID 的命令:

gcloud run revisions list --service hello-world-colors \
  --region $REGION --format 'value(REVISION)'

输出应类似如下所示:

hello-world-colors-00001-man
hello-world-colors-00002-wok

现在,按如下方式在这两个修订版本之间拆分流量(请根据您的修订版本名称更新 X-XXX):

gcloud run services update-traffic hello-world-colors \
  --region $REGION \
  --to-revisions hello-world-colors-0000X-XXX=50,hello-world-colors-0000X-XXX=50

测试

访问函数的公共网址,以测试该函数。一半的时间,您应该会看到橙色修订版本;另一半时间,您应该会看到黄色修订版本:

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

如需了解详情,请参阅回滚、逐步发布和流量迁移

8. 实例数下限

在 Cloud Run functions 中,您可以指定要保持备用状态并且准备处理请求的函数实例数下限。这对于限制冷启动的次数非常有用。

在此步骤中,您将部署一个初始化速度较慢的函数。您会发现冷启动问题。然后,您将部署该函数,并将最小实例值设置为 1,以消除冷启动。

创建

为应用创建一个文件夹,然后导航到该文件夹:

mkdir ../min-instances
cd ../min-instances

创建 main.go 文件,此 Go 服务具有一个 init 函数,该函数会休眠 10 秒以模拟长时间的初始化。它还具有一个用于响应 HTTP 调用请求的 HelloWorld 函数:

package p

import (
        "fmt"
        "net/http"
        "time"
)

func init() {
        time.Sleep(10 * time.Second)
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Slow HTTP Go in Cloud Run functions!")
}

部署

使用默认的最小实例值 0 部署函数的第一个修订版本:

gcloud run deploy go-slow-function \
 --source . \
 --base-image go123 \
 --function HelloWorld \
 --region $REGION \
 --no-allow-unauthenticated

使用以下命令测试函数:

# get the Service URL
SERVICE_URL="$(gcloud run services describe go-slow-function --region $REGION --format 'value(status.url)')"

# invoke the service
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

您会发现,第一次调用时有 10 秒的延迟(冷启动),然后才会看到消息。后续调用应立即返回。

设置实例数下限

如需避免在首次请求时出现冷启动,请重新部署函数,并将 --min-instances 标志设置为 1,如下所示:

gcloud run deploy go-slow-function \
 --source . \
 --base-image go123 \
 --function HelloWorld \
 --region $REGION \
 --no-allow-unauthenticated \
 --min-instances 1

测试

再次测试该函数:

curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

您应该不会再看到第一个请求出现 10 秒的延迟。由于设置了实例数下限,首次调用(在长时间未调用后)的冷启动问题已得到解决!

如需了解详情,请参阅使用最少的实例

9. 恭喜!

恭喜您完成此 Codelab!

所学内容

  • Cloud Run functions 概览以及如何使用自动基础映像更新。
  • 如何编写响应 HTTP 调用请求的函数。
  • 如何编写响应 Pub/Sub 消息的函数。
  • 如何编写响应 Cloud Storage 事件的函数。
  • 如何在两个修订版本之间拆分流量。
  • 如何通过设置实例数下限来避免冷启动。