Lokale Fehlerbehebung


In dieser Anleitung wird gezeigt, wie Sie mit Stackdriver-Tools und mit einem lokalen Entwicklungs-Workflow aufgetretene Fehler eines Knative Serving-Dienstes ermitteln, prüfen und beheben.

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

  • Dienst für Knative Serving schreiben, erstellen und bereitstellen
  • Mit Cloud Logging einen Fehler 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 diesem Dokument verwenden Sie die folgenden kostenpflichtigen Komponenten von Google Cloud:

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.

Hinweise

Code zusammenstellen

Erstellen Sie Schritt für Schritt einen neuen Knative Serving-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 hello-service-Verzeichnis.

      mkdir hello-service
      cd hello-service
      
    2. Generieren Sie eine Datei package.json:

      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==3.0.3
      pytest==8.2.0; python_version > "3.0"
      # pin pytest to 4.6.11 for Python2.
      pytest==4.6.11; python_version < "3.0"
      gunicorn==22.0.0
      Werkzeug==3.0.3
      

    Go

    1. Erstellen Sie ein 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 <var>my-domain</var>.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 Maven-Projekt:

      mvn archetype:generate \
        -DgroupId=com.example \
        -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.4</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.12</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.12</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.4.0</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 = parseInt(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():
        """Example route for testing local troubleshooting.
    
        This route may raise an HTTP 5XX error due to missing environment variable.
        """
        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 image.
    # https://hub.docker.com/_/node
    FROM node:20-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 dependencies.
    # if you need a deterministic and repeatable build create a
    # package-lock.json file and use npm ci:
    # RUN npm ci --omit=dev
    # if you need to include development dependencies during development
    # of your application, use:
    # RUN npm install --dev
    
    RUN npm install --omit=dev
    
    # 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.11
    
    # 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.21-bookworm 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:bookworm-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.4.0</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 Knative Serving 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 Ihre Google Cloud-Projekt-ID. 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 Ihre Google Cloud-Projekt-ID. 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 Ihre Google Cloud-Projekt-ID. 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

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

    Dabei ist PROJECT_ID Ihre Google Cloud-Projekt-ID. 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 Google Cloud-Projekt-ID. hello-service ist sowohl der Name des Container-Images als auch der Name des Knative Serving-Dienstes. Beachten Sie, dass das Container-Image für den Dienst und den Cluster bereitgestellt wird, den Sie zuvor unter gcloud einrichten konfiguriert haben.

    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.

Wenn Ihr Cluster mit einer routingfähigen Standarddomain konfiguriert ist, überspringen Sie die obigen Schritte und kopieren Sie die URL stattdessen in den Webbrowser.

Wenn Sie keine automatischen TLS-Zertifikate und keine Domainzuordnung verwenden, erhalten Sie keine aufrufbare URL für Ihren Dienst.

Verwenden Sie stattdessen die angegebene URL und die IP-Adresse des Ingress-Gateways des Dienstes, um einen curl-Befehl zu erstellen, der Anfragen an Ihren Dienst senden kann:

  1. Führen Sie den folgenden Befehl aus, um die externe IP-Adresse für den Load-Balancer abzurufen:

    kubectl get svc istio-ingressgateway -n ASM-INGRESS-NAMESPACE

    Ersetzen Sie ASM-INGRESS-NAMESPACE durch den Namespace, in dem sich der Cloud Service Mesh-Ingress befindet. Geben Sie istio-system an, wenn Sie Cloud Service Mesh mit der Standardkonfiguration installiert haben.

    Die Ausgabe sieht dann ungefähr so aus:

    NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP  PORT(S)
    istio-ingressgateway   LoadBalancer   XX.XX.XXX.XX   pending      80:32380/TCP,443:32390/TCP,32400:32400/TCP

    Dabei ist der Wert EXTERNAL-IP Ihre externe IP-Adresse für den Load-Balancer.

  2. Führen Sie einen curl-Befehl aus, der diese GATEWAY_IP-Adresse in der URL enthält.

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

    Ersetzen Sie SERVICE-DOMAIN durch die standardmäßig zugewiesene Domain Ihres Dienstes. Sie können sie abrufen, indem Sie die Standard-URL verwenden und daraus das Protokoll http:// entfernen.

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

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 Tools, um weitere Details zu sammeln:

  1. Weitere Informationen finden Sie unter Logs ansehen.

  2. Prüfen Sie mit Cloud Logging die Abfolge von Vorgängen, die zu dem Problem geführt haben, einschließlich Fehlermeldungen.

Rollback zu einer fehlerfreien Version

Wenn Sie wissen, dass eine Überarbeitung funktioniert, können Sie ein Rollback des Dienstes durchführen, um diese Überarbeitung zu verwenden. Sie können beispielsweise kein Rollback für den neuen hello-service-Dienst durchführen, den Sie in dieser Anleitung bereitgestellt haben, da er nur eine Überarbeitung enthält.

So finden Sie eine Überarbeitung und führen einen Rollback des Dienstes durch.

  1. Listen Sie alle Überarbeitungen Ihres Dienstes auf.

  2. Migrieren Sie den gesamten Traffic in die fehlerfreie Überarbeitung.

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 Knative Serving reproduziert und dadurch bestätigt, dass das Problem aktiv ist, wenn der Dienst auf Knative Serving 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 bei Knative Serving 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 Repository-Pfad, der auf die neueste Version des Container-Images verweist. 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 in Google Cloud Observability übereinstimmen.

    Wenn das Problem lokal nicht reproduzierbar ist, kann es für die Knative Serving-Umgebung spezifisch sein. Lesen Sie die Anleitung zur Fehlerbehebung bei Knative Serving 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.

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 Google Cloud Observability 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 laufende Knative Serving-Dienstumgebung so, dass sie diese Variable enthält:

    1. Führen Sie den Befehl zur Dienstaktualisierung mit dem Parameter --update-env-vars aus, um eine Umgebungsvariable hinzuzufügen:

      gcloud run services update hello-service \
        --update-env-vars NAME=Override
      
    2. Warten Sie einige Sekunden, während Knative Serving 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 URL des Knative Serving-Dienstes auf.
    2. "Hello Override!" erscheint auf der Seite.
    3. Prüfen Sie, dass keine unerwarteten Nachrichten oder Fehler in Cloud Logging 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

Sie können die für diese Anleitung erstellten Ressourcen löschen, um Kosten zu vermeiden.

Anleitungsressourcen löschen

  1. Löschen Sie den Knative Serving-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 Knative Serving-Dienste auch über die Google Cloud Console löschen:

    Zu Knative Serving

  2. Entfernen Sie die gcloud-Standardkonfigurationen, die Sie während der Einrichtung der Anleitung hinzugefügt haben.

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  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