跳转至

Pod 与容器

Pod 是 Kubernetes 最小的调度单元,但它不是「一个容器」的同义词。共享网络、共享存储、共同生命周期——理解这三件事,才能写出正确的多容器应用和探针。

难度 · 基础到进阶 预计 60 分钟 前置:架构与对象模型

你将学到

  • Pod 的生命周期、容器状态与重启策略
  • Init Container、Sidecar、Ambassador、Adapter 四种模式
  • liveness / readiness / startup 探针的区别与常见误用
  • requests / limits 与 QoS 等级的关系
  • securityContext 的最小权限写法
  • 用 describe、logs、debug 定位 Pod 故障

Pod 到底是什么

Pod 是一个或多个容器的逻辑分组,组内容器共享:

  • 网络命名空间:同一个 Pod IP,容器之间用 localhost 通信;
  • 存储卷:同一份 volume 可以挂到多个容器;
  • 生命周期:Pod 被删除时,所有容器一起终止。
pod-basic.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  namespace: default
  labels:
    app: demoapp
spec:
  containers:
    - name: demo
      image: ikubernetes/demoapp:v1.0
      imagePullPolicy: IfNotPresent
      ports:
        - name: http
          containerPort: 80
kubectl apply -f pod-basic.yaml
kubectl get pod pod-demo -o wide
kubectl describe pod pod-demo

预期:

NAME       READY   STATUS    RESTARTS   AGE   IP            NODE
pod-demo   1/1     Running   0          12s   10.244.1.15   k8s-node01

自主式 Pod 不会被重建

直接创建的 Pod 没有控制器管理。节点故障或 Pod 被删除后,它不会自动回来。生产环境应使用 Deployment / StatefulSet / DaemonSet 管理 Pod。

生命周期

Pod 阶段

阶段 含义 常见原因
Pending 已接受,但尚未运行 调度失败、拉镜像、挂卷
Running 至少一个容器在运行 正常
Succeeded 所有容器成功退出 Job 完成
Failed 至少一个容器失败退出 应用崩溃、OOM
Unknown 无法获取状态 节点失联

容器状态

状态 含义 排查
Waiting 尚未启动 ImagePullBackOff、CrashLoopBackOff、配置缺失
Running 正在运行 看 readiness 是否通过
Terminated 已退出 看 exitCode 与 reason(OOMKilled 等)

重启策略

策略 行为 适用
Always 容器退出就重启 长期服务(默认)
OnFailure 非 0 退出才重启 Job
Never 不重启 一次性任务

重启策略作用于 Pod 内的容器,不是 Pod 本身。Deployment 的 Pod 被删除后由 ReplicaSet 重建,这是另一层机制。

stateDiagram-v2
  [*] --> Pending
  Pending --> Running: 调度成功 + 容器启动
  Running --> Succeeded: 正常退出(Job)
  Running --> Failed: 非 0 退出 / OOM
  Failed --> Running: 重启策略允许
  Running --> Unknown: 节点失联
  Succeeded --> [*]

图:Pod 阶段与容器重启的关系。CrashLoopBackOff 不是 Pod 阶段,而是容器 Waiting 的一种原因。

多容器模式

Init Container

在主容器启动前运行,必须成功结束。适合做初始化:等依赖、拉配置、改权限、建目录。

init-container.yaml
apiVersion: v1
kind: Pod
metadata:
  name: init-demo
spec:
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          until nc -z mysql 3306; do
            echo "waiting for mysql..."
            sleep 2
          done
  containers:
    - name: app
      image: ikubernetes/demoapp:v1.0

Init Container 的特点:

  • 按顺序执行,前一个成功才执行下一个;
  • 失败时按 restartPolicy 重试;
  • 不参与 readiness,不会暴露端口;
  • 所有 Init Container 完成后,Pod 才进入 Initialized。

Sidecar

与主容器并行运行,扩展主容器的能力,例如日志收集、代理、证书刷新。Sidecar 共享网络和卷,所以常通过 localhost 与主容器通信。

sidecar-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sidecar-demo
spec:
  containers:
    - name: app
      image: ikubernetes/demoapp:v1.0
      env:
        - name: HOST
          value: "127.0.0.1"
        - name: PORT
          value: "8080"
    - name: proxy
      image: envoyproxy/envoy-alpine:v1.14.1
      args:
        - -c
        - /etc/envoy/envoy.yaml
      volumeMounts:
        - name: envoy-config
          mountPath: /etc/envoy
  volumes:
    - name: envoy-config
      configMap:
        name: envoy-config

原生 Sidecar

Kubernetes 1.29+ 支持把 Init Container 的 restartPolicy 设为 Always,让它以 Sidecar 方式运行,并在主容器退出时自动终止。比传统 Sidecar 更可控,但需要确认集群版本与特性门控。

Ambassador 与 Adapter

模式 作用 例子
Ambassador 代理外部访问,主容器只连 localhost 本地 Envoy / HAProxy
Adapter 转换主容器的输出格式 把应用指标转成 Prometheus 格式
Sidecar 扩展能力,与主容器并肩运行 日志收集、TLS 证书刷新

探针

探针决定「容器是否健康」以及「是否接收流量」。三种探针的语义完全不同:

探针 失败后果 用途
livenessProbe 重启容器 进程死锁、无法自愈
readinessProbe 从 Service Endpoints 摘除 启动未完成、依赖不可用
startupProbe 重启容器(启动阶段) 启动很慢的应用
probes.yaml
apiVersion: v1
kind: Pod
metadata:
  name: probe-demo
spec:
  containers:
    - name: app
      image: ikubernetes/demoapp:v1.0
      ports:
        - containerPort: 80
      startupProbe:
        httpGet:
          path: /livez
          port: 80
        failureThreshold: 30
        periodSeconds: 2
      livenessProbe:
        httpGet:
          path: /livez
          port: 80
        periodSeconds: 10
        timeoutSeconds: 2
        failureThreshold: 3
      readinessProbe:
        httpGet:
          path: /readyz
          port: 80
        periodSeconds: 5
        failureThreshold: 3

最常见的探针错误

把 livenessProbe 指向一个依赖外部服务的接口。数据库一抖动,所有 Pod 一起被重启,反而放大故障。Liveness 只应判断「进程是否还能自愈」,依赖健康度交给 readiness。

探针支持的三种方式:

  • httpGet:返回 200–399 视为成功;
  • tcpSocket:端口能建立连接;
  • exec:命令退出码为 0。

调试探针失败:

kubectl describe pod probe-demo | sed -n '/Events/,$p'
kubectl exec probe-demo -- wget -qO- http://localhost/livez

资源与 QoS

resources.yaml
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi
配置 QoS 等级 特点
所有容器都设置 requests=limits(CPU/内存) Guaranteed 最稳定,最后被驱逐
部分设置 requests/limits Burstable 常见,按 requests 调度
完全不设置 BestEffort 最先被驱逐

关键事实:

  • requests 影响调度:节点没有足够可分配资源时,Pod 会 Pending;
  • limits 影响运行:CPU 超限被限流(throttle),内存超限被 OOMKilled;
  • CPU 单位:100m = 0.1 核;内存单位:Mi / Gi 是二进制,M / G 是十进制。

查看资源使用:

kubectl top pod -n demo
kubectl describe node k8s-node01 | sed -n '/Allocated resources/,/Events/p'

安全上下文

最小权限的容器应默认满足:

security-context.yaml
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  runAsGroup: 3000
  fsGroup: 2000
  seccompProfile:
    type: RuntimeDefault
containers:
  - name: app
    image: ikubernetes/demoapp:v1.0
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
          - ALL

注意:

  • readOnlyRootFilesystem: true 时,应用需要写的目录要挂 emptyDir;
  • runAsNonRoot 要求镜像内有非 root 用户,否则容器起不来;
  • 不要用 privileged: true 解决权限问题,优先用 CAP_NET_BIND_SERVICE 等细粒度能力;
  • Pod 级 securityContext 与容器级是合并关系,容器级可覆盖。

命令与参数

字段 作用 Docker 对照
command 覆盖镜像 ENTRYPOINT --entrypoint
args 覆盖镜像 CMD 命令行参数
env 注入环境变量 -e
envFrom 从 ConfigMap/Secret 批量注入 —
containers:
  - name: demo
    image: ikubernetes/demoapp:v1.0
    command: ["/bin/sh", "-c"]
    args: ["echo start; exec /usr/local/bin/demoapp"]
    env:
      - name: APP_MODE
        value: "prod"

用 command 时记得 exec,否则 PID 1 是 shell,信号无法正确传递给应用,优雅终止会失败。

实验:制造一次探针故障

1. 部署一个 readiness 永远失败的 Pod

bad-readiness.yaml
apiVersion: v1
kind: Pod
metadata:
  name: bad-readiness
  labels:
    app: bad
spec:
  containers:
    - name: app
      image: ikubernetes/demoapp:v1.0
      readinessProbe:
        httpGet:
          path: /not-exist
          port: 80
        periodSeconds: 3
kubectl apply -f bad-readiness.yaml
kubectl get pod bad-readiness -w

预期 READY 一直是 0/1,但 STATUS 是 Running。这说明容器活着,只是不接收流量。

kubectl describe pod bad-readiness | grep -A3 -i readiness

2. 用 Service 验证流量被摘除

kubectl expose pod bad-readiness --name=bad-svc --port=80
kubectl get endpoints bad-svc

预期 Endpoints 为空(或没有 bad-readiness 的 IP)。

3. 制造 OOM

oom-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: oom-demo
spec:
  containers:
    - name: stress
      image: polinux/stress:1.0.4
      command: ["stress"]
      args: ["--vm", "1", "--vm-bytes", "200M", "--vm-hang", "1"]
      resources:
        limits:
          memory: 64Mi
kubectl apply -f oom-demo.yaml
kubectl get pod oom-demo -w
kubectl describe pod oom-demo | grep -E 'OOMKilled|Reason|Exit Code'

预期容器反复重启,Last State 显示 OOMKilled,退出码 137。

4. 用临时容器调试

kubectl debug -it bad-readiness --image=busybox:1.36 --target=app -- sh

在临时容器里可以共享进程命名空间(--target),用 wget、netstat 等工具排查,而不用改应用镜像。

排错表

现象 原因 处理
Pending 资源不足 / 污点 / 未绑定 PVC describe pod 看 Events
ImagePullBackOff 镜像名错、私有仓库无凭据 imagePullSecrets、crictl pull
CrashLoopBackOff 应用启动失败 logs --previous、describe 的 Exit Code
Running 但 0/1 readiness 失败 进容器访问探针路径
反复重启,退出码 137 OOMKilled 提高 memory limit 或查内存泄漏
CreateContainerConfigError ConfigMap/Secret 缺失 describe 看缺哪个 key
FailedMount PVC / Secret 挂载失败 describe pod、describe pvc
容器启动即退出,退出码 0 命令执行完就结束 用长期运行的前台进程
Terminating 卡住 finalizer / 优雅终止超时 --force --grace-period=0(谨慎)

进阶挑战

  1. 给一个慢启动应用配置 startupProbe,观察启动阶段 liveness 是否被禁用。
  2. 用 emptyDir 在两个容器之间共享文件,验证 Sidecar 能读到主容器的日志。
  3. 把 readOnlyRootFilesystem 打开,修复应用因无法写临时目录而启动失败的问题。
  4. 用 kubectl get pod -o jsonpath 提取所有容器的 restartCount 和 lastState。
  5. 对比 Guaranteed 与 BestEffort Pod 在节点内存压力下的驱逐顺序。

小结

  • Pod 共享网络、存储和生命周期;多容器是模式,不是目的。
  • Init 负责「启动前」,Sidecar 负责「运行中」,探针负责「是否健康」。
  • requests 决定调度,limits 决定运行边界;QoS 影响驱逐顺序。
  • 排错从 describe 的 Events 开始,再看 logs,最后才进容器。

延伸阅读