Pod 与容器¶
Pod 是 Kubernetes 最小的调度单元,但它不是「一个容器」的同义词。共享网络、共享存储、共同生命周期——理解这三件事,才能写出正确的多容器应用和探针。
你将学到¶
- Pod 的生命周期、容器状态与重启策略
- Init Container、Sidecar、Ambassador、Adapter 四种模式
- liveness / readiness / startup 探针的区别与常见误用
- requests / limits 与 QoS 等级的关系
- securityContext 的最小权限写法
- 用
describe、logs、debug定位 Pod 故障
Pod 到底是什么¶
Pod 是一个或多个容器的逻辑分组,组内容器共享:
- 网络命名空间:同一个 Pod IP,容器之间用
localhost通信; - 存储卷:同一份 volume 可以挂到多个容器;
- 生命周期:Pod 被删除时,所有容器一起终止。
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
预期:
自主式 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¶
在主容器启动前运行,必须成功结束。适合做初始化:等依赖、拉配置、改权限、建目录。
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 与主容器通信。
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 |
重启容器(启动阶段) | 启动很慢的应用 |
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¶
| 配置 | 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是十进制。
查看资源使用:
安全上下文¶
最小权限的容器应默认满足:
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¶
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
预期 READY 一直是 0/1,但 STATUS 是 Running。这说明容器活着,只是不接收流量。
2. 用 Service 验证流量被摘除¶
预期 Endpoints 为空(或没有 bad-readiness 的 IP)。
3. 制造 OOM¶
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. 用临时容器调试¶
在临时容器里可以共享进程命名空间(--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(谨慎) |
进阶挑战¶
- 给一个慢启动应用配置
startupProbe,观察启动阶段 liveness 是否被禁用。 - 用
emptyDir在两个容器之间共享文件,验证 Sidecar 能读到主容器的日志。 - 把
readOnlyRootFilesystem打开,修复应用因无法写临时目录而启动失败的问题。 - 用
kubectl get pod -o jsonpath提取所有容器的restartCount和lastState。 - 对比
Guaranteed与BestEffortPod 在节点内存压力下的驱逐顺序。
小结¶
- Pod 共享网络、存储和生命周期;多容器是模式,不是目的。
- Init 负责「启动前」,Sidecar 负责「运行中」,探针负责「是否健康」。
- requests 决定调度,limits 决定运行边界;QoS 影响驱逐顺序。
- 排错从
describe的 Events 开始,再看logs,最后才进容器。
延伸阅读¶
- Pod 生命周期
- 配置探针
- Pod 安全标准
- 原仓库示例:examples/pods
- 下一章:工作负载