Anleitung: Lokale Fehlerbehebung für einen Cloud Run-Dienst

In dieser Anleitung wird gezeigt, wie ein Dienstentwickler die Fehler eines defekten Cloud Run-Dienstes beheben kann. Dabei kommen Stackdriver-Tools für die Erkennung und ein lokaler Entwicklungsworkflow für die Prüfung zum Einsatz.

Diese Schritt-für-Schritt-"Fallstudie" begleitend zum Leitfaden zur Fehlerbehebung verwendet ein Beispielprojekt, das bei der Bereitstellung zu Laufzeitfehlern führt, die Sie beheben müssen, um das Problem zu finden und zu beheben.

Ziele

  • Einen Dienst schreiben, erstellen und in Cloud Run bereitstellen
  • Error Reporting und Cloud Logging verwenden, um einen Fehler zu identifizieren
  • Das Container-Image aus Container Registry abrufen, um eine Ursachenanalyse durchzuführen
  • Den „Produktions”-Dienst reparieren und dann verbessern, um zukünftige Probleme zu minimieren

Kosten

In dieser Anleitung werden die folgenden kostenpflichtigen Komponenten von Google Cloud verwendet:

Mit dem Preisrechner können Sie eine Kostenschätzung für Ihre voraussichtliche Nutzung vornehmen. Neuen Google Cloud-Nutzern steht möglicherweise eine kostenlose Testversion zur Verfügung.

Hinweis

  1. Melden Sie sich bei Ihrem Google Cloud-Konto an. Wenn Sie mit Google Cloud noch nicht vertraut sind, erstellen Sie ein Konto, um die Leistungsfähigkeit unserer Produkte in der Praxis sehen und bewerten zu können. Neukunden erhalten außerdem ein Guthaben von 300 $, um Arbeitslasten auszuführen, zu testen und bereitzustellen.
  2. Wählen Sie in der Google Cloud Console auf der Seite der Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.

    Zur Projektauswahl

  3. Die Abrechnung für das Cloud-Projekt muss aktiviert sein. So prüfen Sie, ob die Abrechnung für Ihr Projekt aktiviert ist.

  4. Wählen Sie in der Google Cloud Console auf der Seite der Projektauswahl ein Google Cloud-Projekt aus oder erstellen Sie eines.

    Zur Projektauswahl

  5. Die Abrechnung für das Cloud-Projekt muss aktiviert sein. So prüfen Sie, ob die Abrechnung für Ihr Projekt aktiviert ist.

  6. Aktivieren Sie die Cloud Run Admin API.
  7. Installieren und initialisieren Sie das Cloud SDK.
  8. Aktualisieren Sie die Komponenten:
    gcloud components update
  9. Folgen Sie der Anleitung zur lokalen Installation von Docker.

gcloud-Standardeinstellungen einrichten

So konfigurieren Sie gcloud mit Standardeinstellungen für den Cloud Run-Dienst:

  1. Legen Sie ein Standardprojekt fest:

    gcloud config set project PROJECT_ID

    Ersetzen Sie PROJECT_ID durch den Namen des Projekts, das Sie für diese Anleitung erstellt haben.

  2. Konfigurieren Sie gcloud für die von Ihnen ausgewählte Region:

    gcloud config set run/region REGION

    Ersetzen Sie REGION durch die unterstützte Cloud Run-Region Ihrer Wahl.

Cloud Run-Standorte

Cloud Run ist regional. Die Infrastruktur, in der die Cloud Run-Dienste ausgeführt werden, befindet sich demnach in einer bestimmten Region. Aufgrund der Verwaltung durch Google sind die Anwendungen in allen Zonen innerhalb dieser Region redundant verfügbar.

Bei der Auswahl der Region, in der Ihre Cloud Run-Dienste ausgeführt werden, ist vorrangig, dass die Anforderungen hinsichtlich Latenz, Verfügbarkeit oder Langlebigkeit erfüllt werden. Sie können im Allgemeinen die Region auswählen, die Ihren Nutzern am nächsten liegt, aber Sie sollten den Standort der anderen Google Cloud-Produkte berücksichtigen, die von Ihrem Cloud Run-Dienst verwendet werden. Die gemeinsame Nutzung von Google Cloud-Produkten an mehreren Standorten kann sich auf die Latenz und die Kosten des Dienstes auswirken.

Cloud Run ist in diesen Regionen verfügbar:

Unterliegt Preisstufe 1

Unterliegt Preisstufe 2

  • asia-east2 (Hongkong)
  • asia-northeast3 (Seoul, Südkorea)
  • asia-southeast1 (Singapur)
  • asia-southeast2 (Jakarta)
  • asia-south1 (Mumbai, Indien)
  • asia-south2 (Delhi, Indien)
  • australia-southeast1 (Sydney)
  • australia-southeast2 (Melbourne)
  • europe-central2 (Warschau, Polen)
  • europe-west2 (London, Vereinigtes Königreich)
  • europe-west3 (Frankfurt, Deutschland)
  • europe-west6 (Zürich, Schweiz) Blattsymbol Niedriger CO2-Ausstoß
  • northamerica-northeast1 (Montreal) Blattsymbol Niedriger CO2-Ausstoß
  • northamerica-northeast2 (Toronto) Blattsymbol Niedriger CO2-Ausstoß
  • southamerica-east1 (Sao Paulo, Brasilien) Blattsymbol Niedriger CO2-Ausstoß
  • southamerica-west1 (Santiago, Chile)
  • us-west2 (Los Angeles)
  • us-west3 (Salt Lake City)
  • us-west4 (Las Vegas)

Wenn Sie bereits einen Cloud Run-Dienst erstellt haben, können Sie die Region in der Cloud Console im Cloud Run-Dashboard aufrufen.

Code zusammenstellen

Erstellen Sie Schritt für Schritt einen neuen Cloud Run-Greeter-Dienst. Zur Erinnerung: Dieser Dienst ruft absichtlich einen Laufzeitfehler hervor, um die Fehlerbehebung zu üben.

  1. Erstellen Sie ein neues Projekt:

    Node.js

    Erstellen Sie ein Node.js-Projekt, indem Sie das Dienstpaket, die anfänglichen Abhängigkeiten und einige gängige Vorgänge definieren.

    1. Erstellen Sie ein neues hello-service-Verzeichnis:

      mkdir hello-service
      cd hello-service
      
    2. Erstellen Sie ein neues Node.js-Projekt, indem Sie eine package.json-Datei generieren:

      npm init --yes
      npm install --save express@4
      
    3. Öffnen Sie die neue package.json-Datei in Ihrem Editor und konfigurieren Sie ein start-Skript, um node index.js auszuführen. Wenn Sie fertig sind, sieht die Datei so aus:

      {
        "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"
        }
      }

    Wenn Sie diesen Dienst über die unmittelbare Anleitung hinaus weiterentwickeln, sollten Sie die Beschreibung und den Autor angeben und die Lizenz bewerten. Weitere Informationen finden Sie in der package.json-Dokumentation.

    Python

    1. Erstellen Sie ein neues hello-service-Verzeichnis:

      mkdir hello-service
      cd hello-service
      
    2. Erstellen Sie eine Datei „requirements.txt” und kopieren Sie die Abhängigkeiten in diese Datei:

      Flask==2.0.2
      pytest==5.3.0; python_version > "3.0"
      pytest==4.6.6; python_version < "3.0"
      gunicorn==20.1.0
      

    Go

    1. Erstellen Sie ein neues hello-service-Verzeichnis:

      mkdir hello-service
      cd hello-service
      
    2. Erstellen Sie ein Go-Projekt, indem Sie ein neues Go-Modul initialisieren:

      go mod init example.com/hello-service
      

    Sie können den spezifischen Namen nach Belieben aktualisieren: Sie sollten den Namen aktualisieren, wenn der Code in einem über das Web erreichbaren Repository veröffentlicht wird.

    Java

    1. Erstellen Sie ein neues Maven-Projekt:

      mvn archetype:generate \
        -DgroupId=com.example.cloudrun \
        -DartifactId=hello-service \
        -DarchetypeArtifactId=maven-archetype-quickstart \
        -DinteractiveMode=false
      
    2. Kopieren Sie die Abhängigkeiten in die pom.xml-Abhängigkeitsliste (zwischen die <dependencies>-Elemente):

      <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.9.3</version>
      </dependency>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.3.0-alpha10</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.32</version>
      </dependency>
      
    3. Kopieren Sie die Build-Einstellung in pom.xml (unter die <dependencies>-Elemente):

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

  2. Erstellen Sie einen HTTP-Dienst zur Verarbeitung eingehender Anfragen:

    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

    import json
    import os
    
    from flask import Flask
    
    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.")
    
        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 Cloud 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. Erstellen Sie Dockerfile, um das Container-Image zu definieren, das zur Bereitstellung des Dienstes verwendet wird:

    Node.js

    
    # Use the official lightweight Node.js 10 image.
    # https://hub.docker.com/_/node
    FROM node:12-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.9
    
    # Allow statements and log messages to immediately appear in the Cloud Run logs
    ENV PYTHONUNBUFFERED True
    
    # 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.
    # Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
    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.17-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 -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

    In diesem Beispiel wird Jib verwendet, um Docker-Images mit gängigen Java-Tools zu erstellen. Jib optimiert Container-Builds, ohne dass ein Dockerfile erforderlich ist oder Docker installiert sein muss. Weitere Informationen zum Erstellen von Java-Containern mit Jib

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

Code versenden

Das Versenden von Code erfolgt in drei Schritten: ein Container-Image mit Cloud Build erstellen, in die Container Registry hochladen und in Cloud Run bereitstellen.

So versenden Sie den Code:

  1. Erstellen Sie einen Container und veröffentlichen Sie ihn in Container Registry.

    Node.js

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

    Dabei ist PROJECT_ID die ID des GCP-Projekts. Sie können Ihre aktuelle Projekt-ID mithilfe von gcloud config get-value project prüfen.

    Bei Erfolg sollte eine Bestätigungsmeldung mit der ID, der Erstellungszeit und dem Image-Namen angezeigt werden. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Python

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

    Dabei ist PROJECT_ID die ID des GCP-Projekts. Sie können Ihre aktuelle Projekt-ID mithilfe von gcloud config get-value project prüfen.

    Bei Erfolg sollte eine Bestätigungsmeldung mit der ID, der Erstellungszeit und dem Image-Namen angezeigt werden. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Go

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

    Dabei ist PROJECT_ID die ID des GCP-Projekts. Sie können Ihre aktuelle Projekt-ID mithilfe von gcloud config get-value project prüfen.

    Bei Erfolg sollte eine Bestätigungsmeldung mit der ID, der Erstellungszeit und dem Image-Namen angezeigt werden. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

    Java

    1. Verwenden Sie das gcloud Credential Helper, um Docker für das Übertragen per Push in Ihre Container Registry zu autorisieren.
      gcloud auth configure-docker
    2. Verwenden Sie das Jib-Maven-Plug-in, um den Container zu erstellen und per Push in Container Registry zu übertragen.
      mvn compile jib:build -Dimage=gcr.io/PROJECT_ID/hello-service

    Dabei ist PROJECT_ID die ID des GCP-Projekts. Sie können Ihre aktuelle Projekt-ID mithilfe von gcloud config get-value project prüfen.

    Bei Erfolg sollte die Nachricht „BUILD SUCCESS“ angezeigt werden. Das Image wird in Container Registry gespeichert und kann bei Bedarf wiederverwendet werden.

  2. Stellen Sie die Anwendung mit dem folgenden Befehl bereit:

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

    Ersetzen Sie PROJECT_ID durch Ihre GCP-Projekt-ID. hello-service ist sowohl der Name des Container-Images als auch der Name des Cloud Run-Dienstes. Beachten Sie, dass das Container-Image für den Dienst und die Region bereitgestellt wird, die Sie zuvor unter gcloud einrichten konfiguriert haben.

    Antworten Sie mit y und „Ja“ auf die Aufforderung Nicht authentifiziert zulassen. Informationen zur IAM-basierten Authentifizierung finden Sie unter Zugriff verwalten.

    Warten Sie, bis die Bereitstellung abgeschlossen ist. Dies kann ungefähr eine halbe Minute dauern. Bei Erfolg wird in der Befehlszeile die Dienst-URL angezeigt.

Testen

Testen Sie den Dienst, um zu bestätigen, dass Sie ihn erfolgreich bereitgestellt haben. Anfragen sollten mit einem HTTP 500- oder HTTP 503-Fehler fehlschlagen (Teil der Klasse der 5xx-Serverfehler). In der Anleitung wird die Behebung dieser Fehlerantwort beschrieben.

Dem Dienst wird automatisch eine navigierbare URL zugewiesen.

  1. Rufen Sie in Ihrem Webbrowser diese URL auf:

    1. Öffnen Sie einen Webbrowser.

    2. Suchen Sie die Dienst-URL-Ausgabe des vorherigen Bereitstellungsbefehls.

      Wurde vom Bereitstellungsbefehl keine URL ausgegeben, ist ein Fehler aufgetreten. Prüfen Sie die Fehlermeldung und gehen Sie dementsprechend vor: Falls sie keine praktischen Hinweise enthält, sehen Sie im Leitfaden zur Fehlerbehebung nach und versuchen Sie gegebenenfalls noch einmal, den Bereitstellungsbefehl auszuführen.

    3. Rufen Sie diese URL auf, indem Sie sie in die Adressleiste des Browsers kopieren und die Eingabetaste drücken.

  2. Siehe HTTP 500- oder HTTP 503-Fehler.

    Wenn Sie einen HTTP 403-Fehler erhalten, haben Sie möglicherweise allow unauthenticated invocations bei der Eingabeaufforderung der Bereitstellung abgelehnt. Gewähren Sie dem Dienst nicht authentifizierten Zugriff, um dieses Problem zu beheben:

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

Weitere Informationen finden Sie unter Öffentlichen (nicht authentifizierten) Zugriff gewähren.

Problem untersuchen

Vergegenwärtigen Sie sich, dass der oben unter Testen festgestellte HTTP 5xx-Fehler als Produktionslaufzeitfehler aufgetreten ist. In dieser Anleitung wird ein formaler Prozess beschrieben, um ihn zu verarbeiten. Zwar sind die Prozesse zur Behebung von Produktionsfehlern sehr unterschiedlich, in dieser Anleitung wird jedoch eine bestimmte Abfolge von Schritten verwendet, um die Anwendung nützlicher Tools und Techniken zu zeigen.

Zum Untersuchen dieses Problems gehen Sie die folgenden Phasen durch:

  • Sammeln Sie für die weitere Prüfung zusätzliche Details zum gemeldeten Fehler und legen Sie eine Strategie zur Schadensminderung fest.
  • Vermeiden Sie negative Auswirkungen auf Nutzer, indem Sie eine Korrektur oder ein Rollback auf eine als fehlerfrei bekannte Version durchführen.
  • Reproduzieren Sie den Fehler, um sicherzustellen, dass die richtigen Details gesammelt wurden und der Fehler kein einmaliger Vorfall ist.
  • Führen Sie eine Ursachenanalyse für den Fehler durch, um den dafür verantwortlichen Code, die dafür verantwortliche Konfiguration bzw. den dafür verantwortlichen Prozess zu finden.

Zu Beginn der Prüfung haben Sie eine URL, einen Zeitstempel und die Meldung „Interner Serverfehler”.

Weitere Details sammeln

Sammeln Sie weitere Informationen zu dem Problem, um zu verstehen, was passiert ist, und um die nächsten Schritte zu bestimmen.

Verwenden Sie die verfügbaren Stackdriver-Tools, um weitere Details zu sammeln:

  1. Verwenden Sie die Error Reporting-Konsole, die ein Dashboard mit Details und Wiederholungs-Tracking für Fehler mit einem erkannten Stacktrace bereitstellt.

    Zur Error Reporting-Konsole

    Screenshot der Fehlerliste mit den Spalten &quot;Lösungsstatus&quot;, &quot;Vorkommen&quot;, &quot;Fehler&quot; und &quot;Erfasst in&quot;.
    Liste der aufgezeichneten Fehler. Fehler werden für Überarbeitungen, Dienste und Plattformen jeweils nach Meldung gruppiert.
  2. Klicken Sie auf den Fehler, um die Stacktrace-Details aufzurufen, und achten Sie auf die Funktionsaufrufe, die unmittelbar vor dem Fehler gemacht wurden.

    Screenshot eines einzelnen geparsten Stacktraces, der ein häufiges Profil dieses Fehlers darstellt.
    Das "Stacktrace-Beispiel" auf der Seite mit den Fehlerdetails zeigt eine einzelne Instanz des Fehlers. Sie können jede einzelne Instanz prüfen.
  3. Verwenden Sie Cloud Logging, um die Abfolge von Vorgängen zu prüfen, die zu dem Problem geführt haben, einschließlich der Fehlermeldungen, die aufgrund eines fehlenden erkannten Fehler-Stacktrace nicht in der Error Reporting-Konsole enthalten sind:

    Zur Cloud Logging-Konsole

    Wählen Sie im ersten Drop-down-Menü die Option Cloud Run-Überarbeitung aus. Dadurch werden die Logeinträge nach den von Ihrem Dienst generierten Einträgen gefiltert.

Weitere Informationen zu Logs in Cloud Run ansehen

Rollback zu einer fehlerfreien Version

Wenn es sich um einen etablierten Dienst handelt, der bekanntermaßen funktioniert, gibt es eine vorherige Überarbeitung des Dienstes in Cloud Run. In dieser Anleitung wird ein neuer Dienst ohne vorherige Versionen verwendet, sodass Sie kein Rollback durchführen können.

Wenn Sie jedoch einen Dienst mit früheren Versionen haben, zu denen Sie ein Rollback durchführen können, folgen Sie Überarbeitungsdetails anzeigen, um den Containernamen und die Konfigurationsdetails zu extrahieren, die zum Erstellen einer neuen funktionierenden Bereitstellung des Dienstes erforderlich sind.

Fehler reproduzieren

Prüfen Sie anhand der zuvor abgerufenen Details, ob das Problem unter den Testbedingungen konsistent auftritt.

Senden Sie dieselbe HTTP-Anfrage, indem Sie sie noch einmal testen und prüfen, ob derselbe Fehler und dieselben Details gemeldet werden. Es kann einige Zeit dauern, bis die Fehlerdetails angezeigt werden.

Da der Beispieldienst in dieser Anleitung schreibgeschützt ist und keine komplizierten Nebeneffekte auslöst, ist die Reproduktion von Fehlern in der Produktion sicher. Bei vielen echten Diensten ist dies jedoch nicht der Fall: Möglicherweise müssen Sie Fehler in einer Testumgebung reproduzieren oder diesen Schritt auf eine lokale Untersuchung beschränken.

Wenn Sie den Fehler reproduzieren, wird der Kontext für das weitere Vorgehen festgelegt. Wenn Entwickler beispielsweise den Fehler nicht reproduzieren können, kann für die weitere Untersuchung eine zusätzliche Instrumentierung des Dienstes erforderlich sein.

Ursachenanalyse durchführen

Die Ursachenanalyse ist ein wichtiger Schritt einer effektiven Fehlerbehebung, denn durch sie wird dafür gesorgt, dass Sie das Problem und nicht nur ein Symptom beheben.

Weiter oben in dieser Anleitung haben Sie das Problem in Cloud Run reproduziert und dadurch bestätigt, dass das Problem aktiv ist, wenn der Dienst in Cloud Run gehostet wird. Reproduzieren Sie das Problem nun lokal, um festzustellen, ob das Problem im Code isoliert ist oder ob es nur im Produktions-Hosting auftritt.

  1. Wenn Sie die Docker-Befehlszeile nicht lokal mit Container Registry verwendet haben, authentifizieren Sie sie mit gcloud:

    gcloud auth configure-docker

    Für alternative Ansätze siehe Container Registry-Authentifizierungsmethoden.

  2. Wenn der zuletzt verwendete Container-Imagename nicht verfügbar ist, enthält die Dienstbeschreibung die Information über das zuletzt bereitgestellte Container-Image:

    gcloud run services describe hello-service

    Suchen Sie nach dem Namen des Container-Images im Objekt spec. Mit einem gezielteren Befehl kann er direkt abgerufen werden:

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

    Mit diesem Befehl wird ein Name des Container-Images wie gcr.io/PROJECT_ID/hello-service angezeigt.

  3. Rufen Sie das Container-Image aus der Container Registry ab und übertragen Sie es in Ihre Umgebung. Dieser Schritt kann durch das Herunterladen des Container-Images einige Minuten dauern:

    docker pull gcr.io/PROJECT_ID/hello-service

    Spätere Aktualisierungen des Container-Images, die diesen Namen wiederverwenden, können mit demselben Befehl abgerufen werden. Wenn Sie diesen Schritt überspringen, wird mit dem unten stehenden Befehl docker run ein Container-Image abgerufen, sofern keines auf dem lokalen Computer vorhanden ist.

  4. Führen Sie den Befehl lokal aus, um sicherzustellen, dass das Problem nicht nur in Cloud Run auftritt:

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

    Der obige Befehl lässt sich in folgende Elemente aufschlüsseln:

    • Die Umgebungsvariable PORT wird vom Dienst verwendet, um den Port zu ermitteln, der im Container überwacht werden soll.
    • Der Befehl run startet den Container und verwendet standardmäßig den im Dockerfile definierten Einstiegspunktbefehl oder ein übergeordnetes Container-Image.
    • Das Flag --rm löscht die Containerinstanz beim Beenden.
    • Das Flag -e weist einer Umgebungsvariable einen Wert zu. -e PORT=$PORT leitet die Variable PORT vom lokalen System an den Container mit demselben Variablennamen weiter.
    • Das Flag -p veröffentlicht den Container als Dienst, der auf localhost an Port 9000 verfügbar ist. Anfragen an localhost:9000 werden an den Container an Port 8080 weitergeleitet. Dies bedeutet, dass die Ausgabe des Dienstes bezüglich der verwendeten Portnummer nicht mit der Zugriffsweise auf den Dienst übereinstimmt.
    • Das letzte Argument gcr.io/PROJECT_ID/hello-service ist ein Container-Image-tag, ein für Menschen lesbares Label für die sha256-Hash-ID eines Container-Images. Wenn es nicht lokal verfügbar ist, versucht Docker, das Image aus einer Remote-Registry abzurufen.

    Öffnen Sie in Ihrem Browser http://localhost:9000. Prüfen Sie die Terminalausgabe auf Fehlermeldungen, die mit denen der Operations Suite von Google Cloud übereinstimmen.

    Wenn das Problem lokal nicht reproduzierbar ist, tritt es möglicherweise nur in der Cloud Run-Umgebung auf. Lesen Sie die Anleitung zur Cloud Run-Fehlerbehebung für bestimmte Bereiche, die untersucht werden sollen.

    In diesem Fall wird der Fehler lokal reproduziert.

Jetzt, da zweifach bestätigt wurde, dass der Fehler permanent ist und durch den Dienstcode und nicht die Hosting-Plattform verursacht wird, sollten Sie den Code genauer untersuchen.

Für diese Anleitung darf davon ausgegangen werden, dass der Code im Container und der Code im lokalen System identisch sind.

Prüfen Sie den Stacktrace des Fehlerberichts und sehen Sie parallel im Code nach, um die fehlerhaften Zeilen zu finden.

Node.js

Suchen Sie die Quelle der Fehlermeldung in der Datei index.js im Bereich der Zeilennummer, auf die im Stacktrace in den Logs hingewiesen wird:
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

Suchen Sie die Quelle der Fehlermeldung in der Datei main.py im Bereich der Zeilennummer, auf die im Stacktrace in den Logs hingewiesen wird:
NAME = os.getenv("NAME")

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

Go

Suchen Sie die Quelle der Fehlermeldung in der Datei main.go im Bereich der Zeilennummer, auf die im Stacktrace in den Logs hingewiesen wird:

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

Java

Suchen Sie die Quelle der Fehlermeldung in der Datei App.java im Bereich der Zeilennummer, auf die im Stacktrace in den Logs hingewiesen wird:

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";
}

Bei der Untersuchung dieses Codes werden die folgenden Aktionen ausgeführt, wenn die Umgebungsvariable NAME nicht festgelegt ist:

  • In der Operations Suite von Google Cloud wird ein Fehler protokolliert.
  • Eine HTTP-Fehlerantwort wird gesendet.

Das Problem wird durch eine fehlende Variable verursacht, aber die eigentliche Ursache ist spezifischer: Die Codeänderung, die die zwingende Abhängigkeit von einer Umgebungsvariable hinzugefügt hat, enthielt keine entsprechenden Änderungen an Bereitstellungsskripts und Dokumentationen zu Laufzeitanforderungen.

Grundursache beheben

Nachdem wir den Code gesammelt und die potenzielle Grundursache identifiziert haben, können wir nun entsprechende Maßnahmen zur Behebung ergreifen.

  • Prüfen Sie, ob der Dienst lokal mit der vorhandenen NAME-Umgebung funktioniert:

    1. Führen Sie den Container lokal mit der hinzugefügten Umgebungsvariable aus:

      PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
       -e NAME="Local World!" \
       gcr.io/PROJECT_ID/hello-service
    2. Rufen Sie in Ihrem Browser http://localhost:9000 auf.

    3. „Hello Local World!” erscheint auf der Seite.

  • Ändern Sie die ausgeführte Cloud Run-Dienstumgebung so, dass sie diese Variable enthält:

    1. Führen Sie den Befehl zur Dienstaktualisierung aus, um eine Umgebungsvariable hinzuzufügen:

      gcloud run services update hello-service \
        --set-env-vars NAME=Override
      
    2. Warten Sie einige Sekunden, während Cloud Run eine neue Überarbeitung erstellt, die auf der vorherigen Überarbeitung mit der hinzugefügten neuen Umgebungsvariable basiert.

  • Bestätigen Sie, dass der Dienst nun repariert ist:

    1. Rufen Sie in Ihrem Browser die Cloud Run-Dienst-URL auf.
    2. "Hello Override!" erscheint auf der Seite.
    3. Prüfen Sie, dass keine unerwarteten Nachrichten oder Fehler in Cloud Logging oder Error Reporting angezeigt werden.

Zukünftige Fehlerbehebung beschleunigen

Bei diesem Beispiel eines Produktionsproblems war der Fehler auf die Betriebskonfiguration zurückzuführen. Es gibt Codeänderungen, die die Auswirkungen dieses Problems für die Zukunft minimieren.

  • Verbessern Sie das Fehlerlog, sodass es genauere Details umfasst.
  • Anstatt einen Fehler zurückzugeben, sollte der Dienst auf einen sicheren Standardwert zurückgreifen. Wenn eine Standardeinstellung eine Änderung der normalen Funktionalität darstellt, verwenden Sie eine Warnmeldung zu Monitoringzwecken.

Sehen wir uns nun an, wie Sie die Umgebungsvariable NAME als zwingende Abhängigkeit entfernen.

  1. Entfernen Sie den vorhandenen Code zur Verarbeitung von NAME:

    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 Cloud 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. Fügen Sie neuen Code hinzu, der einen Fallback-Wert festlegt:

    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. Testen Sie lokal, indem Sie den Container in den betroffenen Konfigurationsfällen neu erstellen und ausführen:

    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

    Vergewissern Sie sich, dass die Umgebungsvariable NAME weiterhin funktioniert:

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

    Vergewissern Sie sich, dass der Dienst ohne die Variable NAME funktioniert:

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

    Wenn der Dienst kein Ergebnis zurückgibt, vergewissern Sie sich, dass durch die Entfernung des Codes im ersten Schritt keine zusätzlichen Zeilen entfernt wurden, beispielsweise die, die zum Schreiben der Antwort verwendet werden.

  4. Rufen Sie zur Bereitstellung den Abschnitt Code bereitstellen noch einmal auf.

    Bei jeder Bereitstellung für einen Dienst wird eine neue Überarbeitung erstellt und die Bereitstellung von Traffic automatisch gestartet, sobald der Dienst bereit ist.

    So löschen Sie die zuvor festgelegten Umgebungsvariablen:

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

Fügen Sie der automatisierten Testabdeckung für den Dienst die neue Funktionalität für den Standardwert hinzu.

Nach anderen Problemen in den Logs suchen

Möglicherweise werden in der Loganzeige für diesen Dienst andere Probleme angezeigt. Beispielsweise wird ein nicht unterstützter Systemaufruf in den Logs als „Container Sandbox Limitation” angezeigt.

Manchmal führen etwa die Node.js-Dienste zu diesem Logeintrag:

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.

In diesem Fall wirkt sich die fehlende Unterstützung nicht auf den „hello-service”-Beispieldienst aus.

Bereinigen

Wenn Sie ein neues Projekt für diese Anleitung erstellt haben, löschen Sie das Projekt. Wenn Sie ein vorhandenes Projekt verwendet haben und es beibehalten möchten, ohne die Änderungen in dieser Anleitung hinzuzufügen, löschen Sie die für die Anleitung erstellten Ressourcen.

Projekt löschen

Am einfachsten vermeiden Sie weitere Kosten, wenn Sie das zum Ausführen der Anleitung erstellte Projekt löschen.

So löschen Sie das Projekt:

  1. Wechseln Sie in der Cloud Console zur Seite Ressourcen verwalten.

    Zur Seite „Ressourcen verwalten“

  2. Wählen Sie in der Projektliste das Projekt aus, das Sie löschen möchten, und klicken Sie dann auf Löschen.
  3. Geben Sie im Dialogfeld die Projekt-ID ein und klicken Sie auf Shut down (Beenden), um das Projekt zu löschen.

Anleitungsressourcen löschen

  1. Löschen Sie den Cloud Run-Dienst, den Sie in dieser Anleitung bereitgestellt haben:

    gcloud run services delete SERVICE-NAME

    Dabei ist SERVICE-NAME der von Ihnen ausgewählte Dienstname.

    Sie können Cloud Run-Dienste auch über die Google Cloud Console löschen.

  2. Entfernen Sie die Konfiguration der Standardregion gcloud, die Sie während der Einrichtung für die Anleitung hinzugefügt haben:

     gcloud config unset run/region
    
  3. Entfernen Sie die Projektkonfiguration:

     gcloud config unset project
    
  4. Löschen Sie sonstige Google Cloud-Ressourcen, die in dieser Anleitung erstellt wurden:

Nächste Schritte