Introdução às funções do Cloud Run

1. Introdução

Visão geral

O Cloud Run functions é a solução do Google Cloud que oferece funções como serviço com tecnologia do Cloud Run e do Eventarc. Essa tecnologia traz benefícios, como o controle mais preciso de performance e de escalonabilidade, maior controle do ambiente de execução das funções e dos gatilhos de mais de 90 fontes de eventos.

Neste codelab, você vai aprender a criar funções do Cloud Run que respondem a chamadas HTTP e são acionadas por mensagens do Pub/Sub e pelos Registros de Auditoria do Cloud.

Este codelab também usa atualizações automáticas de imagem de base para implantações de funções especificando uma imagem de base com a flag --base-image. As atualizações automáticas de imagem de base para o Cloud Run permitem que o Google faça patches de segurança para os componentes do sistema operacional e do ambiente de execução de linguagem da imagem de base automaticamente. Não é necessário recriar ou reimplantar o serviço para que a imagem de base seja atualizada. Para mais informações, confira as atualizações automáticas de imagem de base.

Se você preferir não usar as atualizações automáticas de imagem de base, remova a flag --base-image dos exemplos mostrados neste codelab.

O que você vai aprender

  • Visão geral das funções do Cloud Run e como usar atualizações automáticas de imagem de base.
  • Como escrever uma função que responda a chamadas HTTP.
  • Como escrever uma função que responde a mensagens do Pub/Sub.
  • Como escrever uma função que responda a eventos do Cloud Storage.
  • Como dividir o tráfego entre duas revisões.
  • Como eliminar as inicializações a frio com o mínimo de instâncias.

2. Configuração e requisitos

Criar uma pasta raiz

Crie uma pasta raiz para todos os exemplos.

mkdir crf-codelab
cd crf-codelab

Configurar variáveis de ambiente

Defina as variáveis de ambiente que serão usadas neste codelab.

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

PROJECT_ID=$(gcloud config get-value project)

Ativar APIs

Ative todos os serviços necessários:

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

3. Função HTTP

Para a primeira função, vamos criar uma função do Node.js autenticada que responda a solicitações HTTP. Vamos usar também um tempo limite de 10 minutos para mostrar como uma função pode ter mais tempo para responder a solicitações HTTP.

Criar

Crie uma pasta para o app e navegue até ela:

mkdir hello-http
cd hello-http

Crie um arquivo index.js que responda a solicitações 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!');
});

Crie um arquivo package.json para especificar as dependências:

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

Implantar

Implante a função:

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

Esse comando usa buildpacks para transformar o código-fonte da função em uma imagem de contêiner pronta para produção.

Observações:

  • A flag --source é usada para informar ao Cloud Run que crie a função em um serviço baseado em contêiner executável.
  • A flag --function (nova) é usada para definir o ponto de entrada do novo serviço como a assinatura da função que você quer invocar.
  • A flag --base-image (nova) especifica o ambiente da imagem de base para sua função, como nodejs22, python312, go123, java21, dotnet8, ruby33 ou php83. Para mais detalhes sobre as imagens de base e os pacotes incluídos em cada imagem, consulte Imagens de base dos ambientes de execução.
  • (opcional) a flag --timeout permite que a função tenha um tempo limite maior para responder às solicitações HTTP. Neste exemplo, usamos 600 segundos para demonstrar um tempo de resposta de 10 minutos.
  • (opcional) o --no-allow-unauthenticated para evitar que sua função seja invocada publicamente

Teste

Teste a função com os seguintes comandos:

# 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

Você vai receber a mensagem HTTP with Node.js in Cloud Run functions! como resposta.

4. Função do Pub/Sub

Para a segunda função, vamos criar uma função Python acionada por uma mensagem do Pub/Sub publicada em um tópico específico.

Configurar tokens de autenticação do Pub/Sub

Se você ativou a conta de serviço do Pub/Sub até 8 de abril de 2021, conceda o papel iam.serviceAccountTokenCreator a ela:

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

Criar

Crie um tópico do Pub/Sub para usar na amostra:

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

Crie uma pasta para o app e navegue até ela:

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

Crie um arquivo main.py que registre uma mensagem contendo o ID do 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'])

Crie um arquivo requirements.txt com o seguinte conteúdo para especificar as dependências:

functions-framework==3.*

Implantar

Implante a função:

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

Recupere o número do projeto a ser usado para a identidade da conta de serviço.

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

Criar o gatilho

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

Teste

Teste a função enviando uma mensagem ao tópico:

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

Você verá o CloudEvent recebido nos registros:

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

5. Função do Cloud Storage

Para a próxima função, vamos criar uma função Node.js que responda a eventos de um bucket do Cloud Storage.

Configurar

Para usar as funções do Cloud Storage, conceda o papel do IAM pubsub.publisher à conta de serviço do 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

Criar

Crie uma pasta para o app e navegue até ela:

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

Crie um arquivo index.js que simplesmente responda a eventos do 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);
});

Crie um arquivo package.json para especificar as dependências:

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

Implantar

Primeiro, crie um bucket do Cloud Storage ou use um que já exista:

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

Implante a função:

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

Depois que a função for implantada, ela vai aparecer na seção do Cloud Run no console do Cloud.

Agora crie o gatilho do 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

Teste

Teste a função fazendo upload de um arquivo para o bucket:

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

Você verá o CloudEvent recebido nos registros:

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

6. Registros de auditoria do Cloud

Para a próxima função, vamos criar uma função Node.js que recebe um evento dos Registros de Auditoria do Cloud quando uma instância de VM do Compute Engine é criada. Em resposta, ele adiciona um rótulo à VM recém-criada, especificando o criador da VM.

Determinar as VMs recém-criadas do Compute Engine

O Compute Engine emite dois registros de auditoria quando uma VM é criada.

O primeiro é emitido no início da criação da VM. O segundo é emitido após a criação da VM.

Nos registros de auditoria, os campos de operação são diferentes e contêm valores first: true e last: true. O segundo registro de auditoria contém todas as informações necessárias para rotular uma instância. Portanto, vamos usar a flag last: true para detectá-lo no Cloud Run functions.

Configurar

Para usar as funções dos Registros de Auditoria do Cloud, ative os registros de auditoria para o Eventarc. Você também precisa usar uma conta de serviço com o papel eventarc.eventReceiver.

  1. Ative os registros de auditoria do Cloud Leitura de administradores, Leitura de dados e Gravação de dados para a API Compute Engine.
  2. Conceda o papel do IAM eventarc.eventReceiver à conta de serviço padrão do Compute Engine:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

Criar a função

Este codelab usa node.js, mas você pode encontrar outros exemplos em https://github.com/GoogleCloudPlatform/eventarc-samples

Crie um arquivo package.json

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

Crie um arquivo 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;
}

Implantar

Implante a função:

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

Agora crie o gatilho. Observe que a função está filtrando os registros de auditoria das inserções do Compute Engine com a flag --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

Teste

Defina as variáveis de ambiente:

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

Execute o comando a seguir para criar uma identidade de serviço:

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

Quando a criação da VM for concluída, você vai ver o rótulo creator nela, na seção Informações básicas do console do Cloud ou usando o seguinte comando:

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

O rótulo vai aparecer na resposta, como no exemplo a seguir:

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

Limpar

Exclua a instância de VM. Ele não será usado novamente neste laboratório.

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

7. Divisão de tráfego

O Cloud Run functions oferece suporte para múltiplas revisões de uma função. O tráfego é dividido entre as diferentes revisões e a função é revertida para uma versão anterior.

Nesta etapa, você vai implantar duas revisões de uma função e dividir o tráfego entre elas em 50/50.

Criar

Crie uma pasta para o app e navegue até ela:

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

Crie um arquivo main.py com uma função Python que lê uma variável de ambiente de cor e responde com Hello World nessa cor de fundo:

import os

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

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

Crie um arquivo requirements.txt com o seguinte conteúdo para especificar as dependências:

functions-framework==3.*

Implantar

Implante a primeira revisão da função com um plano de fundo laranja:

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

A esta altura, você pode testar a função visualizando o gatilho HTTP (a resposta do URI do comando de implantação acima) no navegador. O resultado será Hello World com um fundo laranja:

36ca0c5f39cc89cf.png

Implante a segunda revisão com um plano de fundo amarelo:

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

Como essa é a revisão mais recente, se você testar a função, vai ver Hello World com um fundo amarelo:

391286a08ad3cdde.png

Dividir o tráfego 50/50

Para dividir o tráfego entre as revisões laranja e amarela, encontre os IDs de revisão dos serviços do Cloud Run. Este é o comando para ver os IDs de revisão:

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

A saída será semelhante a esta:

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

Agora, divida o tráfego entre essas duas revisões da seguinte maneira (atualize o X-XXX de acordo com os nomes das revisões):

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

Teste

Acesse o URL público da função para testá-la. Metade do tempo, você vai ver a revisão laranja e, na outra metade, a amarela:

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

Consulte reversões, lançamentos graduais e migração de tráfego para mais informações.

8. Instâncias mínimas

No Cloud Run functions, é possível especificar um número mínimo de instâncias de função a serem mantidas quentes e prontas para atender às solicitações. Isso é útil para limitar o número de inicializações a frio.

Nesta etapa, você vai implantar uma função com inicialização lenta. Você vai observar o problema de inicialização a frio. Depois, implante a função com o valor mínimo de instância definido como 1, para eliminar a inicialização a frio.

Criar

Crie uma pasta para o app e navegue até ela:

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

Crie um arquivo main.go. Esse serviço Go tem uma função init que fica inativa por 10 segundos para simular uma inicialização longa. Ele também tem uma função HelloWorld que responde a chamadas 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!")
}

Implantar

Implante a primeira revisão da função com o valor mínimo padrão da instância como zero:

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

Teste a função com este comando:

# 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

Você vai observar um atraso de 10 segundos (inicialização a frio) na primeira chamada e depois verá a mensagem. As chamadas subsequentes serão retornadas imediatamente.

Definir um número mínimo de instâncias

Para eliminar a inicialização a frio na primeira solicitação, reimplante a função com a flag --min-instances definida como 1, da seguinte forma:

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

Teste

Teste a função novamente:

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

O atraso de 10 segundos não vai aparecer mais na primeira solicitação. O problema de inicialização a frio para a primeira invocação (após um longo período sem uso) terá desaparecido graças às instâncias mínimas.

Consulte Como usar instâncias mínimas para mais informações.

9. Parabéns!

Parabéns por concluir o codelab!

O que vimos

  • Visão geral das funções do Cloud Run e como usar atualizações automáticas de imagem de base.
  • Como escrever uma função que responda a chamadas HTTP.
  • Como escrever uma função que responde a mensagens do Pub/Sub.
  • Como escrever uma função que responda a eventos do Cloud Storage.
  • Como dividir o tráfego entre duas revisões.
  • Como eliminar as inicializações a frio com o mínimo de instâncias.