K8S中如何确保单体应用仅运行一个Pod?避免多副本同时启动
在Kubernetes中部署单实例独占式单体应用的解决方案(不修改代码)
针对你提到的必须严格单实例运行、旧Pod完全停止后才能启动新Pod、故障自动重启的需求,以下是无需修改应用代码的可行方案:
基础核心配置:Deployment + Recreate 更新策略
这是K8S原生就能实现的基础方案,能覆盖大部分常规场景:
- 把Deployment的更新策略设为
Recreate,同时固定副本数为1:
这个策略的逻辑是先彻底删掉所有旧Pod,再创建新Pod,从根源上避免更新过程中出现多实例。spec: replicas: 1 strategy: type: Recreate - 配置健康检查实现故障自动重启:给应用加上
livenessProbe,一旦健康检查失败,K8S会自动重启Pod,示例配置:spec: containers: - name: your-app livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 periodSeconds: 10
解决节点故障/网络分区的多实例风险
上面的方案在正常更新、自愿节点维护时没问题,但如果遇到节点网络分区(旧Pod实际还在运行,但K8S判定它已死亡),可能会启动新Pod导致多实例。可以用以下两种增强方案:
方案1:PVC独占挂载锁
利用K8S PVC的ReadWriteOnce访问模式特性——同一个PVC只能被一个Pod挂载,以此实现实例互斥:
- 创建一个小容量的PVC(哪怕应用不需要持久化数据,也可以用它当锁),访问模式设为
ReadWriteOnce:apiVersion: v1 kind: PersistentVolumeClaim metadata: name: app-singleton-lock spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi - 在Pod模板里挂载这个PVC:
如果旧Pod因为节点故障没正常释放PVC,新Pod会卡在spec: containers: - name: your-app volumeMounts: - name: lock-volume mountPath: /tmp/app-lock volumes: - name: lock-volume persistentVolumeClaim: claimName: app-singleton-lockPending状态,直到K8S清理掉旧Pod的挂载(一般节点失联5分钟后会强制解除),从物理层面避免多实例同时运行。
方案2:Sidecar分布式租约锁
如果对多实例风险要求极高,可以加个Sidecar容器,用K8S的Lease API实现分布式锁,只有拿到锁的Pod才启动主应用:
- 找个现成的租约锁工具或者写个简单的Sidecar(基于K8S client-go实现),它的逻辑是向K8S API申请独占租约,拿到锁后在共享目录里生成一个标记文件。
- 配置主应用的启动命令,等待标记文件出现后再启动:
租约会定期续约,一旦旧Pod失联超过租期,租约自动释放,新Pod就能拿到锁启动,彻底避免网络分区导致的多实例问题。spec: containers: - name: singleton-lock-sidecar image: your-lock-tool-image args: ["--lock-name", "app-singleton-lock", "--lease-duration", "30"] volumeMounts: - name: shared-signal mountPath: /shared - name: your-app command: ["sh", "-c", "until [ -f /shared/lock-acquired ]; do sleep 1; done; /path/to/your/app"] volumeMounts: - name: shared-signal mountPath: /shared volumes: - name: shared-signal emptyDir: {}
补充:限制自愿驱逐的行为
配合PodDisruptionBudget,确保节点维护等自愿驱逐操作也符合单实例要求:
apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: app-singleton-pdb spec: maxUnavailable: 1 selector: matchLabels: app: your-app
这个配置允许在自愿驱逐时删除旧Pod,但不会同时启动多个新Pod,和Recreate策略配合就能保证驱逐时严格先停旧、再启新。
内容的提问来源于stack exchange,提问作者F. Alessandro
相关产品推荐
相关产品推荐

