Z.S.K.'s Records

Kubernetes学习(configmap及secret更新后自动重启POD)

在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
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
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
}

//update if env var exists
result = updateEnvVar(upgradeFuncs.ContainersFunc(item), envar, config.SHAValue)

// if no existing env var exists lets create one
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中写脚本,就很容易用以下两种方式同样可以实现

  1. 当更新configmap时,只进行pod重启, 可以使用patch增加一个环境变量:
1
kubectl patch deployment <deployment-name> -p '{"spec":{"template":{"spec":{"containers":[{"name":"<container-name>","env":[{"name":"RESTART_TIME","value":"'$(date +%s)'"}]}]}}}}'
  1. 或者直接在CI计算一个configmap文件的md5或者hash值, 然后将这个值传入deployment, 这样也会使pod重启.
1
2
3
4
5
6
7
...
spec:
template:
metadata:
annotations:
com.aylei.configmap/hash: ${CONFIGMAP_HASH}
...

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?