如何通过实践实验来调试那些无法正常进行的Kubernetes部署操作
当你更新某个部署配置后,运行 ` kubectl rollout status ` 命令并等待结果。然而这个命令会超时。但是,当你向该服务发送请求时,它仍然会做出响应。那么,更新操作是否已经完成呢?如果还没有完成,那么目前有哪些 Pod 在处理用户的请求呢? 你可以在一个专用的 Kubernetes 实验环境中来探究这种情况。从一个运行正常的应用程序开始,你会故意引入三种故障:无法拉取镜像、 readiness probe 始终检测不通过,以及无法被调度执行的 Pod。你需要找出导致这些问题的原因,检查相关证据,并确认问题已经得到解决后,才能继续进行下一步操作。 在那种由于镜像下载失败而导致的
当你更新某个部署配置后,运行 `kubectl rollout status` 命令并等待结果。然而这个命令会超时。但是,当你向该服务发送请求时,它仍然会做出响应。那么,更新操作是否已经完成呢?如果还没有完成,那么目前有哪些 Pod 在处理用户的请求呢?
你可以在一个专用的 Kubernetes 实验环境中来探究这种情况。从一个运行正常的应用程序开始,你会故意引入三种故障:无法拉取镜像、 readiness probe 始终检测不通过,以及无法被调度执行的 Pod。你需要找出导致这些问题的原因,检查相关证据,并确认问题已经得到解决后,才能继续进行下一步操作。
在那种由于镜像下载失败而导致的故障情况下,部署配置会同时显示这两个错误状态。下表是根据该故障的 JSON 日志数据整理得出的:
| 条件 | 状态 | 原因 |
|---|---|---|
| Available | True | MinimumReplicasAvailable |
| Progressing | False | ProgressDeadlineExceeded |
这些条件实际上在回答不同的问题。接下来的实验将解释为什么即使更新操作遇到了问题,应用程序仍然能够继续接收并处理请求。
目录
如何设置实验环境
你应该已经熟悉容器、部署配置、ReplicaSet、Pod、Service 以及基本的 `kubectl` 命令。在这个实验环境中,我们使用 `kind` 工具在 Docker 容器中运行一个 Kubernetes 节点。
实验环境的具体配置如下:
| 组件 | 版本或配置信息 |
|---|---|
| 主机 | macOS 15.7.4,苹果芯片架构,16 GiB 内存 |
| Colima | 0.10.3版,Docker运行时环境,4颗CPU核心,约5.77 GiB内存可供Docker使用 |
| Docker | 客户端版本29.4.3,服务器版本29.2.1 |
| kind | 0.33.0版 |
| kubectl | 1.36.0版 |
| Kubernetes服务器 | 1.37.0版 |
配套脚本要求使用安装了 Colima 和 `kind` 0.33.0版本的 macOS ARM64 系统。我没有测试过 Linux、Windows 或 Intel 架构的机器。上述内存配置数据是针对测试用机而言的,并非最低要求。
您应该已经安装了Docker CLI、Colima、kind以及kubectl工具,并且确保Colima处于运行状态,同时能够访问Docker Hub。
请从这里下载配套代码。将其解压到一个新的目录中,然后在终端中打开该目录。启动Bash shell(我使用的就是这个shell),接着运行`setup`命令:
bash
bash setup.sh
执行`setup`命令后,会创建`fcc-rollout-lab`集群以及`rollout-lab`命名空间。系统会将认证信息写入`./kubeconfig`文件,通过不可变的ConfigMap加载应用程序源代码,并生成Deployment、Service以及一个名为`http-client`的Pod。此时不需要自定义镜像的构建过程。
接下来,系统会验证v1版本和v2版本之间的差异,将相关的快照和HTTP响应结果保存到证据档案中。需要注意的是,`setup`命令不会覆盖现有的实验室集群或本地的kubeconfig文件。源代码中已经指定了节点镜像和Python镜像的具体版本。
当设置过程完成后,请从配套目录中定义以下Bash函数:
LAB_ROOT="$PWD"
k() {
kubectl --kubeconfig "$LAB_ROOT/kubeconfig" \
--context kind-fcc-rollout-lab \
--namespace rollout-lab "$@"
}
配套代码中还包含了`failures.sh`文件,该文件可用于自动重现各种测试场景并进行检查。如果您需要手动进行操作,可以按照下面的命令步骤来执行;当然,使用自动化脚本也是另一种可行的方法。
在这里,`k`是一个Bash函数,它会使用预先指定的kubeconfig文件、上下文以及命名空间来运行`kubectl`命令。例如,`k get pods`这个命令可以列出实验室中的所有Pod。请在这个Bash终端中运行一次上述定义的函数,在整个操作过程中,请确保继续使用同一个终端和配套目录。
如何建立健康的部署环境
在开始任何操作之前,首先要确认部署路径和服务是否能够正常工作。`setup`命令会通过仅修改Pod模板中的`APP_VERSION`环境变量来实现从v1版本到v2版本的更新。
这个应用程序实际上是一个简单的Python HTTP服务器。当访问`/`路径时,它会返回自己当前的版本号和Pod名称;而访问`/ready`路径时,则会返回HTTP 200响应;其他所有路径都会返回HTTP 404错误码。Pod的名称是通过API从`metadata.name`字段中获取的。
以下是`app/server.py`文件中的路由逻辑:
if self.path == "/":
status = 200
payload = {
"version": os.environ["APP_VERSION"],
"pod": os.environ.get("POD_NAME", "local-test"),
}
elif self.path == "/ready":
status = 200
payload = {"ready": True}
else:
status = 404
payload = {"error": "unknown path"}
“就绪性检查”会通过8080端口访问`/ready`路径来进行验证。这些配置信息来源于`manifests/deployment.yaml`文件中Deployment对象的`spec`字段。需要注意的是,这只是一个示例片段,并非完整的部署配置文件。
replicas: 2
minReadySeconds: 5
progressDeadlineSeconds: 180
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
通过这些发布设置,控制器可以添加一个新的替换Pod。它会等待新的替换Pod准备好后,才会减少原有的两个正常运行的副本。一个新的Pod必须保持“准备就绪”状态至少5秒钟,才能被视为可用。整个过程的截止时间为180秒。
maxUnavailable: 0这一设置对发布过程有一定的限制作用,但它不能保护旧版本的Pod免受与当前操作无关的故障影响。被终止的Pod也可能在活跃副本的数量统计中仍然显示为“可用”状态。这些设置只是可供参考的选择,并不能保证生产环境中的高可用性。
所有记录在案的测试操作都成功完成了,且每个操作都返回了10个正确的HTTP响应结果。这个表格是根据两个 Deployment对象的快照整理得出的:
| 操作名称 | 修订版本 | 更新时间 | 是否准备就绪 | 。是否可用 | >返回的正确HTTP响应数量 |
|---|---|---|---|---|---|
| v1 | 1 | 2 | 2 | 2 | 10/10 |
| v2 | 2 | 2 | 2 | 2 | 10/10 |
例如,其中一个v2版本的请求返回的响应内容如下:
{"version": "v2", "pod": "rollout-demo-5b4b45f68f-qrntz"}
你的Pod名称可能会有所不同。请检查你当前的环境状态:
k get deployment rollout-demo -o json
k get pods -l app=rollout-demo -o wide
在继续下一步操作之前,请确保系统中只剩下两个Pod,这两个Pod都必须处于“准备就绪”状态,并且没有正在被终止。此时,Deployment对象应该有两个已经更新完毕、并且处于可用状态的副本,同时status.observedGeneration的值必须与metadata.generation的值一致。
之后发生的任何故障都会导致APP_VERSION=v2这一状态保持不变。因此,响应中显示的Pod名称就显得非常重要了:仅凭版本号是无法区分旧版本和新版本的。
如何诊断图像拉取失败的问题
第一次发生的故障只会将图像引用地址更改成公共Python仓库中一个故意不存在的标签:
k apply -f failure-lab/manifests/image.json
k rollout status deployment/rollout-demo --timeout=10s
在记录下来的测试过程中,这个操作很快就以代码1结束,并出现了以下错误:
error: timed out waiting for the condition
对于这种测试场景来说,出现这样的错误是正常的。如果是认证问题、连接故障或其他原因导致的错误,就需要采取不同的诊断方法了。不要将任何非零的退出代码都视为预期的结果。
找出导致问题的修订版本
请仔细检查Deployment对象及其相关的其他对象:
k get deployment rollout-demo
k get deployment rollout-demo -o json
k describe deployment rollout-demo
k get replicasets -l app=rollout-demo -o wide
k get pods -l app=rollout-demo -o wide
metadata.generation与status.observedGeneration进行比较。如果后者的值较低,说明控制器尚未观察到当前的目标状态,在解读其部署状态之前请重新检查。
记录的故障快照中包含了三个副本:其中一个已更新,两个处于准备状态,另外两个可用。因此,那些显示为“准备状态”的副本可能全部来自于之前的版本。
kubectl describe deployment命令可以用来识别NewReplicaSet。请检查这个ReplicaSet以及它所管理的那些尚未进入准备状态的Pod。将以下代码中的占位符替换为你实际获取到的信息:
NEW_RS='替换为新的ReplicaSet名称'
BAD_POD='替换为其未进入准备状态的Pod名称'
k get replicaset "$NEW_RS" -o json
k get pod "$BAD_POD" -o json
k describe pod "$BAD_POD"
在ReplicaSet的metadata.ownerReferences中,那些标有controller: true的条目必须指向Deployment的UID;而在Pod的所有者参考信息中,控制器的UID也必须与该ReplicaSet的UID相匹配。新的ReplicaSet模板与Deployment当前的模板除了标签“template-hash”的值不同之外,其他部分都是相同的;其版本注释也与Deployment的版本保持一致。
标签可以帮助你更精确地查找目标对象。UID所有权链能够确定哪些对象属于同一组。有关这些关系的详细信息,请参阅所有者引用相关文档。
阅读故障信息
获取与该Pod实例相关的事件信息:
POD_UID="$(
k get pod "$BAD_POD" -o jsonpath '{.metadata.uid}'
)"
k get events --field-selector "involvedObject_uid=$POD UID" \
--sort-by=.metadata.creationTimestamp -o yaml
该Pod已被调度执行,其PodScheduled字段的值为True,同时nodeName也被设置了。它的容器在尝试拉取镜像时出现了ErrImagePull错误,在重试过程中又出现了ImagePullBackOff错误,因此该Pod的状态仍然为Pending。注册表返回的错误信息正是这样写的:
docker.io/library/python:fcc-rollout-missing-20260928: 未找到该镜像
这条错误信息指出了缺失的标签。导致图像拉取失败的原因可能还包括注册表认证问题、网络限制、DNS故障或其他网络问题。在诊断问题时,请仔细分析事件日志中的具体信息,而不要假设所有的ImagePullBackOff>错误都是由相同的原因引起的。
kubectl STATUS命令显示的信息与Pod的实际状态是不同的。ImagePullBackOff>只是表示容器正在等待拉取镜像,而Pending才是Pod的实际状态。有关这两者之间的区别,请参阅Pod生命周期相关文档。
检查哪些Pod在处理请求
你可以查看Service的EndpointSlices信息,然后从集群内部通过Service发送请求来测试这些Pod是否能够正常响应请求。k get endpointslices -l kubernetes.io/service-name=rollout-demo -o json
k exec --pod-running-timeout=30s --request-timeout=60s http-client -- \
python -u /app/probe.py --url http://rollout-demo:8080/ \
--expected-version v2 --count 10 --interval 0.2
该探针会检查这些Pod的状态和版本信息,并记录下响应请求的Pod的名称。将这些名称与之前正常的ReplicaSet中的Pod进行比较;同时,也要将EndpointSlice中的targetRef.uid值与相应的UUID进行对比。
在本次测试中,所有10个响应都来自那些更新了两个版本的Pod。这些Pod的EndpointSlice信息已经准备就绪;而那个被阻塞的图像Pod也被列了出来,但其端点的状态显示为ready: false且serving: false。
由于新的Pod无法正常投入使用,因此控制器仍然保留着那些正常的旧副本。服务样本确认这些Pod确实做出了响应;10次成功的请求测试结果说明了在这段时间内发生的情况,但这些结果并不能证明在两次测量期间服务始终处于正常运行状态。
如何区分两种超时情况
第一种超时情况发生在你的终端中。执行命令kubectl rollout status --timeout=10s后,由于客户端超时设置,系统会停止等待操作,但并不会取消更新过程或恢复之前的配置。kubectl命令参考文档中对这种超时机制有详细说明。
第二种超时情况是由Deployment控制器引起的。你需要等待它指定的截止时间,然后查看具体的原因:k wait --for=jsonpath="{.status_conditions[?(@.type=="Progressing")].reason}'=ProgressDeadlineExceeded \
deployment/rollout-demo --timeout=240s
k get deployment rollout-demo -o json
这次等待操作设置了240秒的超时时间限制。检查后会发现,Progressing的状态为False,原因也是ProgressDeadlineExceeded。kubectl wait参考文档对这种使用JSONPath进行等待的操作进行了说明。
在之前的测试记录中,Progressing状态的最后更新时间从04:57:14Z变为了05:00:15Z,间隔时间为181秒,而配置的截止时间是180秒。因此,控制器的计时并不能保证实际等待时间一定正好是180秒。
Available=True这一状态仍然存在,是因为那些旧的Pod仍在提供所需的服务能力。Progressing=False表示更新过程已经停滞。相反,即使更新成功完成了,Progressing=True这一状态也可能会持续存在,因此一定要查看具体的原因以及副本的数量。
Deployment控制器会报告截止时间,但不会自动执行回滚操作。在截止时间点进行的快照显示中,缺失的图像仍然被视为所需的图像。
现在再次执行服务检查。在截止时间之后记录的十次请求,也都来自那些旧的Pod。只有在处理“图像相关的问题”时,才需要等待控制器的操作完成。
如何恢复健康的配置状态
在引发新的故障之前,立即进行恢复操作。请将BAD_POD设置为你刚刚检查过的那个出现故障的Pod:
k apply -f failure-lab/manifests/healthy.json
k rollout status deployment/rollout-demo --timeout=300s
k wait --for=delete "pod/$BAD_POD" --timeout=120s
k get deployment rollout-demo -o json
k get replicasets -l app=rollout-demo -o wide
k get pods -l app=rollout-demo -o wide
k get endpointslices -l kubernetes.io/service-name=rollout-demo -o json
k exec --pod-running-timeout=30s --request-timeout=60s http-client -- \
python -u /app/probe.py --url http://rollout-demo:8080/ \
--expected-version v2 --count 10 --interval 0.2
要检查整个恢复过程,而不仅仅是`rollout`命令是否成功执行。最终的配置状态应该与文件`healthy.json`中规定的内容一致。控制器必须已经观察到了这些变化:副本的数量、已更新的副本数量、处于“准备就绪”状态的副本数量,以及可用的副本数量,都应该是2。
最终应该只剩下两个应用程序Pod,它们都属于当前的ReplicaSet,且都处于“准备就绪”状态,并不会终止运行。它们的UID应该与那些处于“准备就绪”状态的Service端点相对应,而通过HTTP请求访问这些Pod时,应该能够得到版本号为v2的响应。
在最初的实验中,恢复操作是使用现有的健康状态下的ReplicaSet和那两个Pod来完成的。它们的配置模板本来就已经与需要恢复的状态相匹配了,因此出现故障的ReplicaSet会被自动缩减到0个副本。
尽管那些健康的Pod仍然存在,但修订信息却发生了变化。这些数据来自于之前的实验记录:
测试案例
故障发生前的状态
发生故障后的修订信息
恢复操作后的状态
图像相关配置
2
3
4
准备就绪状态
4
5
6
调度配置
6
7
8
你的实验结果可能会与这些数据有所不同。请恢复已知的健康配置状态,而不要依赖这些示例中的修订数值。
如何诊断“准备就绪”状态失败的问题
在确认恢复操作成功之后,再引发第二次故障:
k apply -f failure-lab/manifests/readiness.json
k rollout status deployment/rollout-demo --timeout=10s
这次修改仅涉及`readinessProbe.httpGet.path`这个配置项,将其从`/ready`改为`/not-ready`。这样一来,短暂的监控等待时间又会导致操作超时。
重复之前针对“图像相关问题”所进行的检查流程。识别出新的ReplicaSet,将NEW_RS和BAD_POD设置为与新ReplicaSet相关的名称,然后使用新的Pod UID来查询相关事件信息。这次容器会成功启动,但对应的Pod仍然不会处于“准备就绪”状态。
以下数值是根据之前记录的Pod状态快照得出的:
字段
值
阶段
运行中
PodScheduled
True
准备就绪
False
容器重启次数
0
Pod的事件信息解释了原因:
准备就绪检查失败:HTTP请求失败,状态码为404。
该应用程序没有处理/not-ready路径的请求。HTTP检查会接受200到399之间的响应代码,因此状态码为404表示检查失败。即使准备就绪检查失败,容器也会继续运行,同时会被标记为“未准备就绪”,系统会持续进行检测。请参阅相关文档以获取更多信息。
记录中还显示了一些短暂的启动错误,例如“连接被拒绝”。但这些错误本身并不能确定问题所在;因为这样的错误也可能发生在服务器正常开始响应请求之前。后来出现的404状态码说明该服务器确实接收到了请求,而结合其他变化的数据以及已知的路由信息,可以判断是探针路径设置不正确导致的问题。
准备就绪检查失败并没有导致容器重启,因此记录中的重启次数为0。这种情况不属于“崩溃循环”现象。
请重新执行EndpointSlice和Service的相关检测。新的Pod在记录中仍然显示为“未准备就绪”,而另外两个旧的Pod则处于正常运行状态,所有请求响应都来自这两个旧Pod。
该Service并未启用publishNotReadyAddresses功能。因此,端点的准备就绪状态设置非常重要,不能将这一现象普遍应用于所有的Service配置中。EndpointSlice文档中对这些规则和例外情况有详细说明。
关于相关背景知识,freeCodeCamp的Kubernetes自修复教程中也介绍了准备就绪状态的相关内容。其中提到,探针检查失败会导致新的部署任务无法顺利完成。
请再次执行完整的恢复流程,这次使用这个“未准备就绪”的Pod作为测试对象。在继续下一步操作之前,请确保系统中的v2版本已经恢复正常运行状态。
如何诊断放置约束问题
在第三次测试中,我们在Pod模板中添加了以下选择器:
nodeSelector:
rollout-lab.example.com/placement: no-matching-node
请确认这个选择器确实不会与任何节点匹配,然后再将其应用到Pod模板中:
k get nodes -l rollout-lab.example.com/placement=no-matching-node
k apply -f failure-lab/manifests/scheduling.json
k rollout status deployment/rollout-demo --timeout=10s
如果第一个命令返回了某个节点的信息,那就说明预期的“选择器不匹配任何节点”的情况并没有发生。在继续下一步操作之前,请先检查标签配置是否正确。
需要再次进行所有权检查及事件状态检测,并根据当前情况更新NEW_RS、BAD_POD和POD_UID这些变量。新的Pod目前处于Pending状态,这与图像下载失败时的情况相同。Pending状态实际上表示该Pod既在等待调度任务,也在等待容器环境的配置完成,其中包括图像文件的下载过程。
以下是记录下来的各种故障情况:
故障类型
图像获取失败
调度失败
PodScheduled
true
false,原因是“无法进行调度”
nodeName
分配给了实验室节点
未分配任何节点
containerStatuses
容器正在等待启动
未启动任何容器
Diagnostic Event
图像下载失败
调度失败
调度系统显示的信息如下:
0/1个节点可用:有1个节点不符合Pod的节点选择条件。抢占机制:0/1个节点可用;由于无法满足选择条件,抢占机制对调度没有帮助。
由于没有任何节点符合选择条件,因此该Pod始终没有被分配给任何kubelet节点,图像下载操作也从未开始。Kubernetes要求所有节点都必须满足指定的nodeSelector标签要求,这一点在Kubernetes的节点选择机制文档中有详细说明。
请重新执行相关检查。这个未能成功调度的Pod既没有IP地址,也没有EndpointSlice条目;而之前的Pod们则都返回了预期的响应结果。
再次按照整个恢复流程进行操作,这次将这个调度失败的Pod作为BAD_POD来使用。确认v2版本的Kubernetes系统运行正常,且相关服务能够正常响应请求。本次实验主要是用来测试节点选择条件不匹配导致的问题,并非为了检测资源耗尽或其他所有可能的调度故障情况。
如何进行清理
在保存好所有记录之后,请删除这个临时创建的实验室环境:
bash cleanup.sh
该脚本会选中Colima环境的Docker上下文,然后删除fcc-rollout-lab实验室目录,并明确指定要使用的kubeconfig文件。脚本还会记录操作结果,并检查其他类型的集群名称是否发生了变化。这样就可以彻底清除这个实验室环境创建后产生的所有工作负载。
所有的证据文件会保存在本地的对应目录中。如果想要再次运行这个实验,就需要在清理完成后重新提取相关数据;因为系统会自动拒绝使用之前的kubeconfig文件。
结论
共有三种不同的故障原因导致了新版本的部署失败,而之前的版本却能够正常响应所有请求:
故障类型
故障发生的位置
最有力的证据
>成功模拟的故障案例数量
Image
容器图像下载失败
Pod已调度,但标签信息不匹配
在两批测试中,全部20次尝试均失败
Readiness
就绪性检查失败
Pod正在运行,但返回HTTP 404错误;因此被视为“未准备就绪”
10次测试中,有10次失败
Scheduling
节点选择机制失败
PodScheduled的值为false;因为节点选择条件不匹配
10次测试中,有10次失败
在下次遇到部署失败的情况时,请从Deployment开始,依次检查其对应的ReplicaSet和Pod。需要确认Pod是否已经被成功调度、容器是否已经启动以及它是否已经处于“准备就绪”状态。通过查看相关的条件设置和事件记录,可以找出导致故障的具体原因;然后再根据服务端的响应结果,确定哪些Pod实际上完成了请求处理。
<最后,需要区分客户端等待超时时间与控制器处理任务的截止期限,并在得出任何结论之前确认系统已经完全恢复正常运行。这些测试结果是在单节点本地实验室环境中、使用有限的HTTP样本得出的;因此它们并不能反映系统在生产环境中的实际表现,也不能证明系统具备持续可用的特性。 相关文章
在人工智能时代,如何培养软件工程技能?
软件工程技能的提升需要放慢节奏。生成式人工智能已经改变了软件工程领域中技能培养的方式。虽然各种工具和人工智能技术能够加速学习进程,但它们未必真正有助于技能的发展。工程师们必须了解系统的运作原理才能真正掌握相关知识。资深开发人员可以通过运用各种学习方法来指导初级开发者。 作者:本·林德斯
阅读全文
如何利用Claude API构建一个可靠的人工智能助手
大型语言模型能够回答问题、总结文档、编写代码,还能与外部系统进行交互。但构建一个可靠的人工智能应用,仅仅发送指令并显示响应是远远不够的。 一个可用于实际生产的应用程序必须能够管理对话历史记录、提供相关的上下文信息、安全地使用各种工具、处理不同类型的响应,并判断生成的内容是否有用。 在本教程中,我们将构建 ShopHelper ——这个为虚构在线商店设计的客户支持助手。完成制作后,ShopHelper将能够做到以下几件事: 以统一的语气回答常见问题 记住顾客之前说过的话 通过调用代码中的函数来查询订单状态 安全地处理Claude给出的多段式响应 利用工作流程来处理客户支持请求 评估修改提示语后效
阅读全文
演讲主题:背景环境才是新的“规则”或“准则”
Patrick Debois探讨了如何运用经过验证的软件工程实践来管理、评估、分发以及监控各种上下文信息。他指出,将上下文数据视为代码来进行处理——包括对其进行测试、应用持续集成/持续部署机制、使用包管理工具以及进行安全扫描——能够帮助工程领导者可靠地扩展人工智能编码系统的规模,有效控制那些具有非确定性结果的流程,并帮助组织逐步积累长期可用的知识资源。 作者:Patrick Debois
阅读全文
InfoQ在线培训课程聚焦人工智能安全技术以及编码代理验证相关主题
本文介绍了InfoQ提供的两门在线认证课程:这些课程涵盖了生产环境中的人工智能系统在安全与隐私方面的相关要求,同时也讲解了当编程机器人被应用于现有的代码库时所需进行的验证工作。 作者:Artenisa Chatziou
阅读全文