在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 ) constants.Result { 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 ) constants.Result { 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} ...
参考文章: