開始使用 Cloud Run 函式

1. 簡介

總覽

Cloud Run 函式是 Google Cloud 的函式即服務產品,採用 Cloud RunEventarc 技術,可讓您進一步掌控效能和擴充性,以及加強控管函式執行階段,並透過 90 個以上的事件來源觸發作業。

本程式碼研究室將逐步說明如何建立 Cloud Run 函式,以回應 HTTP 呼叫,並透過 Pub/Sub 訊息和 Cloud Audit Logs 觸發。

本程式碼研究室也會使用 --base-image 旗標指定基本映像檔,在部署函式時自動更新基本映像檔。啟用 Cloud Run 的基本映像檔自動更新功能後,Google 就能自動修補基本映像檔的作業系統和語言執行階段元件,您不必重建或重新部署服務,即可更新基礎映像檔。詳情請參閱「自動更新基本映像檔

如果您不想使用自動更新基礎映像檔,可以從本程式碼研究室的範例中移除 --base-image 旗標。

課程內容

  • Cloud Run 函式簡介,以及如何使用自動更新基礎映像檔。
  • 如何編寫可回應 HTTP 呼叫的函式。
  • 如何編寫函式來回應 Pub/Sub 訊息。
  • 如何編寫可回應 Cloud Storage 事件的函式。
  • 如何將流量拆分給兩個修訂版本。
  • 如何透過執行個體數量下限,避免冷啟動。

2. 設定和需求

建立根資料夾

為所有範例建立根資料夾。

mkdir crf-codelab
cd crf-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 函式

首先,請建立 authenticated Node.js 函式,用於回應 HTTP 要求。我們也使用 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

這項指令會使用 buildpacks,將函式原始碼轉換為可立即用於實際工作環境的容器映像檔。

請注意下列事項:

  • --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 bucket 中的事件。

設定

如要使用 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

建立 index.js 檔案,單純回應 Cloud Storage 事件:

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 bucket (或使用現有 bucket):

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 Console 的 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

測試

將檔案上傳至 bucket,測試函式:

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 稽核記錄

接下來,我們要建立 Node.js 函式,在建立 Compute Engine VM 執行個體時接收 Cloud 稽核記錄事件。系統會據此在新建立的 VM 中新增標籤,指明 VM 的建立者。

判斷新建立的 Compute Engine VM

建立 VM 時,Compute Engine 會發出 2 則稽核記錄。

第一個事件會在 VM 建立作業開始時發出,第二個事件會在 VM 建立完成後發出。

稽核記錄中的作業欄位不同,包含 first: truelast: true 值。第二個稽核記錄包含標記執行個體所需的所有資訊,因此我們將使用 last: true 標記在 Cloud Run 函式中偵測到該記錄。

設定

如要使用 Cloud 稽核記錄功能,您必須為 Eventarc 啟用稽核記錄。您也必須使用具備 eventarc.eventReceiver 角色的服務帳戶。

  1. 啟用 Cloud 稽核記錄Admin ReadData ReadData Write 記錄類型,適用於 Compute Engine API。
  2. eventarc.eventReceiver IAM 角色授予預設的 Compute Engine 服務帳戶:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

建立函式

本程式碼研究室使用 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

執行下列指令來建立 VM:

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

VM 建立完成後,您應該會在 Cloud Console 的「Basic information」(基本資訊) 區段中,或使用下列指令,看到 VM 上新增的 creator 標籤:

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

輸出內容應會包含標籤,如下例所示:

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

清除所用資源

請務必刪除 VM 執行個體。本實驗室不會再使用這個檔案。

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

7. 流量拆分

Cloud Run functions 支援多個函式修訂版本,可將流量分配給不同修訂版本,並將函式復原至先前版本。

在這個步驟中,您將部署函式的 2 個修訂版本,然後將流量平均分配給這兩個版本。

建立

建立應用程式資料夾,然後前往該資料夾:

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 秒,模擬長時間的初始化作業。此外,這個函式也有 HelloWorld 函式,可回應 HTTP 呼叫:

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!")
}

部署

部署函式的第一個修訂版本,預設的最小執行個體值為零:

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. 恭喜!

恭喜您完成本程式碼研究室!

涵蓋內容

  • Cloud Run 函式簡介,以及如何使用自動更新基礎映像檔。
  • 如何編寫可回應 HTTP 呼叫的函式。
  • 如何編寫函式來回應 Pub/Sub 訊息。
  • 如何編寫可回應 Cloud Storage 事件的函式。
  • 如何將流量拆分給兩個修訂版本。
  • 如何透過執行個體數量下限,避免冷啟動。