02-Kubernetes控制平台组件-etcd

一、etcd概述

  1、简介与特点

    etcd是CoreOS基于Raft开发的分布式key-value存储,可用于服务发现、共享配置以及一致性保障(如数据库选主、分布式锁等)。

    在分布式系统中,如何管理节点间的状态一直是一个难题,etcd像是专门为集群环境的服务发现和注册而设计,它提供了数据TTL失效、数据改变监视、多值、目录监听、分布式锁原子操作等功能,可以方便的跟踪并管理集群节点的状态。

  (1)etcd 的特点:

    键值对存储∶将数据存储在分层组织的目录中,如同在标准文件系统中;

    监测变更∶监测特定的键或目录以进行更改,并对值的更改做出反应;

    简单∶curl 可访问的用户的API(HTTP+JSON);

    安全∶可选的SSL客户端证书认证;

    快速∶单实例每秒1000次写操作,2000+次读操作;

    可靠∶使用Raft算法保证一致性。

  (2)主要功能:

    基本的 key-value 存储

    监听机制

    key 的过期及续约机制,用于监控和服务发现

    原子 Compare And Swap 和 Compare And Delete,用于分布式锁和 leader 选举

  (3)使用场景:

    可以用于键值对存储,应用程序可以读取和写入etcd中的数据

    etcd比较多的应用场景是用于服务注册与发现

    基于监听机制的分布式异步系统

  2、功能详解

  (1)键值对存储:

    etcd 是一个键值存储的组件,其他的应用都是基于其键值存储的功能展开。

    采用 KV 型数据存储,一般情况下比关系型数据库快。

    支持动态存储(内存)以及静态存储(磁盘)。

    分布式存储,可集成为多节点集群。

    存储方式,采用类似目录结构(B+tree)。

    只有叶子节点才能真正存储数据,相当于文件。

    叶子节点的父节点一定是目录,目录不能存储数据。

  (2)服务注册与发现:

    强一致性、高可用的服务存储目录(基于 Raft 算法的 etcd 天生就是这样一个强一致性、高可用的服务存储目录)。

    一种注册服务和服务健康状况的机制(用户可以在 etcd 中注册服务,并且对注册的服务配置 keyTTL,定时保持服务的心跳以达到监控健康状态的效果)。

        

   (3)消息发布与订阅:

    在分布式系统中,最适用的一种组件间通信方式就是消息发布与订阅。

    即构建一个配置共享中心,数据提供者在这个配置中心发布消息,而消息使用者则订阅他们关心的主题,一旦主题有消息发布,就会实时通知订阅者。

    通过这种方式可以做到分布式系统配置的集中式管理与动态更新。

    应用中用到的一些配置信息放到etcd上进行集中管理。

    应用在启动的时候主动从etcd获取一次配置信息,同时,在etcd节点上注册一个Watcher并等待,以后每次配置有更新的时候,etcd都会实时通知订阅者,以此达到获取最新配置信息的目的。

        

     etcd的安装下载安装包参考链接:

    ETCD_VER=V3.4.17

· DOWNLOAD_URL=https://github.com/etcd-io/etcd/releases/download · rm-f/tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
· rm-rf /tmp/etcd-download-test && mkdir-p /tmp/etcd-download-test
· curl-L $[DOWNLOAD_URL/$[ETCD_VER]/etcd-$(ETCD_VER]-linux-amd64.tar.gz-o /tmp/etcd-
${ETCD_VER}-linux-amd64.tar.gz
· tar xzvf /tmp/etcd-$[ETCD_VER]-linux-amd64.tar.gz-C/tmp/etcd-download-test--strip-
components=1
· rm-f/tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz

  3、第三方库和客户端工具

    目前有很多支持etcd的库和客户端工具

    命令行客户端工具etcdctl

    Go客户端go-etcd

    Java客户端jetcd

    Python 客户端 python-etcd

  4、etcd练习:

    查看集群成员状态:etcdctl member list--write-out=table

    基本的数据读写操作:

      写入数据:etcdctl--endpoints=localhost:12379 put /key1

      读取数据:etcdctl--endpoints=localhost:12379 get /key1 val1 /key1 val1

    按key的前缀查询数据:etcdctl --endpoints=localhost:12379 get --prefix /

    只显示键值:etcdctl--endpoints=localhost:12379get--prefix/--keys-only--debug

  5、TTL:

    TTL(Time To Live)指的是给一个key 设置一个有效期,到期后这个key就会被自动删掉,这在很多分布式锁的实现上都会用到,可以保证锁的实时有效性。

  6、CAS:

    Atomic Compare-and-Swap(CAS)指的是在对 key 进行赋值的时候,客户端需要提供一些条件,当这些条件满足后,才能赋值成功。这些条件包括:

      prevExist∶key当前赋值前是否存在

      prevValue∶key当前赋值前的值

      prevlndex∶key当前赋值前的Index

    这样的话,key的设置是有前提的,需要知道这个key当前的具体情况才可以对其设置。 

二、Raft协议

  Raft 协议基于 quorum 机制,即大多数同意原则,任何的变更都需超过半数的成员确认。

    服务端一般都是集群,客户端发起的任何变更都会发送到服务端,服务端会有一个一致性模块将变更发送给其他 peer,同时会将这个请求写入日志模块中,当其他节点处理完成后,会将处理结果返回给发起方,发起方会去做确认,只要多数操作成功,则认为该操作成功,并把该变更写入状态机。当去读取数据的时候,其实是从状态机中读取的。

        

  Raft 成员的角色:Follower(追随者)、candidate(候选人)、leader(领导者)

  Raft 协议的工作机制:

    初始化:当集群启动的时候,所有的节点都是以 follower 角色启动的,这时候是没有 leader 的;follower 是要不停的接收来自 leader 发来的心跳,如果一个 follower 在一定的时间间隔没有收到来自 leader 的心跳,那么就会把角色改为 candidate,就要发起投票。

     leader election(选主行为):candidate向其他节点发起选举请求将自己作为leader,其他 cadidate 有一个原则,如果这时他们也没有 leader,则同意选择该 candidate 作为 leader,那么该 cadidate 就成为 leader。

    日志复制:所有的写操作都应该从 leader 发起,当 leader 收到一个变更请求后,会记录一条 log 日志,但是并不会提交,follower 会和 leader 保持心跳检测,在发送心跳时,会将该变更同步给 follower,follower接收到之后也写入自己的 log 日志并返回,当 leader 接收到大多数的 follower 确认后,才 commit 日志,表示写入成功。下次发送心跳会把确认发送给 follower,让 follower 也去 commit,当多数的 follower commit 完成,本次写入才算成功。

    Election Timeout:follower 变为 cadidate 的超时时间,默认是 150-300 毫秒之间,每个节点启动的时候都是 follower,然后会随机一个 election timeout,当超过该时间后,就会变成 candidate,由于是随机的,因此每个节点变为 candidate 的时机都不一样,最先变为 candidate 的节点先发起选举。当选举成功后, leader 节点和 follower 节点会保持心跳检测,每一次心跳检测, follower 都会将其 election timeout 充值,保证其不会变为 candidate。

    Term:任期,当集群发生网络隔离又恢复的情况,很有可能会出现两个 leader ,那么此时又更新请求,会使用 term 进行区分,follower 接收到 leader 的 term 大于等于当前的 term,则接收请求,如果小于,则不处理。

   Learner:Raft 4.2.1 引入的新角色

    当出现一个集群需要增加节点时,新节点与Leader的数据差异较大,需要较多数据同步才能跟上 Leader 的最新的数据。

    此时 Leader 的网络带宽很可能被用尽,进而使得 Leader 无法正常保持心跳,进而导致 Follower 重新发起投票,进而可能引发etcd集群不可用。

    Learner角色只接收数据而不参与投票,因此增加Learner节点时,集群的quorum不变。

        

三、etcd 实现 raft 协议

   1、etcd 基于Raft的一致性:

  (1)选举方法:

    初始启动时,节点处于Follower状态并被设定一个election timeout,如果在这一时间周期内没有收到来自 Leader 的 heartbeat,节点将发起选举∶将自己切换为 candidate 之后,向集群中其它 Follower 节点发送请求,询问其是否选举自己成为 Leader。

    当收到来自集群中过半数节点的接受投票后,节点即成为 Leader,开始接收保存 client 的数据并向其它的 Follower 节点同步日志。如果没有达成一致,则 candidate 随机选择一个等待间隔(150ms~300ms)再次发起投票,得到集群中半数以上 Follower 接受的 candidate 将成为 Leader。

    Leader 节点依靠定时向 Follower 发送 heartbeat 来保持其地位。

    任何时候如果其它 Follower 在 election timeout 期间都没有收到来自 Leader 的 heartbeat,同样会将自己的状态切换为 candidate 并发起选举。每成功选举一次,新 Leade r的任期(Term)都会比之前 Leader 的任期大 1。

  (2)日志复制:

    当接 Leader 收到客户端的日志(事务请求)后先把该日志追加到本地的 Log 中,然后通过 heartbeat 把该 Entry 同步给其他 Follower,Follower 接收到日志后记录日志然后向 Leader  发送 ACK,当 Leader收到大多数(n/2+1)Follower 的 ACK 信息后将该日志设置为已提交并追加到本地磁盘中,通知客户端并在下个 heartbeat 中 Leader 将通知所有的 Follower 将该日志存储在自己的本地磁盘中。

  (3)安全性:

    安全性∶是用于保证每个节点都执行相同序列的安全机制。

    如当某个 Follower 在当前 Leader commit Log 时变得不可用了,稍后可能该 Follower 又会被选举为 Leader,这时新 Leader 可能会用新的 Log 覆盖先前已 committed 的 Log,这就会导致节点执行不同序列;Safety 就是用于保证选举出来的 Leader 一定包含先前 committed Log 的机制。

    选举安全性(Election Safety)∶每个任期(Term)只能选举出一个 Leader。

    Leader 完整性(Leader Completeness)∶指 Leader 日志的完整性,当 Log 在任期 Term1 被 Commit 后,那么以后任期 Term2、Term3… 等的 Leader 必须包含该 Log;Raft 在选举阶段就使用 Term 的判断用于保证完整性∶当请求投票的该 Candidate 的 Term 较大或 Term 相同 Index 更大则投票,否则拒绝该请求。

  (4)失效处理:

      1. Leader 失效∶其他没有收到 heartbeat 的节点会发起新的选举,而当 Leader 恢复后由于步进数小会自动成为 Follower(日志也会被新 Leader 的日志覆盖)。

      2.Follower 节点不可用∶Follower 节点不可用的情况相对容易解决。因为集群中的日志内容始终是从 Leader 节点同步的,只要这一节点再次加入集群时重新从 Leader 节点处复制日志即可。

      3.多个 candidate∶冲突后 candidate 将随机选择一个等待间隔(150ms~300ms)再次发起投票,得到集群中半数以上 Follower 接受的 candidate 将成为 Leader。

  2、wal 日志(write ahead log)

    wal 日志是二进制的,解析出来后是以上数据结构 LogEntry。

    其中第一个字段 type,一种是0表示 Normal(数据变更),1表示 ConfChange(etcd 本身的配置变更同步,比如有新的节点加入等)。

    第二个字段是 term,每个 term 代表一个主节点的任期,每次主节点变更 term 就会变化。

    第三个字段是 index,这个序号是严格有序递增的,代表变更序号。

    第四个字段是二进制的 data,将 raft request 对象的 pb 结构整个保存下。

        

    etcd 源码下有个 tools/etcd-dump-logs,可以将 wal 日志 dump 成文本查看,可以协助分析 Raft 协议。

    Raft 协议本身不关心应用数据,也就是 data 中的部分,一致性都通过同步 wal 日志来实现,每个节点将从主节点收到的 data apply 到本地的存储,Raft 只关心日志的同步状态,如果本地存储实现的有 bug,比如没有正确地将 data apply 到本地,也可能会导致数据不一致。

  3、etcd v3存储机制: 

    etcd v3 store分为两部分∶一部分是内存中的索引,kvindex,是基于Google开源的一个Golang 的 Btree 实现的,另外一部分是后端存储。按照它的设计,backend 可以对接多种存储,当前使用的是 boltdb。

    boltdb 是一个单机的支持事务的 KV 存储,etcd 的事务是基于boltdb 的事务实现的。etcd 在 boltdb 中存储的 key 是 reversion, value 是 etcd 自己的 key-value 组合, 也就是说 etcd 会在boltdb中把每个版本都保存下,从而实现了多版本机制。

    reversion 主要由两部分组成,第一部分 main rev,每次事务进行加一,第二部分 sub rev,同一个事务中的每次操作加一,在 k8s 中主要使用了 main rev。

    etcd 提供了命令和设置选项来控制 compact,同时支持 put 操作的参数来精确控制某个 key 的历史版本数。

    内存 kvindex 保存的就是 key 和 reversion 之前的映射关系,用来加速查询。

        

  4、数据存储流程

    当客户端请求发送到 leader 时

    (1)预检查:配额、限速、鉴权、包大小。配额是指 etcd 有大小的配置,这次请求是否还能放进去;包大小是指一个请求包的大小限制,默认时 1.5M,一般情况下也不会超,yml 文件一般不会这么大。

    (2)将请求发送至 KVServer

    (3)如果是写请求,KVServer 将请求发送至一致性模块,一致性模块负责集群的选举和日志复制,此时会将 raftlog 中的该条 storage 记为 unstable

    (4)写入之后再写入 wal,同时将请求发送给其他 follower

    (5)其他节点和当前 leader 一样做日志存储

    (6)当超过半数的 follower 存入日志并响应后,leader 节点的 KVServer 会请求状态机来记录这一次数据,同时更新一致性模块的 raftlog 为 commited

    状态机基于 MVCC 机制,包含了内存的 treeIndex 和磁盘的 boltdb。在内存的 treeIndex 中,key 是请求的 key,value 是该 key 对应的所有版本信息,其中有一个 modified 记录了最新的记录,同时使用 generations 存储所有的历史记录;在 boltdb 中,存储的 key 是版本,value 是该条记录的完整信息。

    当客户端请求发送到 follower 时,其仍然会进行预检查和KVServer,但是在一致性模块中会判断其是否是 leader,如果不是 leader,会将请求转发给 Leader。

 

        

  5、etcd的数据一致性

    etcd 中有 term 和 index,term 记录的是 leader 的变更,index 记录的是 数据的变更,leader 中会有 matchindex 记录 leader 现在和哪个 index 保持一致。

    所有的 follower 中会存 index,leader 会存 matchindex,代表当前 leader 已经和哪个 index 保持一致了,如下图所示,已提交日志表示半数已经确认的日志,那么 leader 的 matchindex 就是 7,第 8 个由于没过半数,因此不能作为已确认 index。

    如果此时发生了选举,那么只能选举 B 作为新的 leader 才能保证数据不丢失,所以 etcd 是使用 index 的方式保证数据一致性的。

        

  6、Watch机制:

    etcd V3的 Watch 机制支持 Watch 某个固定的key,也支持 Watch 一个范围(可以用于模拟目录的结构的 Watch),所以 watchGroup 包含两种 watcher,一种是 key watchers,数据结构是每个 key 对应一组 watcher,另外一种是 range watchers,数据结构是一个 IntervalTree,方便通过区间查找到对应的 watcher。

    同时,每个 WatchableStore 包含两种 watcherGroup,一种是 synced,一种是 unsynced,前者表示该 group 的 watcher 数据都已经同步完毕,在等待新的变更,后者表示该 group 的 watcher 数据同步落后于当前最新变更,还在追赶。

    当 etcd 收到客户端的 watch 请求,如果请求携带了 revision 参数,则比较请求的 revision 和 store 当前的 revision,如果大于当前  revision,则放入 synced 组中,否则放入 unsynced 组。

    同时,etcd 会启动一个后台的 goroutine 持续同步 unsynced 的 watcher,然后将其迁移到 Synced 组。

    在这种机制下,etcd V3 支持从任意版本开始 watch,没有 V2 的1000 条历史 event 表限制的问题(当然这是指没有 compact 的情况下)。

  8、etcd练习:

    查看集群成员状态:etcdctl member list--write-out=table

    启动新 etcd 集群:docker run -d registry.aliyuncs.com/google_containers/etcd:3.5.0-0/usr/local/bin/etcd

    进入etcd容器:docker pslgrep etcd  docker exec-it <containerid> sh

    存入数据:etcdctl put x 0

    读取数据:etcdctl get x - w=json {"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"r evision":2,"raft_term":2},"kvs":[("key":"eA==","create_revision":2,"mod_revision":2,"versi on":1,"value":"MA=="},"count":1}

    修改值:etcdctl put x 1

    查询历史版本值:etcdctl get x --rev=2

    查询最新值:etcdctl get x

四、构建高可用 etcd 集群

  1、etcd成员重要参数

  (1)成员相关参数

# etcd的名字,默认为 default
--name 'default'
# etcd 数据的存储路径
--data-dir '${name}.etcd'
# 集群节点通讯 url
--listen-peer-urls 'http://localhost:2380
# 客户端访问 url
--listen-client-urls 'http://localhost:2379

  (2)集群相关参数

# 集群节点通讯 url
--initial-advertise-peer-urls 'http://localhost:2380'
# 客户端访问 url
--initial-cluster 'default=http://localhost:2380'
# 初始化一个集群还是加入集群 new:初始化集群,existing:加入已有集群
--initial-cluster-state 'new'
# 初始化集群的 token
--initial-cluster-token 'etcd-cluster'
# 对外宣告的地址是什么 
--advertise-client-urls 'http://localhost:2379'

  (3)安全相关参数

    etcd 对外提供的接口是需要安全保障的,其最常用的安全保障就是 MTLS,即 mutual TLS,也就是双向的 TLS,服务端要验证客户端,客户端也要验证服务端,因此 etcd 的每一个对外端口上都有 TLS 的配置参数,比如说 cert、key、client-crl、trusted-ca 等,都需要去设置。要想访问 TLS 保证好的 etcd server,那么就需要带着 key、cert、ca 去访问。

--cert-file
--key-file
--client-crl-file
--trusted-ca-file
--peer-cert-file
--peer-key-file
--peer-trusted-ca-file

  2、灾备:

    创建Snapshot:

etcdctl --endpoints https://127.0.0.1:3379 \
--cert /tmp/etcd-certs/certs/127.0.1.pem \
--key /tmp/etcd-certs/certs/127.0.0.1-key.pem \
--cacert/tmp/etcd-certs/certs/ca.pem \
snapshotsavesnapshot.db

    恢复数据:

etcdctlsnapshot restore snapshot.db \
--name infra2 \
--data-dir=/tmp/etcd/infra2 \
--initial-cluster infra0=http://127.0.0.1:3380,infral=http:/127.0.0.1:4380,infra2=http://127.0.0.1:5380 \
--initial-cluster-token etcd-cluster-1 \
--initial-advertise-peer-urls http://127.0.0.0.1:5380

  3、容量管理:

    单个对象不建议超过1.5M,如果数据很大,其同步开销、内存快照开销都非常大,会使 etcd 集群显著下降

    默认容量2G、不建议超过8G,所以生产系统一般都会设置成 8G

  Alarm & Disarm Alarm:

# 设置etcd存储大小
$etcd--quota-backend-bytes=$((16*1024*1024))
# 写爆磁盘
$ while [1]; do dd if=/dev/urandombs=1024 count=1024|ETCDCTL_API=3etcdctlput keyll break; done
# 查看endpoint状态
$ ETCDCTL_API=3 etcdctl--write out table endpoint status
# 查看alarm
$ETCDCTL_APERetcdctlalarmlist
# 清理碎片
$ETCDCTL_API=3etcdctldefrag
# 清理alarm
$ETCDCTL_APERetcdctlalarmdisarm

  4、碎片整理

  etcd 中存储了所有历史版本的信息,因此随着时间推移,etcd 的存储可能会被撑爆。

  etcd 使用 compaction 可以选择一个 reversion,将该版本之前的数据删除,也可以使用 defrag 对数据进行压缩。之前的版本 compaction 需要人工手动处理,新版本之后 etcd 可以自动处理。

#keep one hour of history
$ etcd--auto-compaction-retention=1
#compactuptorevision3 $ etcdctlcompact 3
$ etcdctldefrag
Finished defragmenting etcd member[127.0.0.1:2379]

五、etcd 高可用集群

  生产环境的高可用集群一般有几类: 

    第一类是类似于用 kuberadmin 搭建一个 kubernetes 集群,这种 etcd 我们通常是通过 kubelet 去扫描,因为 kubelet 可以接受多种数据源,一种数据源是本地目录,其可以扫这个本地目录,我们就可以将 etcd 的 pod 放进去,那么 kubelet 就能扫描到 etcd 的 pod 清单,然后就会启 etcd 的 pod,这也是 kubernetes 控制面怎么拉起自己的 etcd。

    第二类是首先有一个管理集群,管理集群先启动起来,然后在这个管理集群上再去构建作业集群,也就是 kubernetes on kubernetes,那么这种方式就需要使用开源的方式来解决怎么在 kubernetes 上新建一个 kubernetes 集群。对于这种方案,社区主要有 etcd-operator 和 Bitnami 两种解决方案。

      etcd-operator∶coreos开源的,基于 kubernetes CRD 完成 etcd 集群配置。

        Archived:https://qithub.com/coreos/etcd-operator,虽然项目状态是 Archived,即不再维护了,但是这种方式已经深入人心。通过定义一个 CRD 且为这个 CRD 编写 Controller 的这样组合模式,就叫做 operator 模式。

      Etcd statefulsetHelm chart: Bitnami(powered by vmware):它提供了基于 Helm chart 的一个 etcd 的搭建。

        https://bitnami.com/stack/etcd/helm

        https://github.com/bitnami/charts/blob/master/bitnami/etcd

  1、etcd Operator:

    operator 是 CRD 和 Controller 的组合,所以 etcd operator 引入了几个核心对象、扩展对象:

      etcdCluster:用户可以定义一个第三方对象、扩展对象叫做 etcd Cluster。

        在 cluster 中可以定义 etcd 需要的版本是什么、需要多少副本数,当定义好这些对象后,etcd operator 会运行一个控制器,这个控制器会解析 etcd Cluster,然后创建 kubernetes pod,这些 pod 就会运行 etcd 的 image,并且按照用户的输入来组成 etcd 集群。

      backup:数据备份

        是要保证 etcd 集群的安全性,他就可以定义一些备份策略,这些策略可以选择数据往哪备份,公有云的存储都需要认证,所以在 backup 对象里面可以去提供认证的 Credential,那么其就会按照 backup 定义的频度生成 snapshot 并存入远端存储,频度是指要存多少个副本、多少次备份、多久备份一次,定义备份的频度和要保留的备份的数量,其就会按照定义的时间周期一直做 snapshot,并将 snapshot 存储在远端的存储中。

      etcdRestoreResource:数据恢复

        如果要想做数据恢复,可以定义一个 etcdRestoreResource,这个 Resource 会 reference 某一个 Remote volume,他就会从 volume 中拉下来之前的备份,并且通过 etcd operator 来恢复整个 etcd 集群。

        其实就可以理解为 state、snapshot、backup、restore 等的这些能力,这里使用 go 语言重新写了一份。这些能力不再是以命令行的命令去 trigger 的,而是当创建一个 etcd cluster 的时候,他就去起这样一个集群,当去创建一个 etcd backup 的时候,他就去 backup,当去 restore 时,他就去执行 restore,其实就是把之前使用命令行做的事以 Controller 的形式重写了一次

        所以 operator 模式就是 CRD + Controller,CRD 就是模型抽象,把要做的事情以声明式的形式定义出来,Controller 的行为是之前是使用命令或脚本的方式执行的,现在通过控制器的模式以 go 语言的形式把他加载出来

          

        

    参考链接:https://github.com/coreos/etcd-operator.git

  2、基于 Bitnami 安装 etcd 高可用集群

    Bitnami 本身提供了 etcd 的高可用集群,它本质上是一个 statefulset。

    Bitnami 提供了自己的 helm chart 的这种 repo,helm 是一个管理 kubernetes 的应用包的工具

    (1)可以现在本地安装 helm 客户端:

https://github.com/helm/helm/helm/releases

    (2)然后把 bitnami 的 repo 添加到本地

helm repo add bitnami https://charts.bitnami.com/bitnami

    (3) 通过helm安装etcd

 helm install my-release bitnami/etcd

    (4)通过客户端与serve交互

kubectl run my-release-etcd-client--restart='Never'--image docker.io/bitnami/etcd:3.5.0-debian-10-r94--env ROOT_PASSWORD=$(kubectlget secret--namespace default my-release-etcd -o jsonpath="[.data.etcd-root-password]"|base64--decode)--env ETCDCTL_ENDPOINTS="my-release-etcd.default.svc.cluster.local:2379"--namespace default--command---sleep infinity

  3、Kubernetes如何使用etcd

    etcd 是 Kubernetes 的后端存储。对于每一个 Kubernetes Object, 都有对应的 storage.go 负责对象的存储操作:pkg/registry/core/pod/storage/storage.go

    那么 API server 启动脚本中指定 etcd servers 集群的 storage.go 如下所示

spec:
  containers:
  - command:
      -kube-apiserver
      ---advertise-address=192.16B.34.2
      ---enable-bootstrap-token-auth=true
     # 指定 ca
      ---etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
     # 指定 cert
      ---etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
     # 指定 key
      ---etcd-keyfile=/etc/kubernetes/pki/apiserverver-etcd-client.key
     # 指定 etcd 地址
      ---etcd-servers=https://127.0.0.1:2379  

      早期 API Server 对 etcd 做简单的 ping check,现在已经改为真实的 etcd api call

    Kubernets 对象在 etcd 中都有一个固定的存储路径:

ks exec -it etcd-cadminsh
ETCDCTL_API=3
alias ectl='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

ectl get --prefix--keys-only/
  /registry/namespaces/calico-apiserver
  /registry/networkpolicies/calico-apiserver/allow-apiserver
  /registry/operator.tigera.io/tigerastatuses/apiserver
  /registry/pods/calico-apiserver/calico-apiserver-77dffcdf-g2tcx
  /registry/pods/default/toolbox-68f79dd5f8-4664n

     可以看到 kubernetes 对象在 etcd 中存储的时候,是按照类型、名字、对象类型、对象名组装成了 key。

  4、etcd 在集群中的位置:

        

  5、堆叠式 etcd 集群的高可用拓扑。

    在 kubernetes 中如何构架高可用的 etcd 集群,有几种方式,一种是堆叠 etcd 集群,另外一种是外部 etcd 集群的高可用。

    (1)堆叠 etcd 集群

      这种拓扑将相同节点上的控制平面和etcd成员耦合在一起。优点在于建立起来非常容易,并且对副本的管理也更容易。

      但是,堆叠式存在耦合失败的风险。如果一个节点发生故障,则etcd成员和控制平面实例都会丢失,并且集群冗余也会受到损害。可以通过添加更多控制平面节点来减轻这种风险。因此为实现集群高可用应该至少运行三个堆叠的Master节点。

        

    (2)外部etcd集群的高可用拓扑

      该拓扑将控制平面和 etcd 成员解耦,如果丢失一个 Master 节点,对 etcd 成员的影响较小,并且不会像堆叠式拓扑那样对集群冗余产生太大影响。

      但是,此拓扑所需的主机数量是堆叠式拓扑的两倍。具有此拓扑的群集至少需要三个主机用于控制平面节点,三个主机用于 etcd 集群。

        

  6、实践∶etcd集群高可用

    (1)多少个peer最适合?

      1个、3个还是 5个?这个要看实际的需要,根据性能、高可用、成本多方面考虑,保证高可用是首要目标。

    (2)peer多了是否能提升集群并读操作的并发能力?

        apiserver的配置只连本地的etcd peer。

        apiserver的配置指定所有etcd peers,但只有当前连接的etcd member异常,apiserver才会换目标。

        同时对于写操作,由于需要同步更多的节点,因此横向扩容反而会影响性能。

    (3)需要动态flex up(动态扩缩容)吗?

        存储系统的动态扩缩容目前来说还是比较困难的。

    (4)保证apiserver和etcd之间的高效性通讯

      apiserver 和 etcd 部署在同一节点

      apiserver 和 etcd 之间的通讯基于gRPC,Grpc 是基于 HTTP/2 进行通讯的,针对每一个 object,apiserver 和 etcd 之间的 Connection→stream 共享

        HTTP/2的特性:

          Stream quota

          带来的问题?对于大规模集群,会造成链路阻塞

          10000个pod,一次 list 操作需要返回的数据可能超过100M

k get pod--all-namespaces|wc-l 8520
k get pod-oyaml--all-namespaces>pods Ls-l pods -rw-r--r--1 root root75339736 Apr 5 03:13 pods

    (5)etcd存储规划:本地vs远程

      Remote Storage:优势是假设永远可用,劣势是IO效率

      最佳实践∶LocalSSD。利用 localvolume 分配空间。

      多少空间:与集群规模相关

     (6)安全性:

      peer和peer之间的通讯加密,绝对数情况下,都需要使用 TLS 进行加密。

      Kubernetes提供了针对secret的加密:https://kubernetes.io/docs/tasks/<administeKclustTi7encrvDt-data/

    (7)事件分离:

      在 kubernetes 中大部分的对象都是用来做声明的,但是有些对象是纯粹用来做审计的,比如说 event,用来了解这个对象发生过怎样的事情,其特性是每个控制器都会建大量的 event,并且其不是那么重要,即使丢失也不会对集群造成影响,因此允许在kubernetes 中定义 etcd server 的 overrides,允许再起一个 etcd 的 instance,然后把其中的一部分对象转到新的 etcd 里面。

      这样做的好处是如果存不下了,可以分一些出去;还有就是 主要 etcd 的写和 event etcd 的写分开,减少 event 对于集群的影响。

      API server 启动脚本中指定 etcd servers 集群

/usr/local/bin/kube-apiserver --etcd_servers=https://localhost:4001 --etcd- cafile=/etc/ssl/kubernetes/ca.crt--storage-backend=etcd3 --etcd-servers- overrides=/events#https://localhost:4002

  7、减少网络延迟:

    数据中心内的RTT大概是数毫秒,国内的典型RTT约为50ms,两大洲之间的RTT可能慢至 400mso因此建议etcd集群尽量同地域部署。

    当客户端到Leader的并发连接数量过多,可能会导致其他Follower节点发往Leader的 请求因为网络拥塞而被延迟处理。在Follower节点上,可能会看到这样的错误:dropped MsgProp to 247ae21ff9436b2d since streamMsg's sending buffer is full

    可以在节点上通过流量控制工具 (Traffic Control) 提高 etcd 成员之间发送数据的优先级 来避免。

  8、减少磁盘I/O延迟:

    对于磁盘延迟,典型的旋转磁盘写延迟约为10毫秒。对于SSD (Solid State Drives,固态硬盘), 延迟通常低于1毫秒。HDD (Hard Disk Drive,硬盘驱动器)或者网盘在大量数据读写操作的情况下延时会不稳定。因此强烈建议使用SSD。

    同时为了降低其他应用程序的 I/O 操作对 etcd 的干扰,建议将 etcd 的数据存放在单独的磁盘内。 也可以将不同类型的对象存储在不同的若干个 etcd 集群中,比如将频繁变更的 event 对象从主 etcd 集群中分离出来,以保证主集群的高性能。在 APIServer 处这是可以通过参数配置的。这些 etcd 集群最好也分别能有一块单独的存储磁盘。

    如果不可避免地,etcd 和其他的业务共享存储磁盘,那么就需要通过下面 ionice 命令对 etcd 服务设置更高的磁盘 I/O 优先级,尽可能避免其他进程的影响。

$ ionice -c2 -n0 -p 'pgrep etcd'

  9、保持合理的日志文件大小:

    etcd 以日志的形式保存数据,无论是数据创建还是修改,它都将操作追加到日志文件,因此日志文件大小会随着数据修改次数而线性增长。

    当 Kubernetes 集群规模较大时,其对 etcd 集群中的数据更改也会很频繁,集群日记文件会迅速增长。

    为了有效降低日志文件大小,etcd 会以固定周期创建快照保存系统的当前状态,并移除旧日志文件。另外当修改次数累积到一定的数量(默认是10000,通过参数"一snapshot-count"指 定),etcd 也会创建快照文件。

    如果 etcd 的内存使用和磁盘使用过高,可以先分析是否数据写入频度过大导致快照频度过高,确认后可通过调低快照触发的阈值来降低其对内存和磁盘的使用。

  10、设置合理的存储配额:

    存储空间的配额用于控制etcd数据空间的大小。

    合理的存储配额可保证集群操作的可靠性。

    如果没有存储配额,也就是etcd可以利用整个磁盘空间,etcd的性能会因为存储空间的持续增长 而严重下降,甚至有耗完集群磁盘空间导致不可预测集群行为的风险。

    如果设置的存储配额太小,一旦其中一个节点的后台数据库的存储空间超出了存储配额,etcd就会 触发集群范围的当警,并将集群置于只接受读和删除请求的维护模式。

    只有在释放足够的空间、消除后端数据库的碎片和清除存储配额告警之后/集群才能恢复正常操作。

  11、自动压缩历史版本:

    etcd会为每个键都保存了历史版本。

    为了避免出现性能问题或存储空间消耗完导致写不进去的问题,这些历史版本需要进行周期性地压缩。

    压缩历史版本就是丢弃该键给定版本之前的所有信息,节省出来的空间可以用于后续的写操作。

    etcd支持自动压缩历史版本。在启动参数中指定参数"一auto-compaction",其值以小时为单位。也就是etcd会自动压缩该值设置的时间窗口之前的历史版本。

  12、定期消除碎片化:

    压缩历史版本,相当于离散地抹去etcd存储空间某些数据,etcd存储空间中将会出现碎片。这些碎片无法被后台存储使用,却仍占据节点的存储空间。因此定期消除存储碎片,将释放碎片化 的存储空间,重新调整整个存储空间。

  13、优化运行参数:

    当网络延迟和磁盘延迟固定的情况下,可以优化 etcd 运行参数来提升集群的工作效率。

    etcd 基于Raft 协议进行 Leader 选举,当 Leader 选定以后才能开始数据读写操作,因此频繁的 Leader 选举会导致数据读写性能显著降低。

    可以通过调整心跳周期 (Heatbeat Interval) 和选举超时时间 (Election Timeout),来降低 Leader 选举的可能性。

    心跳周期是控制 Leader 以何种频度向 Follower 发起心跳通知。

    心跳通知除表明 Leader 活跃状态之外,还带有待写入数据信息, Follower 依据心跳信息进行数据写入,默认心跳周期是100ms。

    选举超时时间定义了当 Follower 多久没有收到 Leade r心跳,则重新发起选举,该参数的默认设置是 1000ms

    如果 etcd 集群的不同实例部署在延迟较低的相同数据中心,通常使用默认配置即可。

    如果不同实例部署在多数据中心或者网络延迟较高的集群环境,则需要对心跳周期和选举超时时间进行调整。

    建议心跳周期参数推荐设置为接近 etcd 多个成员之间平均数据往返周期的最大值,一般是平均 RTT 的 0.55-1.5 倍。

    如果心跳周期设置得过低,etcd 会发送很多不必要的心跳信息,从而增加 CPU 和网络的负担。 如果设置得过高,则会导致选举频繁超困。选举超时时间也需要根据 etcd 成员之间的平均 RTT 时间来设置。选举超时时间最少设置为 etcd 成员之间 RTT 时间的10倍,以便对网络波动。

    心跳间隔和选举超时时间的值必须对同一个 etcd 集群的所有节点都生效,如果各个节点配置不同, 就会导致集群成员之间协商结果不可预知而不稳定。

  14、etcd备份存储:

    etcd 的默认工作目录下会生成两个子目录:wal 和 snap。

    wal 是用于存放预写式日志,其最大的作用是记录整个数据变化的全部历程。所有数据的修改在提交前,都要先写入wal中。

    snap 是用于存放快照数据。为防止 wal 文件过多,etcd 会定期(当 wal 中数据超过10000条记录时,由参数 “一snapshot-count” 设置)创建快照。当快照生成后, wal中数据就可以被删除

    如果数据遭到破坏或错误修改需要回滚到之前某个状态时,方法就有两个:一是从快照中恢复数据主体,但是未被写入快照的数据会丢失;二是执行所有wal中记录的修改操作,从最原始的数据恢复到数据损坏之前的状态,但恢复的时间较长。

  15、备份方案实践:

    官方推荐 etcd 集群的备份方式是定期创建快照。

    根据集群对 etcd 备份粒度的要求,可适当调节备份的周期。在生产环境中实测,拍摄快照通常会影响集群当时的性能,因此不建议频繁创建快照。

    但是备份周期太长,就可能导致大量数据的丢失。

    这里可以使用增量备份的方式,每30分钟做一次 snapshot,但是使用 sidecar 的方式在每一个 peer 上运行一个备份客户端,用来 watch 所有的对象,一旦有对象发生变更,就做一次记录,每10秒将这些变更事件写回磁盘一次。

        

六、etcd常见问题

  1、数据备份:

  (1)备份方案:

    etcd备份:备份完整的集群信息,灾难恢复。

    现有工具:etcdctl snapshot save。好处是可以一次性的把整个 etcd dump 出来,但是会锁硬盘空间导致磁盘暴涨,但是还有其他的方法,比如 watch kubernetes 的 event,或者 watch etcd 的 event,通过这种方式,是可以把集群的所有细节的变动都拉下来。

    之前社区有一些备份方案,就是直接 watch API Server 来 watch Kubernetes Event,当需要做数据恢复时,去做一个回放,这种恢复对于 subresource(比如 pod)只能拿下来 spce,但是不能拿下来 status,所以会有一些限制。

    因此备份方案一般都会使用 snapshot + 一些 event 事件的方式进行备份,如果恢复的话进行回放即可。

    备份 Kubernetes event。

  (2)频度:

    时间间隔太长的话,能否接受 user data lost,如果有外部资源配置,如负载均衡等,能否接受数据丢失导致的leak

    时间间隔太短的话,对 etcd 的压力又会很大,因为 etcd 做snapshot的时候,etcd会锁住当前数据,并发的写操作需要开辟新的空间进行增量写,导致磁盘空间增长。

  (3)如何保证备份的时效性,同时防止磁盘爆掉:

    使用 Auto defrag 的命令,实际上就是把 defrag 变成一个自动执行的命令,让他自动过一段时间去清理一次磁盘

  (4)增强版backup方案

    目前生产上就会使用以下方案,该方案是在 operator 上做的优化。

    首先当我们启动一个 etcd backup 时,其每 30分钟会生成一个 snapshot,并进行存储,但是这会造成最多30分钟的数据丢失,因此在 backup 的 sidecar 中添加了一个事件监听,每一分钟或每十秒将监听的事件存储到 remote volume 中,当需要做 restore 时,首先从 snapshot 中进行数据恢复,然后再通过 etcdRestoreResource 对 snapshot 后的数据进行回放。

        

   2、安全性:

    peer和peer之间的通讯使用 TLS 进行加密通讯。

    etcd数据加密:

      kubernetes 的任何对象在数据落盘的时候,本身是可以通过非对称加密的形式保存的,这样即使通过 etcd 进行数据恢复时,这里面的数据也是加密数据。

      有哪些对象可以加密,可以在 Encryptionconfiguration 中定义,如下面的代码定义了对 secrets 进行加密,通常也是对 secrets 进行加密,因为其存储了用户的密码、cert.token 等信息,其是需要安全保证的,即 resources 里面定义 secrets;而 providers 中定义使用什么样的加密算法来保存,

    参考链接:https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/

apiVersion: API Server.config.k8s.io/v1
kind: Encryptionconfiguration
resources:
  - resources:
    - secrets
    providers:
    - identity:{}
    - aesgcm:
        keys :
          - name: key1
            secret: c2VjcmV0IGlzIHNlY3VyZQ==
          - name: key2
            secret: dGhpcyBpcyBwYXNzd29yZA==
    - aescbc:
        keys:
        - name: key1
        - secret: c2VjcmV0IGlzIHNlY3VyZQ==
    - secretbox:
        keys:
        - name: key1
        - secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
    - kms:
        name : myKmsPlugin
        endpoint: unix:///tmp/socketfile.sock
        cachesize: 100

  3、查询 APIServer

-- 返回某namespace中的所有Pod
GET /api/vl /namespaces/test/pods
---
200 OK

Content-Type: application/json
{
  "kind": "PodList",
  "apiVersion": "vl",
  "metadata": ("resourceversion":"!0245"},
  "items": [...]
}

  从12345版本开始,监听所有对象变化,可以在 get 请求时加一个 watch,表示对该对象的监听,同时还可以加一个 resourceVersion,表示从哪个版本开始监听。

GET /api/vl /namespaces/test/pods?watch=l&resourceVersion=10245
---
200 OK

Transfer-Encoding: chunked
Content-Type: application/json
  {
    "type": "ADDED",
    "object": { "kind": "Pod", "apiVersion": "vl", "metadata": { "resourceversion": "10596", ... },... } )
  },
  {
  "type": "MODIFIED"
  "object": {"kind": "Pod", "apiVersion": "vl", "metadata": {"resourceversion": "11020",...}
  }
  ...

  分页查询:当我们获取对象列表时,有可能获取的对象太多,因此可以使用 limit 进行分页查询,其查询后会返回一个 continue 的 token,下一次查询带上这个 continue 的 token,就可以做翻页。

GET /api/vl /pods?limit=500
200 OK
Content-Type: application/json
  {
    "kind": "PodList",
    "apiVersion": "vl",
    "metadata": {
      "nresourceVersion": "10245",
      "continue": "ENCODED_CONTINUE_TOKEN",
    },
    "items": [ ... ] // returns pods 1-500
  }


GET /api/vl/pods?limit=500&continue=ENCODED_CONTINUE_TOKEN
200 OK
Content-Type: application/json
  {
  "kind": "PodList",
  "apiVersion": "vl",
  "metadata": {
    "resourceVersion": "10245",
    "continue": "ENCODED_CONTINUE_TOKEN_2",
  },
  "items": [...] // returns pods 501-1000
  }

  4、Resourceversion

    单个对象的 resourceversion:对象的最后修改resourceversion

    List 对象的 resourceversion:生成 list response 时的 resourceversion

    List行为:List对象时,如果不加resourceversion,意味着需要Most Recent数据/请求会击穿APIServer 缓存,直接发送至etcd;APIServer通过Label过滤对象查询时,过滤动作是在APIServer端, APIServer需要向etcd发起 全量查询请求

        

  5、遭遇到的陷阱(简单几个例子)

    频繁的 leader election

    etcd分裂:如果使用 remote volume 的话,可能会有一个 member 永远跟不上,跟不上就会被分裂出去,这也是被证明使用 remote volume 行不通的。

    etcd不响应

    与apierver之间的链路阻塞:因为 demonset 一直 list pod,导致链路阻塞,进而导致节点状态报不上来。

    磁盘暴涨

    (1)少数etcd成员Down:

      两个 etcd 没有响应,当时的 apiserver 的 heathcheck 只看 etcd 里面 2379 的端口是否正常,此时端口正常通,但是数据已经不响应了,但是 apiserver 的健康检查是正常的,那么 apiserver 和 etcd 的长连接就还在。

      apiserver 是挂了负载均衡的,有 40% 的 node 节点是挂在这两个 apiserver 上的,那么他们的状态上报就报不上去,是因为状态上报的时候是通过 apiserver 上报的,但是 apiserver 的数据写不到 etcd,这就会导致这些 node 节点掉线,然后 controller manager 会将在这两个 node 节点上的 pod 全部驱逐掉,这个影响就非常大。

      

  (2)Master 节点出现网络分区:

    其实和上面很相近,主要是出现了网络分区

        

     对于上面的这两种情况,可以做一些调整,实际上社区也对这些情况做了解决方案,就是在健康检查时,不仅要看端口是否正常,还要看是否真的可以操作数据,如果健康检查失败,就把 apiserver 和 etcd 的长连接断掉。

 

posted @ 2022-11-27 14:58  李聪龙  阅读(522)  评论(0编辑  收藏  举报