本教程演示了服务开发者如何使用 Stackdriver 工具对一个无效的 Cloud Run for Anthos 服务进行问题排查,以发现问题并通过本地开发工作流展开调查。
问题排查指南中随附的逐步“案例研究”使用一个示例项目,部署该项目时会导致运行时错误,您可以通过问题排查来找到并解决问题。
请注意,由于 Google Cloud 的运维套件支持限制,本教程不适用于 Cloud Run for Anthos on VMware。
目标
- 编写、构建服务并将其部署到 Cloud Run for Anthos
- 使用 Cloud Logging 识别错误
- 从 Container Registry 检索容器映像以进行根本原因分析
- 修正“生产”服务,然后改进服务以缓解未来可能出现的问题
费用
在本文档中,您将使用 Google Cloud 的以下收费组件:
准备工作
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
- 启用 Cloud Run for Anthos API
- 安装并初始化 Google Cloud CLI。
- 安装
kubectl
组件:gcloud components install kubectl
- 更新组件:
gcloud components update
- 如果您使用的是 Cloud Run for Anthos,请按照设置 Cloud Run for Anthos 中的说明创建新集群。
- 如果您使用的是 Cloud Run for Anthos,请安装 curl 以试用该服务
- 按照说明在本地安装 Docker
设置 gcloud 默认值
如需为您的 Cloud Run for Anthos 服务配置 gcloud 默认值,请执行以下操作:
设置默认项目:
gcloud config set project PROJECT_ID
将 PROJECT_ID 替换为您在本教程中使用的项目名称。
为您的集群配置 gcloud:
gcloud config set run/platform gke gcloud config set run/cluster CLUSTER-NAME gcloud config set run/cluster_location REGION
您需要将其中的:
- 将 CLUSTER-NAME 替换为您使用的集群名称。
- 将 REGION 替换为您选择的受支持的集群位置。
汇编代码
逐步构建一项新的 Cloud Run for Anthos Greeter 服务。请注意,此服务会创建一个运行时错误,以供您进行问题排查练习。
创建新项目:
Node.js
通过定义服务软件包、初始化依赖项和一些常见操作来创建 Node.js 项目。创建一个新的
hello-service
目录:mkdir hello-service cd hello-service
通过生成
package.json
文件来创建新的 Node.js 项目:npm init --yes npm install --save express@4
在编辑器中打开新的
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
创建一个新的
hello-service
目录:mkdir hello-service cd hello-service
创建一个 requirements.txt 文件,并将您的依赖项复制到其中:
Go
创建一个新的
hello-service
目录:mkdir hello-service cd hello-service
通过初始化新的 go 模块创建 Go 项目:
go mod init example.com/hello-service
您可以根据需要更新特定名称:如果代码已发布到可通过网络访问的代码库,则应更新名称。
Java
创建一个新的 maven 项目:
mvn archetype:generate \ -DgroupId=com.example \ -DartifactId=hello-service \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=false
将依赖项复制到您的
pom.xml
依赖项列表中(在<dependencies>
元素之间):将构建设置复制到
pom.xml
(在<dependencies>
元素下方):
创建 HTTP 服务以处理传入请求:
Node.js
Python
Go
Java
创建
Dockerfile
来定义用于部署服务的容器映像:Node.js
Python
Go
Java
此示例使用 Jib 利用常见 Java 工具构建 Docker 映像。无需编写 Dockerfile 或安装 Docker,Jib 便可以优化容器构建。详细了解如何使用 Jib 构建 Java 容器。
交付代码
交付代码包括三个步骤:使用 Cloud Build 构建容器映像、将容器映像上传到 Container Registry,以及将容器映像部署到 Cloud Run for Anthos。
要开发代码,请执行以下操作:
构建容器并将其发布到 Container Registry 上:
Node.js
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 GCP 项目 ID。您可以通过
gcloud config get-value project
查看当前项目 ID。成功完成后,您应该会看到一条包含 ID、创建时间和映像名称的 SUCCESS 消息。该映像存储在 Container Registry 中,并可根据需要重复使用。
Python
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 GCP 项目 ID。您可以通过
gcloud config get-value project
查看当前项目 ID。成功完成后,您应该会看到一条包含 ID、创建时间和映像名称的 SUCCESS 消息。该映像存储在 Container Registry 中,并可根据需要重复使用。
Go
gcloud builds submit --tag gcr.io/PROJECT_ID/hello-service
其中 PROJECT_ID 是您的 GCP 项目 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 是您的 GCP 项目 ID。您可以通过
gcloud config get-value project
查看当前项目 ID。成功后,您会看到一则 BUILD SUCCESS 消息。该映像存储在 Container Registry 中,并可根据需要重复使用。
运行以下命令来部署您的应用:
gcloud run deploy hello-service --image gcr.io/PROJECT_ID/hello-service
将 PROJECT_ID 替换为您的 GCP 项目 ID。
hello-service
既是容器映像名称,又是 Cloud Run for Anthos 服务名称。请注意,容器映像已部署到您之前在设置 gcloud 中配置的服务和集群等待部署完成,这可能需要半分钟左右的时间。 成功完成时,命令行会显示服务网址。
测试
试用该服务,确认您已成功部署该服务。请求应失败并显示 HTTP 500 或 503 错误(5xx 服务器错误类的成员)。下面介绍如何排查此错误响应出现的原因。
如果您的集群配置了可路由的默认网域,请跳过上述步骤,直接将网址复制到网络浏览器中。
如果您不使用自动 TLS 证书和网域映射,则系统不会为您的服务提供可导航的网址。
请改用提供的网址和服务的入站网关的 IP 地址来创建 curl
命令,该命令可向您的服务发出请求:
- 如需获取 Istio 入站网关的外部 IP,请执行以下操作:
kubectl get svc istio-ingress -n gke-system
生成的输出类似如下所示:NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) istio-ingress LoadBalancer XX.XX.XXX.XX pending 80:32380/TCP,443:32390/TCP,32400:32400/TCP
负载平衡器的 EXTERNAL-IP 是您必须使用的 IP 地址。 使用此网址中的
GATEWAY_IP
地址运行curl
命令。curl -G -H "Host: SERVICE-DOMAIN" https://EXTERNAL-IP/
将 SERVICE-DOMAIN 替换为您的服务默认分配的网域。您可以通过使用默认网址并移除协议
http://
来获取此信息。查看 HTTP 500 或 HTTP 503 错误消息。
调查问题
设想上述测试部分遇到的 HTTP 5xx 错误被视为生产运行时错误。本教程介绍处理该错误的正式流程。虽然生产错误解决流程千差万别,但本教程介绍一种具有特定顺序的步骤,以展示实用工具和技术的应用。
如需调查此问题,您需要完成以下阶段:
- 收集有关所报告错误的更多详情,以支持进一步调查问题并设置缓解策略。
- 通过决定推进修复或回滚到已知运行状况良好的版本来缓解用户影响。
- 重现错误来确认已收集到正确的详细信息,以及该错误不是一次性错误
- 根据 Bug 执行根本原因分析,找出造成此错误的代码、配置或流程
在调查开始时,您应具备一个网址、时间戳和“内部服务器错误”消息。
收集更多详细信息
收集有关该问题的更多信息,了解问题原因并确定后续步骤。
使用可用的工具收集更多详细信息:
查看日志,了解更多详细信息。
使用 Cloud Logging 查看导致该问题的操作顺序,包括错误消息。
回滚到运行状况良好的版本
如果您之前有某个修订版本可以正常运行,则可以回滚您的服务以使用该修订版本。例如,您将无法对本教程中部署的新 hello-service
服务执行回滚,因为它只包含一个修订版本。
如需查找修订版本并回滚服务,请执行以下操作:
重现错误
使用您之前获得的详细信息,确认问题在测试条件下始终存在。
通过测试再次发送相同的 HTTP 请求,并查看是否报告了相同的错误和详细信息。系统可能需要一段时间才能显示错误详细信息。
由于本教程中的示例服务为只读,不会产生任何复杂的副作用,因此可以安全地重现生产中的错误。但是,对于许多实际服务而言,情况并非如此:您可能需要在测试环境中重现错误,或者将此步骤限制在本地调查中。
重现错误会为后续步骤创建环境。例如,如果开发者无法重现该错误,则进一步调查时可能需要对服务进行额外的插桩测试。
执行根本原因分析
根本原因分析是有效问题排查中的一个重要步骤,可确保您解决问题,而非症状。
在本教程前面部分,您在 Cloud Run for Anthos 上重现了问题,该操作确认了在 Cloud Run for Anthos 上托管服务时该问题处于活跃状态。现在,请在本地重现问题,以确定问题是否只与代码有关,或者问题是否仅出现在生产托管中。
如果您尚未在本地将 Dock CLI 与 Container Registry 搭配使用,请使用 gcloud 进行身份验证:
gcloud auth configure-docker
对于其他方法,请参阅 Container Registry 身份验证方法。
如果最近使用的容器映像名称不可用,则服务说明会包含最近部署的容器映像的信息:
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
。从 Container Registry 中拉取容器映像到您的环境,此步骤可能需要几分钟时间才能下载容器映像:
docker pull gcr.io/PROJECT_ID/hello-service
您可以使用同一命令检索重用此名称的容器映像的后续更新。如果您跳过此步骤,则当本地机器上不存在容器映像时,下方显示的
docker run
命令会拉取容器映像。在本地运行以确认该问题不是仅存在于 Cloud Run for Anthos 中:
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
是指向容器映像最新版本的代码库路径。如果本地不可用,Docker 会尝试从远程注册表检索映像。
在浏览器中,打开 http://localhost:9000。查看终端输出,找出与 Google Cloud 的运维套件相关的错误消息。
如果无法在本地重现,则可能是仅存在于 Cloud Run for Anthos 环境的问题。查看 Cloud Run for Anthos 问题排查指南,了解需要调查的具体领域。
在此示例中,错误会在本地重现。
经过双重确认后,该错误被认定为由服务代码(而非托管平台)引起的永久性错误,现在可以更密切地调查代码了。
在本教程中,我们可以假设容器内的代码和本地系统中的代码是相同的。
Node.js
在文件index.js
中找出错误消息来源,具体位置在日志显示的堆栈轨迹中调出的行号附近:
Python
在文件main.py
中找出错误消息来源,具体位置在日志显示的堆栈轨迹中调出的行号附近:
Go
在文件 main.go
中找出错误消息来源,具体位置在日志显示的堆栈轨迹中调出的行号附近:
Java
在文件 App.java
中找出错误消息来源,具体位置在日志显示的堆栈轨迹中调出的行号附近:
检查此代码时,如果未设置 NAME
环境变量,则会执行以下操作:
- 在 Google Cloud 的运维套件中记录一条错误
- 发送 HTTP 错误响应
问题是由缺少的变量引起的,但根本原因更具体:向环境变量添加硬依赖项的代码更改中没有对部署脚本和运行时要求文档的相关更改。
解决根本原因
既然我们已经收集了代码并确定了潜在的根本原因,现在便可以采取措施解决问题了。
请检查
NAME
环境已就绪时,该服务是否可以在本地运行:使用添加的环境变量在本地运行容器:
PORT=8080 && docker run --rm -e PORT=$PORT -p 9000:$PORT \ -e NAME="Local World!" \ gcr.io/PROJECT_ID/hello-service
将浏览器导航至 http://localhost:9000
查看网页上显示的“Hello Local World!”
修改正在运行的 Cloud Run for Anthos 服务环境以包含此变量:
运行具有
--update-env-vars
参数的服务更新命令以添加环境变量:gcloud run services update hello-service \ --update-env-vars NAME=Override
等待几秒钟,Cloud Run for Anthos 会根据添加了新环境变量的旧修订版本创建新修订版本。
确认现已修复该服务:
- 将浏览器导航到 Cloud Run for Anthos 服务网址。
- 您将看到网页上显示的“Hello Override!”。
- 验证 Cloud Logging 中是否出现意外消息或错误。
提高今后的问题排查速度
在此示例生产问题中,错误与操作配置有关。我们对代码进行一些更改,以便将来最大限度地减少此问题的影响。
- 改进错误日志,加入更具体的详细信息。
- 让服务回退到安全的默认设置,而不是返回到错误。 如果使用默认值表示对正常功能的更改,请使用警告消息进行监控。
让我们逐步移除作为硬依赖项的 NAME
环境变量。
移除现有的
NAME
处理代码:Node.js
Python
Go
Java
添加设置了后备值的新代码:
Node.js
Python
Go
Java
在受影响的配置情况中重新构建并运行容器,以进行本地测试:
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
如果服务未返回结果,请确认在第一步中移除代码时没有移除多余的行,例如用于写入响应的行。
通过重新访问部署代码部分进行部署。
向服务执行的每次部署都会创建一个新的修订版本,该修订版本准备就绪后会自动开始处理流量。
如需清除先前设置的环境变量:
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 示例服务。
清理
如果您为本教程创建了一个新项目,请删除项目。 如果您使用的是现有项目,希望保留此项目且不保留本教程中添加的任何更改,请删除为教程创建的资源。
删除项目
为了避免产生费用,最简单的方法是删除您为本教程创建的项目。
如需删除项目,请执行以下操作:
- In the Google Cloud console, go to the Manage resources page.
- In the project list, select the project that you want to delete, and then click Delete.
- In the dialog, type the project ID, and then click Shut down to delete the project.
删除教程资源
删除您在本教程中部署的 Cloud Run for Anthos 服务:
gcloud run services delete SERVICE-NAME
其中,SERVICE-NAME 是您选择的服务名称。
您还可以在 Google Cloud 控制台中删除 Cloud Run for Anthos 服务。
移除您在教程设置过程中添加的 gcloud 默认配置:
gcloud config unset run/platform gcloud config unset run/cluster gcloud config unset run/cluster_location
移除项目配置:
gcloud config unset project
删除在本教程中创建的其他 Google Cloud 资源:
后续步骤
- 详细了解如何使用 Cloud Logging 来深入了解生产行为。
- 如需详细了解 Cloud Run for Anthos 问题排查,请参阅 [/anthos/run/archive/docs/troubleshooting#sandbox]。
- 探索有关 Google Cloud 的参考架构、图表和最佳实践。查看我们的 Cloud Architecture Center。