在 Kubernetes 架构中,Etcd 作为 Kubernetes 的元数据存储,在创立一个 Pod 时,Etcd 背地是如何工作,接下来会通过具体的案例进行一步一步机进行剖析!
Etcd 存储概念
kubernetes 资源存储格局
/registry/deployments/default/nginx-deployment
/registry/events/default/nginx-deployment-66b6c48dd5-gl7zl.16c35febf6e3c38c
/registry/events/default/nginx-deployment-66b6c48dd5-gl7zl.16c35fec1db96fed
/registry/events/default/nginx-deployment-66b6c48dd5-gl7zl.16c35fec1fdba240
/registry/events/default/nginx-deployment-66b6c48dd5-gl7zl.16c35fec249adf98
/registry/events/default/nginx-deployment-66b6c48dd5-k55k7.16c35edbfa8784f8
/registry/events/default/nginx-deployment-66b6c48dd5-w9br6.16c35eeb78bfa621
/registry/events/default/nginx-deployment-66b6c48dd5-w9br6.16c35eeba0a4694f
/registry/events/default/nginx-deployment-66b6c48dd5-w9br6.16c35eeba259230a
/registry/events/default/nginx-deployment-66b6c48dd5-w9br6.16c35eeba7251388
/registry/events/default/nginx-deployment-66b6c48dd5-w9br6.16c35fda25f0af06
/registry/events/default/nginx-deployment-66b6c48dd5.16c35eeb788d245f
/registry/events/default/nginx-deployment-66b6c48dd5.16c35febf6bb59a0
/registry/events/default/nginx-deployment.16c35eeb77fc2ef0
/registry/events/default/nginx-deployment.16c35febf56d6b96
/registry/pods/default/nginx-deployment-66b6c48dd5-gl7zl
/registry/replicasets/default/nginx-deployment-66b6c48dd5
Kubernetes 资源在 etcd 中的存储格局由 prefix + “/” + 资源类型 + “/” + namespace + “/” + 具体资源名组成
存储模块
kube-apiserver 启动的时候,会将每个资源的 APIGroup、Version、Resource Handler 注册到路由上。当申请通过认证、限速、受权、准入管制模块查看后,申请就会被转发到对应的资源逻辑进行解决。
同时,kube-apiserver 实现了相似数据库 ORM 机制的通用资源存储机制,提供了对一个资源创立、更新、删除前后的 hook 能力,将其封装成策略接口。当你新增一个资源时,你只须要编写相应的创立、更新、删除等策略即可,不须要写任何 etcd 的 API。
从图中你能够看到,创立一个资源次要由 BeforeCreate、Storage.Create 以及 AfterCreate 三大步骤组成 ;
当收到创立 nginx Deployment 申请后,通用存储模块首先会回调各个资源自定义实现的 BeforeCreate 策略,为资源写入 etcd 做一些初始化工作。上面是 Deployment 资源的创立策略实现,它会进行将 deployment.Generation 设置为 1 等操作。
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (deploymentStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {deployment := obj.(*apps.Deployment)
deployment.Status = apps.DeploymentStatus{}
deployment.Generation = 1
pod.DropDisabledTemplateFields(&deployment.Spec.Template, nil)
}
执行完 BeforeCreate 策略后,它就会执行 Storage.Create 接口,也就是由它真正开始调用底层存储模块 etcd3,将 nginx Deployment 资源对象写入 etcd。
资源平安创立及更新
etcd 提供了 Put 和 Txn 接口给业务增加 key-value 数据,然而 Put 接口在并发场景下若收到 key 雷同的资源创立,就会导致被笼罩。
因而 Kubernetes 很显然无奈间接通过 etcd Put 接口来写入数据。
etcd 事务接口 Txn,它正是为了多 key 原子更新、并发操作安全性等而诞生的,它提供了丰盛的抵触查看机制。
Kubernetes 集群应用的正是事务 Txn 接口来避免并发创立、更新被笼罩等问题。当执行完 BeforeCreate 策略后,这时 kube-apiserver 就会调用 Storage 的模块的 Create 接口写入资源。1.6 版本后的 Kubernete 集群默认应用的存储是 etcd3,它的创立接口简要实现如下:
// Create implements storage.Interface.Create.
func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error {
......
key = path.Join(s.pathPrefix, key)
opts, err := s.ttlOpts(ctx, int64(ttl))
if err != nil {return err}
newData, err := s.transformer.TransformToStorage(data, authenticatedDataString(key))
if err != nil {return storage.NewInternalError(err.Error())
}
startTime := time.Now()
txnResp, err := s.client.KV.Txn(ctx).If(notFound(key),
).Then(clientv3.OpPut(key, string(newData), opts...),
).Commit
从下面的代码片段中,咱们能够得出首先它会依照咱们介绍的 Kubernetes 资源存储格局拼接 key。
而后若 TTL 非 0,它会依据 TTL 从 leaseManager 获取可复用的 Lease ID。Kubernetes 集群默认若不同 key(如 Kubernetes 的 Event 资源对象)的 TTL 差别在 1 分钟内,可复用同一个 Lease ID,防止大量 Lease 影响 etcd 性能和稳定性。
其次若开启了数据加密,在写入 etcd 前数据还将按加密算法进行转换工作。最初就是应用 etcd 的 Txn 接口,向 etcd 发动一个创立 deployment 资源的 Txn 申请。
Resource Version
Resource Version 是 Kubernetes API 中十分重要的一个概念,顾名思义,它是一个 Kubernetes 资源的外部版本字符串,client 可通过它来判断资源是否产生了变动。同时,你能够在 Get、List、Watch 接口中,通过指定 Resource Version 值来满足你对数据一致性、高性能等诉求。
- 未指定 ResourceVersion,默认空字符串。kube-apiserver 收到一个此类型的读申请后,它会向 etcd 收回共识读 / 线性读申请获取 etcd 集群最新的数据。
- ResourceVersion=”0″,赋值字符串 0。kube-apiserver 收到此类申请时,它可能会返回任意资源版本号的数据,然而优先返回较新版本。个别状况下它间接从 kube-apiserver 缓存中获取数据返回给 client,有可能读到过期的数据,实用于对数据一致性要求不高的场景。
- ResourceVersion 为一个非 0 的字符串。kube-apiserver 收到此类申请时,它会保障 Cache 中的最新 ResourceVersion 大于等于你传入的 ResourceVersion,而后从 Cache 中查找你申请的资源对象 key,返回数据给 client。基本原理是 kube-apiserver 为各个外围资源(如 Pod)保护了一个 Cache,通过 etcd 的 Watch 机制来实时更新 Cache。当你的 Get 申请中携带了非 0 的 ResourceVersion,它会期待缓存中最新 ResourceVersion 大于等于你 Get 申请中的 ResoureVersion,若满足条件则从 Cache 中查问数据,返回给 client。若不满足条件,它最多期待 3 秒,若超过 3 秒,Cache 中的最新 ResourceVersion 还小于 Get 申请中的 ResourceVersion,就会返回 ResourceVersionTooLarge 谬误给 client。
Etcd 交互剖析
Kubernetes 组件概述
- kube-apiserver,负责对外提供集群各类资源的增删改查及 Watch 接口,它是 Kubernetes 集群中各组件数据交互和通信的枢纽。kube-apiserver 在设计上可程度扩大,高可用 Kubernetes 集群中个别多正本部署。当收到一个创立 Pod 写申请时,它的根本流程是对申请进行认证、限速、受权、准入机制等查看后,写入到 etcd 即可。
- kube-scheduler 是调度器组件,负责集群 Pod 的调度。基本原理是通过监听 kube-apiserver 获取待调度的 Pod,而后基于一系列筛选和评优算法,为 Pod 调配最佳的 Node 节点。
- kube-controller-manager 蕴含一系列的控制器组件,比方 Deployment、StatefulSet 等控制器。控制器的核心思想是监听、比拟资源理论状态与冀望状态是否统一,若不统一则进行协调工作使其最终统一。
- etcd 组件,Kubernetes 的元数据存储。
- kubelet,部署在每个节点上的 Agent 的组件,负责 Pod 的创立运行。基本原理是通过监听 APIServer 获取调配到其节点上的 Pod,而后依据 Pod 的规格详情,调用运行时组件创立 pause 和业务容器等。
首先咱们通过 kubectl create -f nginx.yml 命令创立 Deployment 资源,发送申请到 kube-apiserver。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
$ kubectl create -f nginx.yml
deployment.apps/nginx-deployment created
kube-apiserver 会接管到创立 nginx deployment 资源的申请日志,并通过 Txn 接口胜利将数据写入到 etcd 后,kubectl create -f nginx.yml 命令就执行结束,返回给 client 了
# etcd 收到创立 nginx deployment 资源的申请日志:2021-12-23 11:49:16.320328 D | etcdserver/api/v3rpc: start time = 2021-12-23 11:49:16.319917473 +0000 UTC m=+416.447615057, time spent = 386.445µs, remote = 127.0.0.1:45596, response type = /etcdserverpb.KV/Txn, request count = 1, request size = 1769, response count = 0, response size = 41, request content = compare:<target:MOD key:"/registry/deployments/default/nginx-deployment" mod_revision:0 > success:<request_put:<key:"/registry/deployments/default/nginx-deployment" value_size:1715 >> failure:<>
解释:
- 申请的模块和接口,KV/Txn;
- key 门路,/registry/deployments/default/nginx-deployment,由 prefix + “/” + 资源类型 + “/” + namespace + “/” + 具体资源名组成;
- 平安的并发创立查看机制,mod_revision 为 0 时,也就是此 key 不存在时,才容许执行 put 更新操作。
kube-controller-manager 通过 Watch 机制,疾速感知到新建 Depolyement 资源后,进入一致性协调逻辑,创立 ReplicaSet 控制器。
# etcd 收到创立 ReplicaSet 资源的申请日志:2021-12-23 11:49:16.331340 D | etcdserver/api/v3rpc: start time = 2021-12-23 11:49:16.330695394 +0000 UTC m=+416.458392972, time spent = 584.196µs, remote = 127.0.0.1:45564, response type = /etcdserverpb.KV/Txn, request count = 1, request size = 1806, response count = 0, response size = 41, request content = compare:<target:MOD key:"/registry/replicasets/default/nginx-deployment-66b6c48dd5" mod_revision:0 > success:<request_put:<key:"/registry/replicasets/default/nginx-deployment-66b6c48dd5" value_size:1741
kube-controller-manager 并会通过通过 Watch 机制感知到新的 ReplicaSet 资源创立后,发动申请创立 Pod,确保理论运行 Pod 数与冀望统一。
2021-12-23 11:49:16.353609 D | etcdserver/api/v3rpc: start time = 2021-12-23 11:49:16.353112742 +0000 UTC m=+416.480810323, time spent = 474.04µs, remote = 127.0.0.1:45628, response type = /etcdserverpb.KV/Txn, request count = 1, request size = 1637, response count = 0, response size = 41, request content = compare:<target:MOD key:"/registry/pods/default/nginx-deployment-66b6c48dd5-gl7zl" mod_revision:0 > success:<request_put:<key:"/registry/pods/default/nginx-deployment-66b6c48dd5-gl7zl" value_size:1573 >>
在这过程中产生了 若干 Event,上面是 etcd 收到新增 Events 资源的申请,你能够看到 Event 事件 key 关联了 Lease;
2021-12-23 11:49:16.356776 D | etcdserver/api/v3rpc: start time = 2021-12-23 11:49:16.356368351 +0000 UTC m=+416.484065931, time spent = 388.188µs, remote = 127.0.0.1:45590, response type = /etcdserverpb.KV/Txn, request count = 1, request size = 776, response count = 0, response size = 41, request content = compare:<target:MOD key:"/registry/events/default/nginx-deployment-66b6c48dd5.16c35febf6bb59a0" mod_revision:0 > success:<request_put:<key:"/registry/events/default/nginx-deployment-66b6c48dd5.16c35febf6bb59a0" value_size:689 lease:955464502472756512 >> failure:<>
这时 kube-scheduler 监听到待调度的 Pod,于是为其调配 Node,通过 kube-apiserver 的 Bind 接口,将调度后的节点 IP 绑定到 Pod 资源上。kubelet 通过同样的 Watch 机制感知到新建的 Pod 后,发动 Pod 创立流程即可。
点击 “ 浏览原文 ” 获取更好的浏览体验!