Kubernetes 的对象在 etcd 里到底长什么样?key 怎么命名?为什么 resourceVersion 是个全局递增的数字?label 一下算不算一次写?这篇直接用 etcdctl 看 key 的结构、value 的编码和 watch 事件,把 API 层和存储层对起来。
/registry/ 下共 623 个 key;按第一段(资源类型)统计的前 8 个:[('events', 244), ('clusterroles', 79), ('clusterrolebindings', 66), ('serviceaccounts', 57), ('configmaps', 25), ('apiregistration.k8s.io', 24), ('pods', 18), ('rolebindings', 14)]
/registry/configmaps/k92/cm1 存在
/registry/namespaces/k92 存在
/registry/k92.example.com/widgets/k92/w1 存在
/registry/clusterroles/view 存在
/registry/minions/kx-main-worker 存在
/registry/leases/kube-node-lease/kx-main-worker 存在
/registry/deployments/kube-system/coredns 存在
规律:/registry/<资源>/<命名空间>/<名字>(集群级资源没有命名空间段);CRD 的资源前面多一段 API 组;节点的路径是历史遗留的 minions;Service 分成 services/specs 和 services/endpoints
services 下的前几个 key:['/registry/services/endpoints/default/kubernetes', '/registry/services/endpoints/kube-system/kube-dns', '/registry/services/endpoints/kube-system/metrics-server']这个小集群的 /registry/ 下共 623 个 key,数量最多的是 events(244)、clusterroles(79)、clusterrolebindings(66)、serviceaccounts(57)、configmaps(25)。规律:ConfigMap 是 /registry/configmaps/k92/cm1(资源/命名空间/名字),集群级的 ClusterRole 是 /registry/clusterroles/view(没有命名空间段),CRD 对象是 /registry/k92.example.com/widgets/k92/w1(前面多一段 API 组),节点是历史遗留的 /registry/minions/<节点名>,Service 分成 services/specs 和 services/endpoints 两棵树。
ConfigMap cm1:202 字节;开头 b'k8s\x00'(魔数 k8s\x00 = protobuf 存储格式);可读片段:['ConfigMap', '*$27fe4aa0-53e9-4849-83df-6540cb0db5dd2', 'kubectl-create', 'Update', 'FieldsV1:"', ' {"f:data":{".":{},"f:hello":{}}}B']
Widget w1(CRD 对象):747 字节;开头 b'{"apiVersion":"k92.example.com';是 JSON:True;能 json.loads 吗:True
同一个 ConfigMap 经 API Server 读出来是 JSON(317 字节),比 etcd 里的 protobuf(202 字节)大——存储格式比 API 格式紧凑内置资源的 value 是 protobuf:ConfigMap cm1 共 202 字节,开头是魔数 k8s\x00,能读出 ConfigMap、kubectl-create、Update、FieldsV1(managedFields)等片段。CRD 对象 w1 是 747 字节的 JSON,以 {"apiVersion":"k92.example.com… 开头。同一个 ConfigMap 经 API Server 读出来的 JSON 是 317 字节,比 etcd 里的 protobuf 大——存储格式比 API 格式紧凑。
etcd 里 key 的 create_revision=145750;mod_revision=145750;version=1 API 里 cm1 的 resourceVersion=145750(与 mod_revision 相同:True) patch 一次之后:mod_revision=145760(create_revision 仍是 145750);version=2;resourceVersion=145760 新建另一个 cm2:resourceVersion=145761,比 cm1 的 145760 大(全局 revision 只增不减,所有对象共用一条时间线)
etcd 里 cm1 的 create_revision=145750、mod_revision=145750、version=1;API 里的 resourceVersion=145750,与 mod_revision 完全相同。patch 一次之后 mod_revision=145760(create_revision 不变)、version=2、resourceVersion=145760;再新建 cm2,resourceVersion=145761,比 cm1 的更大——revision 是整个 etcd 全局递增的,所有对象共用一条时间线(这也是 k6 里 resourceVersion 只能比较、不能当计数的原因)。
etcdctl watch /registry/configmaps/k92/ --prefix 收到的事件(类型, key 末段, mod_revision, version):
('PUT', 'cm3', 145766, 1)
('PUT', 'cm3', 145767, 2)
('DELETE', 'cm3', 145770, None)
含义:create / label 都是 PUT(label 是一次新的写入,version 加 1);delete 是 DELETE 事件,这就是 API Server 的 watch 的数据来源用 etcdctl watch /registry/configmaps/k92/ --prefix 同时在 API 层操作 cm3:create 对应 PUT(mod_revision 145766,version 1),label 也是一次 PUT(145767,version 2),delete 对应 DELETE(145770,没有 value)。API Server 的 watch 就是把 etcd 的 watch 事件转发出去。