调试 CrashLooping 应用

KubernetesBeginner
立即练习

介绍

之前的实验介绍了日志、事件、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 验证了恢复后的应用。