最近在做一个微服务配置中心迁移的项目时,我们团队遇到了一个让人头疼的问题:把应用配置从Apollo迁移到Kubernetes原生的ConfigMap之后,每次修改配置,Pod里的应用程序死活不生效。运维同事一脸困惑地问我:"ConfigMap我确实改了啊,kubectl get configmap看到的内容也是新的,为什么应用还是读的旧配置?"
这个问题,相信不少在生产环境中使用Kubernetes的团队都踩过。今天就把我们从发现问题到最终落地的完整过程分享出来,包括三种主流的滚动更新方案,以及我们最终的选型思路。
一、症状描述:ConfigMap改了,Pod纹丝不动
先还原一下现场。我们的Deployment大概长这样:
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
spec:
containers:
- name: user-service
image: registry.cn-hangzhou.aliyuncs.com/myteam/user-service:v1.2.0
envFrom:
- configMapRef:
name: user-service-config
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: user-service-config
我们通过kubectl edit configmap user-service-config修改了数据库连接池的最大连接数,从50改成了100。改完之后,用kubectl describe configmap确认内容已经更新。但是进到Pod里一看,环境变量还是旧的,挂载的文件虽然过了一会儿更新了,但应用程序根本没有重新读取。
那么问题来了:Kubernetes为什么不在ConfigMap更新后自动重启Pod?
二、原因分析:这是设计,不是Bug
很多人第一反应是"这是不是Kubernetes的Bug",其实不是。这是Kubernetes的刻意设计,原因有两点:
第一,ConfigMap的更新和Pod的生命周期是解耦的。Kubernetes的设计哲学是声明式的,ConfigMap是一个独立的资源对象,它的变更不会自动触发Deployment的滚动更新。只有Pod Template(即.spec.template)发生变化时,Deployment控制器才会创建新的ReplicaSet并执行滚动更新。
第二,自动重启可能带来灾难性后果。想象一下,如果ConfigMap一改,所有引用它的Pod同时重启,在生产环境中这可能意味着服务中断。Kubernetes把这个决策权留给了用户。
根据Kubernetes官方文档的说明,以环境变量方式引用的ConfigMap数据,在Pod运行期间永远不会更新。以Volume挂载方式引用的数据,kubelet会周期性同步(默认约60秒),但应用程序是否重新读取文件,取决于应用自身的实现。
简单总结一下两种引用方式的行为差异:
- envFrom/env方式:Pod运行期间完全不更新,必须重建Pod
- volume挂载方式:文件内容会自动同步(有延迟),但应用不一定会重新加载
我们的应用是Spring Boot项目,启动时读取一次配置文件,运行期间不会主动监听文件变化。所以即使Volume里的文件更新了,应用也感知不到。
三、三种滚动更新方案详解
明确了问题根源之后,我们调研了业界主流的三种方案。下面逐一展开。
方案一:kubectl rollout restart(手动触发)
这是最简单直接的方式。Kubernetes 1.15版本之后,kubectl提供了rollout restart命令,可以在不修改任何配置的情况下触发Deployment的滚动更新。
操作步骤:
# 第一步:更新ConfigMap
kubect
...