2.3. Pod 与容器

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

层级:

L2 · 核心

难度:

基础到进阶

预计:

60 分钟

前置:

架构与对象模型

2.3.1. 你将学到

  • Pod 的生命周期、容器状态与重启策略

  • Init Container、Sidecar、Ambassador、Adapter 四种模式

  • liveness / readiness / startup 探针的区别与常见误用

  • requests / limits 与 QoS 等级的关系

  • securityContext 的最小权限写法

  • 用生命周期钩子、hostNetwork/hostPort 与 sysctls 处理特殊场景

  • 用 describe、logs、debug 定位 Pod 故障

2.3.2. 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。

2.3.3. 生命周期

2.3.3.1. Pod 阶段

阶段

含义

常见原因

Pending

已接受,但尚未运行

调度失败、拉镜像、挂卷

Running

至少一个容器在运行

正常

Succeeded

所有容器成功退出

Job 完成

Failed

至少一个容器失败退出

应用崩溃、OOM

Unknown

无法获取状态

节点失联

2.3.3.2. 容器状态

状态

含义

排查

Waiting

尚未启动

ImagePullBackOff、CrashLoopBackOff、配置缺失

Running

正在运行

看 readiness 是否通过

Terminated

已退出

看 exitCode 与 reason(OOMKilled 等)

2.3.3.3. 重启策略

策略

行为

适用

Always

容器退出就重启

长期服务(默认)

OnFailure

非 0 退出才重启

Job

Never

不重启

一次性任务

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

架构图

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

2.3.4. 多容器模式

2.3.4.1. 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。

2.3.4.2. 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 更可控,但需要确认集群版本与特性门控。

2.3.4.3. Ambassador 与 Adapter

模式

作用

例子

Ambassador

代理外部访问,主容器只连 localhost

本地 Envoy / HAProxy

Adapter

转换主容器的输出格式

把应用指标转成 Prometheus 格式

Sidecar

扩展能力,与主容器并肩运行

日志收集、TLS 证书刷新

2.3.5. 探针

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

探针

失败后果

用途

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

2.3.6. 资源与 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'

2.3.7. 安全上下文

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

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 与容器级是合并关系,容器级可覆盖。

2.3.8. 命令与参数

字段

作用

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,信号无法正确传递给应用,优雅终止会失败。

2.3.9. 生命周期钩子与节点级网络

2.3.9.1. postStart 与 preStop

容器除了探针,还有两个生命周期钩子:

钩子

执行时机

常见用途

postStart

容器创建后、主进程启动前并行触发

初始化文件、注册到外部系统

preStop

容器收到 TERM 之前同步执行

摘流量、等待连接排空、保存状态

lifecycle-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  terminationGracePeriodSeconds: 30
  containers:
    - name: demo
      image: ikubernetes/demoapp:v1.0
      lifecycle:
        postStart:
          exec:
            command: ["/bin/sh", "-c", "echo started > /tmp/started"]
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 5"]

preStop 在容器收到 SIGTERM 之前执行,但总时长受 terminationGracePeriodSeconds 限制。超时后 kubelet 发送 SIGKILL。如果应用不能快速优雅退出,preStop 里不要 sleep 太久,否则会拖慢滚动发布。

2.3.9.2. hostNetwork、hostPort 与 DNS

  • hostNetwork: true:Pod 直接使用节点网络命名空间,Pod IP 就是节点 IP。适合网络代理、监控采集等系统组件,不适合普通业务。

  • hostPort:把容器端口映射到节点端口,依赖 CNI 的 portmap 能力;同一节点上端口不能冲突。它与 hostNetwork 不要同时使用,否则端口语义会重叠。

  • dnsPolicy:hostNetwork Pod 默认可能使用 Default,需要访问集群 Service 时显式设置 ClusterFirstWithHostNet。

host-network-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: host-network-demo
spec:
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet
  containers:
    - name: demo
      image: ikubernetes/demoapp:v1.0
      ports:
        - containerPort: 80

2.3.9.3. sysctls:安全与不安全参数

securityContext.sysctls 可以设置内核参数,但只有部分参数被视为 safe,例如 net.ipv4.ip_unprivileged_port_start;修改 net.core.somaxconn 等参数需要 kubelet 显式允许,且通常要求 privileged。

sysctls-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: sysctls-demo
spec:
  securityContext:
    sysctls:
      - name: net.ipv4.ip_unprivileged_port_start
        value: "0"
  containers:
    - name: demo
      image: ikubernetes/demoapp:v1.0

警告

不要用 hostNetwork 绕过网络策略 hostNetwork Pod 不经过 Pod 网络,NetworkPolicy 对它的约束方式与普通 Pod 不同。生产环境只在 CNI、日志、监控等基础组件中使用,并单独做节点级隔离。

对应原书第 4 章的 lifecycle-demo.yaml、pod-using-hostnetwork.yaml、pod-using-hostport.yaml 与 securitycontext-sysctls-demo.yaml。

2.3.10. 实验:制造一次探针故障

2.3.10.1. 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.3.10.2. 2. 用 Service 验证流量被摘除

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

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

2.3.10.3. 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。

2.3.10.4. 4. 用临时容器调试

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

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

2.3.11. 排错表

现象

原因

处理

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(谨慎)

2.3.12. 进阶挑战

  1. 给一个慢启动应用配置 startupProbe,观察启动阶段 liveness 是否被禁用。

  2. 用 emptyDir 在两个容器之间共享文件,验证 Sidecar 能读到主容器的日志。

  3. 把 readOnlyRootFilesystem 打开,修复应用因无法写临时目录而启动失败的问题。

  4. 用 kubectl get pod -o jsonpath 提取所有容器的 restartCount 和 lastState。

  5. 对比 Guaranteed 与 BestEffort Pod 在节点内存压力下的驱逐顺序。

2.3.13. 小结

  • Pod 共享网络、存储和生命周期;多容器是模式,不是目的。

  • Init 负责「启动前」,Sidecar 负责「运行中」,探针负责「是否健康」。

  • requests 决定调度,limits 决定运行边界;QoS 影响驱逐顺序。

  • 排错从 describe 的 Events 开始,再看 logs,最后才进容器。

  • 生命周期钩子要配合优雅终止窗口;hostNetwork 只用于基础组件。

2.3.14. 延伸阅读