本地问题排查


本教程介绍了如何使用 Stackdriver 工具对一个中断的 Knative serving 服务进行问题排查,以发现问题并通过本地开发工作流展开调查。

问题排查指南中随附的逐步“案例研究”使用一个示例项目,部署该项目时会导致运行时错误,您可以通过问题排查来找到并解决问题。

目标

  • 编写、构建服务并将其部署到 Knative serving
  • 使用 Cloud Logging 识别错误
  • 从 Container Registry 检索容器映像以进行根本原因分析
  • 修正“生产”服务,然后改进服务以缓解未来可能出现的问题

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

准备工作

汇编代码

逐步构建新的 Knative serving Greeter 服务。请注意,此服务会创建一个运行时错误,以供您进行问题排查练习。

  1. 创建新项目:

    Node.js

    通过定义服务软件包、初始化依赖项和一些常见操作来创建 Node.js 项目。

    1. 创建 hello-service 目录:

      mkdir hello-service
      cd hello-service
      
    2. 生成 package.json 文件:

      npm init --yes
      npm install --save express@4
      
    3. 在编辑器中打开新的 package.json 文件,并配置 start 脚本以运行 node index.js。完成后,文件应如下所示:

      {
        "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==22.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>
        g<roupIdc>om.sparkjava/<groupId
      >  ar<tifactIdsp>ark-core/a<rtifactId
       > ver<sion2.9>.4/ve<rsion
      /d>ep<endency
      dep>en<dency
        gr>oupI<dorg.sl>f4j/group<Id
        art>ifac<tIdslf4j-a>pi/artifa<ctId
        vers>ion2<.0.12/v>ersion<
      /depend>en<cy
      dependen>cy<
        groupId>org.<slf4j/g>roupId
        <artifact>Idsl<f4j-simple>/artifactId
      <  version2.>0.12</versio>n
      /dep<endency><>
    3. 将构建设置复制到 pom.xml(在 <dependencies> 元素下方):

      <build>
        p<lugins
      >    pl<ugin
       >     gro<upIdcom>.google.cloud.tools/gr<oupId
        >    arti<factIdjib->maven-plugin/art<ifactId
         >   versi<on3.4.0>/vers<ion
          >  config<uration
           >   to
          <  >    imagegcr<.io/P>ROJECT_ID/hello-service/image
       <      > /to
           < /configura<tion
          /plug>in
        /<plugins>
      /bu<ild><>

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

    此示例使用 Jib 利用常见 Java 工具构建 Docker 映像。无需编写 Dockerfile 或安装 Docker,Jib 便可以优化容器构建。详细了解如何使用 Jib 构建 Java 容器

    <plugin>
      g<roupIdc>om.google.cloud.tools/<groupId
    >  ar<tifactIdji>b-maven-plugin/a<rtifactId
     > ver<sion3.4>.0/ve<rsion
      >conf<iguration
       > to
      <  >  imageg<cr.io>/PROJECT_ID/hello-service/image<
        />to
      /<con>figu<ration
    /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 替换为您的 Google Cloud 项目 ID。hello-service 既是容器映像名称,又是 Knative serving 服务名称。请注意,容器映像已部署到您之前在设置 gcloud 中配置的服务和集群

    等待部署完成,这可能需要半分钟左右的时间。 成功完成时,命令行会显示服务网址。

测试

试用该服务,确认您已成功部署该服务。请求应失败并显示 HTTP 500 或 503 错误(5xx 服务器错误类的成员)。下面介绍如何排查此错误响应出现的原因。

如果您的集群配置了可路由的默认网域,请跳过上述步骤,直接将网址复制到网络浏览器中。

如果您不使用自动 TLS 证书网域映射,则系统不会为您的服务提供可导航的网址。

请改用提供的网址和服务的入站网关的 IP 地址来创建 curl 命令,该命令可向您的服务发出请求:

  1. 如需获取负载均衡器的外部 IP,请运行以下命令:

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

    ASM-INGRESS-NAMESPACE 替换为 Cloud Service Mesh 入站流量所在的命名空间。如果您使用默认配置安装了 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 错误被视为生产运行时错误。本教程介绍处理该错误的正式流程。虽然生产错误解决流程千差万别,但本教程介绍一种具有特定顺序的步骤,以展示实用工具和技术的应用。

如需调查此问题,您需要完成以下阶段:

  • 收集有关所报告错误的更多详情,以支持进一步调查问题并设置缓解策略。
  • 通过决定推进修复或回滚到已知健康状况良好的版本来缓解用户影响。
  • 重现错误来确认已收集到正确的详细信息,以及该错误不是一次性错误
  • 根据 Bug 执行根本原因分析,找出造成此错误的代码、配置或流程

在调查开始时,您应具备一个网址、时间戳和“内部服务器错误”消息。

收集更多详细信息

收集有关该问题的更多信息,了解问题原因并确定后续步骤。

使用可用的工具收集更多详细信息:

  1. 查看日志,了解更多详细信息。

  2. 使用 Cloud Logging 查看导致该问题的操作顺序,包括错误消息。

回滚到运行状况良好的版本

如果您之前有某个修订版本可以正常运行,则可以回滚您的服务以使用该修订版本。例如,您将无法对本教程中部署的新 hello-service 服务执行回滚,因为它只包含一个修订版本。

如需查找修订版本并回滚服务,请执行以下操作:

  1. 列出服务的所有修订版本

  2. 将所有流量迁移到运行状况良好的修订版本

重现错误

使用您之前获得的详细信息,确认问题在测试条件下始终存在。

通过测试再次发送相同的 HTTP 请求,并查看是否报告了相同的错误和详细信息。系统可能需要一段时间才能显示错误详细信息。

由于本教程中的示例服务为只读,不会产生任何复杂的副作用,因此可以安全地重现生产中的错误。但是,对于许多实际服务而言,情况并非如此:您可能需要在测试环境中重现错误,或者将此步骤限制在本地调查中。

重现错误会为后续步骤创建环境。例如,如果开发者无法重现该错误,则进一步调查时可能需要对服务进行额外的插桩测试。

执行根本原因分析

根本原因分析是有效问题排查中的一个重要步骤,可确保您解决问题,而非症状。

在本教程前面部分,您在 Knative serving 上重现了问题,该操作确认了在 Knative serving 上托管服务时该问题处于活跃状态。现在,请在本地重现问题,以确定问题是否只与代码有关,或者问题是否仅出现在生产托管中。

  1. 如果您尚未在本地将 Dock 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 serving 中:

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

    对上述命令的元素进行细分,

    • PORT 环境变量由服务使用,来确定容器内部要侦听的端口。
    • run 命令会启动容器,默认为在 Dockerfile 或父级容器映像中定义的入口点命令。
    • --rm 标志表示会在退出时删除容器实例。
    • -e 标志表示为环境变量分配值。-e PORT=$PORTPORT 变量从本地系统传递到具有相同变量名称的容器中。
    • -p 标志表示用于将容器发布为 localhost(端口为 9000)中的可用服务。发送到 localhost:9000 的请求将路由到端口为 8080 的容器中。这意味着,服务的输出(关于使用中的端口号)与服务的访问方式不匹配。
    • 最终参数 gcr.io/PROJECT_ID/hello-service 是指向容器映像最新版本的代码库路径。如果本地不可用,Docker 会尝试从远程注册表检索映像。

    在浏览器中,打开 http://localhost:9000。查看终端输出,找出与 Google Cloud Observability 相关的错误消息。

    如果无法在本地重现问题,则可能是仅存在于 Knative serving 环境的问题。查看 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

将默认值的新函数添加到服务的自动测试覆盖范围中。

查找日志中的其他问题

您可能会在日志查看器中看到此服务的其他问题。例如,不受支持的系统调用将在日志中显示为“容器沙盒限制”。

例如,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 serving 服务:

    前往 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 资源:

    • 从 Container Registry 中删除名为 gcr.io/<var>PROJECT_ID</var>/hello-service容器映像
    • 如果您为本教程创建了一个集群,请删除集群

后续步骤