一、简介
k8s 集群的数据中心,用于存放集群的配置以及状态信息,非常重要,如果数据丢失那么集群将无法恢复;因此高可用集群部署首先就是 etcd 是高可用集群;
从 etcd 的架构图中我们可以看到,etcd 主要分为四个部分:
- HTTP Server:用于处理用户发送的 API 请求以及其它 etcd 节点的同步与心跳信息请求
- Store:用于处理 etcd 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是 etcd 对用户提供的大多数 API 功能的具体实现
- Raft:Raft 强一致性算法的具体实现,是 etcd 的核心
- WAL:Write Ahead Log(预写式日志),是 etcd 的数据存储方式。除了在内存中存有所有数据的状态以及节点的索引以外,etcd 就通过 WAL 进行持久化存储。WAL 中,所有的数据提交前都会事先记录日志。Snapshot 是为了防止数据过多而进行的状态快照;Entry 表示存储的具体日志内容
二、K8S 架构图
K8S 系统默认就采用 etcd 存储数据,看看下面比较常见的高可用部署方案:
说明:
- 以上两种方案的区别是:一种用 docker 方式部署 etcd,一种是独立部署etcd(官方更推荐用这种独立部署的方式)
- 使用第二种方案的时候 Api-Server 与 etcd 交互会有 2 中方案,第一我们可以在 etcd 集群前面放一个负载均衡器用来调度 etcd 集群;第二种我们可以在 Api-Server 的配置文件中指定多个 etcd 地址,Api-Server 会依次连接,配置方式为 --etcd-servers=$IP1:2379,$IP2:2379,$IP3:2379,$IP4:2379,$IP5:2379
- 3 个 etcd 作为一个及群组出现,互相会有数据同步及探活等数据交互
- 每个 Api-Server 会在 kube-apiserver.yaml 中指定一个 etcd 服务,关于 etcd 之间是如何进行数据同步及选举的见第 4 章节
三、性能介绍
3.1 理解 etcd 的性能
决定 etcd 性能的关键因素,包括:
- 延迟(latency):延迟是完成操作的时间
- 吞吐量(throughput):吞吐量是在某个时间期间之内完成操作的总数量。 当 etcd 接收并发客户端请求时,通常平均延迟随着总体吞吐量增加而增加。
延迟是完成操作的时间。吞吐量是在某个时间期间之内完成操作的总数量。
etcd 使用 Raft 一致性算法来在成员之间复制请求并达成一致。一致性性能,特别是提交延迟,受限于两个物理约束:网络 IO 延迟和磁盘 IO 延迟。完成一个 etcd 请求的最小时间是成员之间的网络往返时延 ( Round Trip Time / RTT ),加需要提交数据到持久化存储的 fdatasync 时间。为了提高吞吐量, etcd 将多个请求打包在一起并提交给 Raft。这个批量策略让 etcd 在重负载试获得高吞吐量。
如下是摘抄自网络上的一些测试评估报告:
3.2 etcd 对服务器的要求
以下根据 etcd 官方文档对服务器资源要求做了简要说明
3.2.1 CPUS
很少有 etcd 部署需要大量的 CPU 资源。典型的 etcd 部署节点,需要 2-4 个 CPU 就可以顺利运行。负载很高的 etcd 集群,比如上千客户端或者每秒超过上万请求,倾向于 CPU 绑定,可以直接从内存获取请求。即便这样重的负载,通常需要 8-16 个 CPU 就可以了。
3.2.2 内存
etcd 占用的内存相对比较小,但是 etcd 性能仍然取决于是否拥有足够的内存。一个 etcd 服务器会积极的缓存 key-value 数据和大部分的跟踪 watcher。通常 8GB 内存就够了,对于负载高的集群,比如有上千 watcher 和超过百万的 keys,相应的就需要 16GB-64GB 内存。
3.2.3 磁盘
高速磁盘是保证 etcd 部署性能和稳定性的关键因素。
慢磁盘会增加 etcd 的请求延时,潜在影响集群的稳定性。因为 etcd 一致性协议依赖于元数据写入持久化日志,并且要求集群大多数成员将请求写入磁盘。此外,etcd 还会到磁盘上增量检查集群的状态,从而可以截断日志。如果这些写操作花费太长的时间,心跳就可能会超时,触发选举操作,从而破坏集群的稳定性。
etcd 对磁盘写入延时非常敏感。通常稳定达到 50 IOPS(比如:一个 7200 转的磁盘)是必须的,对于负载很高的集群,推荐能稳定达到 500 IOPS(比如:一个典型的本地 SSD 盘或者高性能的虚拟块设备盘)。
3.2.4 网络
多节点部署的 etcd 集群会受益于快速和可靠的网络。为了满足 etcd 集群的一致性和分区容忍,一个不可靠网络出现网络分区会导致部分节点无效。低延时可以保证 etcd 成员之间快速通信,高带宽可以减少 etcd 故障节点的恢复时间。1Gb 网络就可以满足常见的 etcd 部署场景,对于大型 etcd 集群,使用 10Gb 网络可以减少平均故障恢复时间。
3.2.5 示例硬件配置
下面以 AWS 云服务器为例子,列举了不同规模的集群对 ETCD 服务器的需求
四、RAFT
4.1 RAFT 简介
在每一个分布式系统中,etcd 往往都扮演了非常重要的地位,由于很多服务配置发现以及配置的信息都存储在 etcd 中,所以整个集群可用性的上限往往就是 etcd 的可用性,而使用 3 ~ 5 个 etcd 节点构成高可用的集群往往都是常规操作。
4.2 RAFT 选举
Etcd 所有服务器只可能处于 Leader、Follower 以及 Candidate 三种状态;在处于正常的状态时,集群中只会存在一个 Leader,其余的服务器都是 Follower。
所有的 Follower 节点都是被动的,它们不会主动发出任何的请求,只会响应 Leader 和 Candidate 发出的请求,对于每一个用户的可变操作,都会被路由给 Leader 节点进行处理,除了 Leader 和 Follower 节点之外,Candidate 节点其实只是集群运行过程中的一个临时状态。
- 步骤 ①:集群启动后所有节点默认均为 Follower。
- 步骤 ②:节点 1 探测 leader 超时, 变为 Candidate,开始选举广播, 发起投票请求。
- 步骤 ③:节点 2 和节点 3 同意投票给节点 1,节点 1 变为 Leader。
说明
为了确保选举的准确性,在每一任期内,最多允许一个服务被选举为 leader,在一个任期内,一个服务只能投一票,只有获得大多数投票才能作为 leader;如果有多个 candidate,最终一定会有一个被选举为 leader,如果多个 candidate 同时发起了选举,导致都没有获得大多数选票时,每一个 candidate 会随机等待一段时间后重新发起新一轮投票( 一般是随机等待 150-300ms )
4.3 Raft 任期
Raft 集群中的时间也被切分成了不同的几个任期(Term),每一个任期都会由 Leader 的选举开始,选举结束后就会进入正常操作的阶段,直到 Leader 节点出现问题才会开始新一轮的选择。
每一个服务器都会存储当前集群的最新任期,它就像是一个单调递增的逻辑时钟,能够同步各个节点之间的状态,当前节点持有的任期会随着每一个请求被传递到其他的节点上。
Raft 协议在每一个任期的开始时都会从一个集群中选出一个节点作为集群的 Leader 节点,这个节点会负责集群中的日志的复制以及管理工作。
4.4 RAFT 数据同步
4.4.1 Leader 主动存储数据
- 步骤 ①:Client 发起数据更新请求,存储数据 444。
- 步骤 ②:etcd-Leader 首先会更新自身日志数据。
- 步骤 ③:etcd-Leade 通知 Follower 节点也更新日志。
- 步骤 ④:Follower 节点更新自身日志数据。
- 步骤 ⑤:当 Follower 节点更新日志成功后,会返回成功通知给 etcd-Leade,至此完成了“提交”操作。
- 步骤 ⑥:当领 etcd-Leader 收到通知后,会更新本地数据。
- 步骤 ⑦:etcd-Leade 更新本地数据成功后,会通知 Follower 节点更新自身数据。
- 步骤 ⑧:Follower 节点进行更新自身数据。
- 步骤 ⑨:Follower 节点更新成功后,把更新结果反馈给 etcd Leader。
4.4.2 Leader 被动存储数据
说明
这里省去了存储数据日志的过程,默认数据日志存储完成,进行存储更改数据。
- 步骤 ①:当前集群所有节点数据一致,存有 111,222,333 三组数据,现在 Follower 节点收到存储数据 444 的请求
- 步骤 ②:Follower 把存储请求转发给当前集群的 Leader
- 步骤 ③:Leader 将收到存储数据 444 的请求并且已经把数据和 log 存储成功
- 步骤 ④:多数 follower 成功写入 log 后,leader 会将该数据提交到状态机,leader 把数据提交后,返回给 client 结果,在下一个心跳中,leader 通知 follower 更新已经提交的数据
五、存储模式介绍
5.1 源码地址
https://github.com/etcd-io/etcd
5.2 etcd3 和 etcd2 的区别
5.2.1 版本说明
目前 etcd 主要经历了 3 个大的版本,分别为 etcd 0.4 版本、etcd 2.0 版本和 etcd 3.0 版本。对于 etcd 2.0 版本,已经可以很好满足 etcd 的初步需求,主要包括:
- 专注于 key-value 存储,而不是一个完整的数据库;
- 通过 HTTP + JSON 的方式暴露给外部 API;
- watch 机制提供持续监听某个 key 变化的功能,以及基于 TTL 的 key 的自动过期机制;
但是在实际过程中,我们也发现了一些问题,比如客户端需要频繁地与服务端进行通信,集群在空间和时间上都需要承受较大的压力,以及垃圾回收 key 的时间不稳定等,同时 “微服务” 架构要求 etcd 能够单集群支撑更大规模的并发,因此诞生了 etcd 3.0 版本,主要对 HTTP + JSON 的通信方式、key 的自动过期机制、watch 机制、数据持久化等进行了优化,下面我们看看 etcd 3.0 版本对每个模块都做了哪些优化。
5.2.2 客户端通信方式
gRPC 是 Google开源的 个高性能、跨语言的 RPC 框架,基于 HTTP/2 协议实现。它使用 protobuf 作为序列化和反序列化协议,即基于 protobuf 来声明数据模型和 RPC 接口服务。protobuf 的效率远高于 JSON,尽管 etcd v2 的客户端已经对 JSON 的序列号和反序列化进行了大量的优化,但是 etcd v3 的 gRPC 序列号和反序列化的速度依旧是 etcd v2 的两倍多。
etcdv3 的客户端使用 gRPC 与 server 进行通信,通信的消息协议使用 protobuf 进行约定,代替了 v2 版本的 HTTP+JSON 格式,使用二进制替代文本,更加节省空间。同时 gRPC 使用的是 HTTP/2 协议,同一个连接可以同时处理多个请求,不必像 HTTP1.1 协议中,多个请求需要建立多个连接。同时,HTTP/2 会对请求的 Header 和请求数据进行压缩编码,常见的有 Header 帧,用于传输 Header 内容,另外就是 Data 帧,来传输正文实体。客户端可以将多个请求放到不同的流中,然后将这些流拆分成帧的形式进行二进制传输,传输的帧也会有一个编号,因此在一个连接中客户端可以发送多个请求,减少了连接数,降低了对服务器的压力,二进制的数据传输格式也会是传输速度更快。
总结一下,其实这里主要进行 2 点优化:
- 二进制取代字符串:通过 gRPC 进行通信,代替了 v2 版本的 HTTP+JSON 格式;
- 减少 TCP 连接:使用 HTTP/2 协议,同一个连接可以同时处理多个请求,摒弃多个请求需要建立多个连接的方式;
5.2.3 键的过期机制
etcdv2 中的键的实效是使用 TTL 机制来实现的,每个有存活时间的键,客户端必须定期的进行刷新重新设置保证它不被自动删除,每次刷新同时还会重新建立连接去更新键。也就是说,即使整个集群都处于空闲状态,也会有很多客户端与服务器进行定期通信,以保证某个 key 不被自动删除。
etcdv3 版本中采用了租约机制进行实现,每个租约会有一个 TTL,然后将一些 key 附加到租约上,当租约到期后,附加到它上边的 key 都会被删除。利用键的过期机制可以实现服务注册功能,我们可以将一个服务的域名、IP 等信息注册到 etcd 中,并给相应的键设置租约,并在 TTL 时间内定期维持一个心跳进行刷新。当服务故障后,心跳消失从而相应的键就会自动删除,从而实现了服务的注册功能和服务的健康检查功能。
总结一下,就是 v2 版本比较傻瓜,需要时刻维护每个 key 的通信,v3 就比较智能,整个统一的过期 key 的代号,我们把代号称之为 “租约”,我们只需要维护这个代号即可,避免客户端去维护所有的 key。
5.2.4 watch 机制
etcdv2 中的键被废除以后,为了能够跟踪 key 的变化,使用了事件机制进行跟踪,维护键的状态,来防止被删除掉的后键还能恢复和 watch 到,但是有一个滑动窗口的大小限制,那么如果要获取 1000 个时间之前的键就获取不到了。因此 etcdv2 中通过 watch 来同步数据不是那么可靠,断开连接一段时间后就会导致有可能中间的键的改动获取不到了。在 etcdv3 中支持 get 和 watch 键的任意的历史版本记录。
另外,v2 中的 watch 本质上还是建立很多 HTTP 连接,每一个 watch 建立一个 tcp 套接字连接,当 watch 的客户端过多的时候会大大消耗服务器的资源,如果有数千个客户端 watch 数千个 key,那么 etcd v2 的服务端的 socket 和内存资源会很快被耗尽。v3 版本中的 watch 可以进行连接复用,多个客户端可以共用相同的 TCP 连接,大大减轻了服务器的压力。
总结一下,其实这里主要进行 2 点优化:
- 实时监听 key 的更新:解决 v2 中途 key 的数据更新,客服端不会感知的问题;
- 多路复用:这个可以想到 select 和 epool 模型,就是一个客户之前需要建立多个 TCP 连接,现在只需要建立一个即可;
5.2.5 数据存储模型
etcd 是一个 key-value 数据库,ectd v2 只保存了 key 的最新的 value,之前的 value 会被直接覆盖,如果需要知道一个 key 的历史记录,需要对该 key 维护一个历史变更的窗口,默认保存最新的 1000 个变更,但是当数据更新较快时,这 1000 个变更其实“不够用”,因为数据会被快速覆盖,之前的记录还是找不到。为了解决这个问题,etcd v3 摒弃了 v2 不稳定的“滑动窗口”式设计,引入 MVCC 机制,采用从历史记录为主索引的存储结构,保存了 key 的所有历史记录变更,并支持数据在无锁状态下的的快速查询。 etcd 是一个 key-value 数据库,etcdv2 的 key 是一个递归的文件目录结构,在 v3 版本中的键改成了扁平化的数据结构,更加简洁,并通过线段树的优化方式,支持 key 的快速查询。
由于 etcd v3 实现了 MVCC,保存了每个 key-value pair 的历史版本,数据大了很多,不能将整个数据库都存放到内存中。因此 etcd v3 摒弃了内存数据库,转为磁盘数据库,即整个数据都存储在磁盘上,底层的存储引擎使用的是BoltDB。
总结一下,其实这里主要进行 3 点优化:
- 保存历史数据:摈弃 v2 的“滑动窗口”式设计,通过 MVCC 机制,保存了所有的历史数据;
- 数据落磁盘:因为要保存历史数据,数据量态度,不适合全内存存储,使用 BoltDB 存储;
- 查询优化:摒弃 v2 的目录式层级化设计,使用线段树优化查询;
5.2.6 MVCC 介绍
5.2.6.1 为什么会选择 MVCC
高并发情况下,会存在大量读写操。对于 etcd v2,它是一个纯内存的数据库,整个数据库有一个 Stop-the-World 的大锁,可以通过锁的机制来解决并发带来的数据竞争,但是通过锁的方式存在一些弊端,具体如下:
- 锁的粒度不好控制,每次操作 Stop-the-World 时都会锁住整个数据库;
- 读锁和写锁会相互阻塞;
如果使用基于锁的隔离机制,并且有一段很长的读事务,那么在这段时间内这个对象就会无法被改写,后面的事务也会被阻塞,直到这个事务完成为止,这种机制对于并发性能来说影响很大。
MVCC 其实就是多版本并发控制,etcd 在 v3 才引入,它可以很好的解决锁带来的问题,每当需要更改或者删除某个数据对象时,DBMS 不会在原地删除或者修改这个已有的数据对象本身,而是针对该数据对象创建一个新的版本,这样一来,并发的读取操作可以在无需加锁的情况下读取老版本的数据,而写操作就可以同时进行,这个模式的好处可以让读取操作不再阻塞。
总而言之,MVCC 能最大的实现高效的读写并发,尤其是高效的读,因此非常适合 etcd 这种“读多写少”的场景。
5.2.6.2 数据模型
将讲解 MVCC 的实现原理前,还需要了解 v2 和 v3 的数据存储模型。
对于 v2,前面其实已经讲过,v2 是一个存内存的数据库,数据会通过 WAL 日志和 Snapshot 来持久化数据,具体持久化数据的方式,后面会整体讲述。
对于 v3,因为它支持历史版本数据的查询,所以它是将数据存储在一个多版本的持久化 K-V 存储里面,当持久化键值数据发生变化时,会先保存之前的旧值,所以数据修改后,key 先前版本的所有值仍然可以访问和 watch。由于 v3 需要保存数据的历史版本,这就极大地增加了存储量,内存存储不了那么多的数据,所以 v3 的数据需要持久化到磁盘中,存储数据为 BoltDB。
那什么是 BoltDB 呢?
BoltDB 是一个纯粹的 Go 语言版的 K-V 存储,它的目标是为项目提供一个简单、高效、可靠的嵌入式的、可序列化的键值数据库,而不要像 MySQL 那样完整的数据库服务器。BoltDB 还是一个支持事务的键值存储,etcd 事务就是基于 BoltDB 的事务实现的。为了大家能充分理解,我再扩展2个问题:
###### 1.v2 会定时快照,v3 需要进行快照么?
答案是不会。v3 实现 MVCC 之后,数据是实时写入 BoltDB 数据库中,也就是数据的持久化已经 “摊销” 到了每次对 key 的写请求上,因此 v3 就不需要再做快照了。
###### 2.v3 中所有的历史数据都会保存下来么?
答案是不会。虽然 v3 没有快照,数据全部落在 BoltDB,但是为了防止数据存储随着时间推移而无限增长,etcd 可能会压缩(其实就是删除)key 的旧版本数据,说的通俗一点,就是删除 BoltDB 中旧版本的数据。
5.2.6.3 初探 MVCC 实现
那么 v3 是怎么实现 MVCC 的呢?我们可以先看如下操作:
etcdctl txn <<< 'put key1 "v1" put key2 "v2"'
etcdctl txn <<< 'put key1 "v12" put key2 "v22"'
BoltDB 中会存入 4 条数据,具体代码如下所示:
rev={3 0}, key=key1, value="v1"
rev={3 1}, key=key2, value="v2"
rev={4 0}, key=key1, value="v12"
rev={4 1}, key=key2, value="v22"
reversion 主要由 2 部分组成,第一部分是 main rev,每操作一次事务就加 1,第二部分是 sub rev,同一个事务中每进行一次操作就加 1。如上示例所示,第一次操作的 main rev 是 3,第二次是 4 。
了解 v3 的磁盘存储之后,可以看到要想从 BoltDB 中查询数据,必须通过 reversion,但是客户端都是通过 key 来查询 value,所以 etcd 在内存中还维护了一个 kvindex ,保存的就是 key reversion 之前的映射关系,用来加速查询。kvindex 是基于 Google 开源的 GoLang 的 B 树实现,也就 v3 在内存中维护的二级索引,这样当客户端通 key 查询 value 的时候, 会先在 kvindex 中查询这个 key 的所有 revision ,然后再通过 revision从 BoltDB 中查询数据。
六、日志和快照管理
6.1 数据持久化
etcd 对数据的持久化,采用的是 WAL 日志加 Snapshot 快照的方式。
etcd 对数据的更新都是先写到 WAL 日志中,当通过 Raft 将 WAL 同步到所有分布式节点之后,再将 WAL 中的数据写到内存。对于 WAL 日志,其实还有个作用,就是实现 redo 和 undo 功能,也就是当数据出现问题时,以为 WAL 日志记录了对数据的所有操作,所以可以通过 WAL 对数据库进行恢复和回滚。
既然有了 WAL 日志,那为什么还需要定期做快照呢?这里其实和 Redis 中的 RDB 和 AOF 日志很像,我们可以把 WAL 日志对标为 Redis 的 AOF 日志, Snapshot 快照对标为 Redis 的 RDB 日志。在 Redis 中进行节点间数据同步时,我们是先全量同步 RDB 日志(快照文件),然后再增量同步 AOF 日志(数据增量文件)。etcd 也不一样,因为 WAL 日志太琐碎了,如果需要通过 WAL 日志去同步数据,太慢了,我们先把之前所有的数据同步过去( Snapshot快照文件),然后再同步后续的增量数据( WAL 日志)。当对 WAL 数据昨晚快照后,就可以将旧的 WAL 数据删除。
6.2 快照管理
至于快照文件是怎么生成的,如果了解 Redis 中的 RDB 文件的生成原理,这个就不难理解了。
其实就是通过写时复制技术 Copy-On-Write 完成的,当需要进行快照时,如果数据有更新,会生成一个数据副本,如图中的“键值对C”,当进行快照时,数据如果未更新,直接落盘,数据如果有更新,同步副本数据即可。
七、常见管理方法
7.1 端口介绍
2379 对外提供服务端口;2380 各 etcd Server 间通讯使用的端口。
7.2 命令介绍
因为每次执行 etcdctl 命令都要带上一大堆的证书信息,我们做一个 alias ,方便后面操作。
export ETCDCTL_API=3
alias etcdctl='etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key'
7.2.1 查看 etcd 集群信息
# 查看集群成员信息
/ # etcdctl member list
52fbc3b72b27629f, started, k8s-master1, https://192.168.223.135:2380, https://192.168.223.135:2379
97081bb482954e76, started, k8s-master2, https://192.168.223.136:2380, https://192.168.223.136:2379
d415186fcf074869, started, k8s-master3, https://192.168.223.137:2380, https://192.168.223.137:2379
# 查看集群各节点状态
/ # alias etcdctl='etcdctl --endpoints=https://192.168.223.136:2379,https://192.168.223.135:2379,https://192.168.223.137:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key'
/ # etcdctl endpoint status --write-out=table
# 查看集群健康状态
/ # etcdctl endpoint health
https://192.168.223.136:2379 is healthy: successfully committed proposal: took = 9.549705ms
https://192.168.223.137:2379 is healthy: successfully committed proposal: took = 10.7308ms
https://192.168.223.135:2379 is healthy: successfully committed proposal: took = 12.239945ms
# 列出所有key
/ # etcdctl get / --prefix --keys-only
…………
7.2.1 内置快照
使用 etcd 内置快照对 etcd 进行数据备份,save 后面的 snapshotdb 为镜像名字,
# 生成名字为 snapshotdb 的快照
/ # export ETCDCTL_API=3
/ # etcdctl --endpoints 127.0.0.1:2379 snapshot save snapshotdb
/ # etcdctl snapshot status --write-out=table /var/lib/etcd/member/snap/snapshotdb
+---------+----------+------------+------------+
| HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
+---------+----------+------------+------------+
| e798773 | 295646 | 1301 | 2.6 MB |
+---------+----------+------------+------------+
7.2.1.1 备份还原 kubeadm 安装的 etcd 集群
7.2.1.1.1 准备工作
#建立备份存放目录
mkdir -p /backup_$(date +%Y%m%d)
#备份/etc/kubernetes目录
cp -r /etc/kubernetes/ /backup_$(date +%Y%m%d)/
#备份/var/lib/etcd目录
cp -r /var/lib/etcd/ /backup_$(date +%Y%m%d)/
#备份 /var/lib/kubelet目录
cp -r /var/lib/kubelet/ /backup_$(date +%Y%m%d)/
#使用kubeadm创建的k8s集群,etcd是使用容器运行的,因此备份和还原数据库需要将容器中的etcdctl命令拷贝到操作节点系统下的/usr/bin/目录下
docker cp $(docker ps | grep -v etcd-mirror | grep -w etcd | awk '{print $1}'):/usr/local/bin/etcdctl /usr/bin/
7.2.1.1.2 备份
备份 ETCDCTL_API 为 3 的 etcd 数据到之前的备份目录下。
可以在多个 master 节点上执行备份操作
ETCDCTL_API=3 etcdctl --endpoints="https://127.0.0.1:2379" --cert="/etc/kubernetes/pki/etcd/server.crt" --key="/etc/kubernetes/pki/etcd/server.key" --cacert="/etc/kubernetes/pki/etcd/ca.crt" snapshot save /backup_$(date +%Y%m%d)/snap-$(date +%Y%m%d%H%M).db
7.2.1.1.3 恢复
恢复步骤:
- 需要先停掉所有 Master 节点的 kube-apiserver 和 etcd,确保 kube-apiserver 已经停止了。
- 需要分别在master1、master2、master3上进行同样的操作。
[root@k8s-Master1 kubernetes]# cd /etc/kubernetes/
[root@k8s-Master1 kubernetes]# mv manifests/ manifests.bak
- 等待 api-server 和 etcd 服务 stop
stop 之前的样子
stop 之后的样子
7.2.1.1.4 变更/var/lib/etcd
需要分别在 master1、master2、master3 上进行同样的操作
mv /var/lib/etcd /var/lib/etcd.bak
7.2.1.1.5 scp bak 文件
[root@k8s-Master1 kubernetes]# scp /backup_20221208/snap-202212081539.db root@k8s-master2:/backup_20221208/
snap-202212081539.db 100% 2948KB 20.6MB/s 00:00
[root@k8s-Master1 kubernetes]# scp /backup_20221208/snap-202212081539.db root@k8s-master3:/backup_20221208/
snap-202212081539.db 100% 2948KB 25.5MB/s 00:00
[root@k8s-Master1 kubernetes]#
7.2.1.1.6 还原数据
# k8s-master1 上执行
ETCDCTL_API=3 etcdctl snapshot restore /backup_20221208/snap-202212081539.db \
--endpoints=192.168.223.135:2379 \
--name=k8s-master1 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--initial-advertise-peer-urls=https://192.168.223.135:2380 \
--initial-cluster-token=etcd-cluster-0 \
--initial-cluster=k8s-master1=https://192.168.223.135:2380,k8s-master2=https://192.168.223.136:2380,k8s-master3=https://192.168.223.137:2380 \
--data-dir=/var/lib/etcd
# k8s-master2 上执行
ETCDCTL_API=3 etcdctl snapshot restore /backup_20221208/snap-202212081539.db \
--endpoints=192.168.223.136:2379 \
--name=k8s-master2 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--initial-advertise-peer-urls=https://192.168.223.136:2380 \
--initial-cluster-token=etcd-cluster-0 \
--initial-cluster=k8s-master1=https://192.168.223.135:2380,k8s-master2=https://192.168.223.136:2380,k8s-master3=https://192.168.223.137:2380 \
--data-dir=/var/lib/etcd
# k8s-master3 上执行
ETCDCTL_API=3 etcdctl snapshot restore /backup_20221208/snap-202212081539.db \
--endpoints=192.168.223.137:2379 \
--name=k8s-master3 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--initial-advertise-peer-urls=https://192.168.223.137:2380 \
--initial-cluster-token=etcd-cluster-0 \
--initial-cluster=k8s-master1=https://192.168.223.135:2380,k8s-master2=https://192.168.223.136:2380,k8s-master3=https://192.168.223.137:2380 \
--data-dir=/var/lib/etcd
7.2.1.1.7 验证集群可用性
7.2.1.2 备份还原二进制安装的 etcd 集群
7.2.1.2.1 安装 etcdctl
yum install -y etcd
7.2.1.2.1 备份
ETCDCTL_API=3 etcdctl \
snapshot save snap.db \
--endpoints=https://192.168.10.160:2379 \
--cacert=/opt/etcd/ssl/ca.pem \
--cert=/opt/etcd/ssl/server.pem \
--key=/opt/etcd/ssl/server-key.pem
7.2.1.2.2 暂停 kube-apiserver 和 etcd
systemctl stop kube-apiserver
systemctl stop etcd etcd
mv /var/lib/etcd/default.etcd /var/lib/etcd/default.etcd.bak
7.2.1.2.3 在每个节点上恢复数据
# 节点一
ETCDCTL_API=3 etcdctl snapshot restore snap.db \
--name etcd-1 \
--initial-cluster= "etcd-1=https://192.168.10.160:2380,etcd-2=https://192.168.10.161:2380,etcd-3=https:192.168.10.162:2380" \
--initial-advertise-peer-url=https://192.168.10.160:2380 \
--data-dir=/var/lib/etcd/default.etcd
# 节点二
ETCDCTL_API=3 etcdctl snapshot restore snap.db \
--name etcd-2 \
--initial-cluster= "etcd-1=https://192.168.10.160:2380,etcd-2=https://192.168.10.161:2380,etcd-3=https:192.168.10.162:2380" \
--initial-advertise-peer-url=https://192.168.10.162:2380 \
--data-dir=/var/lib/etcd/default.etcd
# 节点三
ETCDCTL_API=3 etcdctl snapshot restore snap.db \
--name etcd-3 \
--initial-cluster= "etcd-1=https://192.168.10.160:2380,etcd-2=https://192.168.10.161:2380,etcd-3=https:192.168.10.162:2380" \
--initial-advertise-peer-url=https://192.168.10.162:2380 \
--data-dir=/var/lib/etcd/default.etcd
######7.2.1.2.4 验证集群可用性
mv /var/lib/etcd/default.etcd.bak /var/lib/etcd/default.etcd
systemctl start kube-apiserver
systemctl start etcd.service
7.2.2 重启 etcd 服务
systemctl restart etcd
7.2.3 删除故障节点
/ # export ETCDCTL_API=3
# 查看当前etcd集群清单
/ # etcdctl --endpoints 127.0.0.1:2379 --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key --cacert=/etc/kubernetes/pki/etcd/ca.crt member list
52fbc3b72b27629f, started, k8s-master1, https://192.168.223.135:2380, https://192.168.223.135:2379
97081bb482954e76, started, k8s-master2, https://192.168.223.136:2380, https://192.168.223.136:2379
d415186fcf074869, started, k8s-master3, https://192.168.223.137:2380, https://192.168.223.137:2379
# 删除某个etcd节点
/ # etcdctl member remove 52fbc3b72b27629f
Member 52fbc3b72b27629f removed from cluster 2adefcce7f8dcf42
7.2.4 向集群中添加节点
/ # etcdctl member add etcd --peer-urls="https://192.168.223.135:2380"
Member d5ce641905ab71cb added to cluster 2adefcce7f8dcf42
ETCD_NAME="etcd"
ETCD_INITIAL_CLUSTER="k8s-master2=https://192.168.223.136:2380,k8s-master3=https://192.168.223.137:2380,etcd=https://192.168.223.135:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="https://192.168.223.135:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"
注意
添加节点前新的节点要在配置文件中添加 - --initial-cluster-state=existing 参数表示 etcd 不作为新集群初始化
7.2.5 查看当前节点状态
/ # etcdctl endpoint status --write-out=table
+------------------------+------------------+---------+---------+-----------+-----------+------------+
| ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+------------------------+------------------+---------+---------+-----------+-----------+------------+
| https://127.0.0.1:2379 | d5ce641905ab71cb | 3.3.10 | 3.0 MB | false | 177 | 419601 |
+------------------------+------------------+---------+---------+-----------+-----------+------------+
八、续订 etcd
8.1 kubeadm 续订 etcd 证书
8.1.1 获取 kubeadm configmap 文件
kubectl get configmap -n kube-system kubeadm-config -o yaml > /root/kubeadm/kubeadm-config.yaml
8.1.2 更新 etcd-peer 证书
8.1.2.1 证书简介
etcd 节点间用来相互通信的证书
8.1.2.2 证书延期
kubeadm alpha certs renew etcd-peer --config=/root/kubeadm/kubeadm-config.yaml
8.1.3 更新 etcd-healthcheck-client 证书
8.1.3.1 证书简介
存活态探针的证书,用于对 etcd 执行健康检查
8.1.3.2 证书延期
kubeadm certs renew etcd-healthcheck-client --config=/root/kubeadm/kubeadm-config.yaml
8.1.4 更新 etcd-server 证书
8.1.4.1 证书简介
用于提供 etcd 服务的证书
8.1.4.2 证书延期
kubeadm certs renew etcd-server --config=/root/kubeadm/kubeadm-config.yaml
8.1.5 重启 etcd 容器
注意
每个集群的环境都不一样,建议重启前挨个 grep 拆开,确定 etcd 容器后在重启。
[root@k8s-Master1 etcd]# docker ps -a | grep etcd | grep "advertise" | grep -v Exit
bf0661bf4393 2c4adeb21b4f "etcd --advertise-cl…" 40 hours ago Up 3 minutes k8s_etcd_etcd_kube-system_28eac234-7252-11ed-9caa-000c29d4a613_0
[root@k8s-Master1 etcd]# docker ps -a | grep etcd | grep "advertise" | grep -v Exit | awk '{print $1}' | xargs docker restart
bf0661bf4393
查看 etcd 集群状态
[root@k8s-Master1 etcd]# kubectl exec -it -n kube-system etcd sh
/ # export ETCDCTL_API=3
/ # alias etcdctl='etcdctl --endpoints=https://127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/et
cd/server.crt --key=/etc/kubernetes/pki/etcd/server.key'
/ # etcdctl member list
97081bb482954e76, started, k8s-master2, https://192.168.223.136:2380, https://192.168.223.136:2379
d415186fcf074869, started, k8s-master3, https://192.168.223.137:2380, https://192.168.223.137:2379
d5ce641905ab71cb, started, k8s-master1, https://192.168.223.135:2380, https://192.168.223.135:2379
8.2 外挂 etcd 证书续期
看下 etcd 证书配置文件,发现是 8760h
[root@k8s-Master1 etcd]# cat config.json
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "8760h"
}
}
}
}
8.2.1 备份 etcd 数据
[root@k8s-Master1 etcd]# cd /var/lib
[root@k8s-Master1 etcd]# tar -zvcf etcd.tar.gz etcd/
8.2.2 修改 ca 配置文件
[root@k8s-Master1 etcd]# cat ca-config.json
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
# 将默认证书签署过期时间修改为10年
"expiry": "87600h"
}
}
}
}
8.2.3 生成新证书
#删除过期证书
[root@k8s-Master1 etcd]# rm -f /etc/etcd/ssl/*
# 创建新证书
[root@k8s-Master1 etcd]# cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=config.json -profile=kubernetes etcd-csr.json | cfssljson -bare etcd
[root@k8s-Master1 etcd]# cp etcd.pem etcd-key.pem ca.pem /etc/etcd/ssl/
#拷贝到其他etcd节点
[root@k8s-Master1 etcd]# scp -r /etc/etcd/ssl root@${other_node}:/etc/etcd/
# 重启etcd服务(记住,要3个节点一起重启,不然会hang住)
[root@k8s-Master1 etcd]# systemctl restart etcd
8.2.4 重启 k8s 中间件
etcd 替换成功后,再重启 kube-apiserver,kube-controller,kube-scheduler 这 3 个容器
九、etcd 的主要监控指标
9.1 行内 grafana 图的地址
9.2 主要告警规则
十、常见问题及处理方法
10.1 某个节点损坏
0. 假设顺坏的是200.54
先停止服务:systemctl stop etcd.service
1. 找到一台正常的节点服务器,比如https://10.10.200.51:2379
etcdctl --cacert=ca.pem --cert=server.pem --key=server-key.pem \
--endpoints="https://10.10.200.51:2379" member list
2. 从集群中删除损坏的节点(假设是200.54)
etcdctl --cacert=ca.pem --cert=server.pem --key=server-key.pem \
--endpoints="https://10.10.200.51:2379" member remove 78c05f4178265b0e
3. 重新加入这个节点
etcdctl --cacert=ca.pem --cert=server.pem --key=server-key.pem \
--endpoints="https://10.10.200.51:2379" member add etcd54 \
--peer-urls=https://10.10.200.54:2380
4. 修改配置并启动损坏的节点(假设是200.54上操作)
删除所有数据rm -rf data/*
修改配置:
initial-cluster-state: "new" # 改成下面这句
initial-cluster-state: "existing"
systemctl start etcd.service
systemctl status etcd.service
10.2 database space exceeded 报错恢复步骤
从报错的字面意思来看,是超出数据库空间导致。执行 etcdctl endpoint status,查看集群此时各节点的状态,发现 DB SIZE 为 2.1GB。ETCD 官方文档说明(https://etcd.io/docs/v3.3.12/dev-guide/limit/)提到 ETCD 默认的存储大小是2GB。超出后,集群无法进行写入。
10.2.1 备份数据
使用 snapshot save 命令备份集群数据(前面有写)
10.2.2 获取reversion
etcdctl --write-out="json" --cacert /var/lib/etcd/cert/ca.pem --key /var/lib/etcd/cert/etcd-client-key.pem --cert /var/lib/etcd/cert/etcd-client.pem --endpoints='*.*.*.*:2379' endpoint status |grep -o '"revision":[0-9]*'
10.2.3 compact
etcdctl --cacert /var/lib/etcd/cert/ca.pem --key /var/lib/etcd/cert/etcd-client-key.pem --cert /var/lib/etcd/cert/etcd-client.pem --endpoints='*.*.*.*:2379' compact $revision
10.2.4 defrag
etcdctl --cacert /var/lib/etcd/cert/ca.pem --key /var/lib/etcd/cert/etcd-client-key.pem --cert /var/lib/etcd/cert/etcd-client.pem --endpoints='*.*.*.*:2379' defrag
10.2.5 删除报警
# 必需删除,否则集群仍然无法使用
etcdctl --write-out="table" --cacert /var/lib/etcd/cert/ca.pem --key /var/lib/etcd/cert/etcd-client-key.pem --cert /var/lib/etcd/cert/etcd-client.pem --endpoints='*.*.*.*:2379 alarm disarm
十一、etcd 参考文档
官方文档
https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/configure-upgrade-etcd/
https://kubernetes.io/zh-cn/docs/reference/setup-tools/kubeadm/kubeadm-certs/#cmd-certs-renew
https://github.com/etcd-io/etcd/issues/8169
https://kubernetes.feisky.xyz/concepts/components/etcd
https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/configure-upgrade-etcd/#准备开始
其它文档
https://cloud.tencent.com/developer/article/1888165
https://juejin.cn/post/7067910112287031333
https://blog.csdn.net/wo18237095579/article/details/119956018
https://bbotte.github.io/service_config/etcd-cluster-troubleshooting.html
https://cloud.tencent.com/developer/article/1708414
https://cloud.tencent.com/developer/news/279231
https://chende.ren/2021/04/23183930-k8s-etcd.html
https://cloud.tencent.com/developer/article/1683582
https://developer.aliyun.com/article/939873
https://www.jianshu.com/p/0ef46c0fba47 数据备份恢复干货
https://blog.51cto.com/dangzhiqiang/2286890 etcd 对服务器资源的推荐
https://juejin.cn/post/6992916118058827813 etcd 性能测试
评论区