Tutoriel : Dépannage local d'un service Cloud Run

Ce tutoriel explique comment un développeur de services peut résoudre un problème de service Cloud Run à l'aide d'outils Stackdriver pour la découverte et un processus de développement local pour l'enquête.

Cette étude de cas détaillée du guide de dépannage utilise un exemple de projet qui génère des erreurs d'exécution lors du déploiement, que vous devez corriger pour trouver et résoudre le problème.

Vous pouvez utiliser ce tutoriel avec Cloud Run (entièrement géré) ou Cloud Run pour Anthos sur Google Cloud. Vous ne pouvez pas utiliser ce tutoriel avec Cloud Run pour Anthos On-Prem en raison des limitations de la suite d'opérations Google Cloud.

Objectifs

  • Écrire, créer et déployer un service sur Cloud Run.
  • Utiliser Error Reporting et Cloud Logging pour identifier une erreur.
  • Récupérer l'image de conteneur à partir de Container Registry pour une analyse de la cause fondamentale.
  • Corrigez le service de production, puis améliorez-le pour limiter les problèmes à venir.

Coûts

Ce tutoriel utilise des composants facturables de Cloud Platform, dont :

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût.

Les nouveaux utilisateurs de Cloud Platform peuvent bénéficier d'un essai gratuit.

Avant de commencer

  1. Connectez-vous à votre compte Google.

    Si vous n'en possédez pas déjà un, vous devez en créer un.

  2. Dans Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Cloud.

    Accéder à la page de sélection du projet

  3. Vérifiez que la facturation est activée pour votre projet Google Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Activer l'API Cloud Run
  5. Installez et initialisez le SDK Cloud.
  6. Pour Cloud Run pour Anthos sur Google Cloud, installez le composant gcloud kubectl :
    gcloud components install kubectl
  7. Mettez à jour les composants :
    gcloud components update
  8. Si vous utilisez Cloud Run pour Anthos sur Google Cloud, créez un cluster en suivant les instructions de la section Configurer Cloud Run pour Anthos sur Google Cloud.
  9. Si vous utilisez Cloud Run pour Anthos sur Google Cloud, installez curl pour tester le service.
  10. Suivez les instructions pour installer Docker localement.

Configurer les paramètres par défaut de gcloud

Pour configurer gcloud avec les valeurs par défaut pour votre service Cloud Run, procédez comme suit :

  1. Définissez le projet par défaut :

        gcloud config set project PROJECT-ID

    Remplacez PROJECT-ID par le nom du projet que vous avez créé pour ce tutoriel.

  2. Si vous utilisez Cloud Run (entièrement géré), configurez gcloud pour la région choisie :

        gcloud config set run/region REGION

    Remplacez REGION par la région Cloud Run compatible de votre choix.

  3. Si vous utilisez Cloud Run pour Anthos sur Google Cloud, configurez gcloud pour votre cluster :

        gcloud config set run/cluster CLUSTER-NAME
        gcloud config set run/cluster_location REGION

    Remplacer

    • CLUSTER-NAME par le nom que vous avez utilisé pour votre cluster ;
    • REGION par l'emplacement de cluster compatible de votre choix.

Emplacements Cloud Run

Cloud Run est régional, ce qui signifie que l'infrastructure qui exécute vos services Cloud Run est située dans une région spécifique, et gérée par Google pour être disponible de manière redondante dans toutes les zones de cette région.

Lors de la sélection de la région dans laquelle exécuter vos services Cloud Run, vous devez tout d'abord considérer vos exigences en termes de latence, de disponibilité et de durabilité. Vous pouvez généralement sélectionner la région la plus proche de vos utilisateurs, mais vous devez considérer l'emplacement des autres produits Google Cloud utilisés par votre service Cloud Run. L'utilisation conjointe de produits Google Cloud sur plusieurs sites peut avoir une incidence sur la latence et le coût de votre service.

Cloud Run est disponible dans les régions suivantes :

  • asia-east1 (Taïwan)
  • asia-northeast1 (Tokyo)
  • europe-north1 (Finlande)
  • europe-west1 (Belgique)
  • europe-west4 (Pays-Bas)
  • us-central1 (Iowa)
  • us-east1 (Caroline du Sud)
  • us-east4 (Virginie du Nord)
  • us-west1 (Oregon)

Si vous avez déjà créé un service Cloud Run, vous pouvez afficher la région dans le tableau de bord Cloud Run de Cloud Console.

Assemblage du code

Créez un service greeter Cloud Run étape par étape. Nous vous rappelons que ce service crée une erreur d'exécution intentionnellement pour l'exercice de dépannage.

  1. Créez un projet :

    Node.js

    Créez un projet Node.js en définissant le package de service, les dépendances initiales et certaines opérations courantes.

    1. Créez un répertoire hello-service :

      mkdir hello-service
          cd hello-service
          
    2. Créez un projet Node.js en générant un fichier package.json :

      npm init --yes
          npm install --save express@4
          
    3. Ouvrez le nouveau fichier package.json dans votre éditeur et configurez un script start pour exécuter node index.js. Lorsque vous avez terminé, le fichier se présente comme suit :

          {
            "name": "hello-service",
            "version": "1.0.0",
            "description": "",
            "main": "index.js",
            "scripts": {
                "start": "node index.js",
                "test": "echo \"Error: no test specified\" && exit 1"
            },
            "keywords": [],
            "author": "",
            "license": "ISC",
            "dependencies": {
                "express": "^4.17.1"
            }
          }

    Si vous continuez à faire évoluer ce service au-delà du tutoriel immédiat, pensez à renseigner la description et l'auteur, et à évaluer la licence. Pour en savoir plus, consultez la documentation relative à package.json.

    Python

    1. Créez un répertoire hello-service :

      mkdir hello-service
          cd hello-service
          
    2. Créez un fichier requirements.txt et copiez-y vos dépendances :

      Flask==1.1.1
          pytest==5.3.0; python_version > "3.0"
          pytest==4.6.6; python_version < "3.0"
          gunicorn==20.0.4
          

    Go

    1. Créez un répertoire hello-service :

      mkdir hello-service
          cd hello-service
          
    2. Créez un projet Go en initialisant un nouveau module Go :

      go mod init example.com/hello-service
          

    Vous pouvez mettre à jour le nom spécifique si vous le souhaitez : vous devez mettre à jour le nom si le code est publié dans un dépôt de code accessible sur le Web.

    Java

    1. Créez un projet Maven :

      mvn archetype:generate \
            -DgroupId=com.example.cloudrun \
            -DartifactId=hello-service \
            -DarchetypeArtifactId=maven-archetype-quickstart \
            -DinteractiveMode=false
          
    2. Copiez les dépendances dans votre liste de dépendances pom.xml (entre les éléments <dependencies>) :

      <dependency>
            <groupId>com.sparkjava</groupId>
            <artifactId>spark-core</artifactId>
            <version>2.9.1</version>
          </dependency>
          <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.3.0-alpha5</version>
          </dependency>
          <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
          </dependency>
          <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.30</version>
          </dependency>
      
    3. Copiez le paramètre de création dans votre pom.xml (sous les éléments <dependencies>) :

      <build>
            <plugins>
              <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>jib-maven-plugin</artifactId>
                <version>2.1.0</version>
                <configuration>
                  <to>
                    <image>gcr.io/PROJECT_ID/hello-service</image>
                  </to>
                </configuration>
              </plugin>
            </plugins>
          </build>
      

  2. Créez un service HTTP pour gérer les requêtes entrantes :

    Node.js

    const express = require('express');
        const app = express();
    
        app.get('/', (req, res) => {
          console.log('hello: received request.');
    
          const {NAME} = process.env;
          if (!NAME) {
            // Plain error logs do not appear in Stackdriver Error Reporting.
            console.error('Environment validation failed.');
            console.error(new Error('Missing required server parameter'));
            return res.status(500).send('Internal Server Error');
          }
          res.send(`Hello ${NAME}!`);
        });
        const port = process.env.PORT || 8080;
        app.listen(port, () => {
          console.log(`hello: listening on port ${port}`);
        });

    Python

    from flask import Flask
        import json
        import os
        import sys
    
        app = Flask(__name__)
    
        @app.route("/", methods=["GET"])
        def index():
            print("hello: received request.")
    
            NAME = os.getenv("NAME")
    
            if not NAME:
                print("Environment validation failed.")
                raise Exception("Missing required service parameter.")
    
            # Flush the stdout to avoid log buffering.
            sys.stdout.flush()
    
            return f"Hello {NAME}"
        if __name__ == "__main__":
            PORT = int(os.getenv("PORT")) if os.getenv("PORT") else 8080
    
            # This is used when running locally. Gunicorn is used to run the
            # application on Cloud Run. See entrypoint in Dockerfile.
            app.run(host="127.0.0.1", port=PORT, debug=True)

    Go

    
        // Sample hello demonstrates a difficult to troubleshoot service.
        package main
    
        import (
        	"fmt"
        	"log"
        	"net/http"
        	"os"
        )
    
        func main() {
        	log.Print("hello: service started")
    
        	http.HandleFunc("/", helloHandler)
    
        	port := os.Getenv("PORT")
        	if port == "" {
        		port = "8080"
        		log.Printf("Defaulting to port %s", port)
        	}
    
        	log.Printf("Listening on port %s", port)
        	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
        }
    
        func helloHandler(w http.ResponseWriter, r *http.Request) {
        	log.Print("hello: received request")
    
        	name := os.Getenv("NAME")
        	if name == "" {
        		log.Printf("Missing required server parameter")
        		// The panic stack trace appears in Stackdriver Error Reporting.
        		panic("Missing required server parameter")
        	}
    
        	fmt.Fprintf(w, "Hello %s!\n", name)
        }
        

    Java

    import static spark.Spark.get;
        import static spark.Spark.port;
    
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
    
        public class App {
    
          private static final Logger logger = LoggerFactory.getLogger(App.class);
    
          public static void main(String[] args) {
            int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
            port(port);
    
            get(
                "/",
                (req, res) -> {
                  logger.info("Hello: received request.");
                  String name = System.getenv("NAME");
                  if (name == null) {
                    // Standard error logs do not appear in Stackdriver Error Reporting.
                    System.err.println("Environment validation failed.");
                    String msg = "Missing required server parameter";
                    logger.error(msg, new Exception(msg));
                    res.status(500);
                    return "Internal Server Error";
                  }
                  res.status(200);
                  return String.format("Hello %s!", name);
                });
          }
        }

  3. Créez un Dockerfile pour définir l'image de conteneur utilisée pour déployer le service :

    Node.js

    
        # Use the official lightweight Node.js 10 image.
        # https://hub.docker.com/_/node
        FROM node:10-slim
    
        # Create and change to the app directory.
        WORKDIR /usr/src/app
    
        # Copy application dependency manifests to the container image.
        # A wildcard is used to ensure copying both package.json AND package-lock.json (when available).
        # Copying this first prevents re-running npm install on every code change.
        COPY package*.json ./
    
        # Install production dependencies.
        # If you add a package-lock.json, speed your build by switching to 'npm ci'.
        # RUN npm ci --only=production
        RUN npm install --only=production
    
        # Copy local code to the container image.
        COPY . ./
    
        # Run the web service on container startup.
        CMD [ "npm", "start" ]
        

    Python

    
        # Use the official Python image.
        # https://hub.docker.com/_/python
        FROM python:3.8
    
        # Copy application dependency manifests to the container image.
        # Copying this separately prevents re-running pip install on every code change.
        COPY requirements.txt .
    
        # Install production dependencies.
        RUN pip install -r requirements.txt
    
        # Copy local code to the container image.
        ENV APP_HOME /app
        WORKDIR $APP_HOME
        COPY . .
    
        # Run the web service on container startup.
        # Use gunicorn webserver with one worker process and 8 threads.
        # For environments with multiple CPU cores, increase the number of workers
        # to be equal to the cores available.
        CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
        

    Go

    
        # Use the offical golang image to create a binary.
        # This is based on Debian and sets the GOPATH to /go.
        # https://hub.docker.com/_/golang
        FROM golang:1.13-buster as builder
    
        # Create and change to the app directory.
        WORKDIR /app
    
        # Retrieve application dependencies.
        # This allows the container build to reuse cached dependencies.
        # Expecting to copy go.mod and if present go.sum.
        COPY go.* ./
        RUN go mod download
    
        # Copy local code to the container image.
        COPY . ./
    
        # Build the binary.
        RUN go build -mod=readonly -v -o server
    
        # Use the official Debian slim image for a lean production container.
        # https://hub.docker.com/_/debian
        # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
        FROM debian:buster-slim
        RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
            ca-certificates && \
            rm -rf /var/lib/apt/lists/*
    
        # Copy the binary to the production image from the builder stage.
        COPY --from=builder /app/server /server
    
        # Run the web service on container startup.
        CMD ["/server"]
        

    Java

    Cet exemple utilise Jib pour créer des images Docker à l'aide d'outils Java courants. Jib optimise les créations de conteneurs sans fichier Docker ni fichier Docker installé. Découvrez comment créer des conteneurs Java avec Jib.

    <plugin>
          <groupId>com.google.cloud.tools</groupId>
          <artifactId>jib-maven-plugin</artifactId>
          <version>2.1.0</version>
          <configuration>
            <to>
              <image>gcr.io/PROJECT_ID/hello-service</image>
            </to>
          </configuration>
        </plugin>
    

Transmettre le code

La transmission du code se déroule en trois étapes : création d'une image de conteneur avec Cloud Build, importation de l'image de conteneur dans Container Registry, puis déploiement de l'image de conteneur dans Cloud Run ou Cloud Run sur GKE.

Pour transmettre votre code, procédez comme suit :

  1. Créez votre conteneur et publiez-le dans Container Registry :

    Node.js

        gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    PROJECT-ID correspond à l'ID de votre projet GCP. Vous pouvez vérifier l'ID de votre projet actuel avec gcloud config get-value project.

    En cas de réussite, un message SUCCESS apparaît contenant l'ID, l'heure de création et le nom de l'image. Celle-ci est stockée dans Container Registry et peut être réutilisée au besoin.

    Python

        gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    PROJECT-ID correspond à l'ID de votre projet GCP. Vous pouvez vérifier l'ID de votre projet actuel avec gcloud config get-value project.

    En cas de réussite, un message SUCCESS apparaît contenant l'ID, l'heure de création et le nom de l'image. Celle-ci est stockée dans Container Registry et peut être réutilisée au besoin.

    Go

        gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service

    PROJECT-ID correspond à l'ID de votre projet GCP. Vous pouvez vérifier l'ID de votre projet actuel avec gcloud config get-value project.

    En cas de réussite, un message SUCCESS apparaît contenant l'ID, l'heure de création et le nom de l'image. Celle-ci est stockée dans Container Registry et peut être réutilisée au besoin.

    Java

    mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/hello-service

    PROJECT-ID correspond à l'ID de votre projet GCP. Vous pouvez vérifier l'ID de votre projet actuel avec gcloud config get-value project.

    En cas de réussite, un message BUILD SUCCESS apparaît. Celle-ci est stockée dans Container Registry et peut être réutilisée si vous le souhaitez.

  2. Exécutez la commande suivante pour déployer votre application :

        gcloud run deploy hello-service --image gcr.io/PROJECT_ID/hello-service

    Remplacez PROJECT_ID par l'ID de votre projet GCP. hello-service est le nom de l'image du conteneur et le nom du service Cloud Run. Notez que l'image du conteneur est déployée sur le service et la région (Cloud Run) ou sur le cluster (Cloud Run pour Anthos sur Google Cloud) que vous avez précédemment configurés dans la section Configurer gcloud.

    Si vous déployez sur Cloud Run (entièrement géré), répondez y, "oui" à l'invite "Autoriser sans authentification". Pour en savoir plus sur l'authentification basée sur IAM, consultez la page Gérer l'accès.

    Patientez jusqu'à la fin du déploiement, soit environ 30 secondes. En cas de réussite, la ligne de commande affiche l'URL du service.

Essayez-le !

Essayez le service pour vérifier que vous l'avez déployé. Les requêtes doivent échouer avec une erreur HTTP 500 ou 503 (membres de la classe d'erreurs serveur 5xx). Le tutoriel vous explique comment résoudre cette erreur de réponse.

  • Pour Cloud Run (entièrement géré), une URL de navigation est automatiquement attribuée au service.

    Accédez à cette URL avec votre navigateur Web :

    1. Ouvrez un navigateur Web.

    2. Recherchez l'URL de service générée par la commande de déploiement précédente.

      Si la commande déploiement n'a pas fourni d'URL, une erreur s'est produite. Examinez le message d'erreur et agissez en conséquence : en l'absence de conseils exploitables, consultez le guide de dépannage et réessayez d'utiliser la commande de déploiement.

    3. Accédez à cette URL en la copiant dans la barre d'adresse de votre navigateur, puis en appuyant sur ENTRÉE.

    4. Consultez l'erreur HTTP 500 ou HTTP 503.

    Si vous recevez une erreur HTTP 403, vous avez peut-être refusé allow unauthenticated invocations à l'invite de déploiement. Accordez un accès non authentifié au service pour résoudre ce problème :

    gcloud run services add-iam-policy-binding hello-service \
          --member="allUsers" \
          --role="roles/run.invoker"
        

    Pour en savoir plus, consultez la section Autoriser l'accès public (non authentifié).

  • Pour Cloud Run pour Anthos sur Google Cloud, sans domaine personnalisé, aucune URL de navigation n'est fournie pour votre service.

    Utilisez plutôt l'URL fournie et l'adresse IP de la passerelle d'entrée du service pour créer une commande curl pouvant envoyer des requêtes à votre service :

    1. Pour obtenir l'adresse IP externe de la passerelle d'entrée Istio, procédez comme suit :
      kubectl get svc ISTIO-GATEWAY -n NAMESPACE 
      Remplacez ISTIO-GATEWAY et NAMESPACE comme suit :
      Version du cluster ISTIO-GATEWAY NAMESPACE
      1.15.3-gke.19 et version ultérieure
      1.14.3-gke.12 et version ultérieure
      1.13.10-gke.8 et version ultérieure
      istio-ingress gke-system
      Toutes les autres versions istio-ingressgateway istio-system
      où le résultat ressemble à ce qui suit :
          NAME            TYPE           CLUSTER-IP     EXTERNAL-IP  PORT(S)
          ISTIO-GATEWAY    LoadBalancer   XX.XX.XXX.XX   pending     80:32380/TCP,443:32390/TCP,32400:32400/TCP
          
      L'adresse externe EXTERNAL-IP de l'équilibreur de charge est l'adresse IP que vous devez utiliser.
    2. Exécutez une commande curl en utilisant cette adresse GATEWAY_IP dans l'URL.

          curl -G -H "Host: SERVICE-DOMAIN" https://EXTERNAL-IP/

      Remplacez SERVICE-DOMAIN par le domaine par défaut de votre service. Pour ce faire, utilisez l'URL par défaut et supprimez le protocole http://.

    3. Consultez l'erreur HTTP 500 ou HTTP 503.

    Si votre cluster est configuré avec un domaine par défaut routable, ignorez les étapes ci-dessus et copiez l'URL dans votre navigateur Web.

Examiner le problème

Vérifiez si l'erreur HTTP 5xx rencontrée ci-dessus dans Essayez-le ! s'est produite en tant qu'erreur d'exécution de production. Ce tutoriel vous guide à travers un processus formel de gestion. Bien que les processus de résolution des erreurs de production varient considérablement, ce tutoriel présente une séquence particulière d'étapes pour montrer l'application d'outils et de techniques utiles.

Pour examiner ce problème, procédez comme suit :

  • Rassemblez plus de détails sur l'erreur signalée afin d'effectuer une analyse approfondie et de définir une stratégie d'atténuation.
  • Pour réduire les répercussions sur l'utilisateur, vous pouvez décider d'appliquer une correction ou un rollback à une version connue et opérationnelle.
  • Reproduisez l'erreur pour vérifier que les informations correctes ont bien été collectées et que l'erreur n'est pas ponctuelle.
  • Effectuez une analyse des causes fondamentales du bug pour trouver le code, la configuration ou le processus à l'origine de cette erreur.

Au début de l'enquête, vous avez une URL, un horodatage et un message "Erreur de serveur interne".

Collecte d'informations supplémentaires

Rassemblez plus d'informations sur le problème pour comprendre ce qui s'est passé et déterminer la marche à suivre.

Utilisez les outils Stackdriver disponibles pour collecter plus de détails :

  1. Utilisez la console Error Reporting, qui fournit un tableau de bord avec des détails et un suivi récurrent des erreurs avec une trace de la pile reconnue.

    Accéder à la console Error Reporting

    Capture d'écran de la liste des erreurs, y compris les colonnes
    Liste des erreurs enregistrées. Les erreurs sont regroupées par message selon les versions, les services et les plates-formes.
  2. Cliquez sur l'erreur pour afficher les détails des traces de pile, en notant les appels de fonction effectués juste avant l'erreur.

    Capture d'écran d'une seule trace de la pile analysée, montrant un profil commun de cette erreur.
    L'exemple de trace de la pile sur la page Détails de l'erreur affiche une seule instance de l'erreur. Vous pouvez examiner les instances individuelles.
  3. Utilisez Cloud Logging pour examiner la séquence des opérations menant au problème, y compris les messages d'erreur qui ne sont pas inclus dans la console Error Reporting en raison de l'absence de trace de pile d'erreur reconnue :

    Accéder à la console Cloud Logging

    • Si vous utilisez Cloud Run (entièrement géré), sélectionnez Révision dans Cloud Run > hello-service dans la première liste déroulante. Les entrées de journal générées par votre service seront alors filtrées.

    • Si vous utilisez Cloud Run pour Anthos sur Google Cloud, sélectionnez Conteneur Kubernetes > hello-service dans la première liste déroulante.

    En savoir plus sur l'affichage des journaux dans Cloud Run.

Procéder au rollback vers la version correcte

S'il s'agit d'un service établi, connu pour fonctionner, une révision précédente du service sera effectuée sur Cloud Run. Dans ce tutoriel, nous utilisons un nouveau service sans version précédente. Vous ne pouvez donc pas effectuer de rollback.

Toutefois, si vous disposez d'un service avec des versions précédentes à partir desquelles pouvez procéder à un rollback, consultez la section Afficher les détails d'une révision pour extraire le nom du conteneur et les détails de configuration nécessaires pour créer un déploiement opérationnel de votre service.

Reproduire l'erreur

En utilisant les détails que vous avez obtenus précédemment, vérifiez que le problème se produit régulièrement dans les conditions de test.

Envoyez la même requête HTTP en essayant de nouveau, et vérifiez si la même erreur et les mêmes détails sont signalés. L'affichage des détails de l'erreur peut prendre un certain temps.

Étant donné que l'exemple de service de ce tutoriel est en lecture seule et ne déclenche aucun effet secondaire compliqué, la reproduction des erreurs en production est sécurisée. Toutefois, pour de nombreux services réels, ce ne sera pas le cas : vous devrez peut-être reproduire les erreurs dans un environnement de test ou limiter cette étape à une enquête locale.

La reproduction de l'erreur permet d'établir le contexte pour la poursuite des travaux. Par exemple, si les développeurs ne peuvent pas reproduire l'erreur, une analyse plus approfondie peut nécessiter une instrumentation supplémentaire du service.

Effectuer une analyse des causes fondamentales

L'analyse des causes fondamentales est une étape importante d'un dépannage efficace pour vous permettre de résoudre le problème plutôt qu'un symptôme.

Auparavant, dans ce tutoriel, vous avez reproduit le problème sur Cloud Run, ce qui confirme qu'il est actif lorsque le service est hébergé sur Cloud Run. À présent, reproduisez le problème localement pour déterminer s'il est isolé du code ou s'il apparaît uniquement dans l'hébergement de production.

  1. Si vous n'avez pas utilisé Docker CLI en local avec Container Registry, authentifiez-le avec gcloud :

        gcloud auth configure-docker

    Pour découvrir d'autres approches, consultez la page Méthodes d'authentification de Container Registry.

  2. Si le nom de l'image de conteneur utilisé le plus récemment n'est pas disponible, la description du service contient les informations de l'image de conteneur la plus récemment déployée :

        gcloud run services describe hello-service

    Recherchez le nom de l'image du conteneur dans l'objet spec. Une commande plus ciblée peut la récupérer directement :

        gcloud run services describe hello-service \
           --format="value(spec.template.spec.containers.image)"

    Cette commande indique un nom d'image de conteneur tel que gcr.io/PROJECT_ID/hello-service.

  3. Extrayez l'image du conteneur à partir de Container Registry vers votre environnement. Cette opération peut prendre plusieurs minutes lors du téléchargement de l'image du conteneur :

        docker pull gcr.io/PROJECT_ID/hello-service

    Les mises à jour ultérieures de l'image de conteneur qui réutilisent ce nom peuvent être récupérées avec la même commande. Si vous ignorez cette étape, la commande docker run ci-dessous extrait une image de conteneur si celle-ci n'est pas présente sur l'ordinateur local.

  4. Exécutez localement pour vérifier que le problème n'est pas unique au sein de Cloud Run :

        PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
           gcr.io/PROJECT_ID/hello-service

    En détaillant les éléments de la commande ci-dessus,

    • La variable d'environnement PORT est utilisée par le service pour déterminer le port d'écoute à l'intérieur du conteneur.
    • La commande run démarre le conteneur, affichant par défaut la commande "entrypoint" définie dans le fichier Dockerfile ou une image de conteneur parent.
    • L'indicateur --rm supprime l'instance de conteneur en cas de fermeture.
    • L'indicateur -e attribue une valeur à une variable d'environnement. -e PORT=$PORT propage la variable PORT du système local dans le conteneur avec le même nom de variable.
    • L'indicateur -p publie le Container as a Service disponible sur localhost sur le port 9000. Les requêtes vers localhost:9000 seront acheminées vers le conteneur sur le port 8080. Cela signifie que le numéro de port de sortie utilisé n'indiquera pas comment accéder au service.
    • L'argument final gcr.io/PROJECT_ID/hello-service est une image de conteneur tag, un libellé lisible pour l'identifiant de hachage sha256 d'une image de conteneur. S'il n'est pas disponible localement, Docker tente de récupérer l'image à partir d'un registre distant.

    Dans votre navigateur, ouvrez http://localhost:9000. Vérifiez le résultat de terminal en cas de messages d'erreur qui correspondent à ceux de la suite d'opérations de Google Cloud.

    Si le problème ne peut être reproduit localement, il peut être unique à l'environnement Cloud Run. Consultez le guide de dépannage de Cloud Run pour connaître les domaines spécifiques à examiner.

    Dans ce cas, l'erreur est reproduite localement.

Maintenant que l'erreur est confirmée par deux fois comme étant persistante et causée par le code de service au lieu de la plate-forme d'hébergement, il est temps d'examiner le code de plus près.

Pour les besoins de ce tutoriel, vous pouvez supposer que le code contenu dans le conteneur et le code du système local sont identiques.

Revoyez la trace de la pile du rapport d'erreurs et la référence croisée avec le code pour trouver les lignes spécifiques qui sont responsables du problème.

Node.js

Recherchez la source du message d'erreur dans le fichier index.js autour du numéro de ligne indiqué dans la trace de la pile affichée dans les journaux :
const {NAME} = process.env;
    if (!NAME) {
      // Plain error logs do not appear in Stackdriver Error Reporting.
      console.error('Environment validation failed.');
      console.error(new Error('Missing required server parameter'));
      return res.status(500).send('Internal Server Error');
    }

Python

Recherchez la source du message d'erreur dans le fichier main.py autour du numéro de ligne indiqué dans la trace de la pile affichée dans les journaux :
NAME = os.getenv("NAME")

    if not NAME:
        print("Environment validation failed.")
        raise Exception("Missing required service parameter.")

Go

Recherchez la source du message d'erreur dans le fichier main.go autour du numéro de ligne indiqué dans la trace de la pile affichée dans les journaux :

name := os.Getenv("NAME")
    if name == "" {
    	log.Printf("Missing required server parameter")
    	// The panic stack trace appears in Stackdriver Error Reporting.
    	panic("Missing required server parameter")
    }

Java

Recherchez la source du message d'erreur dans le fichier App.java autour du numéro de ligne indiqué dans la trace de la pile affichée dans les journaux :

String name = System.getenv("NAME");
    if (name == null) {
      // Standard error logs do not appear in Stackdriver Error Reporting.
      System.err.println("Environment validation failed.");
      String msg = "Missing required server parameter";
      logger.error(msg, new Exception(msg));
      res.status(500);
      return "Internal Server Error";
    }

En examinant ce code, les actions suivantes sont effectuées lorsque la variable d'environnement NAME n'est pas définie :

  • Une erreur est consignée dans la suite d'opérations de Google Cloud.
  • Une réponse d'erreur HTTP est envoyée.

Le problème est causé par une variable manquante, mais la cause fondamentale est plus spécifique : la modification du code contribuant à une trop grande dépendance envers une variable d'environnement n'incluait pas les modifications liées aux scripts de déploiement et à la documentation relative aux exigences d'exécution.

Corriger la cause fondamentale

Maintenant que nous avons collecté le code et identifié la cause fondamentale potentielle, nous pouvons prendre des mesures pour la corriger.

  • Vérifiez si le service fonctionne localement avec l'environnement NAME disponible sur place :

    1. Exécutez le conteneur localement avec la variable d'environnement ajoutée :

          PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
           -e NAME="Local World!" \
           gcr.io/PROJECT_ID/hello-service
    2. Accédez à http://localhost:9000 dans votre navigateur.

    3. "Hello Local World !" apparaît sur la page.

  • Modifiez l'environnement de service Cloud Run en cours pour inclure cette variable :

    1. Exécutez la commande de mise à jour des services pour ajouter une variable d'environnement :

      gcloud run services update hello-service \
            --set-env-vars NAME=Override
          
    2. Patientez quelques secondes pendant que Cloud Run crée une nouvelle révision basée sur la révision précédente avec la nouvelle variable d'environnement ajoutée.

  • Vérifier que le problème de service est maintenant corrigé :

    1. Accédez à l'URL du service Cloud Run dans votre navigateur.
    2. "Hello Override !" apparaît sur la page.
    3. Vérifiez qu'aucune erreur ni aucun message inattendu n'apparaît dans Cloud Logging ou Error Reporting.

Accélération des prochains dépannages

Dans cet exemple de problème de production, l'erreur était liée à la configuration opérationnelle. Des modifications sont apportées au code afin de minimiser l'impact de ce problème à l'avenir.

  • Améliorez le journal d'erreurs pour inclure des détails plus spécifiques.
  • Au lieu de renvoyer une erreur, demandez au service de revenir à une valeur par défaut sûre. Si l'utilisation d'une valeur par défaut représente un changement de la fonctionnalité normale, utilisez un message d'avertissement à des fins de surveillance.

Passons maintenant à la suppression d'une trop grande dépendance envers la variable d'environnement NAME.

  1. Supprimez le code de gestion NAME existant :

    Node.js

    const {NAME} = process.env;
        if (!NAME) {
          // Plain error logs do not appear in Stackdriver Error Reporting.
          console.error('Environment validation failed.');
          console.error(new Error('Missing required server parameter'));
          return res.status(500).send('Internal Server Error');
        }

    Python

    NAME = os.getenv("NAME")
    
        if not NAME:
            print("Environment validation failed.")
            raise Exception("Missing required service parameter.")

    Go

    name := os.Getenv("NAME")
        if name == "" {
        	log.Printf("Missing required server parameter")
        	// The panic stack trace appears in Stackdriver Error Reporting.
        	panic("Missing required server parameter")
        }

    Java

    String name = System.getenv("NAME");
        if (name == null) {
          // Standard error logs do not appear in Stackdriver Error Reporting.
          System.err.println("Environment validation failed.");
          String msg = "Missing required server parameter";
          logger.error(msg, new Exception(msg));
          res.status(500);
          return "Internal Server Error";
        }

  2. Ajoutez un nouveau code définissant une valeur de remplacement :

    Node.js

    const NAME = process.env.NAME || 'World';
        if (!process.env.NAME) {
          console.log(
            JSON.stringify({
              severity: 'WARNING',
              message: `NAME not set, default to '${NAME}'`,
            })
          );
        }

    Python

    NAME = os.getenv("NAME")
    
        if not NAME:
            NAME = "World"
            error_message = {
                "severity": "WARNING",
                "message": f"NAME not set, default to {NAME}",
            }
            print(json.dumps(error_message))

    Go

    name := os.Getenv("NAME")
        if name == "" {
        	name = "World"
        	log.Printf("warning: NAME not set, default to %s", name)
        }

    Java

    String name = System.getenv().getOrDefault("NAME", "World");
        if (System.getenv("NAME") == null) {
          logger.warn(String.format("NAME not set, default to %s", name));
        }

  3. Effectuez un test local en recréant et en exécutant le conteneur dans les cas de configuration concernés :

    Node.js

        docker build --tag gcr.io/PROJECT_ID/hello-service .

    Python

        docker build --tag gcr.io/PROJECT_ID/hello-service .

    Go

        docker build --tag gcr.io/PROJECT_ID/hello-service .

    Java

    mvn compile jib:build

    Vérifiez que la variable d'environnement NAME fonctionne toujours :

        PORT=8080 && docker run --rm -e $PORT -p 9000:$PORT \
         -e NAME="Robust World" \
         gcr.io/PROJECT_ID/hello-service

    Vérifiez que le service fonctionne sans la variable NAME :

        PORT=8080 && docker run --rm -e $PORT -p 9000:$PORT \
         gcr.io/PROJECT_ID/hello-service

    Si le service ne renvoie pas de résultat, vérifiez que la suppression du code lors de la première étape n'a pas supprimé les lignes supplémentaires, telles que celles utilisées pour rédiger la réponse.

  4. Déployez-le en consultant à nouveau la section Déployer votre code.

    Chaque déploiement sur un service crée une nouvelle révision et commence automatiquement à acheminer le trafic une fois prêt.

    Pour effacer les variables d'environnement définies précédemment :

    gcloud run services update hello-service --clear-env-vars

Ajoutez la nouvelle fonctionnalité de la valeur par défaut à la couverture des tests automatisés pour le service.

Rechercher d'autres problèmes dans les journaux

D'autres problèmes peuvent survenir dans la visionneuse de journaux de ce service. Par exemple, un appel système non compatible apparaîtra dans les journaux comme une "Limitation du bac à sable de conteneur".

Par exemple, les services Node.js génèrent parfois ce message de journal :

Container Sandbox Limitation: Unsupported syscall statx(0xffffff9c,0x3e1ba8e86d88,0x0,0xfff,0x3e1ba8e86970,0x3e1ba8e86a90). Please, refer to https://gvisor.dev/c/linux/amd64/statx for more information.
    

Dans ce cas, la non-compatibilité n'a aucune incidence sur l'exemple de service "hello-service".

Nettoyer

Si vous avez créé un projet pour ce tutoriel, supprimez-le. Si vous avez utilisé un projet existant et que vous souhaitez le conserver sans les modifications ajoutées dans ce tutoriel, supprimez les ressources créées pour ce tutoriel.

Supprimer le projet

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

Pour supprimer le projet :

  1. Dans Cloud Console, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Supprimer les ressources du tutoriel

  1. Supprimez le service Cloud Run que vous avez déployé dans ce tutoriel :

        gcloud run services delete SERVICE-NAME

    SERVICE-NAME est le nom du service que vous avez choisi.

    Vous pouvez également supprimer des services Cloud Run à partir de Google Cloud Console.

  2. Supprimez les configurations gcloud par défaut que vous avez ajoutées lors de la configuration du tutoriel.

    Si vous utilisez Cloud Run (entièrement géré), supprimez le paramètre de région :

     gcloud config unset run/region
        

    Si vous utilisez Cloud Run pour Anthos sur Google Cloud, supprimez la configuration du cluster :

     gcloud config unset run/cluster run/cluster
         gcloud config unset run/cluster run/cluster_location
        
  3. Supprimez la configuration du projet :

     gcloud config unset project
        
  4. Supprimez les autres ressources Google Cloud créées dans ce tutoriel :

Étape suivante