探索和调试 Kubernetes 应用

KubernetesBeginner
立即练习

简介

现在,你已经能够使用清单描述期望状态,并创建 Pod 和 Deployment。下一项关键技能是理解:当 Kubernetes 无法实现这种期望状态时,应该如何处理。

在本实验中,你将操作两个小型 Deployment:一个运行正常,另一个包含故意设置的镜像标签拼写错误。你将按照一套可重复的流程,从宽泛的现象逐步定位到具体证据;修复清单,而不是只修改集群中的活动对象;最后通过日志和容器内执行的命令,检查恢复后的应用。

本实验的目标不是记住所有可能的故障,而是养成冷静排查问题的习惯:观察、缩小范围、检查证据、修复期望状态,并验证恢复情况

创建一个可控故障

环境启动: 本实验会为你启动一个完整的 Kubernetes 集群。配置控制平面、节点和网络组件通常需要 2~3 分钟。请耐心等待环境加载完成后再开始操作。

实际的问题排查通常始于某个现象。在本步骤中,你将部署一个正常工作负载和一个故意损坏的工作负载,以便在相同的集群条件下进行对比。

进入准备好的工作目录并列出其中的文件。cd 用于切换当前目录;ls 用于列出目录中的文件名。以下命令分成两行,并会按顺序执行:

cd /home/labex/project/debug-lab
ls

你应该能看到 healthy-web.yamlbroken-web.yaml。两者都定义了副本数为 1 的 Deployment,但其中一个包含一个不易察觉的配置错误,你将在后续步骤中定位它。

应用这两个清单。kubectl apply 会将期望状态发送到 API 服务器,每个 -f 参数指定一个输入文件。同一条命令可以接受多个 -f 参数:

kubectl apply -f healthy-web.yaml -f broken-web.yaml

先等待已知正常的 Deployment:

kubectl rollout status deployment/healthy-web --timeout=60s

当看到 deployment "healthy-web" successfully rolled out 时,说明集群能够成功调度 Pod,并运行缓存的 NGINX 镜像。这为后续排查建立了一个有用的基准。

现在,给另一个 Deployment 一小段时间尝试完成发布:

kubectl rollout status deployment/broken-web --timeout=15s || true

出现超时是预期结果。|| true 会让 Shell 继续执行,因为这里的失败是实验所需的证据,而不是应当终止实验的原因。

对比 Deployment 摘要:

kubectl get deployments

healthy-web 应显示 1/1 已就绪,而 broken-web 应显示 0/1。现在可以确认,问题仅影响特定工作负载,并不是整个集群都发生了故障。

使用资源摘要缩小问题范围

在本步骤中,你会先查看整体情况,再深入细节。Kubernetes 控制器会创建一系列相互关联的对象,因此 Deployment 的问题通常会先在 ReplicaSet 和 Pod 上显现出来。

同时列出相关对象类型。逗号可以让一次 kubectl get 请求获取多种资源类型;-o wide 则会增加节点和 IP 等实用列:

kubectl get deployments,replicasets,pods -o wide

从上到下阅读输出:

  • Deployment 会报告期望副本数和可用副本数。
  • ReplicaSet 会将期望副本数进一步落实到 Pod。
  • Pod 会报告容器是否就绪,以及简短的状态原因。

使用标签将视图限制为故障应用:

kubectl get pods -l app=broken-web -o wide

Pod 名称包含自动生成的后缀,因此,相比于把可能变化的名称复制到脚本中,使用标签更加稳妥。

在当前阶段,只查询最关键的字段。-o custom-columns='...' 可以根据明确指定的对象字段生成表格。每个条目都包含一个标题,例如 NAME,后面跟着提供对应值的 JSON 字段路径。末尾的反斜杠会将显示为两行的 Shell 命令连接成一条命令:

kubectl get pods -l app=broken-web \
  -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,WAITING_REASON:.status.containerStatuses[0].state.waiting.reason,NODE:.spec.nodeName'

等待原因最初可能是 ErrImagePull,随后变为 ImagePullBackOff。这两种状态都表示容器从未启动,因为 Kubernetes 无法获取所需镜像。相比简单地说「Pod 挂了」,这种信息更加准确。

使用 describe 检查 Pod

在本步骤中,你将使用 describe 了解容器为什么处于等待状态。摘要已经告诉你出了什么问题;现在需要进一步收集原因。

将自动生成的 Pod 名称保存到 Shell 变量中。$(...) 是命令替换语法:Shell 会执行其中的 kubectl 命令,并将输出赋值给 BROKEN_POD。JSONPath 会选出第一个匹配 Pod 的名称,echo 则会打印保存的值:

BROKEN_POD=$(kubectl get pods -l app=broken-web -o jsonpath='{.items[0].metadata.name}')
echo "$BROKEN_POD"

检查该 Pod:

kubectl describe pod "$BROKEN_POD"

describe 会将有用的字段和最近的事件汇总在一起。重点关注以下三个区域:

  • Containers → Image 显示请求的确切镜像。
  • State → Waiting → Reason 描述当前容器状态。
  • Events 记录 kubelet 的尝试过程和错误消息。

在当前场景中,事件消息会说明找不到标签 1.27-alpine-missing。集群正在按照清单中的要求执行;真正错误的是期望状态本身。

使用 JSONPath 直接确认镜像:

kubectl get pod "$BROKEN_POD" -o jsonpath='Image: {.spec.containers[0].image}{"\n"}'

当大型 YAML 或 describe 输出包含的信息多于当前所需内容时,JSONPath 非常有用。在这里,它可以单独提取最终需要修复的字段。

将事件作为时间线阅读

在本步骤中,你将把事件作为 Kubernetes 活动的时间线来阅读。事件是生命周期较短的诊断记录,可以帮助解释调度、拉取镜像、启动容器、重启以及许多其他状态变化。

按时间顺序列出命名空间中的最近事件。--sort-by 会按照指定的元数据字段对对象排序;引号用于确保 JSON 风格的字段路径作为一个完整参数传递:

kubectl get events --sort-by='.metadata.creationTimestamp'

最后几行通常是最新事件。查找 OBJECT 列引用故障 Pod,且 REASON 包含 PullingFailedBackOff 等值的条目。

你可以按自动生成的 Pod 名称过滤事件,以减少无关信息。--field-selector 会在服务器端根据对象字段进行过滤,而不是根据标签过滤。逗号表示两个条件必须同时匹配;反斜杠用于将一条命令分成多行,以便阅读:

BROKEN_POD=$(kubectl get pods -l app=broken-web -o jsonpath='{.items[0].metadata.name}')
kubectl get events \
  --field-selector involvedObject.kind=Pod,involvedObject.name="$BROKEN_POD" \
  --sort-by='.metadata.creationTimestamp'

可以将 getdescribeevents 理解为互补的观察视角:

  • get 用于快速定位不健康的对象。
  • describe 会汇总某个对象的配置、状态以及相关事件。
  • events 提供按时间排序的视图,可以显示反复尝试的过程。

反复出现的 BackOff 并不表示 Kubernetes 已经放弃该 Pod,而是表示在多次失败后,Kubernetes 正在逐渐延长每次拉取尝试之间的间隔。

修复期望状态并验证恢复

在本步骤中,你将修复期望状态并验证恢复情况。现在已经有足够证据采取行动:清单请求了一个不存在的镜像标签。应先修复保存下来的清单,再应用修复后的文件,从而确保文件内容与集群中的活动状态保持一致。

先显示两个清单中的镜像行进行对比。grep 用于搜索文本,-n 会在每个匹配项前显示行号;同一条命令会搜索两个文件:

grep -n 'image:' healthy-web.yaml broken-web.yaml

正常清单使用 nginx:1.27-alpine;故障清单则额外添加了不存在的 -missing 后缀。

只替换这个后缀。sed 使用 s/old/new/ 格式执行文本替换;-i 会直接修改指定文件,而不是只打印修改后的文本:

sed -i 's/nginx:1.27-alpine-missing/nginx:1.27-alpine/' broken-web.yaml

在本地验证修复后的文件:

kubectl apply --dry-run=client -f broken-web.yaml

预览文件与集群中活动对象之间的差异:

kubectl diff -f broken-web.yaml || true

如果发现差异,kubectl diff 会以代码 1 退出,因此 || true 可以让实验流程继续执行。在差异输出中,以 - 开头的行包含旧镜像,以 + 开头的行包含修复后的镜像。

应用修复并等待恢复:

kubectl apply -f broken-web.yaml
kubectl rollout status deployment/broken-web --timeout=60s

确认两个 Deployment 现在都已恢复正常:

kubectl get deployments

两者都应显示 1/1 已就绪。Kubernetes 会根据修正后的 Pod 模板创建新的 ReplicaSet 和 Pod;你不需要手动修复之前失败的 Pod。

阅读应用日志

在本步骤中,由于容器现在已经启动,你将使用应用日志获取新的证据。kubectl logs 用于读取容器的标准输出和标准错误流。

选择由 broken-web 管理的新正常 Pod。这里会再次使用前面介绍过的命令替换和 JSONPath。--field-selector=status.phase=Running 增加了一个服务器端条件,确保选中的 Pod 正在运行:

WEB_POD=$(kubectl get pods -l app=broken-web \
  --field-selector=status.phase=Running \
  -o jsonpath='{.items[0].metadata.name}')
echo "$WEB_POD"

由于还没有请求页面,NGINX 可能尚未产生日志中的访问记录。现在从 Pod 内部生成一条请求。在 kubectl exec POD -- COMMAND 中,-- 用于分隔 kubectl 选项和容器内要执行的命令。wget -qO- 会静默获取页面,并将页面写入标准输出;管道符 | 会把输出传给 head,后者只显示开头部分:

kubectl exec "$WEB_POD" -- wget -qO- http://127.0.0.1 | head

HTML 内容应以 <!DOCTYPE html> 开头,这证明 NGINX 已在本地的 80 端口上响应请求。

现在读取最近的日志。--tail=10 将输出限制为最近 10 行,避免启动过程产生的大量信息掩盖有用的请求记录:

kubectl logs "$WEB_POD" --tail=10

查找包含 GET / HTTP/1.1 的 HTTP 请求,以及表示成功响应的 200 状态码。当容器能够运行但应用行为不正确时,日志尤其有用。对于镜像拉取失败,日志通常无法提供帮助,因为容器根本没有启动。

从容器内部进行检查

在本步骤中,你将从容器内部检查已恢复的应用。kubectl exec 可以在一个已经运行的容器中执行命令,用于检查其文件系统、进程、环境变量、DNS 视图或本地网络行为。

重新获取正在运行的 Pod 名称:

WEB_POD=$(kubectl get pods -l app=broken-web \
  --field-selector=status.phase=Running \
  -o jsonpath='{.items[0].metadata.name}')

让容器报告其主机名:

kubectl exec "$WEB_POD" -- hostname

输出应与 Pod 名称一致,因为 Kubernetes 默认会将 Pod 名称设置为容器的主机名。

检查容器内的 NGINX 配置语法:

kubectl exec "$WEB_POD" -- nginx -t

如果看到 syntax is oktest is successful,说明应用配置在内部是有效的。

最后,执行一个简洁的由内而外的健康检查。>/dev/null 会丢弃下载的 HTML 内容;只有当 wget 成功时,&& 才会执行 echo。因此,只有在收到 HTTP 响应后,才会显示成功消息:

kubectl exec "$WEB_POD" -- wget -qO- http://127.0.0.1 >/dev/null && echo "NGINX responded inside the Pod"

请谨慎使用 exec。它要求容器已经处于运行状态,因此无法用于诊断前面遇到的镜像拉取失败。本次故障的证据链如下:

get -> describe -> events -> repair manifest -> rollout status -> logs -> exec

不同类型的故障可能会在证据链的不同环节停止,但从成本较低的摘要逐步深入到详细检查,可以让排查过程始终保持聚焦。

总结

你已经在 Kubernetes v1.35 上完成了一套完整的初学者调试流程:对比正常和异常工作负载,使用标签和简洁字段缩小问题范围,通过 describe 和事件定位无效的镜像标签,修复声明式配置的真实来源,并使用发布状态、日志以及容器内命令验证恢复情况。

核心经验是:根据工作负载当前所处的生命周期阶段,选择相匹配的证据。当容器尚未启动时,应检查状态和事件;容器运行后,则可以通过日志和 exec 了解应用层行为。下一个挑战将要求你独立运用这套流程。