教學課程:本機 Cloud Run 服務疑難排解

本教學課程說明服務開發人員如何使用 Google Cloud Observability 工具進行探索,並透過本機開發工作流程進行調查,排解 Cloud Run 服務故障問題。

這份逐步「個案研究」是疑難排解指南的輔助說明,使用部署時會導致執行階段錯誤的範例專案,您可透過疑難排解找出並修正問題。

目標

  • 編寫、建構服務並部署至 Cloud Run
  • 使用 Error Reporting 和 Cloud Logging 找出錯誤
  • 從 Container Registry 擷取容器映像檔,進行根本原因分析
  • 修正「正式環境」服務,然後改善服務,減少日後發生問題

費用

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

您可以使用 Pricing Calculator,根據預測用量估算費用。

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

事前準備

  1. 登入 Google Cloud 帳戶。如果您未曾使用過 Google Cloud,歡迎 建立帳戶,親自體驗實際使用我們產品的成效。新客戶還能獲得價值 $300 美元的免費抵免額,用於執行、測試及部署工作負載。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

  6. 啟用 Cloud Run Admin API
  7. 安裝並初始化 gcloud CLI。
  8. 更新元件:
    gcloud components update
  9. 按照操作說明在本機安裝 Docker

必要的角色

如要取得完成教學課程所需的權限,請要求管理員在專案中授予您下列 IAM 角色:

如要進一步瞭解如何授予角色,請參閱「管理專案、資料夾和組織的存取權」。

您或許也能透過自訂角色或其他預先定義的角色,取得必要權限。

設定 gcloud 預設值

如要針對 Cloud Run 服務設定 gcloud 的預設值:

  1. 設定您的預設專案:

    gcloud config set project PROJECT_ID

    將 PROJECT_ID 替換為您為本教學課程建立的專案名稱。

  2. 為所選區域設定 gcloud:

    gcloud config set run/region REGION

    將 REGION 替換為您選擇的支援 Cloud Run 區域。

Cloud Run 位置

Cloud Run 具有區域性,這表示執行 Cloud Run 服務的基礎架構位於特定區域,並由 Google 負責管理,實現該區域內所有可用區之間的備援可用性。

選擇執行 Cloud Run 服務的區域時,主要考量重點在於延遲時間、可用性或耐用性需求。原則上可以選擇距離使用者最近的區域,但也應考慮 Cloud Run 服務所用其他 Google Cloud產品的位置。如果跨多個位置使用 Google Cloud 產品,可能會影響服務的延遲時間和費用。

Cloud Run 可在下列地區使用:

採用級別 1 定價

採用級別 2 定價

  • africa-south1 (約翰尼斯堡)
  • asia-east2 (香港)
  • asia-northeast3 (韓國首爾)
  • asia-southeast1 (新加坡)
  • asia-southeast2 (雅加達)
  • asia-south2 (印度德里)
  • australia-southeast1 (雪梨)
  • australia-southeast2 (墨爾本)
  • europe-central2 (波蘭華沙) 節能綠葉圖示 低二氧化碳
  • europe-west10 (柏林)
  • europe-west12 (杜林) 節能綠葉圖示 低二氧化碳排放
  • europe-west2 (英國倫敦) 節能綠葉圖示 低二氧化碳排放
  • europe-west3 (德國法蘭克福)
  • europe-west6 (瑞士蘇黎世) 節能綠葉圖示 低二氧化碳
  • me-central1 (杜哈)
  • me-central2 (達曼)
  • northamerica-northeast1 (蒙特婁) 節能綠葉圖示 低二氧化碳排放
  • northamerica-northeast2 (多倫多) 節能綠葉圖示 低二氧化碳排放
  • southamerica-east1 (巴西聖保羅) 節能綠葉圖示 低二氧化碳排放
  • southamerica-west1 (智利聖地牙哥) 節能綠葉圖示 低二氧化碳排放
  • us-west2 (洛杉磯)
  • us-west3 (鹽湖城)
  • us-west4 (拉斯維加斯)

如果您已建立 Cloud Run 服務,可以在Google Cloud 控制台的 Cloud Run 資訊主頁中查看區域。

正在組合程式碼

逐步建構新的 Cloud Run greeter 服務。提醒您,這項服務會刻意建立執行階段錯誤,以便進行疑難排解練習。

  1. 建立新專案:

    Node.js

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

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

      mkdir hello-service
      cd hello-service
      
    2. 產生 package.json 檔案,建立新的 Node.js 專案:

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

      {
        "name": "hello-broken",
        "description": "Broken Cloud Run service for troubleshooting practice",
        "version": "1.0.0",
        "private": true,
        "main": "index.js",
        "scripts": {
          "start": "node index.js",
          "test": "echo \"Error: no test specified\" && exit 0",
          "system-test": "NAME=Cloud c8 mocha -p -j 2 test/system.test.js --timeout=360000 --exit"
        },
        "engines": {
          "node": ">=20.0.0"
        },
        "author": "Google LLC",
        "license": "Apache-2.0",
        "dependencies": {
          "express": "^5.0.0"
        },
        "devDependencies": {
          "c8": "^12.0.0",
          "google-auth-library": "^9.0.0",
          "got": "^11.5.0",
          "mocha": "^10.0.0"
        }
      }
      

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

    Python

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

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

      pytest==9.0.3; python_version >= "3.10"
      Flask==3.1.3; python_version >= '3.9'
      gunicorn==23.0.0
      Werkzeug==3.1.9; python_version >= '3.10'
      

    Go

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

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

      go mod init example.com/hello-service
      

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

    Java

    1. 建立新的 Maven 專案:

      mvn archetype:generate \
        -DgroupId=com.example.cloudrun \
        -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.14
    
    # 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.25-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,以及將容器映像檔部署至 Cloud Run。

如要推送程式碼:

  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

    1. 使用 gcloud 憑證輔助程式,授權 Docker 推送至 Container Registry。
      gcloud auth configure-docker
    2. 使用 Jib Maven 外掛程式建構容器,並推送至 Container Registry。
      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 替換為 Google Cloud 專案 ID。hello-service 是容器映像檔名稱,也是 Cloud Run 服務名稱。請注意,容器映像檔會部署到您先前在「設定 gcloud」中設定的服務和區域

    在「allow unauthenticated」提示中,回覆 y (是)。如要進一步瞭解 IAM 驗證,請參閱「管理存取權」。

    等待部署作業完成,這項作業約需半分鐘。部署成功後,指令列會顯示服務網址。

立即體驗

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

系統會自動為服務指派可導覽的網址。

  1. 使用網路瀏覽器前往這個網址:

    1. 開啟網路瀏覽器

    2. 找出先前部署指令輸出的服務網址。

      如果部署指令未提供網址,表示發生錯誤。查看錯誤訊息並採取相應行動:如果沒有可行的指引,請參閱疑難排解指南,並視情況重試部署指令。

    3. 將這個網址複製到瀏覽器的網址列,然後按下 ENTER 鍵,即可前往該網址。

  2. 查看 HTTP 500 或 HTTP 503 錯誤。

    如果收到 HTTP 403 錯誤,表示您可能在部署提示中拒絕了 allow unauthenticated invocations。如要修正這個問題,請授予服務公開存取權:

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

詳情請參閱「允許公開 (未經驗證) 存取」。

調查問題

假設您在「試用」中遇到的 HTTP 5xx 錯誤是實際執行階段錯誤。本教學課程將逐步說明處理這類問題的正式程序。雖然正式環境錯誤解決程序差異很大,但本教學課程會提供一系列步驟,說明如何應用實用的工具和技術。

如要調查這個問題,請按照下列階段操作:

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

調查開始時,您會取得網址、時間戳記和「內部伺服器錯誤」訊息。

收集更多詳細資料

收集更多問題相關資訊,瞭解發生什麼事,並決定後續步驟。

使用可用的 Google Cloud Observability 工具收集更多詳細資料:

  1. 使用 Error Reporting 控制台,其中提供資訊主頁,可追蹤具有可辨識堆疊追蹤的錯誤詳細資料和發生頻率。

    前往 Error Reporting 控制台

    錯誤清單的螢幕截圖,包括「解決狀態」、「發生次數」、「錯誤」和「出現位置」欄。
    記錄的錯誤清單。系統會依訊息將錯誤分組,並跨修訂版本、服務和平台顯示。
  2. 按一下錯誤即可查看堆疊追蹤詳細資料,請留意錯誤發生前執行的函式呼叫。

    單一剖析堆疊追蹤的螢幕截圖,顯示這項錯誤的常見剖析。
    錯誤詳細資料頁面中的「堆疊追蹤範例」會顯示單一錯誤例項。您可以查看個別執行個體。
  3. 使用 Cloud Logging 查看導致問題的操作序列,包括因缺少可辨識的錯誤堆疊追蹤,而未包含在 Error Reporting 控制台中的錯誤訊息:

    前往 Cloud Logging 控制台

    在第一個下拉式方塊中,依序選取「Cloud Run Revision」>「hello-service」。這樣一來,系統就會篩選記錄項目,只顯示服務產生的項目。

進一步瞭解如何在 Cloud Run 中查看記錄

復原至正常版本

如果這是已建立且已知可運作的服務,Cloud Run 上會有該服務的先前修訂版本。本教學課程使用的新服務沒有先前版本,因此無法復原。

不過,如果服務有可復原的舊版本,請按照「查看修訂版本詳細資料」一文的說明,擷取容器名稱和設定詳細資料,以便為服務建立新的工作部署作業。

重現錯誤

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

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

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

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

執行根本原因分析

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

在本教學課程中,您先前已在 Cloud Run 重現問題,確認服務託管於 Cloud Run 時會發生問題。現在,請在本機重現問題,判斷問題是否僅限於程式碼,或只在正式版託管環境中出現。

  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. 在本機執行,確認問題並非 Cloud Run 獨有:

    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 旗標會將容器發布為 localhost 上通訊埠 9000 的可用服務。傳送至 localhost:9000 的要求會轉送至通訊埠 8080 上的容器。這表示服務輸出的使用中通訊埠編號,與服務的存取方式不符。
    • 最後一個引數 gcr.io/PROJECT_ID/hello-service 是容器映像檔 tag,這是容器映像檔 sha256 雜湊 ID 的人類可讀標籤。如果本機沒有,docker 會嘗試從遠端登錄檔擷取映像檔。

    在瀏覽器中開啟 http://localhost:9000。檢查終端機輸出內容,看看是否有與「{ops_name}」相符的錯誤訊息。

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

    在本例中,錯誤是在本機重現。

現在,您已再次確認錯誤持續發生,且是由服務程式碼 (而非託管平台) 造成,接下來就要仔細檢查程式碼。

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

重新查看錯誤報告的堆疊追蹤,並與程式碼交叉比對,找出發生錯誤的特定行。

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

  • 修改執行中的 Cloud Run 服務環境,加入以下變數:

    1. 執行服務更新指令,新增環境變數:

      gcloud run services update hello-service \
        --set-env-vars NAME=Override
      
    2. Cloud Run 會根據先前的修訂版本建立新的修訂版本,並加入新的環境變數,請等待幾秒鐘。

  • 確認服務已修正:

    1. 在瀏覽器中前往 Cloud Run 服務網址。
    2. 頁面上會顯示「Hello Override!」。
    3. 確認 Cloud Logging 或 Error Reporting 中未出現非預期的訊息或錯誤。

加快日後疑難排解的速度

在這個正式環境問題範例中,錯誤與作業設定有關。我們已修改程式碼,盡可能降低日後發生這個問題的影響。

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

接下來,我們將逐步移除 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=$PORT -p 9000:$PORT \
     -e NAME="Robust World" \
     gcr.io/PROJECT_ID/hello-service

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

    PORT=8080 && docker run --rm -e PORT=$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 範例服務。

Terraform 疑難排解

如需 Terraform 相關疑難排解資訊或有任何問題,請參閱「Terraform 政策驗證疑難排解」或與 Terraform 支援團隊聯絡。

清除所用資源

為避免系統向您的 Google Cloud 帳戶收取額外費用,請刪除您在本教學課程中部署的所有資源。

刪除專案

如果您是為了這個教學課程建立新專案,請刪除專案。如果您使用現有專案,並想保留專案,但不要本教學課程新增的變更,請刪除為本教學課程建立的資源。

如要避免付費,最簡單的方法就是刪除您為了本教學課程所建立的專案。

刪除專案的方法如下:

  1. 前往 Google Cloud 控制台的「Manage resources」(管理資源) 頁面。

    前往「Manage resources」(管理資源)

  2. 在專案清單中選取要刪除的專案,然後點選「Delete」(刪除)。
  3. 在對話方塊中輸入專案 ID,然後按一下 [Shut down] (關閉) 以刪除專案。

刪除教學課程資源

  1. 刪除您在本教學課程中部署的 Cloud Run 服務。Cloud Run 服務在收到要求前不會產生費用。

    如要刪除 Cloud Run 服務,請執行下列指令:

    gcloud run services delete SERVICE-NAME

    將 SERVICE-NAME 改為您的服務名稱。

    您也可以從Google Cloud 控制台刪除 Cloud Run 服務。

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

     gcloud config unset run/region
    
  3. 移除專案設定:

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

後續步驟