Начало работы с функциями Cloud Run

1. Введение

Обзор

Функции Cloud Run – это предложение Google Cloud "Функция как услуга" на базе Cloud Run и Eventarc, которое обеспечивает более расширенный контроль над производительностью и масштабируемостью, а также над средой выполнения функций и триггерами из более чем 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

Для первой функции давайте создадим аутентифицированную функцию Node.js, которая отвечает на HTTP-запросы. Также используем тайм-аут в 10 минут, чтобы показать, как функция может получить больше времени для ответа на HTTP-запросы.

Создать

Создайте папку для приложения и перейдите в нее:

mkdir hello-http
cd hello-http

Создайте файл index.js, который отвечает на 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!');
});

Создайте файл 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

Эта команда использует пакеты сборки, чтобы преобразовать исходный код функции в готовый к работе образ контейнера.

Обратите внимание на следующее:

  • Флаг --source указывает Cloud Run, что функцию нужно встроить в сервис на основе контейнера, который можно запустить.
  • Флаг --function (новый) используется, чтобы задать точку входа нового сервиса в качестве сигнатуры функции, которую вы хотите вызвать.
  • Флаг --base-image (новый) указывает базовую среду образа для функции, например nodejs22, python312, go123, java21, dotnet8, ruby33 или php83. Подробные сведения о базовых образах и пакетах, включенных в каждый образ, можно найти в статье Базовые образы сред выполнения.
  • (необязательно) Флаг --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. Функция издатель-подписчик

Для второй функции давайте создадим функцию Python, запускаемую сообщением Pub/Sub, опубликованным в определенной теме.

Как настроить токены аутентификации издатель-подписчик

Если вы включили сервисный аккаунт Pub/Sub 8 апреля 2021 года или ранее, назначьте ему роль iam.serviceAccountTokenCreator:

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:

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, назначьте роль IAM pubsub.publisher сервисному аккаунту 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 или используйте существующий.

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 Run в Cloud Console.

Теперь создайте триггер 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

Теперь создадим функцию Node.js, которая получает событие журнала аудита Cloud при создании экземпляра виртуальной машины Compute Engine. В ответ на это он добавляет ярлык к новой ВМ, указывая ее создателя.

Как определить недавно созданные виртуальные машины Compute Engine

При создании виртуальной машины Compute Engine создает два журнала аудита.

Первое событие генерируется в начале создания ВМ. Второе – после создания виртуальной машины.

В журналах аудита поля операций различаются и содержат значения first: true и last: true. Второй журнал аудита содержит всю информацию, необходимую для маркировки экземпляра, поэтому мы будем использовать флаг last: true для его обнаружения в функциях Cloud Run.

Настроить

Чтобы использовать функции Cloud Audit Log, необходимо включить журналы аудита для Eventarc. Вам также потребуется сервисный аккаунт с ролью eventarc.eventReceiver.

  1. Включите журналы аудита Cloud Admin Read, Data Read и Data Write для Compute Engine API.
  2. Предоставьте сервисному аккаунту Compute Engine по умолчанию роль IAM eventarc.eventReceiver:
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

Теперь создайте триггер. Обратите внимание, что функция фильтрует журналы аудита для вставок Compute Engine с флагом --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

Тестирование

Задайте переменные среды:

# 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

После создания виртуальной машины вы увидите добавленный ярлык creator в разделе Basic information (Основная информация) в Cloud Console или с помощью следующей команды:

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

В выходных данных вы должны увидеть ярлык, как в следующем примере:

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

Организовать

Удалите экземпляр виртуальной машины. В этой практической работе он больше не понадобится.

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

7. Разделение трафика

Функции Cloud Run поддерживают несколько версий функций, разделение трафика между разными версиями и откат функции к предыдущей версии.

На этом шаге вы развернете две версии функции, а затем разделите трафик между ними поровну.

Создать

Создайте папку для приложения и перейдите в нее:

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. Чтобы посмотреть идентификаторы версий, используйте следующую команду:

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

Тестирование

Протестируйте функцию, перейдя по ее общедоступному URL. В половине случаев вы должны увидеть оранжевую версию, а в другой половине – желтую:

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

Подробнее об откатах, постепенном развертывании и переносе трафика…

8. Минимум экземпляров

В функциях Cloud Run можно указать минимальное количество экземпляров функций, которые должны быть готовы к обработке запросов. Это позволяет ограничить количество холодных запусков.

На этом шаге вы развернете функцию с медленной инициализацией. Вы столкнетесь с проблемой холодного запуска. Затем вы развернете функцию с минимальным значением экземпляра, равным 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.
  • Как распределить трафик между двумя версиями.
  • Как избавиться от холодных запусков с помощью минимального количества экземпляров.