探索Kubernetes在ETCD中保存数据的规范
写在前面
Kubernetes (简称k8s)是容器集群管理系统,是一个开源的平台,可以实现容器集群的自动化部署、自动扩缩容、维护等功能,具体的大家可以参考 Kubernetes中文文档 或者Kubernetes官网的文档。
K8s的架构复杂,涉及到概念非常多,其基础组件包含 ETCD、kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy等,其运行时环境为docker或Rkt,当然还包含很多插件。在我看来,k8s是DevOps的未来,因此不禁想写一些它的故事。
ETCD在k8s技术栈的地位,就仿佛数据库(Mysql、Postgresql或oracle等)在Web应用中的地位,它存储了k8s集群中所有的元数据(以key-value的方式)。那么很现实的一个问题是:这些元数据是如何组织并保存的呢?本文就对此问题探究一番。
适用人群
入门——初级——中级√——高级;本文适应中级及以上。
探究正文
相关环境
- 两台2个核4G内存(2C4G)的虚拟机,ip分别为
192.168.205.137
和192.168.205.139
- k8s相关控件-1.8.6
- etcd-3.3.10
- docker-18.06.1-ce
我这里采用了kubeadm的部署方法,探索了高可用(HA)部署方案(推荐参考一下这里 ,未来会考虑写一篇关于k8s高可用原理的博文,敬请期待😝)。
k8s中ETCD数据的增删改查
首先应该明确,K8s中所有元数据的增删改查都是由kube-apiserver来执行的,那么这些数据在ETCD中必然有一套存储规范,这样才能保证在集群中部署成千上万的应用时不会出差错。在此基础上可以认为,只要掌握了k8s在ETCD中存储数据的规范,便可以像k8s一样手动来操作ETCD了(虽然不建议这么做)。不过更大的价值是能对k8s的理解更进一步,便于以后debug或者二次开发k8s的某些功能时更有底气。
初探ETCD中的数据
本文对ETCD的操作主要使用了其官方客户端工具etcdctl,这里不对etcdctl进行详解了(需要用一整篇博客来介绍它才行),只就用到的一些命令进行阐释。
获取ETCD中的所有的key值
下面的命令可以获取ETCD中的所有的key值:
# 获取ETCD中的所有数据
# --prefix 表示获取所有key值头为某个字符串的数据, 由于传入的是"",所以会匹配所有的值
# --keys-only 表示只返回key而不返回value
# 对输出的结果使用grep过滤掉空行
/$ ETCDCTL_API=3 etcdctl get "" --prefix --keys-only |grep -Ev "^$"
# 输出结果如下所示,实际数据会非常整齐
/registry/apiextensions.k8s.io/customresourcedefinitions/globalbgpconfigs.crd.projectcalico.org
/registry/apiextensions.k8s.io/customresourcedefinitions/globalfelixconfigs.crd.projectcalico.org
/registry/apiextensions.k8s.io/customresourcedefinitions/globalnetworkpolicies.crd.projectcalico.org
# ... 略过很多条目
/registry/namespaces/default
/registry/namespaces/kube-public
/registry/namespaces/kube-system
/registry/pods/kube-system/canal-mljsv
/registry/pods/kube-system/canal-qlvh6
# ... 略过很多条目
/registry/services/endpoints/kube-system/kube-scheduler
/registry/services/specs/default/kubernetes
/registry/services/specs/kube-system/kube-dns
compact_rev_key
# 总共有240条记录
/$ etcdctl get "" --prefix --keys-only |grep -Ev "^$" |wc -l
240
ETCD中key值的规律
通过观察可以简单得出下面几个规律:
- k8s主要把自己的数据注册在
/registry/
前缀下面(在ETCD-v3版本后没有了目录的概念,只能一切皆前缀了)。 - 通过观察k8s中
deployment、namespace、pod
等在ETCD中的表示,可以知道这部分资源的key的格式为/registry/#{k8s对象}/#{命名空间}/#{具体实例名}
。 - 存在一个与众不同的key值
compact_rev_key
,搜索可以知道这是apiserver/compact.go中用来记录无效数据版本使用的;运行etcdctl get compact_rev_key
可以发现,输出的是一个整形数值。 - 在查看ETCD时,k8s中除了必要的网络插件canal,未部署其他的应用,此时ETCD中只有240条数据,个人觉得这个量级没有想象中的多。
有了上面的规律,可以初步得出一个结论:在研究k8s时重点关注/registry/
前缀的key及其value即可。
ETCD中的value值
通过上面的内容知道,k8s在ETCD中保存数据时key的取值非常讲究,规律非常容易概括出来。那么这些key值所对应的值是什么样子呢?我试着输出了/registry/ranges/serviceips
和/registry/services/endpoints/default/kubernetes
这两个key所对应的值,效果见下面编码展示区。
# 获取"/registry/ranges/serviceips"所对应的值
# 发现这里有很多奇怪的字符=。=
# 可以大体推断出来,集群所有service的ip范围为10.96.0.0/12, 与api-server的yaml文件中配置的一致
/$ etcdctl get /registry/ranges/serviceips
/registry/ranges/serviceips
k8s
v1RangeAllocation&
"*28Bz
10.96.0.0/12"
# 获取"/registry/services/endpoints/default/kubernetes"所对应的值
# 发现这里有很多奇怪的字符=。=
# 在default命名空间的kubernetes这个service所对应的endpoint有两个ip
# 分别为192.168.205.137和192.168.205.139
/$ etcdctl get /registry/services/endpoints/default/kubernetes
/registry/services/endpoints/default/kubernetes
k8s
v1 Endpoints�
O
kubernetesdefault"*$0b6bb724-f066-11e8-be14-000c29d2cb3a2ں��z;
192.168.205.137
192.168.205.139
https�2TCP"
可以很明显看出来,ETCD中保存的并不是输出友好的数据(比如json或xml等就是输出友好型数据)。当然,如果进一步研究可以知道,ETCD保存的是Protocol Buffers序列化后的值。如果大家对Protobuf有研究,可以知道这个协议也是个key-value的协议,只不过会把其key-value值按照特定的算法进行压缩,不过并没有压缩的很厉害,显式输出这些值多少也能获取到一些信息;比如/registry/services/endpoints/default/kubernetes
对应的192.168.205.137、192.168.205.139
等值。
ETCD中其他的key及其value
通过上面的探索,对ETCD中存储的数据有了大体的了解,接下来就可以开始更加刺激的冒险了。
据说flannel需要使用ETCD保存网络元数据
那么,flannel在ETCD中保存的数据是什么,保存在哪个key中了呢?下面把所有网关相关的几个关键词 canal|flannel|calico
输出可以知道,里面只有一个可能包含flannel所需数据的key,即/registry/configmaps/kube-system/canal-config
,输出内容后对比关于flannel的etcd配置这篇文章,很大程度可以认为就是它了(需要进一步去canal项目的源码中去确认)。
/$ etcdctl get "" --prefix --keys-only |grep -Ev "^$" |grep "canal\|flannel\|calico"
# ... 忽略很多条
/registry/configmaps/kube-system/canal-config
# ... 忽略很多条
# 可以看到里面有一个配置项 net-conf, 对比flannel的配置,可以认为这个地方很可能就是canal项目中flannel在etcd中需要的值。这里设置了网段为"10.244.0.0/16"
/$ etcdctl get /registry/configmaps/kube-system/canal-config
# ... 省略很多
net-conf.jsonI{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
default命名空间中endpoint实例kubernetes的值
# 在k8s的HA部署时,会发现default命名空间有一个默认的service
# 明明没有selector(如果不熟悉这个概念可以自行去搜索了解一下),却有实实在在的Endpoints值
# 那么这个值是如何写入的呢?
/$ kubectl describe svc kubernetes
Name: kubernetes
Namespace: default
Labels: component=apiserver
provider=kubernetes
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP: 10.96.0.1
Port: https 443/TCP
TargetPort: 6443/TCP
Endpoints: 192.168.205.137:6443,192.168.205.139:6443
Session Affinity: ClientIP
Events: <none>
# 通过在ETCD中检索可以获取到两个key值
# 一个对应的是default命名空间中的service,另一个是对应的endpoint
/$ etcdctl get "" --prefix --keys-only |grep -Ev "^$" |grep "default" |grep "kubernetes"
/registry/services/endpoints/default/kubernetes
/registry/services/specs/default/kubernetes
我把/registry/services/endpoints/default/kubernetes
输入到搜索引擎搜索了一下,发现有人在github上抛出类似的问题,从其The three apiservers (Ip Adresses .31,.32,.33) are constantly overwriting the etcd-key /registry/services/endpoints/default/kubernetes
可以推测出来,这个值是api-server这个控件主动去写入的。
这能得出一个结论:在部署高可用集群时,如果想把多个api-server注册到集群,那么所有的api-server的服务都将会出现在default命名空间的kubernetes这个endpoints上;这也就意味着难以把集群中的一个api-server单独隔离出来而不让它对外提供服务(我当前想debug的一个问题需要这么操作,得出这个结论表示很无奈)。
小结
文本对k8s数据仓库ETCD进行了探究,总结了ETCD保存k8s数据时key值的规范,并尝试查看了value值的内容。最后对几个感兴趣的key值及其value值进行了探索。通过探究可以知道,k8s把集群的信息非常有条理地保存在ETCD中,key值定义有章可循,比较方便debug;同时,虽然ETCD中的value值是protobuf序列化后的数据,不适合展示,不过输出到文本后依然有一定的参考价值。
参考
- Production-Grade Container Orchestration - Kubernetes Kubernetes官网
- Kubernetes是什么 _ Kubernetes(K8S)中文文档_Kubernetes中文社区 k8s中文文档
- GitHub - cookeem/kubeadm-ha: Kubernetes high availiability deploy based on kubeadm (English/中文 for v1.11.x/v1.9.x/v1.7.x/v1.6.x) k8s高可用部署方案
- Installing Calico for policy and flannel for networking 网络插件的安装
- flannel Container Networking | Configuring flannel Networking 描述了flannel配置etcd作为datastore的做法,可以推敲出etcd中保存的值可能的样子
- flannel/configuration.md at master · coreos/flannel · GitHub flannel配置etcd作为datastore的文档
- Kubernetes service multiple apiserver endpoints · Issue #19989 · kubernetes/kubernetes · GitHub 从这里的描述可以看出api-server本身主动向ETCD写数据