介绍
之前的实验介绍了日志、事件、kubectl exec、端口转发和探针。CrashLooping(崩溃循环)应用结合了其中多项技能。Pod 启动后容器退出,Kubernetes 会重启它,你需要先读取相关证据,然后再进行任何更改。
在本实验中,你将部署一个因缺少必需环境变量而退出的应用。你将观察其重启行为,读取当前和之前的日志,检查事件,识别 Deployment 中缺失的配置,然后修复 Deployment 并验证应用是否变得可访问。
准备 CrashLoop 调试命名空间
在此步骤中,你将为 CrashLoop 调试练习准备一个干净的命名空间。
从 /home/labex/project 开始。你将在此目录中创建并更新应用清单。
mkdir -p /home/labex/project
cd /home/labex/project
检查 Kubernetes API 服务器是否可达。如果虚拟机刚启动且节点尚未就绪,请等待几秒钟后再运行该命令。
kubectl get nodes --request-timeout=5s
预期输出包括:
NAME STATUS ROLES AGE VERSION
labex-v135 Ready control-plane ... v1.35...
等待集群网络 Pod 就绪。修复后的应用稍后将通过 Service 进行测试。
kubectl wait --namespace kube-system --for=condition=Ready pod -l k8s-app=calico-node --timeout=120s
预期输出:
pod/calico-node-... condition met
创建一个名为 ckad-prep-crashloop 的命名空间,并将其设置为当前命名空间:
kubectl create namespace ckad-prep-crashloop
kubectl config set-context --current --namespace=ckad-prep-crashloop
确认当前命名空间:
kubectl config view --minify --output 'jsonpath={..namespace}'; echo
预期输出:
ckad-prep-crashloop
部署并观察 CrashLoop
在此步骤中,你将部署一个反复退出的应用。
容器脚本需要环境变量 APP_MESSAGE。如果缺少该变量,脚本会打印明确的错误并以状态码 1 退出。Kubernetes 将重启容器,从而产生你需要调试的 CrashLoop 行为。
创建 crash-demo.yaml:
cat > crash-demo.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: crash-demo
spec:
replicas: 1
selector:
matchLabels:
app: crash-demo
template:
metadata:
labels:
app: crash-demo
spec:
terminationGracePeriodSeconds: 0
containers:
- name: web
image: busybox:1.36
command:
- /bin/sh
- -c
- |
if [ -z "${APP_MESSAGE:-}" ]; then
echo "startup failed: APP_MESSAGE is required"
exit 1
fi
mkdir -p /www
echo "$APP_MESSAGE" > /www/index.html
httpd -f -p 8080 -h /www
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: crash-demo
spec:
selector:
app: crash-demo
ports:
- port: 80
targetPort: 8080
EOF
应用清单:
kubectl apply -f crash-demo.yaml
预期输出:
deployment.apps/crash-demo created
service/crash-demo created
给 Kubernetes 一点时间重启失败的容器,然后检查 Pod:
sleep 12
kubectl get pods -l app=crash-demo
预期输出包括一个不健康的 Pod。根据时间点不同,确切状态可能是 Error 或 CrashLoopBackOff:
NAME READY STATUS RESTARTS AGE
crash-demo-... 0/1 CrashLoopBackOff ... ...
存储 Pod 名称并检查其容器状态。JSONPath 允许你读取特定的关键字段,而无需滚动查看整个对象。第一个值是重启次数。第二个值是等待原因,如果你在最新的失败容器处于 Error 状态而不是在 CrashLoopBackOff 中等待时捕获到 Pod,该值可能为空。
CRASH_POD="$(kubectl get pods -l app=crash-demo -o jsonpath='{.items[0].metadata.name}')"
echo "$CRASH_POD"
kubectl get pod "$CRASH_POD" -o jsonpath='{.status.containerStatuses[0].restartCount}{" "}{.status.containerStatuses[0].state.waiting.reason}{"\n"}'
预期输出包括大于 0 的重启次数。根据时间点不同,原因可能为空,也可能显示为 CrashLoopBackOff 等值:
2
或:
... CrashLoopBackOff
读取日志和事件以查找原因
在此步骤中,你将使用日志、之前的日志和事件来确定容器退出的原因。
当容器快速重启时,kubectl logs 可能显示当前的容器尝试,而 kubectl logs --previous 则读取上一个已终止的容器尝试。对于 CrashLoop 调试,--previous 通常是揭示有用启动错误的命令。
读取当前日志:
kubectl logs "$CRASH_POD" --container web
预期输出包括:
startup failed: APP_MESSAGE is required
读取之前的容器日志。如果 Kubernetes 已经保留了上一个已终止容器的日志,这将显示来自较早尝试的相同启动错误。在某些快速 CrashLoop 的情况下,运行时可能会报告无法检索之前的日志。这仍然传达了一个重要的调试要点:在可用时使用 --previous,但始终将其与当前日志和 Pod 事件进行比较。
kubectl logs "$CRASH_POD" --container web --previous
预期输出通常包括:
startup failed: APP_MESSAGE is required
看到类似以下的消息也是正常的:
unable to retrieve container logs for ...
检查 Pod 的事件。事件显示了 Kubernetes 重启失败容器的过程。
kubectl describe pod "$CRASH_POD" | sed -n '/Events:/,$p'
预期输出包括诸如 BackOff 或 Started 之类的事件:
Warning BackOff ... Back-off restarting failed container
现在检查 Deployment 的环境变量配置。env 列表缺失,这解释了日志中的消息。
kubectl get deployment crash-demo -o jsonpath='{.spec.template.spec.containers[0].env}'; echo
预期输出为空:
修复 Deployment 并验证恢复
在此步骤中,你将修复缺失的环境变量并验证应用是否恢复。
错误消息指出了缺失的配置:APP_MESSAGE。无需更改容器命令,只需在 Deployment 上设置所需的环境变量即可。由于 Pod 模板发生了变化,Kubernetes 将创建一个新的 ReplicaSet。
设置环境变量:
kubectl set env deployment/crash-demo APP_MESSAGE=crashloop-fixed
预期输出:
deployment.apps/crash-demo env updated
等待滚动更新完成:
kubectl rollout status deployment/crash-demo --timeout=120s
预期输出:
deployment "crash-demo" successfully rolled out
检查新的 Pod。它应该处于 Running 和 Ready 状态:
kubectl get pods -l app=crash-demo
预期输出包括:
NAME READY STATUS RESTARTS AGE
crash-demo-... 1/1 Running 0 ...
创建一个客户端 Pod 并测试 Service:
kubectl run crash-client --image=busybox:1.36 --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/crash-client --timeout=120s
kubectl exec crash-client -- wget -qO- http://crash-demo
预期输出:
crashloop-fixed
最后,确认 Deployment 现在包含了该环境变量:
kubectl get deployment crash-demo -o jsonpath='{.spec.template.spec.containers[0].env[0].name}{"="}{.spec.template.spec.containers[0].env[0].value}{"\n"}'
预期输出:
APP_MESSAGE=crashloop-fixed
总结
你使用之前阶段介绍的证据来源调试了一个 CrashLooping 应用。你观察了失败的 Pod 状态,读取了当前和之前的容器日志,检查了事件,识别出 Deployment 中缺失的环境变量,通过 kubectl set env 修复了 Pod 模板,并通过其 Service 验证了恢复后的应用。


