有不少人平常会讲“Pod重启了”,然而,于Kubernetes当中,此种说法实际上不太严谨。
鉴于众人所说的“Pod 重启”,常常混淆了两件截然不同之事:其一为,同一个 Pod 当中的容器重启了;其二为,旧的 Pod 被删掉之后,又构建了一个全新的 Pod。
本文所要讲明白说清晰的,恰恰是Kubernetes在不同场景状况之下,究竟会引发触发哪一种行为表现。本文乃是依据基于Kubernetes 1.35 GA验证通过核实没问题的生产环境内部机制指南。
用于配套的仓库,其网址是,github.com/opscart/k8s-pod-restart-mechanics。
术语问题
被工程师们常常提及的“Pod重启了”这个表述,实际上并不严谨,在Kubernetes当中,人们常说的“Pod重启”,当中通常混淆了几种全然不同的情况。
请问,能否这样表述会更准确些,即现象方面,Pod UID 出现了变化、Pod IP 出现了变化、Restart Count 出现了变化?
同一个 Pod 内部的容器进程被重新拉起
+1
容器重启
旧 Pod 被删除,控制器重新创建一个新 Pod
通常会变
新 Pod 从 0 开始
Pod 重建 / Pod 被替换
Pod 原地调整 CPU
原地 resize
Pod在原地进行内存调整,并且所采用的策略是RestartContainer。
+1
同 Pod 内容器重启
有一种极为实用的判断方式是,首先查看Pod UID是否发生了改变,接着查看restartCount是否有所增加。
核心洞察:kubelet 实际上在看什么
kubelet所观察的对象是Pod Spec,并非ConfigMap、Secret,也不是Istio的CRD。倘若Pod Spec未发生变化,那么kubelet便不会触发任何动作。
这个事实,能够对生产环境里面那多数的,“为什么我的配置没有更新”这样的排障问题作出解释。
需要注意的是:
就决策矩阵变更项而言,同 Pod 内容器,会重启吗,还是会触发 Pod 重建或者替换呢,并且这种情况是否会自动发生呢。
容器镜像
不会
会,由Deployment触发,由StatefulSet等控制器触发。
环境变量(Pod Spec 中定义)
不会自动发生
通常需要
否 —— 需要手动 rollout
ConfigMap(volume 挂载)
不会
不会
部分自动 —— 文件会更新,但是否生效取决于应用是否热加载
设置了环境变量的,从配置映射中来获取值的,配置映射(环境变量从其获取 / 值从其获取)。
不会自动发生
通常需要
否,需要通过手动进行rollout,又或者借助Reloader这一方式。
Secret(volume 挂载)
不会
不会
部分自动 —— 文件会更新,但是否生效取决于应用是否热加载
Secret(envFrom / valueFrom)
不会自动发生
通常需要
否 —— 需要手动 rollout 或借助 Reloader
不会
不会
会 —— kubelet 自动轮转
CPU resize(K8s 1.35+)
不会
不会
否 —— 手动 patch
Memory resize(K8s 1.35+)
取决于 resizePolicy
不会
否 —— 手动 patch
用于Istio服务网格的虚拟服务规则制定,以及用于目标规则设定的相关内容,其在服务治理等方面有着重要效用。
不会
不会
会 —— xDS push
NetworkPolicy
不会
不会
会 —— CNI Agent
Service ports
不会
不会
会 —— kube-proxy
RBAC
不会
不会
会 —— API Server
节点 drain / eviction
不会在原 Pod 内重启
会 —— Pod 被驱逐后由控制器补出新 Pod
向下的这张流程图形,将同一个矩阵予以转化,变成了更适宜于线上进行排障之际所运用的决策路径。
图1:完整的决策流程图——此更改是否需要重启 pod?
场景 1:ConfigMap ,为何相同的变更会呈现出两种不同的行为?
图2,ConfigMap环境变量与,卷挂载——环境变量pod冻结,卷pod通过,kubelet符号链接交换自动同步。
1)环境变量模式(envFrom / valueFrom)
当处于execve()这个操作的时候,内核会将环境变量进行复制,复制的目的地是 /proc/ ,有这样的情况发生。
关于“/environ” ,这段内存是归进程自身所拥有的,外部的系统是没有办法再去对其进行修改操作的情况下,你在更新 “ConfigMap” 之后, “kubelet” 是看不到 “Pod Spec” 所发生的变化的,在这样的情形下,于是就什么事情都不会去做了,进程会在没有期限的情况下保留旧有的值。
2)卷挂载模式
在卷挂载模式当中,kubelet 进行同步配置所采用的方式并非是“对文件内容作出修改”,而是借助原子性的符号链接切换(symlink swap):
/etc/config/
├── ..2025_12_19_11_30_00/ ← 新数据目录(由 kubelet 创建)
│ └── APP_COLOR ← "red"
├── ..data ───────────────── ..2025_12_19_11_30_00/ ← 原子切换 symlink
└── APP_COLOR ────────────── ..data/APP_COLOR
此次符号链接切换,会于..data 之上生成 IN_CREATE ,并非在文件自身产生 IN_MODIFY。若应用于已打开的文件描述符处监听 IN_MODIFY ,那么它会全然错过此次变更。这为 nginx 在无显式 inotify 处理情况下,不会因 ConfigMap 变化而自动 reload 的缘由。
实验结果(配套仓库 01-configmap/)
ConfigMap 更新:APP_COLOR blue → red
Pod A(环境变量): APP_COLOR=blue ← 值被冻结,restart count: 0
Pod B(volume 挂载): APP_COLOR=red ← 自动同步,restart count: 0
正确的 inotify 模式:监听目录,而不是文件
watcher.Add(filepath.Dir(configPath)) // 监听 /etc/config/,能捕获 IN_CREATE
// watcher.Add(configPath) // 会完全错过 symlink 切换
for event := range watcher.Events {
if event.Op&fsnotify.Create == fsnotify.Create {
reloadConfig()
}
}
场景2:镜像进行更新,在此过程之中,有着重建、容器重启以及CrashLoop这三者之间的区别,是怎样的呢?
这三种现象看起来很像,但本质完全不同。
成功的镜像更新:Pod 被重建
BEFORE: Pod UID aaa-bbb, IP 10.244.1.5, nginx:1.25, restarts: 0
AFTER: Pod UID xxx-yyy, IP 10.244.1.6, nginx:1.27, restarts: 0
↑ UID 变了 —— 这是重建,不是容器重启
图3,滚动更新流程,呈现新的ReplicaSet的创建,pod的重建,以及保留旧的RS用于回滚。
ImagePullBackOff:旧 Pod 会被保留
Old pod: Running ← Kubernetes 会让它继续存活
New pod: ImagePullBackOff ← 新 Pod 拉镜像失败卡住,旧 Pod 不会被删掉
依旧是那同一个 Pod,重启的次数持续不断地在增加,出现了 CrashLoopBackOff 的情况。
Pod UID: aaa-bbb ← 没变
Restart count: 0 → 1 → 2 → 3 ← 同一个 Pod 对象内,容器不断崩溃
诊断规则
重启计数持续地不断上升,用户标识符保持不变:此种情况就是闪退循环。
重新启动计数是0,然而用户标识符变了,这属于滚动更新,这是容器编排单元重新构建。
StatefulSet 说明
StatefulSet的相关说明是,StatefulSet这样一种情况,其Pod在镜像发生变化的时候,同样是会被重新构建的,不过序号身份也就是像pod - 0、pod - 1这种,以及PVC绑定是可以被保留下来的。容器重启语义跟Deployment是全然一致的,而身份持久化并不意味着容器是仅在原地进行重启的。
场景 3:原地资源调整(K8s 1.35 GA)
Kubernetes 1.35 使 Pod 能够在原地进行 resize 并进入 GA 阶段,CPU 以及内存均可在不重新构建 Pod 的情形下予以调整,所以,原地 resize 是否能够使用,取决于 CRI 以及节点 OS 的支持情况;文中是在 containerd 1.7+ 与 Linux cgroups v2 相配合的条件下完成验证的。容器会出现何种状况,取决于你明确界定的 resizePolicy:
resizePolicy:
- resourceName: cpu
restartPolicy: NotRequired # 只更新 cgroup quota,不碰进程
- resourceName: memory
restartPolicy: RestartContainer # 在同一个 Pod 内重启容器
针对于05-resource-resize/领域所开展的实验结果,这项过程对K8s 1.35及以上版本存在要求。
CPU resize 200m → 500m (NotRequired):
UID: d7c99204 IP: 10.244.0.7 Restarts: 0 ← 全部不变
Memory resize 256Mi → 512Mi (RestartContainer):
UID: d7c99204 IP: 10.244.0.7 Restarts: 1
↑ 同一个 Pod ↑ 同一个 IP ↑ 这是我们的策略选择,不是 K8s 强制要求
重点:memory的默认的resizePolicy是NotRequired。要是你省略它,memory resize会无声无息地更新cgroup,而不是重启容器——你的JVM heap依旧维持旧有的大小。针对memory,务必始终明确地界定resizePolicy。
如何执行 resize
kubectl patch pod my-pod -n my-namespace \
--subresource resize \
-p '{"spec":{"containers":[{"name":"app","resources":{
"requests":{"cpu":"250m","memory":"128Mi"},
"limits":{"cpu":"500m","memory":"256Mi"}
}}]}}'
# 注意:不要加 --type=merge,否则和 --subresource resize 一起会触发校验错误
场景 4:Istio 路由 —— 通过 xDS 实现零重启
伊斯蒂奥虚拟服务以及目标规则的变更绝不会引致容器重新启动。伊斯蒂奥德会跟每一个特使边车维持一条持久的双向gRPC流,路由更新会在毫秒级别被推送,以内存里的方式进行切换,不触及任何容器组外部基本单元,也不会去写文件。
实验结果(配套仓库 04-istio-routing/)
四个 Pod,三次路由变更:
100% v1 → 80/20 canary → 100% v2
Restart counts: BEFORE 0 0 0 0 / AFTER 0 0 0 0
Pod age: 三次变更过程中始终不变
场景 5:Stakater Reloader,将那需要人工操办的步骤予以自动化。
在应用借助环境变量去消费 ConfigMap 的情形下,一旦 ConfigMap 做出更新,就总是得有人去实施:kubectl rollout restart ,以此来确保应用能够同步更新,适应新的配置信息,从而保持正常、稳定的运行状态。
Reloader 的功用是将这一步骤予以自动化,它凭借 Kubernetes Watch 事件开展工作,由此可知检测近乎属于实时情形,并非采用轮询方式。
metadata:
annotations:
reloader.stakater.com/auto: "true"
生产环境当中存在的坑,Helm 默认的安装参数为,watchGlobally=false ,这所代表的意思是,Reloader 仅仅会监听它自身所处的 namespace,其他的 namespace 里面就算 Deployment 加上了注解,也会被悄然无声地忽略掉,不会产生报错,所以建议在安装的时候明确地加以开启。
helm install reloader stakater/reloader \
--namespace reloader \
--set reloader.watchGlobally=true
图 4:Stakater Reloader 内部工作流
实验得出的结果,位于配套仓库里的,是07 - stakater - reloader/。
ConfigMap 已更新,未执行 kubectl rollout restart
新 Pod APP_MESSAGE: "Hello from OpsCart v2 — auto reloaded!"
滚动容器重启被自动触发
当热加载出问题时
热加载并不总是比容器重启更安全。它有两类典型故障模式。
1)语义错误的配置被静默接受
表现为:
而要是运用“坏配置 + 容器重启”这般形式,失败常常会马上显现出来。所以。
建议: 在原子替换配置前,先做校验。
2)Envoy 悄悄拒绝 xDS 推送
被推送的是一个 RouteConfiguration,它是 Istiod 进行这项操作的结果,然而其所引用的 cluster ,这个 cluster 处于还未完成传播的状态,Envoy 会因为这样的情况拒绝这次更新,并且会继续沿用旧有的路由规则,在此过程中不会有任何 Pod event 被触发,对此给出的建议是监控 pilot_xds_push_errors,以及使用 istioctl proxy-status。
可观测性:每个运维都该知道的三个命令
1)判断到底是“容器重启”还是“Pod 重建”
kubectl get pod -o custom-columns=\
"NAME:.metadata.name,UID:.metadata.uid,IP:.status.podIP,RESTARTS:.status.containerStatuses[0].restartCount"
2)查看 Pod 上的事件
kubectl describe pod | grep -A 20 "Events:"`
3)查看原地 resize 状态
kubectl get pod -o jsonpath='{.status.resize}'
结论
容器重启,虽有着破坏性,可它是诚实的,其失败模式,会即刻且明显地展现出来。而hot - reload,优先着重于优化可用性,却把失败弄得更延后、更隐蔽。这两种策略,都是合理的。关键之处在于,你得有意识地去做出选择。目标可不是把重启自动化得更快。目标是要理解得足够深入呀:只有当进程确实需要死亡的时候,才去触发一次容器重启;在不需要的时候,则运用其他所有机制。
配套仓库:github.com/opscart/k8s-pod-restart-mechanics
作者是Shamsher Khan,他身为高级DevOps工程师,还是IEEE高级会员。

相关标签: # Kubernetes # Pod重启 # 配置变更 # 容器重启 # 热加载