K8S 资源清单 - 探针、Init Container 与 Pod 生命周期

上图展示了 Pod 从创建到终止的完整生命周期,本文按以下顺序依次讲解:Init Container → 健康探针 → 生命周期钩子。
注意:Pod 生命周期中的 initC、startupProbe、livenessProbe、readinessProbe、hook 都是可选的,可以根据实际需求选择全部、部分或者完全不用。
一、Init Container - 初始化容器
目标:了解 Init Container 的执行顺序、与普通容器的区别及其典型使用场景。
什么是 Init Container
Init Container 是 Pod 中在主容器启动之前执行的专用容器,用于完成初始化任务。
Init Container 与普通容器非常相似,但有以下两个关键区别:
Init Container 总是运行到成功完成为止
这意味着每个 Init Container 的退出码必须为 0。假设定义了 4 个 Init Container:
场景 1:Init 1 就失败了
Init 1 ❌ ──┐ ├─ 失败,后面的 Init 2、3、4 全都不会执行 │ ↓ Init 2 ❌(跳过) Init 3 ❌(跳过) Init 4 ❌(跳过)场景 2:Init 1 通过,Init 2 失败
Init 1 ✅ → Init 2 ❌ ──┐ ├─ 失败,后面的 Init 3、4 不会执行 │ ↓ Init 3 ❌(跳过) Init 4 ❌(跳过)场景 3:Init 1、2、3 通过,Init 4 失败
Init 1 ✅ → Init 2 ✅ → Init 3 ✅ → Init 4 ❌ ──┐ ├─ 失败,从头再来 │ ↓ 全部重跑核心规则:只要中间任何一个失败,后面的就不会执行,Kubernetes 会不断重试整个序列,直到所有 Init Container 都成功退出为止。
成功的标志:容器进程的退出码(exit code)必须为 0。 任何非 0 的退出码(如 1、127、137 等)都视为失败。
每个 Init Container 都必须在下一个 Init Container 启动之前成功完成
Init Container 是按照定义顺序依次执行的,不会并行启动:
时间线 → ├── Init 1 运行中 ──┤ │ (成功退出) │ ├── Init 2 运行中 ──┤ │ (成功退出) │ ├── Init 3 运行中 ──┤ │ (成功退出) │ └── 主容器启动 ──────┘这种线性执行的特性可以用于延迟操作——例如第一个 Init Container 等待数据库就绪,第二个等待缓存服务就绪,第三个进行数据迁移,全部完成后主容器才启动。
如果 Pod 的 Init Container 失败,Kubernetes 会不断地重启该 Pod,直到 Init Container 成功为止。但如果 Pod 的
restartPolicy为Never,则失败后不会重新启动。
补充说明:
- Init Container 与应用容器可以使用不同的镜像,可以把一些危险的工具(如 curl、nc、dig 等)放在 Init Container 中使用,避免打包到应用镜像中
- Init Container 多个之间是线性启动的,因此可以利用这一特性做一些延迟操作(如等待上游服务就绪)
- Init Container 不支持 readinessProbe,除此之外与普通容器的定义方式无异(支持资源限制、volume 挂载、环境变量等)
apiVersion: v1
kind: Pod
metadata:
name: initc-1
labels:
app: initc
spec:
initContainers:
- name: init-myservice
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'until nslookup myservice.default.svc.cluster.local; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'until nslookup mydb.default.svc.cluster.local; do echo waiting for mydb; sleep 2; done;']
containers:
- name: myapp-container
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
Init Container 阻塞性质演示
核心目的:演示 Init Container 的阻塞特性——只要 Init Container 没跑完,主容器就一直等着,Pod 一直卡在 Init 状态。
Step 1:创建 Init Container Pod
cat > 3.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: initc-1
labels:
app: initc
spec:
initContainers:
- name: init-myservice
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'until nslookup myservice.default.svc.cluster.local; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'until nslookup mydb.default.svc.cluster.local; do echo waiting for mydb; sleep 2; done;']
containers:
- name: myapp-container
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
EOF
Step 2:部署,观察阻塞效果
kubectl apply -f 3.pod.yaml
kubectl get pod initc-1 -w
# 输出:Pod 一直卡在 Init:0/2,不会变成 Running
# 这就是阻塞——Init Container 没跑完,主容器被挡住不让启动
Step 3:查看 Init Container 阻塞在哪里
kubectl describe pod initc-1
# Events 中可以看到:init-myservice 在反复尝试 nslookup myservice.default.svc.cluster.local
kubectl logs initc-1 -c init-myservice
# 日志显示不断打印:waiting for myservice
Step 4:创建依赖 Service,逐个解除阻塞
先创建 myservice,第一个 Init Container 会完成,但会被第二个 init-mydb 继续阻塞:
kubectl expose pod initc-1 --name=myservice --port=80 --target-port=80
kubectl get pod initc-1 -w
# 观察:Init:0/2 → Init:1/2(第一个通过了,但第二个还在阻塞)
再创建 mydb,第二个 Init Container 完成,主容器才真正启动:
kubectl expose pod initc-1 --name=mydb --port=3306
kubectl get pod initc-1 -w
# 观察:Init:1/2 → Init:2/2 → Running(全部解除阻塞,主容器启动)
Step 5:验证 Init Container 日志留存
kubectl logs initc-1 -c init-myservice
# 即使 Init Container 已执行完毕,日志仍然可查
清理:kubectl delete pod initc-1 --now && kubectl delete svc myservice mydb
Init Container 退出码验证
核心目的:验证 只有退出码为 0 才算成功,非零退出码(如 1)一律视为失败,Pod 会不断重启。
Step 1:创建 Pod
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp
image: wangyanglinux/myapp:v1.0
initContainers:
- name: randexit
image: wangyanglinux/tools:randexitv1
randexitv1 镜像随机返回 0 或 1(大于 50 返回 1,小于 50 返回 0)。
Step 2:部署并观察两种结果
kubectl apply -f 4.pod.yaml
kubectl get pod myapp-pod -w
反复部署,你会观察到两种情况:
| 退出码 | 结果 | Pod 状态 |
|---|---|---|
| 0 | ✅ 成功 | Init:0/1 → Init:1/1 → Running |
| 1 | ❌ 失败 | Init:0/1 → CrashLoopBackOff → 不断重启 |
结论:Init Container 只认退出码 0 为成功,任何非零值(1、2、127、130…)都算失败,Pod 会无限重启直到成功。
Step 3:查看失败详情
kubectl describe pod myapp-pod
# Events 中可以看到退出码
kubectl logs myapp-pod -c randexit
清理:kubectl delete pod myapp-pod
Init Container 与普通容器的区别
| 特性 | Init Container | 普通容器 |
|---|---|---|
| 执行顺序 | 串行,按定义顺序依次执行 | 并行,同时启动 |
| 执行条件 | 必须全部成功完成后,主容器才能启动 | 独立运行 |
| 重启行为 | 失败后不断重启直到成功 | 根据 restartPolicy |
| 资源计算 | 取所有 Init Container 的最大值作为 Pod 资源 | 所有容器的总和 |
| readinessProbe | ❌ 不支持 | ✅ 支持 |
| lifecycle hooks | ❌ 不支持 | ✅ 支持 postStart/preStop |
| 镜像更新 | 修改 Init Container 镜像后,Pod 必须重启 | 滚动更新 |
典型使用场景
- 等待依赖服务就绪:等待数据库、缓存等外部依赖创建完成
- 数据库迁移:在应用启动前执行 schema 迁移脚本
- 初始化数据:从远程下载配置文件、证书等
- 权限设置:修改文件系统权限、创建目录结构
- 预填充数据:向共享 Volume 中写入初始数据
二、标签选择器(Label Selector)
什么是标签?
标签(Label)是附加到 Kubernetes 对象(如 Pod、Service、Deployment)上的键值对,用于组织和选择资源。
metadata:
labels:
app: myapp
env: production
version: v1
什么是标签选择器?
标签选择器用于根据标签筛选资源。Kubernetes 中很多资源(Service、Deployment、NetworkPolicy)都依赖标签选择器来工作。
三种匹配方式:
| 匹配方式 | 语法 | 示例 |
|---|---|---|
| 等值匹配 | key=value | app=myapp |
| 不等值匹配 | key!=value | env!=test |
| 集合匹配 | key in (v1,v2) | version in (v1,v2) |
实验:标签选择器
核心目的:验证 Service 通过标签选择器匹配 Pod,只有标签匹配的 Pod 才会被加入 Endpoints。
Step 1:创建三个带不同标签的 Pod
mkdir -p /root/4/6
# 1.pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-1
namespace: default
labels:
app: myapp
spec:
containers:
- name: myapp-1
image: wangyanglinux/myapp:v1.0
# 2.pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-2
namespace: default
labels:
app: myapp
version: v1
spec:
containers:
- name: myapp-1
image: wangyanglinux/myapp:v1.0
# 3.pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-3
namespace: default
labels:
app: test
version: v1
spec:
containers:
- name: myapp-1
image: wangyanglinux/myapp:v1.0
Step 2:部署 Pod
cd /root/4/6
kubectl apply -f 1.pod.yaml
kubectl apply -f 2.pod.yaml
kubectl apply -f 3.pod.yaml
Step 3:查看所有 Pod 的标签
kubectl get pods --show-labels
NAME READY STATUS LABELS
pod-1 1/1 Running app=myapp
pod-2 1/1 Running app=myapp,version=v1
pod-3 1/1 Running app=test,version=v1
Step 4:创建 Service(selector: app=myapp)
kubectl create service clusterip myapp --tcp=80:80
kubectl get svc myapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp ClusterIP 10.1.105.95 <none> 80/TCP 10s
Service 的 selector 只匹配标签为 app=myapp 的 Pod,因此:
pod-1(app=myapp)✅ 匹配pod-2(app=myapp)✅ 匹配pod-3(app=test)❌ 不匹配
Step 5:查看 Endpoints
kubectl get endpoints myapp
只有 pod-1 和 pod-2 被选中(app=myapp),pod-3(app=test)不匹配。
Step 6:验证负载均衡
curl {myapp的ip}/hostname.html
# 输出:pod-1 或 pod-2(轮询)
# 注意:pod-3 永远不会出现
Step 7:用标签选择器筛选 Pod
# 等值匹配
kubectl get pods -l app=myapp
# 输出:pod-1, pod-2
# 不等值匹配
kubectl get pods -l app!=test
# 输出:pod-1, pod-2
# 集合匹配
kubectl get pods -l app in (myapp,test)
# 输出:pod-1, pod-2, pod-3
三、Pod 探针
Pod 启动后,Kubernetes 通过三种探针持续检测容器状态,确保应用健康运行。
前置条件:下面的探针实验接着「标签选择器」实验继续,pod-1、pod-2、pod-3 和 myapp Service 仍在运行。探针实验做完后统一清理。
探针是什么? 探针是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 调用由容器实现的 Handler。
三种 Handler 类型
| Handler | 检测方式 | 成功条件 |
|---|---|---|
| ExecAction | 在容器内执行指定命令 | 命令退出时返回码为 0 |
| TCPSocketAction | 对指定端口上的容器 IP 地址进行 TCP 检查 | 端口打开 |
| HTTPGetAction | 对指定端口和路径上的容器 IP 地址执行 HTTP GET 请求 | 响应状态码 ≥200 且 <400 |
三种探测结果
| 结果 | 说明 |
|---|---|
| 成功(Success) | 容器通过了诊断 |
| 失败(Failure) | 容器未通过诊断。Liveness Probe 会重启容器,Readiness Probe 会从 Endpoints 移除 |
| 未知(Unknown) | 诊断失败,但不采取任何行动,kubelet 会继续检查 |
Unknown 场景:当 kubelet 无法与容器通信(如节点故障、网络中断、kubelet 挂掉),探针无法执行,返回 Unknown。此时 Pod 不会被重启,也不会被移除流量,kubelet 会持续重试。
Liveness Probe 介绍:k8s 通过添加存活探针,解决虽然活着但是已经死了的问题。
三种探针对比
| 探针 | 检测目标 | 失败后果 | 用途 |
|---|---|---|---|
| Liveness Probe | 容器是否在运行 | 容器被重启 | 检测死锁、无响应 |
| Readiness Probe | 容器是否准备好接收流量 | 从 Endpoints 移除 | 检查依赖是否就绪 |
| Startup Probe | 容器是否完成启动 | 容器被终止 | 阻止其他探针在启动期执行 |
探针参数说明
探针有一些公共参数,适用于所有类型的探针:
| 参数 | 说明 | 默认值 | 最小值 |
|---|---|---|---|
initialDelaySeconds | 容器启动后等待多少秒后探针开始工作 | 0 秒 | 0 |
periodSeconds | 执行探测的时间间隔 | 10 秒 | 1 |
timeoutSeconds | 探针执行检测请求后,等待响应的超时时间 | 1 秒 | 1 |
successThreshold | 探针检测失败后认为成功的最小连接成功次数(必须为 1 才能激活和启动) | 1 | 1 |
failureThreshold | 探测失败的重试次数,重试一定次数后将认为失败 | 3 | 1 |
Readiness Probe 特别说明:K8s 通过添加就绪探针,解决尤其是在扩容时保证提供给用户的服务都是可用的。
Readiness Probe - HTTP GET 方式
就绪探测规则:
- 如果 Pod 内的容器不添加就绪探测,默认就绪
- 如果添加了就绪探测,只有就绪通过以后,才标记为就绪状态
- 当前 Pod 内的所有容器都就绪,才标记当前 Pod 就绪
| 结果 | 行为 |
|---|---|
| 成功 | 将当前容器标记为就绪 |
| 失败 | 静默(不采取行动,Pod 不接收流量) |
| 未知 | 静默(不采取行动) |
目标:验证 Readiness Probe 基于 HTTP GET 方式检测容器是否就绪,只有当指定路径返回 200-399 状态码时容器才被视为就绪。
Step 1:创建具备 HTTP GET 就绪探测的 Pod
cat > 4.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
labels:
app: myapp
env: test
spec:
containers:
- name: readiness-httpget-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
EOF
Step 2:部署并观察就绪状态
kubectl apply -f 4.pod.yaml
kubectl get pod readiness-httpget-pod -w
由于 myapp 镜像的 /index1.html 默认不存在,Readiness Probe 会持续失败,Pod 一直处于 NotReady 状态。
Step 3:创建就绪文件,使 Pod 就绪
kubectl exec readiness-httpget-pod -- touch /usr/local/nginx/html/index1.html
kubectl get pod readiness-httpget-pod -w
# 观察:NotReady → Ready
Step 4:删除就绪文件,Pod 恢复未就绪
kubectl exec readiness-httpget-pod -- rm /usr/local/nginx/html/index1.html
kubectl get pod readiness-httpget-pod -w
# 观察:Ready → NotReady(Pod 不会被重启,RESTARTS 仍为 0)
清理:kubectl delete pod readiness-httpget-pod
Readiness Probe - Exec 方式
目标:验证 Readiness Probe 是持续检测的——就绪状态跟随容器整个生命周期,文件存在时就绪,文件删除后立即变为未就绪,不会被"记住"以前的状态。
Step 1:创建具备 Exec 就绪探测的 Pod
cat > 5.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: readiness-exec-pod
namespace: default
spec:
containers:
- name: readiness-exec-container
image: wangyanglinux/tools:busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","touch /tmp/live ; sleep 60; rm -rf /tmp/live; sleep 3600"]
readinessProbe:
exec:
command: ["test","-e","/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
EOF
test -e /tmp/live 检测文件是否存在,存在返回 0(就绪),不存在返回 1(未就绪)。
Step 2:部署并观察就绪状态
kubectl apply -f 5.pod.yaml
kubectl get pod readiness-exec-pod -w
容器启动时创建 /tmp/live,60 秒后删除。所以:
- 前 60 秒:Ready(文件存在,探测成功)
- 60 秒后:NotReady(文件被删除,探测失败)
Step 3:验证 RESTARTS 不变
kubectl get pod readiness-exec-pod
# RESTARTS 始终为 0,Readiness 失败不会重启
清理:kubectl delete pod readiness-exec-pod
Readiness Probe - TCP Socket 方式
目标:验证 Readiness Probe 基于 TCP Socket 检测端口是否打开。
Step 1:创建具备 TCP Socket 就绪探测的 Pod
cat > 6.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: readiness-tcp-pod
spec:
containers:
- name: readiness-exec-container
image: wangyanglinux/myapp:v1.0
readinessProbe:
initialDelaySeconds: 5
timeoutSeconds: 1
tcpSocket:
port: 80
EOF
Step 2:部署并观察
kubectl apply -f 6.pod.yaml
kubectl get pod readiness-tcp-pod -w
# 容器 80 端口默认打开,TCP 探测成功,Pod 很快变为 Ready
Step 3:验证就绪状态
kubectl describe pod readiness-tcp-pod
# Events 中可以看到 TCP 探测结果
清理:kubectl delete pod readiness-tcp-pod
统一清理:探针实验全部做完后,执行以下命令清理标签选择器实验的资源:
kubectl delete pod pod-1 pod-2 pod-3 && kubectl delete svc myapp
Liveness Probe - Exec 方式
存活探测规则:
- 如果 Pod 内的容器不指定存活探测,可能会发生容器运行但是无法提供服务的情况
- Liveness Probe 检测容器是否"活着",失败则重启容器
| 结果 | 行为 |
|---|---|
| 成功 | 静默(容器继续运行) |
| 失败 | 根据重启策略进行重启 |
| 未知 | 静默(不采取行动) |
目标:验证当 Liveness Probe 失败时,Kubernetes 会自动重启容器。
Step 1:创建测试 Pod
cat > 7.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
spec:
containers:
- name: liveness-exec-container
image: wangyanglinux/tools:busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","touch /tmp/live ; sleep 60; rm -rf /tmp/live;sleep 3600"]
livenessProbe:
exec:
command: ["test","-e","/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
EOF
容器启动时创建 /tmp/live,60 秒后删除。test -e /tmp/live 检测文件是否存在,文件不存在时 Liveness Probe 失败,容器被重启。
Step 2:部署并观察
kubectl apply -f 7.pod.yaml
kubectl get pod liveness-exec-pod -w
- 前 60 秒:容器正常运行,Liveness Probe 成功
- 60 秒后:文件被删除,Liveness Probe 失败,容器被重启
- 观察 RESTARTS 字段不断增加
Step 3:查看重启详情
kubectl describe pod liveness-exec-pod
# Events 中可以看到 Liveness Probe 失败后容器被重启
清理:kubectl delete pod liveness-exec-pod
Liveness Probe - HTTP GET 方式
目标:验证基于 HTTP GET 的存活探测,当指定路径返回非 2xx/3xx 状态码时容器被重启。
Step 1:创建测试 Pod
cat > 8.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: liveness-httpget-pod
namespace: default
spec:
containers:
- name: liveness-httpget-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 3
EOF
Step 2:部署并观察
kubectl apply -f 8.pod.yaml
kubectl get pod liveness-httpget-pod -w
/index.html 默认存在,Liveness Probe 成功,容器正常运行。
Step 3:触发 Liveness Probe 失败
kubectl exec liveness-httpget-pod -- rm /usr/local/nginx/html/index.html
kubectl get pod liveness-httpget-pod -w
# 观察:RESTARTS 不断增加
清理:kubectl delete pod liveness-httpget-pod
Liveness Probe - TCP Socket 方式
目标:验证基于 TCP Socket 的存活探测,当端口关闭时容器被重启。
Step 1:创建测试 Pod
cat > 9.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp-pod
spec:
containers:
- name: liveness-tcp-container
image: wangyanglinux/myapp:v1.0
livenessProbe:
initialDelaySeconds: 5
timeoutSeconds: 1
tcpSocket:
port: 80
EOF
Step 2:部署并观察
kubectl apply -f 9.pod.yaml
kubectl get pod liveness-tcp-pod -w
80 端口默认打开,Liveness Probe 成功,容器正常运行。
Step 3:触发 Liveness Probe 失败
kubectl exec liveness-tcp-pod -- /bin/sh -c "kill -9 1"
kubectl get pod liveness-tcp-pod -w
# 观察:容器被重启,RESTARTS 不断增加
清理:kubectl delete pod liveness-tcp-pod
Startup Probe - 启动保护
目标:验证 Startup Probe 在启动期间禁用 Readiness Probe,启动完成后才激活。
Step 1:创建测试 Pod
cat > 10.pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: startupprobe-1
namespace: default
spec:
containers:
- name: myapp-container
image: wangyanglinux/myapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
port: 80
path: /index2.html
initialDelaySeconds: 1
periodSeconds: 3
startupProbe:
httpGet:
path: /index1.html
port: 80
failureThreshold: 30
periodSeconds: 10
EOF
关键点:
- startupProbe 探测
/index1.html,failureThreshold: 30×periodSeconds: 10= 最多等待 300 秒 - readinessProbe 探测
/index2.html,但在 startupProbe 成功之前不会执行
Step 2:部署并观察
kubectl apply -f 10.pod.yaml
kubectl get pod startupprobe-1 -w
/index1.html不存在 → startupProbe 持续失败 → Pod 一直0/1 NotReady/index2.html不存在 → readinessProbe 也不会执行(被 startupProbe 挡住)
Step 3:先创建 readiness 文件
kubectl exec startupprobe-1 -- touch /usr/local/nginx/html/index2.html
kubectl get pod startupprobe-1 -w
# /index2.html 存在,但 startupProbe 还没通过 → Pod 仍然 NotReady
Step 4:再创建 startup 文件,Pod 启动并就绪
kubectl exec startupprobe-1 -- touch /usr/local/nginx/html/index1.html
kubectl get pod startupprobe-1 -w
# startupProbe 成功 → readinessProbe 开始执行 → /index2.html 已存在 → Pod Ready
清理:kubectl delete pod startupprobe-1
四、Pod 生命周期钩子
目标:理解 postStart 和 preStop 钩子的执行时机、阻塞行为以及优雅关闭的实现。
什么是 Lifecycle Hook
Kubernetes 提供两个生命周期钩子:
Pod 创建 → Init Containers → 主容器启动 → postStart → ... → preStop → Pod 终止
| 钩子 | 执行时机 | 失败后果 |
|---|---|---|
| postStart | 容器创建后立即执行(与 ENTRYPOINT 并行) | 容器被杀死并重启 |
| preStop | 容器终止前执行 | 阻塞等待完成(最多 terminalGracePeriodSeconds) |
postStart 详解
postStart 在容器被创建后立即执行,与容器的 ENTRYPOINT 并行运行。
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Container started > /usr/share/message"]
注意:postStart 与 ENTRYPOINT 之间没有先后顺序保证。如果 postStart 卡住或需要长时间执行,容器会保持在 Waiting 状态(
postStart hook)直到超时或完成。
preStop 详解
preStop 在容器被终止前执行,常用于优雅关闭(Graceful Shutdown)。
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "nginx -s quit; sleep 10"]
preStop + terminationGracePeriodSeconds 配合使用:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: nginx
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "nginx -s quit; while pgrep nginx; do sleep 1; done"]
postStart 与 preStop - Exec 方式
目标:验证 postStart 在容器启动时执行写入操作,preStop 在容器终止前持续执行直到超时。
Step 1:创建带 Exec 钩子的 Pod
# 11.pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-exec-pod
spec:
containers:
- name: lifecycle-exec-container
image: wangyanglinux/myapp:v1.0
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo postStart > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh", "-c", "i=0; while true; do echo preStop $i >> /usr/share/message; sleep 2; i=$((i+1)); done"]
Step 2:部署并验证 postStart
kubectl apply -f 11.pod.yaml
kubectl exec lifecycle-exec-pod -- cat /usr/share/message
# 输出:postStart
postStart 在容器创建后立即执行,写入了 postStart 内容。
Step 3:观察 preStop
开启两个终端:
终端 1 - 实时查看 message 文件:
kubectl exec lifecycle-exec-pod -- tail -f /usr/share/message
终端 2 - 删除 Pod 触发 preStop:
kubectl delete pod lifecycle-exec-pod
回到终端 1,观察 preStop 0、preStop 1、preStop 2… 不断追加到文件,说明 preStop 在持续执行。等待几秒后 Pod 才真正终止。
清理:kubectl delete pod lifecycle-exec-pod
postStart 与 preStop - HTTP 方式
目标:验证 postStart 和 preStop 通过 HTTP GET 请求触发钩子,kubelet 在节点上向指定地址发送请求。
Step 1:在节点上启动测试 WebServer
# 在 k8s-node01 上执行(192.168.66.11)
docker run -it --rm -p 1234:80 wangyanglinux/myapp:v1.0
Step 2:创建带 HTTP 钩子的 Pod
# 12.pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-httpget-pod
labels:
name: lifecycle-httpget-pod
spec:
containers:
- name: lifecycle-httpget-container
image: wangyanglinux/myapp:v1.0
ports:
- containerPort: 80
lifecycle:
postStart:
httpGet:
host: 192.168.66.11
path: index.html
port: 1234
preStop:
httpGet:
host: 192.168.66.11
path: hostname.html
port: 1234
Step 3:部署并观察
kubectl apply -f 12.pod.yaml
kubectl get pod lifecycle-httpget-pod -w
- postStart 触发:kubelet 向
192.168.66.11:1234/index.html发送 HTTP GET - WebServer 日志中可以看到 postStart 的请求记录
Step 4:删除 Pod 触发 preStop
kubectl delete pod lifecycle-httpget-pod
- preStop 触发:kubelet 向
192.168.66.11:1234/hostname.html发送 HTTP GET - WebServer 日志中可以看到 preStop 的请求记录,Pod 在请求完成后才终止
清理:kubectl delete pod lifecycle-httpget-pod && docker stop $(docker ps -q)
postStart 与 preStop - 优雅关闭演示
Step 1:创建带 Lifecycle Hook 的 Pod
cat > lifecycle-demo.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
terminationGracePeriodSeconds: 30
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo '>>> postStart: 容器已启动' > /usr/share/nginx/html/index.html"]
preStop:
exec:
command: ["/bin/sh", "-c", "echo '>>> preStop: 正在关闭...'; nginx -s quit; while pgrep nginx; do sleep 1; done; echo '>>> preStop: 已安全关闭'"]
EOF
Step 2:部署并验证 postStart
kubectl apply -f lifecycle-demo.yaml
kubectl get pod lifecycle-demo -w
# 查看 postStart 写入的内容
kubectl exec lifecycle-demo -- cat /usr/share/nginx/html/index.html
# 输出:>>> postStart: 容器已启动
Step 3:验证 preStop 优雅关闭
# 新开一个终端,持续查看 Pod 事件
kubectl get pod lifecycle-demo -w
# 删除 Pod 触发 preStop
kubectl delete pod lifecycle-demo
观察输出:
lifecycle-demo 1/1 Terminating 0 ...
lifecycle-demo 0/1 Terminating 0 ... (preStop 执行中)
lifecycle-demo 0/1 Terminating 0 ... (还在等待)
lifecycle-demo 0/1 Terminating 0 ...
...
lifecycle-demo 0/1 Terminating 0 30s
lifecycle-demo 0/1 Terminating 0 30s
...
lifecycle-demo 0/1 Terminating 0 (超出 terminationGracePeriodSeconds)
lifecycle-demo 0/1 Terminating 0
Pod 会在 Terminating 状态停留最多 30 秒(terminationGracePeriodSeconds),等待 preStop 执行完成。若 preStop 超时,kubelet 会直接发送 SIGKILL。
preStop 延伸话题 - terminationGracePeriodSeconds
在 k8s 中,理想的状态是 Pod 优雅释放,但并不是每一个 Pod 都会这么顺利:
- Pod 卡死,处理不了优雅退出的命令或者操作
- 优雅退出的逻辑有 BUG,陷入死循环
- 代码问题,导致执行的命令没有效果
对于以上问题,k8s 的 Pod 终止流程中还有一个"最多可以容忍的时间",即 grace period(在 pod.spec.terminationGracePeriodSeconds 字段定义),这个值默认是 30 秒。
当我们执行 kubectl delete 的时候也可以通过 --grace-period 参数显式指定一个优雅退出时间来覆盖 Pod 中的配置。如果我们配置的 grace period 超过时间之后,k8s 就只能选择强制 kill Pod。
值得注意的是:preStop Hook 和 SIGTERM 信号是并行发生的。k8s 不会等待 preStop Hook 完成。如果你的应用程序完成关闭并在
terminationGracePeriodSeconds完成之前退出,k8s 会立即进入下一步。
# 覆盖 grace period
kubectl delete pod <pod-name> --grace-period=60
# 强制删除(立即移除,不等待)
kubectl delete pod <pod-name> --grace-period=0 --force
五、总结
Init Container
- 串行执行,每个 Init Container 必须成功后才能执行下一个
- 所有 Init Container 成功后,主容器才会启动
- 适用于等待依赖就绪、数据初始化、权限设置等场景
- 不支持 readinessProbe 和 lifecycle hooks
三大探针
| 探针 | 检测目标 | 失败后果 | 典型用途 |
|---|---|---|---|
| Liveness Probe | 容器是否"活着" | 容器被重启 | 检测死锁、死循环 |
| Readiness Probe | 容器是否"就绪" | 从 Service Endpoints 移除 | 等待依赖就绪、流量控制 |
| Startup Probe | 容器是否完成启动 | 容器被终止 | 保护启动缓慢的应用 |
Lifecycle Hook
- postStart:容器启动后执行(与 ENTRYPOINT 并行),失败则重启容器
- preStop:容器终止前执行,用于优雅关闭应用
- preStop 的执行时间受
terminationGracePeriodSeconds限制,超时后强制杀进程
六、综合演示 - Pod All
目标:将 Init Container、Liveness Probe、Readiness Probe、Lifecycle Hook 全部组合到一个 Pod 中,验证它们互不冲突、协同工作。
前置条件
先创建两个 Service 供 Init Container 使用:
kubectl run myservice --image=wangyanglinux/myapp:v1.0 --port=80 --expose
kubectl run mydb --image=wangyanglinux/myapp:v1.0 --port=3306 --expose
在节点上启动测试 WebServer(供 Lifecycle Hook 使用):
# 在 k8s-node01 上执行
docker run -it --rm -p 1234:80 wangyanglinux/myapp:v1.0
YAML 配置
# pod-all.yaml
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-pod
labels:
app: lifecycle-pod
spec:
containers:
- name: busybox-container
image: wangyanglinux/tools:busybox
command: ["/bin/sh","-c","touch /tmp/live ; sleep 600; rm -rf /tmp/live; sleep 3600"]
livenessProbe:
exec:
command: ["test","-e","/tmp/live"]
initialDelaySeconds: 1
periodSeconds: 3
lifecycle:
postStart:
httpGet:
host: 192.168.66.11
path: index.html
port: 1234
preStop:
httpGet:
host: 192.168.66.11
path: hostname.html
port: 1234
- name: myapp-container
image: wangyanglinux/myapp:v1.0
livenessProbe:
httpGet:
port: 80
path: /index.html
initialDelaySeconds: 1
periodSeconds: 3
timeoutSeconds: 3
readinessProbe:
httpGet:
port: 80
path: /index1.html
initialDelaySeconds: 1
periodSeconds: 3
initContainers:
- name: init-myservice
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'until nslookup myservice.default.svc.cluster.local; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: wangyanglinux/tools:busybox
command: ['sh', '-c', 'until nslookup mydb.default.svc.cluster.local; do echo waiting for mydb; sleep 2; done;']
部署与观察
kubectl apply -f pod-all.yaml
kubectl get pod lifecycle-pod -w
观察顺序:
- Init Container 阶段:
Init:0/2→Init:1/2→Init:2/2(等待 myservice 和 mydb 就绪) - 容器启动阶段:Init 全部完成后,busybox-container 和 myapp-container 同时启动
- postStart 触发:kubelet 向
192.168.66.11:1234/index.html发送 HTTP GET - Probe 检测阶段:
- busybox-container 的 Liveness Probe 开始检测
/tmp/live - myapp-container 的 Liveness Probe 检测
/index.html(存在,成功) - myapp-container 的 Readiness Probe 检测
/index1.html(不存在,NotReady)
- busybox-container 的 Liveness Probe 开始检测
- 60 秒后:busybox 删除
/tmp/live,Liveness Probe 失败,容器被重启 - 删除 Pod:触发 preStop,kubelet 向
192.168.66.11:1234/hostname.html发送 HTTP GET
关键验证
# 查看 Pod 状态
kubectl get pod lifecycle-pod -w
# 查看 Init Container 日志
kubectl logs lifecycle-pod -c init-myservice
kubectl logs lifecycle-pod -c init-mydb
# 查看容器日志
kubectl logs lifecycle-pod -c busybox-container
kubectl logs lifecycle-pod -c myapp-container
# 查看事件(观察 Probe 和 Hook 的执行记录)
kubectl describe pod lifecycle-pod
清理:
kubectl delete pod lifecycle-pod
kubectl delete svc myservice mydb
docker stop $(docker ps -q)
结论:Init Container、Liveness Probe、Readiness Probe、Lifecycle Hook 可以同时配置在同一个 Pod 中,各自独立工作、互不干扰。Init Container 负责前置初始化,Probe 负责健康检查,Hook 负责生命周期事件处理,三者各司其职。
