Wprowadzenie do funkcji Cloud Run

1. Wprowadzenie

Omówienie

Funkcje Cloud Run to usługa Google Cloud typu „funkcje jako usługa” oparta na technologii Cloud Run i Eventarc. Zapewnia ona zaawansowane zarządzanie wydajnością i skalowalnością oraz większą kontrolę nad środowiskiem wykonawczym funkcji i aktywatorami z ponad 90 źródeł zdarzeń.

W tym samouczku dowiesz się, jak tworzyć funkcje Cloud Run, które odpowiadają na wywołania HTTP i są wywoływane przez wiadomości Pub/Sub oraz logi kontrolne Cloud.

W tym samouczku używamy też automatycznych aktualizacji obrazu podstawowego na potrzeby wdrożeń funkcji, określając obraz podstawowy za pomocą flagi --base-image. Automatyczne aktualizacje obrazu bazowego w Cloud Run umożliwiają Google automatyczne wprowadzanie poprawek zabezpieczeń do systemu operacyjnego i komponentów środowiska wykonawczego języka obrazu bazowego. Aby zaktualizować obraz bazowy, nie musisz ponownie tworzyć ani wdrażać usługi. Więcej informacji znajdziesz w artykule automatyczne aktualizacje obrazu bazowego.

Jeśli nie chcesz korzystać z automatycznych aktualizacji obrazu bazowego, możesz usunąć flagę --base-image z przykładów podanych w tym samouczku.

Czego się nauczysz

  • Omówienie funkcji Cloud Run i sposobu korzystania z automatycznych aktualizacji obrazu podstawowego.
  • Jak napisać funkcję, która odpowiada na wywołania HTTP.
  • Jak napisać funkcję, która odpowiada na wiadomości Pub/Sub.
  • Jak napisać funkcję, która reaguje na zdarzenia Cloud Storage.
  • Jak podzielić ruch między 2 wersje.
  • Jak uniknąć uruchomień „na zimno” dzięki minimalnej liczbie instancji.

2. Konfiguracja i wymagania

Tworzenie folderu głównego

Utwórz folder główny dla wszystkich przykładów.

mkdir crf-codelab
cd crf-codelab

Konfigurowanie zmiennych środowiskowych

Ustaw zmienne środowiskowe, które będą używane w tych ćwiczeniach z programowania.

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

PROJECT_ID=$(gcloud config get-value project)

Włącz interfejsy API

Włącz wszystkie niezbędne usługi:

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

3. Funkcja HTTP

W przypadku pierwszej funkcji utwórzmy uwierzytelnioną funkcję Node.js, która odpowiada na żądania HTTP. Użyjmy też 10-minutowego limitu czasu, aby pokazać, jak funkcja może mieć więcej czasu na odpowiedź na żądania HTTP.

Utwórz

Utwórz folder aplikacji i przejdź do niego:

mkdir hello-http
cd hello-http

Utwórz plik index.js, który odpowiada na żądania 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!');
});

Utwórz plik package.json, aby określić zależności:

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

Wdróż

Wdróż funkcję:

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

To polecenie używa pakietów kompilacji do przekształcenia kodu źródłowego funkcji w obraz kontenera gotowy do wykorzystania w środowisku produkcyjnym.

Uwaga:

  • Flaga --source informuje Cloud Run, że funkcja ma zostać skompilowana w usługę opartą na kontenerze, którą można uruchomić.
  • Flaga --function (nowa) służy do ustawiania punktu wejścia nowej usługi jako sygnatury funkcji, którą chcesz wywołać.
  • Flaga --base-image (nowa) określa środowisko obrazu bazowego dla funkcji, np. nodejs22, python312, go123, java21, dotnet8, ruby33 lub php83. Więcej informacji o obrazach bazowych i pakietach zawartych w każdym obrazie znajdziesz w artykule Obrazy bazowe środowisk wykonawczych.
  • (opcjonalnie) flaga --timeout umożliwia funkcji dłuższy czas oczekiwania na odpowiedź na żądania HTTP. W tym przykładzie użyto 600 sekund, aby zademonstrować 10-minutowy czas reakcji.
  • (opcjonalnie) --no-allow-unauthenticated, aby uniemożliwić publiczne wywoływanie funkcji.

Test

Przetestuj funkcję za pomocą tych poleceń:

# 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

W odpowiedzi powinien pojawić się komunikat HTTP with Node.js in Cloud Run functions!.

4. Funkcja Pub/Sub

W przypadku drugiej funkcji utwórzmy funkcję w Pythonie aktywowaną przez wiadomość Pub/Sub opublikowaną w określonym temacie.

Konfigurowanie tokenów uwierzytelniania Pub/Sub

Jeśli konto usługi Pub/Sub zostało włączone 8 kwietnia 2021 r. lub wcześniej, przypisz rolę iam.serviceAccountTokenCreator do konta usługi 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

Utwórz

Utwórz temat Pub/Sub, który będzie używany w przykładzie:

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

Utwórz folder aplikacji i przejdź do niego:

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

Utwórz plik main.py, który rejestruje wiadomość zawierającą identyfikator 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'])

Aby określić zależności, utwórz plik requirements.txt o tej treści:

functions-framework==3.*

Wdróż

Wdróż funkcję:

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

Pobierz numer projektu, który będzie używany jako tożsamość konta usługi.

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

Tworzenie aktywatora

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

Test

Przetestuj funkcję, wysyłając wiadomość do tematu:

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

W logach powinien pojawić się odebrany CloudEvent:

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

5. Funkcja Cloud Storage

W przypadku następnej funkcji utwórzmy funkcję Node.js, która reaguje na zdarzenia z zasobnika Cloud Storage.

Skonfiguruj

Aby korzystać z funkcji Cloud Storage, przypisz rolę pubsub.publisher do konta usługi 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

Utwórz

Utwórz folder aplikacji i przejdź do niego:

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

Utwórz plik index.js, który po prostu reaguje na zdarzenia 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);
});

Utwórz plik package.json, aby określić zależności:

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

Wdróż

Najpierw utwórz zasobnik Cloud Storage (lub użyj istniejącego zasobnika):

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

Wdróż funkcję:

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

Po wdrożeniu funkcji możesz ją zobaczyć w sekcji Cloud Run w Cloud Console.

Teraz utwórz aktywator 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

Test

Przetestuj funkcję, przesyłając plik do zasobnika:

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

W logach powinien pojawić się odebrany CloudEvent:

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

6. Logi kontrolne Cloud

W przypadku następnej funkcji utwórzmy funkcję Node.js, która będzie odbierać zdarzenie Cloud Audit Log, gdy zostanie utworzona instancja maszyny wirtualnej Compute Engine. W odpowiedzi dodaje etykietę do nowo utworzonej maszyny wirtualnej, określając jej twórcę.

Określanie nowo utworzonych maszyn wirtualnych Compute Engine

Gdy tworzona jest maszyna wirtualna, Compute Engine wysyła 2 logi kontrolne.

Pierwsze zdarzenie jest emitowane na początku tworzenia maszyny wirtualnej. Drugi jest emitowany po utworzeniu maszyny wirtualnej.

W logach kontrolnych pola operacji są różne i zawierają wartości first: truelast: true. Drugi dziennik kontrolny zawiera wszystkie informacje potrzebne do oznaczenia instancji, dlatego użyjemy flagi last: true, aby wykryć ją w funkcjach Cloud Run.

Skonfiguruj

Aby korzystać z funkcji Cloud Audit Log, musisz włączyć logi kontrolne dla Eventarc. Musisz też używać konta usługi z rolą eventarc.eventReceiver.

  1. Włącz dzienniki kontroli Cloud dla typów logów Odczyt przez administratora, Odczyt danych i Zapis danych w przypadku interfejsu Compute Engine API.
  2. Przyznaj domyślnemu kontu usługi Compute Engine rolę uprawnień eventarc.eventReceiver:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

Tworzenie funkcji

W tym samouczku używamy node.js, ale inne przykłady znajdziesz na stronie https://github.com/GoogleCloudPlatform/eventarc-samples.

Tworzenie pliku package.json

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

Tworzenie pliku 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;
}

Wdróż

Wdróż funkcję:

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

Teraz utwórz regułę. Zwróć uwagę, jak funkcja filtruje logi kontrolne dotyczące wstawień w Compute Engine z użyciem flagi --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

Test

Ustaw zmienne środowiskowe:

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

Aby utworzyć maszynę wirtualną, uruchom to polecenie:

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

Po utworzeniu maszyny wirtualnej w sekcji Informacje podstawowe w konsoli Google Cloud lub za pomocą tego polecenia powinna być widoczna dodana etykieta creator:

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

W danych wyjściowych powinna się pojawić etykieta, jak w tym przykładzie:

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

Czyszczenie danych

Pamiętaj, aby usunąć instancję maszyny wirtualnej. Nie będzie już używana w tym module.

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

7. Dzielenie ruchu

Funkcje Cloud Run obsługują wiele wersji funkcji, dzielenie ruchu między różne wersje i przywracanie funkcji do poprzedniej wersji.

W tym kroku wdrożysz 2 wersje funkcji, a następnie podzielisz między nie ruch w stosunku 50:50.

Utwórz

Utwórz folder aplikacji i przejdź do niego:

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

Utwórz plik main.py z funkcją Pythona, która odczytuje zmienną środowiskową koloru i odpowiada komunikatem Hello World w tym kolorze tła:

import os

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

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

Aby określić zależności, utwórz plik requirements.txt o tej treści:

functions-framework==3.*

Wdróż

Wdróż pierwszą wersję funkcji z pomarańczowym tłem:

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

Jeśli w tym momencie przetestujesz funkcję, wyświetlając w przeglądarce aktywator HTTP (adres URI wyjściowy powyższego polecenia wdrożenia), powinna się pojawić ikona Hello World na pomarańczowym tle:

36ca0c5f39cc89cf.png

Wdróż drugą wersję z żółtym tłem:

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

Ponieważ jest to najnowsza wersja, po przetestowaniu funkcji powinna się wyświetlić wartość Hello World na żółtym tle:

391286a08ad3cdde.png

Podziel ruch po równo

Aby podzielić ruch między wersje pomarańczową i żółtą, musisz znaleźć identyfikatory wersji usług Cloud Run. Aby wyświetlić identyfikatory wersji, użyj tego polecenia:

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

Dane wyjściowe powinny być podobne do tych:

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

Teraz podziel ruch między te 2 wersje w ten sposób (zaktualizuj X-XXX zgodnie z nazwami wersji):

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

Test

Przetestuj funkcję, otwierając jej publiczny adres URL. W połowie przypadków powinna się wyświetlać wersja pomarańczowa, a w pozostałych – żółta:

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

Więcej informacji znajdziesz w artykule Przywracanie, wdrażanie stopniowe oraz migracja ruchu.

8. Minimalna liczba instancji

W funkcjach Cloud Run możesz określić minimalną liczbę instancji funkcji, które mają być utrzymywane w gotowości do obsługi żądań. Pomaga to ograniczyć liczbę uruchomień „na zimno”.

W tym kroku wdrożysz funkcję z powolną inicjalizacją. Zauważysz problem z uruchomieniem „na zimno”. Następnie wdrożysz funkcję z minimalną liczbą instancji ustawioną na 1, aby wyeliminować uruchomienie „na zimno”.

Utwórz

Utwórz folder aplikacji i przejdź do niego:

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

Utwórz plik main.go. Ta usługa Go ma funkcję init, która usypia ją na 10 sekund, aby zasymulować długą inicjalizację. Ma też funkcję HelloWorld, która odpowiada na wywołania 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!")
}

Wdróż

Wdróż pierwszą wersję funkcji z domyślną minimalną wartością instancji równą zero:

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

Przetestuj funkcję za pomocą tego polecenia:

# 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

Podczas pierwszego połączenia zobaczysz komunikat po 10-sekundowym opóźnieniu (zimny start). Kolejne wywołania powinny być zwracane natychmiast.

Ustawianie minimalnej liczby instancji

Aby uniknąć uruchamiania „na zimno” przy pierwszym żądaniu, ponownie wdróż funkcję z ustawioną na 1 flagą --min-instances w ten sposób:

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

Test

Ponownie przetestuj funkcję:

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

W pierwszym żądaniu nie powinno już być 10-sekundowego opóźnienia. Dzięki minimalnej liczbie instancji problem z uruchomieniem „na zimno” przy pierwszym wywołaniu (po długim czasie bez wywołań) został rozwiązany.

Więcej informacji znajdziesz w artykule o używaniu minimalnej liczby instancji.

9. Gratulacje!

Gratulujemy ukończenia ćwiczenia!

Omówione zagadnienia

  • Omówienie funkcji Cloud Run i sposobu korzystania z automatycznych aktualizacji obrazu podstawowego.
  • Jak napisać funkcję, która odpowiada na wywołania HTTP.
  • Jak napisać funkcję, która odpowiada na wiadomości Pub/Sub.
  • Jak napisać funkcję, która reaguje na zdarzenia Cloud Storage.
  • Jak podzielić ruch między 2 wersje.
  • Jak uniknąć uruchamiania „na zimno” dzięki minimalnej liczbie instancji.