GitHub地址: https://github.com/QingyaFan/container-cloud/issues/2
Kubernetes中提供configmap,用来管理应用的配置,configmap具备热更新的能力,但只有通过目录挂载的configmap才具备热更新能力,其余通过环境变量,通过subPath挂载的文件都不能动态更新。这篇文章里我们来看看configmap热更新的原理,以及为什么只有目录形式挂载才具备热更新能力。
configmap热更新原理
我们首先创建一个configmap(configmap-test.yaml)用于说明,其内容如下。我们初始化好这个configmapkubectl apply -f configmap-test.yaml
。
apiVersion: v1
kind: ConfigMap
metadata:
name: marvel-configmap
data:
marvel: |
{
name: "iron man",
skill: [
"fight", "fly"
]
}
configmap资源对象会存储在etcd中,我们看下存储的是什么东东,哦,原来就是明文存储的。
[root@bogon ~]# ETCDCTL_API=3 etcdctl get /registry/configmaps/default/marvel-configmap
/registry/configmaps/default/marvel-configmap
k8s
v1 ConfigMap?
?
marvel-configmapdefault"*$02d3b66f-da26-11e9-a8c5-0800275f21132????b?
0kubectl.kubernetes.io/last-applied-configuration?{"apiVersion":"v1","data":{"marval":"{
name: "iron man",
skill: [
"fight", "fly"
]
}
"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"marvel-configmap","namespace":"default"}}
zD
marval:{
name: "iron man",
skill: [
"fight", "fly"
]
}
"
接下来使用一个redis的pod来挂载这个configmap:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: redis
labels:
name: redis
spec:
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
name: redis
spec:
containers:
- image: redis:5.0.5-alpine
name: redis
resources:
limits:
cpu: 1
memory: "100M"
requests:
cpu: "200m"
memory: "55M"
ports:
- containerPort: 6379
name: redis
volumeMounts:
- mountPath: /data
name: data
- mountPath: /etc/marvel
name: marvel
volumes:
- name: data
emptyDir: {}
- name: marvel
configMap:
name: marval-configmap
items:
- key: marvel
path: marvel
restartPolicy: Always
imagePullPolicy: Always
我们启动这个deploy,然后修改一下configmap,把用拳头锤别人的浩克加上,看多长时间可以得到更新。
apiVersion: v1
kind: ConfigMap
metadata:
name: marvel-configmap
data:
marvel: |
{
name: "iron man",
skill: [
"fight", "fly"
]
},
{
name: "hulk",
skill: [
"fist"
]
}
经过测试,经过了11s时间,pod中的内容得到了更新。再把黑寡妇也加上,耗时48s得到了更新。
apiVersion: v1
kind: ConfigMap
metadata:
name: marvel-configmap
data:
marvel: |
{
name: "iron man",
skill: [
"fight", "fly"
]
},
{
name: "hulk",
skill: [
"fist"
]
},
{
name: "Black widow",
skill: [
"magic"
]
}
所以更新延迟不一定,为什么呢?接下来我们看下configmap热更新的原理。
是kubelet在做事
kubelet是每个节点都会安装的主要代理,负责维护节点上的所有容器,并监控容器的健康状况,同步容器需要的数据,数据可能来自配置文件,也可能来自etcd。kubelet有一个启动参数--sync-frequency
,控制同步配置的时间间隔,它的默认值是1min,所以更新configmap的内容后,真正容器中的挂载内容变化可能在0~1min
之后。修改一下这个值,修改为5s,然后更改configmap的数据,检查热更新延迟时间,都降低到了3s左右,但同时kubelet的资源消耗会上升,尤其运行比较多pod的node上,性能会显著下降。
怎么实现的呢
Kubelet是管理pod生命周期的主要组件,同时它也会维护pod所需的资源,其中之一就是configmap,实现定义在pkg/kubelet/configmap/
中,kubelet主要是通过 configmap_manager 来管理每个pod所使用的configmap,configmap_manager有三种:
- Simple Manager
- TTL Based Manager
- Watch Manager
默认使用 Watch Manager
。其实Manager管理的主要是缓存中的configmap对象,而kubelet同步的是Pod和缓存中的configmap对象。如下图所示:
Simple Manager
Simple Manager直接封装了访问api-server的逻辑,其更新延迟(图中delay)为0。
TTL Based Manager
当pod启动或者更新时,pod 引用的缓存中的configmap都会被无效化。获取configmap时(GetObject()
),先尝试从TTL缓存中获取,如果没有,过期或者无效,将会从api-server获取,获取的内容更新到缓存中,替换原来的内容。
CacheBasedManager
的定义在 pkg/kubelet/util/manager/cache_based_manager.go
中。
func NewCacheBasedManager(objectStore Store, getReferencedObjects func(*v1.Pod) sets.String) Manager {
return &cacheBasedManager{
objectStore: objectStore,
getReferencedObjects: getReferencedObjects,
registeredPods: make(map[objectKey]*v1.Pod),
}
}
Watch Manager
每当pod启动或更新时,kubelet会对该 pod 新引用的所有configmap对象启动监控(watches),watch负责利用新的configmap对缓存的configmap更新或替换。
WatchBasedManager
的定义在 pkg/kubelet/util/manager/watch_based_manager.go
中。
func NewWatchBasedManager(listObject listObjectFunc, watchObject watchObjectFunc, newObject newObjectFunc, groupResource schema.GroupResource, getReferencedObjects func(*v1.Pod) sets.String) Manager {
objectStore := NewObjectCache(listObject, watchObject, newObject, groupResource)
return NewCacheBasedManager(objectStore, getReferencedObjects)
}
总结
只有当Pod使用目录形式挂载configmap时才会得到热更新能力,其余两种使用configmap的方式是Pod环境变量注入和subPath形式。
因为kubelet是定时(以一定的时间间隔)同步Pod和缓存中的configmap内容的,且三种Manager更新缓存中的configmap内容可能会有延迟,所以,当我们更改了configmap的内容后,真正反映到Pod中可能要经过syncFrequency + delay
这么长的时间。