Comienza a usar las funciones de Cloud Run

1. Introducción

Descripción general

Cloud Run Functions es la oferta de funciones como servicio de Google Cloud potenciada por Cloud Run y Eventarc, lo que brinda un control más avanzado sobre el rendimiento y la escalabilidad, y mayor control sobre el entorno de ejecución de las funciones y los activadores de más de 90 fuentes de eventos.

En este codelab, se te guiará para crear Cloud Run Functions que respondan a llamadas HTTP y se activen con mensajes de Pub/Sub y Registros de auditoría de Cloud.

En este codelab, también se usan actualizaciones automáticas de imágenes base para las implementaciones de funciones. Para ello, se especifica una imagen base con la marca --base-image. Las actualizaciones automáticas de imágenes base para Cloud Run permiten que Google aplique parches de seguridad al sistema operativo y a los componentes del entorno de ejecución del lenguaje de la imagen base automáticamente. No es necesario volver a compilar o volver a implementar el servicio para que se actualice la imagen base. Para obtener más información, consulta las actualizaciones automáticas de imágenes base.

Si prefieres no usar las actualizaciones automáticas de imágenes base, puedes quitar la marca --base-image de los ejemplos que se muestran en este codelab.

Qué aprenderás

  • Descripción general de Cloud Run Functions y cómo usar las actualizaciones automáticas de imágenes base.
  • Cómo escribir una función que responda a llamadas HTTP
  • Cómo escribir una función que responda a los mensajes de Pub/Sub
  • Cómo escribir una función que responda a eventos de Cloud Storage
  • Cómo dividir el tráfico entre dos revisiones
  • Cómo eliminar los inicios en frío con instancias mínimas

2. Configuración y requisitos

Crea una carpeta raíz

Crea una carpeta raíz para todos los ejemplos.

mkdir crf-codelab
cd crf-codelab

Configura variables de entorno

Configura las variables de entorno que se usarán en este codelab.

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

PROJECT_ID=$(gcloud config get-value project)

Habilita las APIs

Habilita todos los servicios necesarios con el siguiente comando:

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

3. Función de HTTP

Para la primera función, creemos una función de Node.js autenticada que responda a solicitudes HTTP. También usaremos un tiempo de espera de 10 minutos para mostrar cómo una función puede tener más tiempo para responder a solicitudes HTTP.

Crear

Crea una carpeta para la app y navega a ella:

mkdir hello-http
cd hello-http

Crea un archivo index.js que responda a las solicitudes 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!');
});

Crea un archivo package.json para especificar las dependencias:

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

Implementar

Sigue estos pasos para implementar la función:

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

Este comando usa paquetes de compilación para transformar el código fuente de tu función en una imagen de contenedor lista para la producción.

Ten en cuenta lo siguiente:

  • La marca --source se usa para indicarle a Cloud Run que compile la función en un servicio basado en contenedores ejecutables.
  • La marca --function (nueva) se usa para establecer el punto de entrada del servicio nuevo como la firma de la función que deseas invocar.
  • La marca --base-image (nueva) especifica el entorno de la imagen base para tu función, como nodejs22, python312, go123, java21, dotnet8, ruby33 o php83. Para obtener más detalles sobre las imágenes base y los paquetes incluidos en cada imagen, consulta Imágenes base de los tiempos de ejecución.
  • (Opcional) La marca --timeout permite que la función tenga un tiempo de espera más largo para responder a las solicitudes HTTP. En este ejemplo, se usan 600 segundos para demostrar un tiempo de respuesta de 10 minutos.
  • (Opcional) El --no-allow-unauthenticated para evitar que se pueda invocar tu función públicamente

Prueba

Prueba la función con los siguientes 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

Deberías ver el mensaje HTTP with Node.js in Cloud Run functions! como respuesta.

4. Función de Pub/Sub

Para la segunda función, crearemos una función de Python que se active con un mensaje de Pub/Sub publicado en un tema específico.

Configura tokens de autenticación de Pub/Sub

Si habilitaste la cuenta de servicio de Pub/Sub el 8 de abril de 2021 o antes de esa fecha, otorga el rol iam.serviceAccountTokenCreator a la cuenta de servicio de 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

Crear

Crea un tema de Pub/Sub para usarlo en la muestra:

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

Crea una carpeta para la app y navega a ella:

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

Crea un archivo main.py que registre un mensaje que contenga el ID de 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'])

Crea un archivo requirements.txt con el siguiente contenido para especificar las dependencias:

functions-framework==3.*

Implementar

Sigue estos pasos para implementar la función:

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

Recupera el número de proyecto que se usará para la identidad de la cuenta de servicio.

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

Crea el activador

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

Prueba

Envía un mensaje al tema para probar la función:

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

Debería ver el CloudEvent recibido en los registros:

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

5. Función de Cloud Storage

Para la siguiente función, crearemos una función de Node.js que responda a eventos de un bucket de Cloud Storage.

Configurar

Para usar las funciones de Cloud Storage, otorga el rol de IAM pubsub.publisher a la cuenta de servicio de 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

Crear

Crea una carpeta para la app y navega a ella:

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

Crea un archivo index.js que simplemente responda a los eventos de 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);
});

Crea un archivo package.json para especificar las dependencias:

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

Implementar

Primero, crea un bucket de Cloud Storage (o usa uno existente que ya tengas):

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

Sigue estos pasos para implementar la función:

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

Una vez que se implementa la función, puedes verla en la sección Cloud Run de la consola de Cloud.

Ahora crea el activador de 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

Prueba

Sube un archivo al bucket para probar la función:

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

Debería ver el CloudEvent recibido en los registros:

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

6. Registros de auditoría de Cloud

Para la siguiente función, crearemos una función de Node.js que recibe un evento de Registros de auditoría de Cloud cuando se crea una instancia de VM de Compute Engine. En respuesta, agrega una etiqueta a la VM recién creada, en la que se especifica el creador de la VM.

Determinar las VMs de Compute Engine recién creadas

Compute Engine emite 2 registros de auditoría cuando se crea una VM.

El primero se emite al principio de la creación de la VM. El segundo se emite después de que se crea la VM.

En los registros de auditoría, los campos de operación son diferentes y contienen valores first: true y last: true. El segundo registro de auditoría contiene toda la información que necesitamos para etiquetar una instancia, por lo que usaremos la marca last: true para detectarla en las funciones de Cloud Run.

Configurar

Para usar las funciones de Registros de auditoría de Cloud, debes habilitar los registros de auditoría para Eventarc. También debes usar una cuenta de servicio con el rol eventarc.eventReceiver.

  1. Habilita los tipos de registros de Registros de auditoría de Cloud Lectura de administración, Lectura de datos y Escritura de datos para la API de Compute Engine.
  2. Otorga a la cuenta de servicio predeterminada de Compute Engine el rol de IAM eventarc.eventReceiver:
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

Crea la función

En este codelab, se usa Node.js, pero puedes encontrar otros ejemplos en https://github.com/GoogleCloudPlatform/eventarc-samples.

Crea un archivo package.json

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

Crea un archivo 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;
}

Implementar

Sigue estos pasos para implementar la función:

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

Ahora, crea el activador. Observa cómo la función filtra los registros de auditoría para las inserciones de Compute Engine con la marca --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

Prueba

Establece las variables de entorno:

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

Ejecuta el siguiente comando para crear una VM:

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

Una vez que se complete la creación de la VM, deberías ver la etiqueta creator agregada en la VM en la consola de Cloud en la sección Información básica o con el siguiente comando:

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

Deberías ver la etiqueta en el resultado como en el siguiente ejemplo:

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

Limpia

Asegúrate de borrar la instancia de VM. No se volverá a usar en este lab.

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

7. División del tráfico

Cloud Run Functions admite múltiples revisiones de tus funciones, lo que permite dividir el tráfico entre ellas o revertir funciones a una versión anterior.

En este paso, implementarás 2 revisiones de una función y, luego, dividirás el tráfico entre ellas en partes iguales.

Crear

Crea una carpeta para la app y navega a ella:

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

Crea un archivo main.py con una función de Python que lea una variable de entorno de color y responda con Hello World en ese color de fondo:

import os

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

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

Crea un archivo requirements.txt con el siguiente contenido para especificar las dependencias:

functions-framework==3.*

Implementar

Implementa la primera revisión de la función con un fondo naranja:

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

En este punto, si pruebas la función viendo el activador HTTP (el URI que se generó con el comando de implementación anterior) en tu navegador, deberías ver Hello World con un fondo naranja:

36ca0c5f39cc89cf.png

Implementa la segunda revisión con un fondo amarillo:

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 esta es la revisión más reciente, si pruebas la función, deberías ver Hello World con un fondo amarillo:

391286a08ad3cdde.png

Divide el tráfico en partes iguales

Para dividir el tráfico entre las revisiones naranja y amarilla, debes encontrar los IDs de revisión de los servicios de Cloud Run. Este es el comando para ver los IDs de revisión:

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

El resultado debería ser similar al siguiente ejemplo:

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

Ahora, divide el tráfico entre estas dos revisiones de la siguiente manera (actualiza X-XXX según los nombres de tus revisiones):

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

Prueba

Para probar la función, visita su URL pública. La mitad del tiempo, deberías ver la revisión naranja y, la otra mitad, la revisión amarilla:

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

Consulta Reversiones, lanzamientos graduales y migración de tráfico para obtener más información.

8. Cantidad mínima de instancias

En Cloud Run Functions, puedes especificar una cantidad mínima de instancias de función que se deben mantener en espera y listas para entregar solicitudes. Esto es útil para limitar la cantidad de inicios en frío.

En este paso, implementarás una función con una inicialización lenta. Observarás el problema de inicio en frío. Luego, implementarás la función con el valor de instancia mínimo establecido en 1 para deshacerte del inicio en frío.

Crear

Crea una carpeta para la app y navega a ella:

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

Crea un archivo main.go. Este servicio de Go tiene una función init que duerme durante 10 segundos para simular una inicialización larga. También tiene una función HelloWorld que responde a las llamadas 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!")
}

Implementar

Implementa la primera revisión de la función con el valor de instancia mínimo predeterminado de cero:

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

Prueba la función con 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

Observarás un retraso de 10 segundos (arranque en frío) en la primera llamada y, luego, verás el mensaje. Las llamadas posteriores deberían devolverse de inmediato.

Establece la cantidad mínima de instancias

Para deshacerte del inicio en frío en la primera solicitud, vuelve a implementar la función con la marca --min-instances establecida en 1 de la siguiente manera:

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

Prueba

Vuelve a probar la función:

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

Ya no deberías ver la demora de 10 segundos en la primera solicitud. El problema de inicio en frío para la primera invocación (después de mucho tiempo sin invocar) desapareció gracias a las instancias mínimas.

Consulta Usa una cantidad mínima de instancias para obtener más información.

9. ¡Felicitaciones!

¡Felicitaciones por completar el codelab!

Temas abordados

  • Descripción general de Cloud Run Functions y cómo usar las actualizaciones automáticas de imágenes base.
  • Cómo escribir una función que responda a llamadas HTTP
  • Cómo escribir una función que responda a los mensajes de Pub/Sub
  • Cómo escribir una función que responda a eventos de Cloud Storage
  • Cómo dividir el tráfico entre dos revisiones
  • Cómo eliminar los inicios en frío con instancias mínimas