Làm quen với các hàm Cloud Run

1. Giới thiệu

Tổng quan

Các hàm Cloud Run là dịch vụ Hàm dưới dạng dịch vụ của Google Cloud, được hỗ trợ bởi Cloud RunEventarc, giúp bạn kiểm soát hiệu suất và khả năng mở rộng một cách nâng cao hơn, đồng thời kiểm soát nhiều hơn về thời gian chạy và các trình kích hoạt hàm từ hơn 90 nguồn sự kiện.

Lớp học lập trình này sẽ hướng dẫn bạn tạo các hàm Cloud Run phản hồi các lệnh gọi HTTP, đồng thời được kích hoạt bằng thông báo Pub/Sub và Nhật ký kiểm tra Cloud.

Lớp học lập trình này cũng sử dụng tính năng cập nhật tự động hình ảnh cơ sở cho việc triển khai hàm bằng cách chỉ định một hình ảnh cơ sở bằng cờ --base-image. Tính năng tự động cập nhật hình ảnh cơ sở cho Cloud Run cho phép Google tự động vá bảo mật cho hệ điều hành và các thành phần thời gian chạy ngôn ngữ của hình ảnh cơ sở. Bạn không cần phải tạo lại hoặc triển khai lại dịch vụ để cập nhật hình ảnh cơ sở. Để biết thêm thông tin, hãy xem bài viết về tính năng tự động cập nhật hình ảnh cơ sở

Nếu muốn không sử dụng tính năng tự động cập nhật hình ảnh cơ sở, bạn có thể xoá cờ --base-image khỏi các ví dụ minh hoạ trong lớp học lập trình này.

Kiến thức bạn sẽ học được

  • Tổng quan về các hàm Cloud Run và cách sử dụng tính năng tự động cập nhật hình ảnh cơ sở.
  • Cách viết một hàm phản hồi các lệnh gọi HTTP.
  • Cách viết một hàm phản hồi các thông báo Pub/Sub.
  • Cách viết một hàm phản hồi các sự kiện Cloud Storage.
  • Cách phân chia lưu lượng truy cập giữa hai bản sửa đổi.
  • Cách loại bỏ các lần khởi động nguội bằng số lượng phiên bản tối thiểu.

2. Thiết lập và yêu cầu

Tạo thư mục gốc

Tạo một thư mục gốc cho tất cả các ví dụ.

mkdir crf-codelab
cd crf-codelab

Thiết lập các biến môi trường

Đặt các biến môi trường sẽ được dùng trong suốt lớp học lập trình này/

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

PROJECT_ID=$(gcloud config get-value project)

Bật API

Bật tất cả các dịch vụ cần thiết:

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

3. Hàm HTTP

Đối với hàm đầu tiên, hãy tạo một hàm Node.js đã xác thực để phản hồi các yêu cầu HTTP. Chúng ta cũng sẽ sử dụng thời gian chờ 10 phút để minh hoạ cách một hàm có thể có nhiều thời gian hơn để phản hồi các yêu cầu HTTP.

Tạo

Tạo một thư mục cho ứng dụng rồi chuyển đến thư mục đó:

mkdir hello-http
cd hello-http

Tạo một tệp index.js phản hồi các yêu cầu HTTP:

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

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

Tạo tệp package.json để chỉ định các phần phụ thuộc:

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

Triển khai

Triển khai hàm:

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

Lệnh này dùng buildpack để chuyển đổi mã nguồn hàm của bạn thành một hình ảnh vùng chứa sẵn sàng cho hoạt động sản xuất.

Xin lưu ý những điều sau:

  • cờ --source được dùng để yêu cầu Cloud Run tạo hàm thành một dịch vụ dựa trên vùng chứa có thể chạy
  • cờ --function (mới) được dùng để đặt điểm truy cập của dịch vụ mới thành chữ ký hàm mà bạn muốn gọi
  • cờ --base-image (mới) chỉ định môi trường hình ảnh cơ sở cho hàm của bạn, chẳng hạn như nodejs22, python312, go123, java21, dotnet8, ruby33 hoặc php83. Để biết thêm thông tin về hình ảnh cơ sở và các gói có trong mỗi hình ảnh, hãy xem phần Hình ảnh cơ sở thời gian chạy.
  • (không bắt buộc) cờ --timeout cho phép hàm có thời gian chờ lâu hơn để phản hồi các yêu cầu HTTP. Trong ví dụ này, 600 giây được dùng để minh hoạ thời gian phản hồi là 10 phút.
  • (không bắt buộc) --no-allow-unauthenticated để ngăn người dùng gọi hàm của bạn một cách công khai

Kiểm thử

Kiểm thử chức năng bằng các lệnh sau:

# 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

Bạn sẽ thấy thông báo HTTP with Node.js in Cloud Run functions! dưới dạng phản hồi.

4. Hàm Pub/Sub

Đối với hàm thứ hai, hãy tạo một hàm Python được kích hoạt bằng một thông báo Pub/Sub được đăng lên một chủ đề cụ thể.

Thiết lập mã thông báo xác thực Pub/Sub

Nếu bạn đã bật tài khoản dịch vụ Pub/Sub vào hoặc trước ngày 8 tháng 4 năm 2021, hãy cấp vai trò iam.serviceAccountTokenCreator cho tài khoản dịch vụ 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

Tạo

Tạo một chủ đề Pub/Sub để dùng cho mẫu:

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

Tạo một thư mục cho ứng dụng rồi chuyển đến thư mục đó:

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

Tạo một tệp main.py ghi lại một thông báo chứa mã nhận dạng CloudEvent:

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'])

Tạo tệp requirements.txt có nội dung sau để chỉ định các phần phụ thuộc:

functions-framework==3.*

Triển khai

Triển khai hàm:

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

Truy xuất số dự án sẽ được dùng cho danh tính tài khoản dịch vụ.

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

Tạo điều kiện kích hoạt

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

Kiểm thử

Kiểm thử hàm bằng cách gửi một thông báo đến chủ đề:

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

Bạn sẽ thấy CloudEvent đã nhận được trong nhật ký:

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

5. Hàm Cloud Storage

Đối với hàm tiếp theo, hãy tạo một hàm Node.js phản hồi các sự kiện từ một bộ chứa Cloud Storage.

Thiết lập

Để sử dụng các hàm Cloud Storage, hãy cấp vai trò IAM pubsub.publisher cho tài khoản dịch vụ 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

Tạo

Tạo một thư mục cho ứng dụng rồi chuyển đến thư mục đó:

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

Tạo một tệp index.js chỉ phản hồi các sự kiện 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);
});

Tạo tệp package.json để chỉ định các phần phụ thuộc:

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

Triển khai

Trước tiên, hãy tạo một vùng lưu trữ trên Cloud Storage (hoặc sử dụng vùng lưu trữ hiện có):

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

Triển khai hàm:

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

Sau khi triển khai hàm, bạn có thể thấy hàm đó trong phần Cloud Run của Cloud Console.

Bây giờ, hãy tạo điều kiện kích hoạt 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

Kiểm thử

Kiểm thử hàm bằng cách tải một tệp lên vùng chứa:

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

Bạn sẽ thấy CloudEvent đã nhận được trong nhật ký:

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

6. Cloud Audit Logs

Đối với hàm tiếp theo, hãy tạo một hàm Node.js nhận sự kiện Nhật ký kiểm tra trên đám mây khi một phiên bản máy ảo Compute Engine được tạo. Để phản hồi, công cụ này sẽ thêm một nhãn vào VM mới tạo, chỉ định người tạo VM.

Xác định các VM Compute Engine mới được tạo

Compute Engine phát 2 Nhật ký kiểm tra khi một máy ảo được tạo.

Sự kiện đầu tiên được phát ra khi bắt đầu quá trình tạo VM. Sự kiện thứ hai được phát sau khi VM được tạo.

Trong nhật ký kiểm tra, các trường thao tác sẽ khác nhau, chứa các giá trị first: truelast: true. Nhật ký kiểm tra thứ hai chứa tất cả thông tin chúng ta cần để gắn nhãn một phiên bản. Do đó, chúng ta sẽ sử dụng cờ last: true để phát hiện phiên bản đó trong các hàm Cloud Run.

Thiết lập

Để sử dụng các chức năng của Cloud Audit Log, bạn phải bật Nhật ký kiểm tra cho Eventarc. Bạn cũng cần sử dụng một tài khoản dịch vụ có vai trò eventarc.eventReceiver.

  1. Bật Nhật ký kiểm tra của Cloud Quản trị viên đọc, Đọc dữ liệuGhi dữ liệu cho Compute Engine API.
  2. Cấp vai trò IAM eventarc.eventReceiver cho tài khoản dịch vụ Compute Engine mặc định:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

Tạo hàm

Lớp học lập trình này sử dụng node.js nhưng bạn có thể tìm thấy các ví dụ khác tại https://github.com/GoogleCloudPlatform/eventarc-samples

Tạo tệp package.json

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

Tạo tệp 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;
}

Triển khai

Triển khai hàm:

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

Bây giờ, hãy tạo điều kiện kích hoạt. Lưu ý cách hàm lọc trên Nhật ký kiểm tra cho các lượt chèn Compute Engine bằng cờ --trigger-event-filters.

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

Kiểm thử

Đặt các biến môi trường:

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

Chạy lệnh sau để tạo một máy ảo:

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

Sau khi quá trình tạo máy ảo hoàn tất, bạn sẽ thấy nhãn creator được thêm vào máy ảo trong Cloud Console ở phần Thông tin cơ bản hoặc bằng cách sử dụng lệnh sau:

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

Bạn sẽ thấy nhãn trong đầu ra như ví dụ sau:

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

Dọn dẹp

Hãy nhớ xoá thực thể máy ảo. Bạn sẽ không dùng lại khoá này trong phòng thí nghiệm này.

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

7. Phân tách lưu lượng truy cập

Các hàm Cloud Run hỗ trợ nhiều bản sửa đổi của hàm, phân chia lưu lượng truy cập giữa các bản sửa đổi và khôi phục hàm về phiên bản trước.

Trong bước này, bạn sẽ triển khai 2 bản sửa đổi của một hàm, sau đó chia lưu lượng truy cập giữa 2 bản sửa đổi này theo tỷ lệ 50-50.

Tạo

Tạo một thư mục cho ứng dụng rồi chuyển đến thư mục đó:

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

Tạo một tệp main.py bằng hàm Python đọc một biến môi trường màu và phản hồi bằng Hello World trong màu nền đó:

import os

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

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

Tạo tệp requirements.txt có nội dung sau để chỉ định các phần phụ thuộc:

functions-framework==3.*

Triển khai

Triển khai bản sửa đổi đầu tiên của hàm có nền màu cam:

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

Tại thời điểm này, nếu kiểm thử hàm bằng cách xem trình kích hoạt HTTP (đầu ra URI của lệnh triển khai ở trên) trong trình duyệt, bạn sẽ thấy Hello World có nền màu cam:

36ca0c5f39cc89cf.png

Triển khai bản sửa đổi thứ hai có nền màu vàng:

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

Vì đây là bản sửa đổi mới nhất, nên nếu kiểm thử hàm này, bạn sẽ thấy Hello World có nền màu vàng:

391286a08ad3cdde.png

Phân chia lưu lượng truy cập theo tỷ lệ 50-50

Để phân chia lưu lượng truy cập giữa bản sửa đổi màu cam và màu vàng, bạn cần tìm mã nhận dạng bản sửa đổi của các dịch vụ Cloud Run. Đây là lệnh để xem mã nhận dạng bản sửa đổi:

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

Kết quả sẽ tương tự như kết quả sau:

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

Bây giờ, hãy chia lưu lượng truy cập giữa hai bản sửa đổi này như sau (cập nhật X-XXX theo tên bản sửa đổi của bạn):

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

Kiểm thử

Kiểm thử hàm bằng cách truy cập vào URL công khai của hàm đó. Một nửa thời gian, bạn sẽ thấy bản sửa đổi màu cam và nửa còn lại là bản sửa đổi màu vàng:

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

Hãy xem phần khôi phục, triển khai từng bước và di chuyển lưu lượng truy cập để biết thêm thông tin.

8. Số thực thể tối thiểu

Trong các hàm Cloud Run, bạn có thể chỉ định số lượng tối thiểu các phiên bản hàm cần được duy trì trạng thái hoạt động và sẵn sàng xử lý các yêu cầu. Điều này hữu ích trong việc hạn chế số lần khởi động nguội.

Ở bước này, bạn sẽ triển khai một hàm có quá trình khởi chạy chậm. Bạn sẽ nhận thấy vấn đề khởi động nguội. Sau đó, bạn sẽ triển khai hàm với giá trị phiên bản tối thiểu được đặt thành 1 để loại bỏ tình trạng khởi động nguội.

Tạo

Tạo một thư mục cho ứng dụng rồi chuyển đến thư mục đó:

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

Tạo tệp main.go. Dịch vụ Go này có một hàm init ngủ trong 10 giây để mô phỏng quá trình khởi chạy dài. Nó cũng có một hàm HelloWorld phản hồi các lệnh gọi 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!")
}

Triển khai

Triển khai bản sửa đổi đầu tiên của hàm với giá trị mặc định tối thiểu của phiên bản là 0:

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

Kiểm thử hàm bằng lệnh sau:

# 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

Bạn sẽ thấy độ trễ 10 giây (khởi động nguội) trong cuộc gọi đầu tiên, sau đó sẽ thấy thông báo. Các lệnh gọi tiếp theo sẽ trả về ngay lập tức.

Đặt số thực thể tối thiểu

Để loại bỏ quá trình khởi động nguội trong yêu cầu đầu tiên, hãy triển khai lại hàm với cờ --min-instances được đặt thành 1 như sau:

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

Kiểm thử

Kiểm thử lại hàm:

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

Bạn sẽ không còn thấy độ trễ 10 giây trong yêu cầu đầu tiên nữa. Vấn đề khởi động nguội cho lệnh gọi đầu tiên (sau một thời gian dài không có) đã được giải quyết nhờ số lượng phiên bản tối thiểu!

Hãy xem phần sử dụng số lượng phiên bản tối thiểu để biết thêm thông tin.

9. Xin chúc mừng!

Chúc mừng bạn đã hoàn thành lớp học lập trình này!

Nội dung đã đề cập

  • Tổng quan về các hàm Cloud Run và cách sử dụng tính năng tự động cập nhật hình ảnh cơ sở.
  • Cách viết một hàm phản hồi các lệnh gọi HTTP.
  • Cách viết một hàm phản hồi các thông báo Pub/Sub.
  • Cách viết một hàm phản hồi các sự kiện Cloud Storage.
  • Cách phân chia lưu lượng truy cập giữa hai bản sửa đổi.
  • Cách loại bỏ các lần khởi động nguội bằng số lượng phiên bản tối thiểu.