本機疑難排解


本教學課程說明如何使用 Stackdriver 工具進行探索,以及使用本機開發工作流程進行調查,排解 Knative 服務中斷的問題。

這份逐步「案例研究」是疑難排解指南的輔助文件,會使用範例專案,說明如何排解部署時發生的執行階段錯誤,找出並修正問題。

目標

  • 撰寫、建構服務,並將服務部署到 Knative serving
  • 使用 Cloud Logging 找出錯誤
  • 從 Container Registry 擷取容器映像檔,進行根本原因分析
  • 修正「生產」服務,然後改善服務,以減少日後發生問題的機率

費用

在本文件中,您會使用 Google Cloud的下列計費元件:

如要根據預測用量估算費用,請使用 Pricing Calculator

初次使用 Google Cloud 的使用者可能符合免費試用資格。

事前準備

組裝程式碼

逐步建構新的 Knative serving 問候語服務。 提醒您,這項服務會刻意建立執行階段錯誤,以供疑難排解練習使用。

  1. 建立新專案:

    Node.js

    定義服務套件、初始依附元件和一些常見作業,建立 Node.js 專案。

    1. 建立 hello-service 目錄:

      mkdir hello-service
      cd hello-service
      
    2. 產生 package.json 檔案:

      npm init --yes
      npm install express@4
      
    3. 在編輯器中開啟新的 package.json 檔案,並設定要執行 node index.jsstart 指令碼。完成後,檔案會如下所示:

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

    如果您在本教學課程結束後,仍持續開發這項服務,請考慮填寫說明、作者,並評估授權。詳情請參閱 package.json 說明文件

    Python

    1. 建立新的 hello-service 目錄:

      mkdir hello-service
      cd hello-service
      
    2. 建立 requirements.txt 檔案,並將依附元件複製到該檔案中:

      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==23.0.0
      Werkzeug==3.0.3
      

    Go

    1. 建立 hello-service 目錄:

      mkdir hello-service
      cd hello-service
      
    2. 初始化新的 Go 模組,建立 Go 專案:

      go mod init <var>my-domain</var>.com/hello-service
      

    您可以視需要更新特定名稱:如果程式碼發布至可透過網路存取的程式碼存放區,就應該更新名稱。

    Java

    1. 建立 Maven 專案:

      mvn archetype:generate \
        -DgroupId=com.example \
        -DartifactId=hello-service \
        -DarchetypeArtifactId=maven-archetype-quickstart \
        -DinteractiveMode=false
      
    2. 將依附元件複製到 pom.xml 依附元件清單 (介於 <dependencies> 元素之間):

      <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. 將建構設定複製到 pom.xml (位於 <dependencies> 元素下方):

      <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. 建立 HTTP 服務來處理連入要求:

    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. 建立 Dockerfile,定義用於部署服務的容器映像檔:

    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 official Go image to create a binary.
    # This is based on Debian and sets the GOPATH to /go.
    # https://hub.docker.com/_/golang
    FROM golang:1.23-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

    本範例使用 Jib,透過常見的 Java 工具建構 Docker 映像檔。Jib 可最佳化容器建構作業,不需要 Dockerfile,也不必安裝 Docker。進一步瞭解如何使用 Jib 建構 Java 容器

    <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>
    

推送程式碼

推送程式碼包含三個步驟:使用 Cloud Build 建構容器映像檔、將容器映像檔上傳到 Container Registry,然後將容器映像檔部署到 Knative serving。

如要推送程式碼:

  1. 建構容器並發布至 Container Registry:

    Node.js

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

    其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用 gcloud config get-value project 檢查目前的專案 ID。

    若成功執行,您應會看到包含 ID、建立時間和映像檔名稱的「SUCCESS」(成功) 訊息。映像檔儲存在 Container Registry 中,日後如有需要,可以重複使用。

    Python

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

    其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用 gcloud config get-value project 檢查目前的專案 ID。

    若成功執行,您應會看到包含 ID、建立時間和映像檔名稱的「SUCCESS」(成功) 訊息。映像檔儲存在 Container Registry 中,日後如有需要,可以重複使用。

    Go

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

    其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用 gcloud config get-value project 檢查目前的專案 ID。

    若成功執行,您應會看到包含 ID、建立時間和映像檔名稱的「SUCCESS」(成功) 訊息。映像檔儲存在 Container Registry 中,日後如有需要,可以重複使用。

    Java

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

    其中 PROJECT_ID 是您的 Google Cloud 專案 ID。您可以使用 gcloud config get-value project 檢查目前的專案 ID。

    成功後,您應該會看到「BUILD SUCCESS」(建構成功) 訊息。映像檔會儲存在 Container Registry 中,日後如有需要,可以重複使用。

  2. 執行下列指令來部署您的應用程式:

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

    PROJECT_ID 替換為專案 ID。 Google Cloud hello-service 是容器映像檔名稱,也是 Knative 服務服務的名稱。請注意,容器映像檔是部署到您之前在設定 gcloud 中設定的服務和叢集。

    請等待部署完成,這可能需要半分鐘的時間。 成功完成後,指令列會顯示服務網址。

立即體驗

試用服務,確認您已成功部署。要求應會失敗,並顯示 HTTP 500 或 503 錯誤 (5xx 伺服器錯誤類別的成員)。本教學課程將逐步說明如何排解這項錯誤回應。

如果叢集已設定可路由的預設網域,請略過上述步驟,改為將網址複製到網頁瀏覽器。

如果您未使用自動 TLS 憑證網域對應,系統就不會提供服務的可瀏覽網址。

請改用提供的網址和服務的 Ingress 閘道 IP 位址,建立可對服務提出要求的 curl 指令:

  1. 如要取得負載平衡器的外部 IP,請執行下列指令:

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

    ASM-INGRESS-NAMESPACE 替換為 Cloud Service Mesh Ingress 所在的命名空間。如果您使用預設設定安裝 Cloud Service Mesh,請指定 istio-system

    輸出結果類似如下:

    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

    其中,EXTERNAL-IP 值是負載平衡器的外部 IP 位址。

  2. 使用網址中的 GATEWAY_IP 位址來執行 curl 指令。

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

    SERVICE-DOMAIN 替換為服務的預設指派網域。取得方式是採用預設網址,然後移除通訊協定 http://

  3. 看到 HTTP 500 或 HTTP 503 錯誤訊息。

調查問題

請注意,上述「試用」中遇到的 HTTP 5xx 錯誤,是正式環境執行階段錯誤。本教學課程將逐步說明處理這類問題的正式程序。雖然解決製作錯誤的程序差異很大,但本教學課程會介紹特定步驟順序,說明如何運用實用工具和技術。

如要調查這個問題,請完成下列階段:

  • 收集回報錯誤的詳細資料,以利進一步調查並制定緩解策略。
  • 決定繼續修正或復原至已知正常版本,減輕對使用者的影響。
  • 重現錯誤,確認已收集正確的詳細資料,且錯誤並非一次性故障
  • 對錯誤執行根本原因分析,找出造成錯誤的程式碼、設定或程序

調查開始時,您會看到網址、時間戳記和「Internal Server Error」訊息。

收集更多詳細資料

收集更多問題相關資訊,瞭解實際情況並決定後續步驟。

使用可用工具收集更多詳細資料:

  1. 詳情請查看記錄檔

  2. 使用 Cloud Logging 檢查導致問題的操作順序,包括錯誤訊息。

復原至正常版本

如果您知道某個修訂版本可以正常運作,可以將服務回溯至該版本。舉例來說,您無法對本教學課程中部署的新 hello-service 服務執行回溯作業,因為該服務只包含一個修訂版本。

如要找出修訂版本並回溯服務,請按照下列指示操作:

  1. 列出服務的所有修訂版本

  2. 將所有流量遷移至狀態良好的修訂版本

重現錯誤

使用先前取得的詳細資料,確認問題是否持續在測試條件下發生。

再次試用並傳送相同的 HTTP 要求,查看是否回報相同的錯誤和詳細資料。錯誤詳細資料可能需要一段時間才會顯示。

由於本教學課程中的範例服務是唯讀服務,不會觸發任何複雜的副作用,因此在實際工作環境中重現錯誤是安全的。不過,許多實際服務並非如此:您可能需要在測試環境中重現錯誤,或將這個步驟限制在本地調查。

重現錯誤可為後續工作建立背景資訊。舉例來說,如果開發人員無法重現錯誤,可能需要對服務進行額外檢測,才能進一步調查。

執行根本原因分析

根本原因分析是有效疑難排解的重要步驟,可確保您修正問題,而非只是解決症狀。

在本教學課程中,您先前已在 Knative serving 上重現問題,確認服務在 Knative serving 上代管時,問題確實會發生。現在請在本機重現問題,判斷問題是否與程式碼無關,或只會在實際工作環境主機中發生。

  1. 如果您尚未在本機使用 Docker CLI 搭配 Container Registry,請使用 gcloud 驗證:

    gcloud auth configure-docker

    如需替代方法,請參閱「Container Registry 驗證方法」。

  2. 如果無法取得最近使用的容器映像檔名稱,服務說明會提供最近部署的容器映像檔資訊:

    gcloud run services describe hello-service

    spec 物件中找出容器映像檔名稱。更精確的指令可以直接擷取該值:

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

    這個指令會顯示容器映像檔名稱,例如 gcr.io/PROJECT_ID/hello-service

  3. 從 Container Registry 將容器映像檔提取至環境,下載容器映像檔可能需要幾分鐘:

    docker pull gcr.io/PROJECT_ID/hello-service

    日後更新容器映像檔時,只要沿用這個名稱,就能使用相同指令擷取映像檔。如果略過這個步驟,下方的 docker run 指令會提取容器映像檔 (如果本機電腦上沒有的話)。

  4. 在本機執行,確認問題並非 Knative 服務特有:

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

    上述指令的元素可細分為:

    • 服務會使用 PORT 環境變數,判斷要在容器內監聽的通訊埠。
    • run 指令會啟動容器,預設為 Dockerfile 或父項容器映像檔中定義的進入點指令。
    • --rm 旗標會在結束時刪除容器執行個體。
    • -e 旗標會為環境變數指派值。-e PORT=$PORT 正在將 PORT 變數從本機系統傳播到容器中, 且變數名稱相同。
    • -p 標記會將容器發布為服務,並在通訊埠 9000 的本機主機上提供。系統會將對 localhost:9000 的要求,轉送至容器的 8080 通訊埠。這表示服務輸出的使用中連接埠號碼,與服務的存取方式不符。
    • 最後一個引數 gcr.io/PROJECT_ID/hello-service 是指向最新版容器映像的存放區路徑。如果本機沒有,Docker 會嘗試從遠端登錄檔擷取映像檔。

    在瀏覽器中開啟 http://localhost:9000。檢查終端機輸出內容,查看與 Google Cloud Observability 上的錯誤訊息相符的訊息。

    如果問題無法在本機重現,可能是 Knative 服務環境特有的問題。請參閱 Knative serving 疑難排解指南,瞭解要調查的特定領域。

    在這種情況下,錯誤會在本地重現。

現在已確認錯誤持續發生,且是由服務程式碼而非代管平台所致,因此接下來要更仔細地檢查程式碼。

在本教學課程中,您可以放心地假設容器內的程式碼與本機系統中的程式碼相同。

Node.js

index.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

main.py 檔案中,找出記錄檔中堆疊追蹤記錄所指出行號附近的錯誤訊息來源:
NAME = os.getenv("NAME")

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

Go

main.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

在記錄中顯示的堆疊追蹤記錄中,找出 App.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";
}

檢查這段程式碼後,會發現如果未設定 NAME 環境變數,系統會採取下列行動:

  • 錯誤會記錄到 Google Cloud Observability
  • 傳送 HTTP 錯誤回應

問題是由於缺少變數所致,但根本原因更具體:程式碼變更會新增環境變數的硬性依附元件,但未包含部署指令碼和執行階段需求文件中的相關變更。

修正根本原因

收集程式碼並找出潛在根本原因後,我們就能採取步驟修正問題。

  • 檢查服務是否在本機運作,並提供 NAME 環境:

    1. 在本機執行容器,並新增環境變數:

      PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \
       -e NAME="Local World!" \
       gcr.io/PROJECT_ID/hello-service
    2. 在瀏覽器中前往 http://localhost:9000

    3. 看到頁面顯示「Hello Local World!」

  • 修改正在執行的 Knative serving 服務環境,加入這個變數:

    1. 執行服務更新指令,並使用 --update-env-vars 參數新增環境變數:

      gcloud run services update hello-service \
        --update-env-vars NAME=Override
      
    2. 等待幾秒,Knative serving 會根據先前的修訂版本建立新的修訂版本,並新增環境變數。

  • 確認服務已修復:

    1. 在瀏覽器中前往 Knative serving 服務網址。
    2. 頁面上會顯示「Hello Override!」。
    3. 確認 Cloud Logging 中沒有任何非預期的訊息或錯誤。

提升日後的疑難排解速度

在這個生產問題範例中,錯誤與作業設定有關。我們將進行程式碼變更,盡量減少日後發生這個問題的影響。

  • 改善錯誤記錄,加入更具體的詳細資料。
  • 服務應改為回溯至安全預設值,而非傳回錯誤。如果使用預設值會導致正常功能發生變化,請使用警告訊息進行監控。

讓我們逐步移除 NAME 環境變數,做為硬性依附元件。

  1. 移除現有的 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. 新增設定備用值的新程式碼:

    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. 透過受影響的設定案例,重新建構及執行容器,在本機進行測試:

    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

    確認 NAME 環境變數仍可運作:

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

    確認服務在沒有 NAME 變數的情況下也能運作:

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

    如果服務未傳回結果,請確認移除第一個步驟中的程式碼時,是否也移除了額外行數,例如用於撰寫回應的行數。

  4. 如要部署,請返回「部署程式碼」部分。

    每次部署到服務都會建立一個新的修訂版本,並會在就緒時自動開始處理流量。

    如要清除先前設定的環境變數,請執行下列操作:

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

將預設值的新功能新增至服務的自動化測試涵蓋範圍。

在記錄中尋找其他問題

您可能會在這項服務的記錄檢視器中看到其他問題。舉例來說,記錄檔會將不支援的系統呼叫顯示為「Container Sandbox Limitation」。

舉例來說,Node.js 服務有時會產生下列記錄訊息:

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.

在本例中,缺少支援不會影響 hello-service 服務範例。

清除所用資源

您可以刪除為本教學課程建立的資源,以免產生費用。

刪除教學課程資源

  1. 刪除您在本教學課程中部署的 Knative Serving 服務:

    gcloud run services delete SERVICE-NAME

    其中 SERVICE-NAME 是您選擇的服務名稱。

    您也可以從Google Cloud 控制台刪除 Knative 服務:

    前往 Knative serving

  2. 移除您在教學課程設定期間新增的 gcloud 預設設定:

     gcloud config unset run/platform
     gcloud config unset run/cluster
     gcloud config unset run/cluster_location
    
  3. 移除專案設定:

     gcloud config unset project
    
  4. 刪除在本教學課程中建立的其他 Google Cloud 資源:

後續步驟

  • 進一步瞭解如何使用 Cloud Logging 深入瞭解生產環境行為。
  • 如要進一步瞭解如何排解 Knative serving 問題,請參閱這篇文章
  • 探索 Google Cloud 的參考架構、圖表和最佳做法。 歡迎瀏覽我們的雲端架構中心