Premiers pas avec les fonctions Cloud Run

1. Introduction

Présentation

Cloud Run Functions est l'offre Functions as a Service de Google Cloud, optimisée par Cloud Run et Eventarc. Elle vous offre un contrôle plus avancé des performances et de l'évolutivité, ainsi qu'un contrôle accru sur l'environnement d'exécution des fonctions et les déclencheurs associés à plus de 90 sources d'événements.

Cet atelier de programmation vous guidera dans la création de fonctions Cloud Run qui répondent aux appels HTTP et qui sont déclenchées par les messages Pub/Sub et les journaux d'audit Cloud.

Cet atelier de programmation utilise également les mises à jour automatiques des images de base pour les déploiements de fonctions en spécifiant une image de base à l'aide de l'indicateur --base-image. Les mises à jour automatiques des images de base pour Cloud Run permettent à Google d'appliquer automatiquement des correctifs de sécurité au système d'exploitation et aux composants d'exécution du langage de l'image de base. Vous n'avez pas besoin de recompiler ni de redéployer votre service pour que l'image de base soit mise à jour. Pour en savoir plus, consultez la section Mises à jour automatiques des images de base.

Si vous préférez ne pas utiliser les mises à jour automatiques des images de base, vous pouvez supprimer l'option --base-image des exemples présentés dans cet atelier de programmation.

Points abordés

  • Présentation de Cloud Run Functions et de l'utilisation des mises à jour automatiques des images de base.
  • Écrire une fonction qui répond aux appels HTTP
  • Comment écrire une fonction qui répond aux messages Pub/Sub
  • Écrire une fonction qui répond aux événements Cloud Storage
  • Découvrez comment répartir le trafic entre deux révisions.
  • Découvrez comment éliminer les démarrages à froid grâce au nombre minimal d'instances.

2. Préparation

Créer un dossier racine

Créez un dossier racine pour tous les exemples.

mkdir crf-codelab
cd crf-codelab

Configurer des variables d'environnement

Définissez les variables d'environnement qui seront utilisées tout au long de cet atelier de programmation.

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

PROJECT_ID=$(gcloud config get-value project)

Activer les API

Activez tous les services nécessaires :

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

3. Fonction HTTP

Pour la première fonction, créons une fonction Node.js authentifiée qui répond aux requêtes HTTP. Utilisons également un délai avant expiration de 10 minutes pour montrer comment une fonction peut disposer de plus de temps pour répondre aux requêtes HTTP.

Créer

Créez un dossier pour l'application et accédez-y :

mkdir hello-http
cd hello-http

Créez un fichier index.js qui répond aux requêtes 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!');
});

Créez un fichier package.json pour spécifier les dépendances :

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

Déployer

Déployez la fonction :

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

Cette commande utilise des buildpacks pour transformer le code source de votre fonction en image de conteneur prête pour la production.

Remarques :

  • L'indicateur --source est utilisé pour indiquer à Cloud Run de compiler la fonction dans un service basé sur un conteneur exécutable.
  • L'indicateur --function (nouveau) est utilisé pour définir le point d'entrée du nouveau service sur la signature de fonction que vous souhaitez appeler.
  • Le flag --base-image (nouveau) spécifie l'environnement d'image de base de votre fonction, comme nodejs22, python312, go123, java21, dotnet8, ruby33 ou php83. Pour en savoir plus sur les images de base et les packages inclus dans chaque image, consultez Images de base des environnements d'exécution.
  • (facultatif) L'option --timeout permet à la fonction de disposer d'un délai d'attente plus long pour répondre aux requêtes HTTP. Dans cet exemple, 600 secondes sont utilisées pour illustrer un délai de réponse de 10 minutes.
  • (facultatif) --no-allow-unauthenticated pour empêcher l'appel public de votre fonction

Tester

Testez la fonction à l'aide des commandes suivantes :

# 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

Le message HTTP with Node.js in Cloud Run functions! doit s'afficher en réponse.

4. Fonction Pub/Sub

Pour la deuxième fonction, créons une fonction Python déclenchée par un message Pub/Sub publié dans un sujet spécifique.

Configurer des jetons d'authentification Pub/Sub

Si vous avez activé le compte de service Pub/Sub le 8 avril 2021 ou avant cette date, attribuez le rôle iam.serviceAccountTokenCreator au compte de service 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

Créer

Créez un sujet Pub/Sub à utiliser pour l'exemple :

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

Créez un dossier pour l'application et accédez-y :

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

Créez un fichier main.py qui enregistre un message contenant l'ID 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'])

Créez un fichier requirements.txt avec le contenu suivant pour spécifier les dépendances :

functions-framework==3.*

Déployer

Déployez la fonction :

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

Récupérez le numéro de projet à utiliser pour l'identité du compte de service.

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

Créer le déclencheur

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

Tester

Testez la fonction en envoyant un message au sujet :

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

L'événement CloudEvent reçu doit apparaître dans les journaux :

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

5. Fonction Cloud Storage

Pour la prochaine fonction, créons une fonction Node.js qui répond aux événements d'un bucket Cloud Storage.

Configurer

Pour utiliser les fonctions Cloud Storage, attribuez le rôle IAM pubsub.publisher au compte de service 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

Créer

Créez un dossier pour l'application et accédez-y :

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

Créez un fichier index.js qui répond simplement aux événements 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);
});

Créez un fichier package.json pour spécifier les dépendances :

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

Déployer

Commencez par créer un bucket Cloud Storage (ou utilisez un bucket existant) :

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

Déployez la fonction :

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

Une fois la fonction déployée, vous pouvez la voir dans la section Cloud Run de la console Cloud.

Créez maintenant le déclencheur 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

Tester

Testez la fonction en important un fichier dans le bucket :

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

L'événement CloudEvent reçu doit apparaître dans les journaux :

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

6. Cloud Audit Logs

Pour la fonction suivante, créons une fonction Node.js qui reçoit un événement Cloud Audit Logs lorsqu'une instance de VM Compute Engine est créée. En réponse, il ajoute un libellé à la VM nouvellement créée, en spécifiant le créateur de la VM.

Identifier les VM Compute Engine nouvellement créées

Compute Engine émet deux journaux d'audit lorsqu'une VM est créée.

La première est émise au début de la création de la VM. Le deuxième est émis après la création de la VM.

Dans les journaux d'audit, les champs d'opération sont différents et contiennent les valeurs first: true et last: true. Le deuxième journal d'audit contient toutes les informations dont nous avons besoin pour étiqueter une instance. Nous utiliserons donc l'indicateur last: true pour le détecter dans les fonctions Cloud Run.

Configurer

Pour utiliser les fonctions Cloud Audit Logs, vous devez activer les journaux d'audit pour Eventarc. Vous devez également utiliser un compte de service avec le rôle eventarc.eventReceiver.

  1. Activez les journaux d'audit Cloud de type Lecture administrateur, Lecture de données et Écriture de données pour l'API Compute Engine.
  2. Attribuez le rôle IAM eventarc.eventReceiver au compte de service Compute Engine par défaut :
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

Créer la fonction

Cet atelier de programmation utilise Node.js, mais vous trouverez d'autres exemples sur https://github.com/GoogleCloudPlatform/eventarc-samples.

Créez un fichier package.json.

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

Créez un fichier 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;
}

Déployer

Déployez la fonction :

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

Créez maintenant le déclencheur. Notez que la fonction filtre les journaux d'audit pour les insertions Compute Engine avec l'indicateur --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

Tester

Définissez les variables d'environnement :

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

Exécutez la commande suivante pour créer une VM :

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

Une fois la VM créée, vous devriez voir le libellé creator ajouté à la VM dans la section Informations de base de la console Cloud ou à l'aide de la commande suivante :

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

Vous devriez voir le libellé dans le résultat, comme dans l'exemple suivant :

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

Effectuer un nettoyage

Veillez à supprimer l'instance de VM. Vous ne l'utiliserez plus dans cet atelier.

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

7. Répartition du trafic

Cloud Run Functions est compatible avec plusieurs révisions de vos fonctions, ce qui vous permet de répartir le trafic entre différentes révisions et d'effectuer un rollback de votre fonction vers une version précédente.

Dans cette étape, vous allez déployer deux révisions d'une fonction, puis répartir le trafic entre elles à 50/50.

Créer

Créez un dossier pour l'application et accédez-y :

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

Créez un fichier main.py avec une fonction Python qui lit une variable d'environnement de couleur et répond avec Hello World dans cette couleur d'arrière-plan :

import os

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

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

Créez un fichier requirements.txt avec le contenu suivant pour spécifier les dépendances :

functions-framework==3.*

Déployer

Déployez la première révision de la fonction avec un arrière-plan orange :

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

À ce stade, si vous testez la fonction en affichant le déclencheur HTTP (l'URI généré par la commande de déploiement ci-dessus) dans votre navigateur, vous devriez voir Hello World avec un arrière-plan orange :

36ca0c5f39cc89cf.png

Déployez la deuxième révision avec un arrière-plan jaune :

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

Comme il s'agit de la dernière révision, si vous testez la fonction, vous devriez voir Hello World avec un arrière-plan jaune :

391286a08ad3cdde.png

Répartir le trafic à 50/50

Pour répartir le trafic entre les révisions orange et jaune, vous devez trouver les ID de révision des services Cloud Run. Voici la commande permettant d'afficher les ID de révision :

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

La sortie devrait ressembler à ce qui suit :

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

Répartissez maintenant le trafic entre ces deux révisions comme suit (mettez à jour le fichier X-XXX en fonction des noms de vos révisions) :

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

Tester

Testez la fonction en accédant à son URL publique. La moitié du temps, vous devriez voir la révision orange et l'autre moitié, la révision jaune :

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

Pour en savoir plus, consultez Rollbacks, déploiements progressifs et migration du trafic.

8. Nombre minimal d'instances

Dans Cloud Run Functions, vous pouvez spécifier un nombre minimal d'instances de fonction à garder en attente et prêtes à diffuser des requêtes. Cela permet de limiter le nombre de démarrages à froid.

Dans cette étape, vous allez déployer une fonction avec une initialisation lente. Vous observerez le problème de démarrage à froid. Vous allez ensuite déployer la fonction avec la valeur minimale d'instance définie sur 1 pour éliminer le démarrage à froid.

Créer

Créez un dossier pour l'application et accédez-y :

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

Créez un fichier main.go. Ce service Go comporte une fonction init qui met en veille pendant 10 secondes pour simuler une longue initialisation. Il dispose également d'une fonction HelloWorld qui répond aux appels 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!")
}

Déployer

Déployez la première révision de la fonction avec la valeur minimale par défaut d'instance (zéro) :

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

Testez la fonction avec cette commande :

# 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

Vous constaterez un délai de 10 secondes (démarrage à froid) lors du premier appel, puis vous verrez le message. Les appels suivants doivent être renvoyés immédiatement.

Définir un nombre minimal d'instances

Pour éviter le démarrage à froid lors de la première requête, redéployez la fonction en définissant l'indicateur --min-instances sur 1, comme suit :

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

Tester

Testez à nouveau la fonction :

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

Vous ne devriez plus voir le délai de 10 secondes dans la première requête. Le problème de démarrage à froid pour la première invocation (après une longue période d'inactivité) a disparu grâce aux instances minimales.

Pour en savoir plus, consultez Utiliser un nombre minimal d'instances.

9. Félicitations !

Bravo ! Vous avez terminé cet atelier de programmation.

Points abordés

  • Présentation de Cloud Run Functions et de l'utilisation des mises à jour automatiques des images de base.
  • Écrire une fonction qui répond aux appels HTTP
  • Comment écrire une fonction qui répond aux messages Pub/Sub
  • Écrire une fonction qui répond aux événements Cloud Storage
  • Découvrez comment répartir le trafic entre deux révisions.
  • Découvrez comment éliminer les démarrages à froid grâce au nombre minimal d'instances.