写点什么

Kubernetes 核心组件 -ETCD 详解

作者:巨子嘉
  • 2022 年 2 月 19 日
  • 本文字数:5413 字

    阅读完需:约 18 分钟

Kubernetes核心组件-ETCD详解

1. Kubernetes 核心组件-ETCD 详解


Kubernetes 是典型的主从分布式架构,由集中式管理节点(Master Node),分布式的工作节点(Worker Node)组成以及辅助工具组成。其中 ETCD 是管理节点的核心组件,主要负责集群状态集中式存储,功能架构与 Zookeeper 类似。本篇主要是详细讲解 ETCD 架构及核心技术。

1.1. ETCD 发展与演进

ETCD 是用 Go 语言编写,通过 Raft 一致性算法,实现的一个高可用的分布式键值(key-value)数据库,核心里程碑如下:

2013 年 6 月,由 CoreOS 团队于发布的开源。

2014 年 6 月,作为 Kubernetes 核心元数据的存储服务一起发布,自此 ETCD 社区得到飞速发展。

2015 年 2 月,发布了第一个正式稳定版本 2.0,重构了 Raft 一致性算法,提供了用户树形数据视图,支持每秒超过 1000 次的写入性能。

2017 年 1 月,发布了 3.1 版本,提供了一套全新的 API,同时提供了 gRPC 接口,通过 gRPC 的 proxy 扩展并极大地提高 ETCD 的读取性能,支持每秒超过 10000 次的写入。

2018 年 11 月,项目进入 CNCF 的孵化项目。

2019 年 8 月,发布了 3.4 版本,该版本由 Google、Alibaba 等公司联合打造,进一步改进 etcd 的性能及稳定性。

2021 年 7 月,发布了 3.5 版本,支持 Go Module 版本号语义及模块化,提升了性能及稳定性,增强了集群运维能力。

ETCD 经过长时间的持续演进已趋于成熟,性能及稳定顶得到了极大的提升。当前针对 kubernetes 集群中 List Pod 等 expensive request 导致 OOM 等不稳定的问题,以及 Range Stream,QoS 特性,计划在 ETCD 3.6 版本实现,拭目以待。

1.2. ETCD 架构及功能

用户读请求会经由 HTTP Server,转发给 Store 进行具体的事务处理;如果涉及到节点的修改请求,则交给 Raft 模块进行状态的变更,日志记录,同步提交给集群其他节点并等待确认,最后完成数据的提交并再次同步。ETCD 整体架构主要分为四个模块:

HTTP  Server:用于处理用户发送的 API 请求,以及其它 ETCD 节点同步与心跳信息的请求。

Store:用于处理 ETCD 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是 ETCD 对用户提供的大多数 API 功能的具体实现。

Raft:Raft 强一致性算法的具体实现,是 ETCD 的核心。

WAL:Write Ahead Log(预写式日志),是 ETCD 的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引外,ETCD 就通过 WAL 进行持久化存储。WAL 中所有的数据提交前都会事先记录日志。Entry 表示存储的具体日志内容。Snapshot 是为了防止数据过多而进行的状态快照;

通常一个 ETCD 集群是由 3 个或者 5 个节点组成,多个节点之间通过 Raft 一致性算法完成协同;其中会有一个节点作为 Leader 主节点,负责数据的同步与分发。当 Leader 主节点出现故障时会自动选举另一节点为 Leader 主节点。

在 ETCD 架构中,有一个非常关键的概念 quorum,quorum 的定义是(n+1)/2,即超过集群中半数节点的临界值;只要确保 quorum 数量的节点正常工作,那么整个集群就能正常工作并对外提供服务;

为了保障部分节点故障之后,依然能继续提供服务,就需要解决一个非常复杂分布式一致性的问题。ETCD 是采用 Raft 一致性算法来实现,通过一套数据同步机制,在 Leader 切换后能够重新同步所有提交的数据,保证整个集群数据一致。

客户端在多个节点中,任意选择其中的一个就可以完成数据的读写,内部的状态及数据协同由 ETCD 自身完成。ETCD 内部的机制比较复杂,但是面向用户提供的接口非常简单,可以通过客户端或者通过 HTTP 的方式连接集群并操作数据,可以大致将接口分为了 5 组:

Put(key,value)/Delete(key):Put 向集群中写入数据,Delete 删除集群中的数据。

Get(key)/Get(keyFrom,keyEnd):支持两种查询方式,指定单个 key 的查询与指定的一个 key 的范围的查询。

Watch(key)/Watch(keyPrefix):支持 Watch 机制实现增量数据更新监听,支持指定单个 key 与一个 key 前缀,实际应用中的建议使用 key 前缀。

 Transactions(if/then/else ops).Commit():支持简单的事务,可以通过指定一组条件满足时执行某些动作,当条件不成立的时候执行另一组操作。

Leases: Grant/Revoke/KeepAlive:支持租约机制,通常情况下在分布式系统中需要去检测一个节点是否存活的场景,就需要租约机制。

1.3. ETCD 核心技术与原理

1.3.1. 从 Paxos 到 Raft,实现分布式一致性

Paxos 算法是 1990 年 Leslie 提出的,是一个经典且完备的分布式一致性算法。其目标是在不同的节点间,对一个 key 的取值达成共识(同一个值)。在 Paxos 中一个决策过程(Round、Phase)分为两个阶段:

准备阶段 phase1:Proposer 向超过半数(n/2+1)的 Acceptor 发起 prepare 消息(发送编号);如果 Acceptor 收到一个编号为 N 的 Prepare 请求,且 N 大于该 Acceptor 已经响应过的所有 Prepare 请求的编号,那么就将 N 作为响应反馈给 Proposer,同时该 Acceptor 承诺不再接受任何编号小于 N 的提案,否则拒绝返回。

投票阶段 phase2:如果超过半数 Acceptor 回复 promise,Proposer 向 Acceptor 发送 accept 消息。Acceptor 检查 accept 消息是否符合规则,只要该 Acceptor 没有对编号大于 N 的 Prepare 请求做出过响应,它就接受该提案。

在实际发展中,Paxos 算法也演化出了一系列的变种:PBFT 算法是一种共识算法,较高效地解决了拜占庭将军问题;Multi-Paxos 算法优化了 prepare 阶段的效率,同时允许多个 Leader 并发提议;以及 FastPaxos、EPaxos 等,这些演变是针对某些问题进行的优化,内核思想还是依托于 Paxos 思想。

Paxos 从被提出一直是分布式一致性算法的标准协议,但是它不易理解并且实现起来非常复杂,以至于到目前为止都没有一个完整的实现方案,很多实现都是都是 Paxos-likeRaft 算法是斯坦福大学 2017 年提出,Raft 通过分治思想把算法流程分为三个子问题,分别是选举(Leader election)、日志复制(Log replication)、安全性(Safety)

(一)选举(Leader election):Raft 定义集群节点有 4 种状态,分别是 Leader、Follower、Candidate、PreCandidate。正常情况下,Leader 节点会按照心跳间隔时间,定时广播心跳消息给 Follower 节点,以维持 Leader 身份。Follower 收到后回复消息给 Leader。Leader 都会带有一个任期号(term),用于比较各个节点数据新旧,识别过期 Leader 等。当 Leader 节点异常时,Follower 节点会接收 Leader 的心跳消息超时,当超时时间大于竞选超时时间后,会进入 PreCandidate 状态,不自增任期号仅发起预投票,获得大多数节点认可后,进入 Candidate 状态并等待一个随机时间,然后发起选举流程,自增任期号投票给自己,并向其他节点发送竞选投票信息。当节点收到其他节点的竞选消息后,首先判断竞选节点的数据及任期号大于本节点,并且在本节点未发起选举给自己投,则可以投票给竞选节点,否则拒绝投票。

(二)日志复制(Log replication):Raft 日志由有序索引的一个个条目组成,每个日志条目包含了任期号和提案内容。Leader 通过维护两个字段来追踪各个 Follower 的进度信息。一个是 NextIndex,表示 Leader 发送给该 Follower 节点的下一个日志条目索引;另一个是 MatchIndex,表示该 Follower 节点已复制的最大日志条目索引。

以“hello=world”为例,从 Client 提交提案到接收响应的整个流程为例,展示 Raft 日志全流程,通过一下 8 步 Leader 同步日志条目给各个 Follower,保证 etcd 集群的数据一致性:

1. 当 Leader 接收到 Client 提交的提案信息后,生成日志条目,同时遍历各个 Follower 的日志进度,生成对各个 Follower 追加日志的 RPC 消息;

2. 通过网络模块将追加日志的 RPC 消息广播给各个 Follower;

3. Follower 接收到追加日志消息并持久化之后,回复 Leader 已复制最大日志条目索引,即 MatchIndex;

4. Leader 接收到 Follower 应答后,更新对应 Follower 的 MatchIndex;

5. Leader 根据各个 Follower 提交的 MatchIndex 信息,计算出日志条目已提交索引位置,该位置代表日志条目被一半以上节点持久化;

6. Leader 通过心跳告知各个 Follower 已提交日志索引位置;

7. 当 Client 的提案,被标识为已提交后,Leader 回复 Client 该提案通过。


(三)安全性(Safety):通过为选举及日志复制增加一系列规则,来保证 Raft 算法的安全性:

1. 一个任期号,只能有一个 Leader 被选举,Leader 选举需要集群一半以上节点支持

2. 节点收到选举投票时,如果候选者最新日志条目的任期号小于自己,拒绝投票,任期号相同但是日志比自己短,同样拒绝投票。

3. Leader 完全特性,如果某个日志条目在某个任期号中已被提交,则这个日志条目必然出现在更大任期号的所有 Leader 中;

4. Leader 只能追加日志条目,不能删除已持久化的日志条目;

5. 日志匹配特性,Leader 发送日志追加信息时,会带上前一个日志条目的索引位置(用 P 表示)和任期号,Follower 接收到 Leader 的日志追加信息后,会校验索引位置 P 的任期号与 Leader 是否一致,一致才能追加。

ETCD 使用 Raft 协议来维护集群内各个节点状态的一致性,多个分布式节点相互通信构成整体对外服务,每个节点都存储了完整的数据,并且通过 Raft 协议保证每个节点维护的数据是一致的。每个 ETCD 节点都维护了一个状态机,并且任意时刻至多存在一个有效的主节点。主节点处理所有来自客户端写操作,通过 Raft 协议保证写操作对状态机的改动会可靠的同步到其他节点。

1.3.2. 以 boltdb 为基础 ,实现高效 KV 存储引擎 

ETCD 是使用 boltdb 来持久化存储 KV 核心的元数据,boltdb 整个数据库就一个 db 文件,基于 B+树的索引,读效率高效且稳定;读事务可多个并发,写事务只能串行,并且开销较大不能并发,只能靠批量操作来缓解性能问题。

(一)page 文件块:boltdb 以 4k 定长为存储单元划分空间,即 page 文件块,所有其他对象都是以此为基础扩展抽象。

boltdb 存储的管理单元是 page,每个 page 都由 header+ data 组成,page 的类型有以下几种:

meta page:元数据信息。包含存储 B+树 root buck 位置,可用块的数量 freelist,当前最大块的 offset 等。分为 metaA 与 metaB, 用来控制进行中的事务与已完成的事务(写事务只有一个进行中).  根据 meta 中的 txid 的值的大小来判断当前生效的是哪个 meta。(txid 会根据事务操作递增)

bucket page:存储 bucket 名称数据,bucket 名称也是采用 B+树结构存储。根 root bucket 的 pageid 由 meat 的 root 字段指定

branch page:存储分支数据内容。分支数据结构中只有 key 信息,没有 value 信息。指向的都是下一级节点的 pageid 信息

leaf page:存储叶子数据内容。

freelist page:Freelist 用于管理当前可用的 pageid 列表。由 Meta 元数据信息中的 freelist 字段来定位所在的 pageid 位置。


(二)B+树索引:boltdb 是采用了变异的 B+树索引:节点分支个数不固定;叶子节点不相互感知,不保证所有的叶子节点在同一层。但是索引查找和数据组织形式都是标准 B+树。

其中 Bucket 本质上是命名空间,是一些 key/value 的集合,不同的 Bucket 可以有同名的 key/value ;node 是 B+树节点的抽象封装,page 是磁盘物理的概念node 则是逻辑上的抽象;在 boltdb 中,Bucket 是可以嵌套的,boltdb 天生就有一个 Bucket,这个是自动生成的,由 meta 指向,不是用户创建的,后续创建的 Bucket 都是这个 Bucket 的 sub bucket。

boltdb 实现事务的方式非常简单,就是绝对不覆盖更新数据。其中 meta 是通过两个互为备份的 page 页轮转写实现的,每次都写新的地方,最后更改路径引用,具体步骤如下:。

1. 通过 key 查询到位于 B+树的那个 page;

2. 把这个 page 读出来,构建 node 节点,更新 node 的内容;

3. 把 node 的内容写到空闲的的 page,并且一层层往上;

4. 最终更新 meta 索引内容;

1.3.3. MVCC 多版本 &Watch 机制

ETCD 通过 term 标识集群 Leader 的任期,当集群发生 Leader 切换,term 的值就会+1。通过 revision 标识全局数据的版本,当数据发生变更(创建、修改、删除),revision 都会+1。在相同集群多 Leader 任期时,revision 依然会保持全局单调递增,使得集群任意一次的修改都对应一个唯一的 revision,以此来实现数据的 MVCC 多版本及数据的 Watch 机制

(一)MVCC 多版本:如上图,在同一个 Leader 任期之内,所有的修改操作对应的 term 值始终都等于 2,而 revision 则保持单调递增。当重启集群之后,所有的修改操作对应的 term 值都变成了 3。在新的 Leader 任期内,所有的 term 值都等于 3,且不会发生变化,而对应的 revision 值同样保持单调递增。从一个更大的维度去看,可以发现在 term=2 和 term=3 的两个 Leader 任期之间,数据对应的 revision 值依旧保持了全局单调递增。

(二)Watch 机制:在 clientv3 库中,Watch 特性被抽象成 Watch、Close、RequestProgress 三个简单 API 提供给开发者使用,屏蔽了 client 与 gRPCWatchServer 交互的复杂细节,实现了一个 client 支持多个 gRPCStream,一个 gRPCStream 支持多个 watcher,显著降低了你的开发复杂度。同时当 watch 连接的节点故障,clientv3 库支持自动重连到健康节点,并使用之前已接收的最大版本号创建新的 watcher,避免旧事件回放等。

1.4. 总结

ETCD 是在 ZooKeeper 与 doozer 之后的项目,深度借鉴了前两个项目的精华,同时也解决了前两个项目的问题。使用场景都相差不大,主要是分布式协同协调场景,包含集群监控,集群选主,服务发现,消息分布与订阅,负载均衡,分布式通知与协调,分布式锁及分布式队列等。

同时 ETCD 在 kubernetes 生态体系的带领下,快速发展至稳定,无论是整体架构设计,性能,稳定性及用户体验都是远远超越了 ZooKeeper 与 doozer;不仅在 kubernetes 生态有大量产品集成依赖,非 kubernetes 体系的也有越来越多的产品集成使用。



关注“巨子嘉”,巨子出品,必属精品

发布于: 刚刚阅读数: 2
用户头像

巨子嘉

关注

云原生,DevOps 2019.09.20 加入

还未添加个人简介

评论

发布
暂无评论
Kubernetes核心组件-ETCD详解