在Kubernetes中, 更新configmap及secret是无法自动更新POD的, 如果需要配置生效, 需要重新部署,这无疑会增加多余操作, 刚好最近在测试gitlab的CI/CD, 需要这个自动能力,调研了以下几种可用方式.
为何configmap/secret更新后POD不自动重启 configmap/secret更新后大约10s后会更新到pod中, 但是pod无法感应到configmap/secret的更新, 这其实是由于configmap/secret机制的, 因为获取configmap是否更新的操作是由kubelet是定时(以一定的时间间隔)同步Pod和缓存中的configmap内容的,且三种Manager更新缓存中的configmap内容可能会有延迟,所以,当我们更改了configmap的内容后,真正反映到Pod中可能要经过syncFrequency + delay这么长的时间, 因此无法做到立即重启pod, 所以把重启的操作交给操作人员来决定.
由于configmap与secret的机制相差无几, 因此下面的例子以configmap为例, secret只不过加密的configmap,原理一样.
要实现cm/secret更新后自动重启Pod, 一般分为以下几种方法:
配置文件inotifywatch 
发送重启信号 
Sidecar 
reloader 
 
 
选型的话最好就是不需要业务端支持.不可能实现这个还需要改造一堆代码,不现实 
发送信号 这个一般都需要应用程序本身支持接收信号量机制, 比如nginx就支持使用HUP来做热重启
这个因为涉及到需要修改业务代码, 一般就不会考虑
Inotifywatch检查 可以使用脚本来watch配置文件是否存在更新, 
Github有现成的可用,主要实现用了inotifywatch 参考这里configmap-auto-reload 
sidecar 这个github也有现成的方案, prometheus跟alertmanager的热重启用的就是这个sidecar
可以参考configmap-reload 
reloader 这个开源库使用的是kubernetes的list/watch机制来监听指定configmap的变化来执行滚动升级操作
实现可以参考reloader 
最终经过一番测试之后,选择了reloader, 用这个实现pod的自动重启对业务来说影响最小.
reloader使用 这里使用一个例子: 使用了两个configmap,一个挂载到了容器的env, 一个挂载到了容器的配置文件.
1 2 3 4 5 6 7 8 9 10 apiVersion:  v1 kind:  ConfigMap metadata:   name:  demo-config    namespace:  stage  data:   READ_TIMEOUT_SECONDS:  "1"    WRITE_TIMEOUT_SECONDS:  "10"    NAME:  "elithrar"  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 apiVersion:  v1 kind:  ConfigMap metadata:   name:  nginx-config    namespace:  stage  data:   nginx.conf:  |      user  nginx;     worker_processes  auto;     #worker_processes  4;     error_log  /var/log/nginx/error.log debug;     pid        /var/run/nginx.pid;     events {         worker_connections  1024;     }     http {         include       /etc/nginx/mime.types;         default_type  application/octet-stream;         log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                           '$status $body_bytes_sent "$http_referer" '                           '"$http_user_agent" "$http_x_forwarded_for"';         access_log  /var/log/nginx/access.log  main;         sendfile        on;         keepalive_timeout  65;         include /etc/nginx/conf.d/*.conf;     } conf.d__default.conf:  |     server {         listen       80;         server_name  localhost;         location / {             root   /usr/share/nginx/html;             index  index.html index.htm;         }         location /nginx_status {             stub_status;         }         error_page   500 502 503 504  /50x.html;         location = /50x.html {             root   /usr/share/nginx/html;         }       } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 apiVersion:  apps/v1 kind:  Deployment metadata:   name:  demo-deployment    namespace:  stage    annotations:      configmap.reloader.stakater.com/reload:  "nginx-config, demo-config"    labels:      app:  config-demo-app  spec:   replicas:  1    selector:      matchLabels:        app:  config-demo-app    template:      metadata:        labels:          app:  config-demo-app      spec:        volumes:          -  name:  configmap-volume            configMap:              name:  nginx-config              items:                -  key:  nginx.conf                  path:  nginx.conf                -  key:  mime.types                  path:  mime.types                -  key:  conf.d__default.conf                  path:  conf.d/default.conf        containers:        -  name:  config-demo-app          image:  nginx:latest           ports:            -  containerPort:  80          volumeMounts:            -  mountPath:  /etc/nginx/              name:  configmap-volume              readOnly:  true          envFrom:            -  configMapRef:                name:  demo-config  
更新configmap后会发现pod很快就重启了
从reloader中看到以下日志:
进入容器看到env更新了
configmap配置文件也对应更新了
完美
Reloader源码分析 再来看deployment的yaml文件,会发现在容易的env中新增了两个环境变量(因为有两个configmap)
核心思路就是通过计算新的configmap与旧的configmap的哈希值是否有变化,源代码核心的就是这一段了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 func  updateContainers (upgradeFuncs callbacks.RollingUpgradeFuncs, item interface {}, config util.Config, autoReload bool ) 	var  result constants.Result    	envar := constants.EnvVarPrefix + util.ConvertToEnvVarName(config.ResourceName) + "_"  + config.Type 	container := getContainerToUpdate(upgradeFuncs, item, config, autoReload) 	if  container == nil  { 		return  constants.NoContainerFound 	} 	 	result = updateEnvVar(upgradeFuncs.ContainersFunc(item), envar, config.SHAValue) 	 	if  result == constants.NoEnvVarFound { 		e := v1.EnvVar{ 			Name:  envar, 			Value: config.SHAValue, 		} 		container.Env = append (container.Env, e) 		result = constants.Updated 	} 	return  result } func  updateEnvVar (containers []v1.Container, envar string , shaData string ) 	for  i := range  containers { 		envs := containers[i].Env 		for  j := range  envs { 			if  envs[j].Name == envar { 				if  envs[j].Value != shaData { 					envs[j].Value = shaData 					return  constants.Updated 				} 				return  constants.NotUpdated 			} 		} 	} 	return  constants.NoEnvVarFound } 
gitlab CI/CD中实现重启 当然,如果结合gitlab的CI/CD功能,因为可以在gitlab-ci.yml中写脚本,就很容易用以下两种方式同样可以实现
当更新configmap时,只进行pod重启, 可以使用patch增加一个环境变量: 
 
1 kubectl patch deployment <deployment-name> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container-name>","env":[{"name":"RESTART_TIME","value":"' $(date  +%s)'"}]}]}}}}'  
或者直接在CI计算一个configmap文件的md5或者hash值, 然后将这个值传入deployment, 这样也会使pod重启. 
 
1 2 3 4 5 6 7 ... spec:   template:      metadata:        annotations:          com.aylei.configmap/hash:  ${CONFIGMAP_HASH}  ... 
参考文章: