Etcd 常见问题以及解决办法

etcd备份存储


它主要通过snapshot去备份的,snapshot是不能很频繁去做的,因为做snapshot的时候要去锁住磁盘空间,然后做一个快照,这个时候做的任何修改都要开辟新的空间来写,也就是snapshot频繁的时候,它就快速的让你磁盘占用的空间变的非常的大。

有了wal log,那么wal log存放的是增量数据的写入日志,它们两个结合到一起就是全局。

备份方案实践


上面是社区方案里面做了增强, 比如说在每个etcd member里面,会有一个备份的客户端,是一个sidecar形式,这个sidecar会去watch etcd leader,它每30分钟去做一次snapshot,这样snapshot的频度没有那么大,30分钟的数据备份一定是不足够的,因为极端的情况下可能是29.5秒的数据全部没有了,那么怎么减少snapshot频度又可以保证数据的时效性。

在这基础之上先做snapshot,然后去监听从快照结束reversion开始监听etcd事件,也就是通过etcd的客户端去做watch,watch所有对象,所有对象修改的信息,我们都可以通过每10s一次把这些watch到的event写入到磁盘,也就是通过snapshot的方式加上event这种方式去做增强备份。

30分钟snapshot+10s一次的event,这个event是实时去监听的,每10s写入一次,这个就是增强备份方案。

增强版backup方案


当谈论etcd备份的时候,来看看现有工具是什么。那是etcdctl snapshot save,它的好处是一次性的将整个etcd dump出来,效率比较高,但是它有它的问题,比如它锁住硬盘空间导致磁盘空间暴涨,其实还有其他方法,比如watch k8s,etcd event,这样可以把集群所有的细节变动都拉下来,

对于etcd来说我们是关心所有etcd写入的event,从这个层面就可以将集群的所有变更都记录下来。

那么就可以snapshot加上一些事件驱动,去关注事件流,将这些日志存下来再回放那个的这样维护方案。

备份时间太长数据可能丢失的越多,越短导致etcd的压力大,备份的时候,时效性上来之后,它的硬盘空间磁盘碎片就多了,因为它会snapshot会锁住磁盘,那能不能做一些auto defrag,就将defrag也做成一个自动执行的命令,让它自动过段时间去清理一次磁盘。

用户在建立etcd backup对象的时候,那么backup-operator就会每隔30分钟往remote storage写一个snapshot,但是30分钟的频次是不够的,我们会导致最多30分钟的数据丢失,这是不可接受的,那我们要在原来的基础上做个增强,我们在backup的sidecar里面去写一个etcd的watch client,它去监听snapshot后面所有的时间,其实就是wal log,它其实就是监听snapshot之后的reversion所有的变更,那么每1分钟或者每10s将这种增量信息也存放到remote volume,也存放到远端存储。watch结果是什么?(比如这边更新对象,那边就会有一个put,put k=value1,value2,它就会有一个一个这样的event出来)

当去做数据的restore的时候,我先去snapshot里面拉取30分钟的频度快照,然后接下来我去remote volume里面拉取这一堆时间的增量,在原来快照的基础之上,再将这些增量replay出来,最后实现了对整个etcd的备份和恢复,即减小了snapshot的频次,又保证了数据的时效性。

安全性


数据加密


任何k8s对象在落盘的时候,本身是可以通过非对称加密的形式保存的,这样即使通过etcd这一端去做数据恢复,那么这里面的数据也是一个加密数据,你是看不到的。

有哪些对象需要加密可以通过EncryptionConfiguration里面配置,通常就是secret,因为secret往往存放着用户的密码 证书 token这些信息,它是需要安全保证的,providers这里就会提供加密算法来保存,都是非对称加密算法,这些数据一旦保存,你没有这些key是解不开的,这样就保证的数据的安全性。

查询apiserver


数据存放在etcd里面了,通过什么样的方式在apiserver这边查询呢?比如说要查询test/namespace里面所有的pod,前面api是它的前缀,v1就是group version,pod属于core group,所以group信息不需要体现,所以这里只写了一个v1。

然后查询test namespace里面所有的pod就是一个rest调用,这个rest调用最后会被转化为etcd调用。

基本上后缀都是一致的。

etcd里面有reversion的概念,任何的对象都有一个版本信息,这个reversion信息在k8s里面它表现的行为为resource version,无论是单个的对象还是list,在去get 对象的时候可以加上watch参数,不仅仅要get还要保证长连接去watch,同时可以去指定resource version,这样就是从之前某个版本来watch,最后转化为etcd里面的请求就是加上reversion信息,这样的话你正真的watch是这个对象从你指定的reversion到当前reversion的所有变化的信息,只要没有compact,那么这些历史信息就存着,就可以返回给你。

 分页查询


很多时候etcd里面存储的对象是非常多的,如果任何的查询都要将全量返回回来,你可想而知开销有多大,我们希望在做操作的时候返回output的量,在查询数据库的时候不能全量返回,要做分页查询。

那么我们去get某个对象的时候,kubelet其实默认加了一个限制,叫做limit,limit其实默认是查询500个,返回前500之后,返回的list里面会给你一个continue的token,k8s会按照这个token继续往下查下一部分。

所以做大数据查询的时候,基本上都是通过这种方式去做分页查询的,先返回500给你一个token,下次加上continue token,它就知道这次查询的时候是和上次连接起来的,它就会将下一批发送给你。

新的查询又会给你新的continue token,你带着新的continue token它就会给你返回1000-1500。

通过这样机制就做了分页查询的支持。

 ResourceVersion


resource version到底怎么使用,我们任何的对象都有resource version,可以看到为204,所以单个对象的resourceversion就是对象最后修改的时间,之前说过最后修改时间就是它的mode version,这个对象会被创建,会被修改,他的mode reversion就是当这个对象修改的时候这个etcd里面reversion的增长的值,注意不同的对象的reversion是公用同一个增长序列的,这个reversion是整个集群增长的,不同的对象它的mode reversion就是它最后修改一次的reversion,这个reversion就是当你查询对象它显示的resourceversion。

再来回顾乐观锁,当两个控制器要去修改一个对象的时候,那么这两个进程拿到的是当前这个对象的mode reversion,当一个进程要去修改它,这个请求先发过去了,修改对象resourceversion也就是mode reversion,和etcd里面是一样的,这个请求被接受并且被修改了,那么这个对象的mode reversion是发生变化了的,这个时候第二个请求再发过去,它基于老的modereversion,在这个请求被etcd处理的时候他就会发现你是基于老版本,我现在已经增长了,所以你这个请求是不合法的,它就给你返回409 conflict,这个时候客户端有责任去拉去新的版本,包括新的reversion,在那个基础之上重新做修改。

如果list对象的时候,不加resource version,这就意味着告诉apiserver说我不相信你的cache,你要把最新的数据还给我,就会导致请求穿透apiserver,直接到etcd,在写代码的时候一定要注意。

当使用label去做对象过滤查询的时候,这个过滤时在apiserver做的,etcd本身是没有过滤能力的,所以apiserver依然会将全量请求发送到etcd里面,发过来以后再apiserver这边做过滤。

上面是容易出问题的点。

频繁的leader election

通过网络存储来挂载etcd member,这样减少了备份的需求,就不需要备份了,因为最少有份数据保存在网盘,最后就会发现一个member永远跟不上,跟不上就会出现问题,分裂出去了。

Etcd与APIServer之间链路阻塞


daemonset不停的去list pod导致链路阻塞,导致节点状态汇报不上来

很多问题都是在早期,后面社区恢复掉,5个节点的etcd,把两个etcd搞的没有响应了,当时apiservre的healthy check,它只是看etcd里面健康的,也就是2379的端口,只要这个端口还通,它就认为它是活着的,但是实际上这两个节点已经503了,它已经不能正确的响应了,只有右边的3 4 5这三个节点是正常工作的,但是apiserver看到这些,但是apiserver看到端口是通的,它就认为它是活的,apiserver这边没有反应,apiserver健康,但是连接的etcd本身已经不正常工作,那么就会有40%节点挂载在这两个apiserver上面,因为负载均衡,它们的状态上报就会经过这两个apiserver,这样状态就报告不上去,对于每个节点的kubelet来说,它认为apiserver是活着的,所以他长连接继续保持,继续状态的上报,apisever接到请求之后,写数据写不进去,后面的etcd坏了,没有能力将请求转发到leader上面了,那么这些节点的状态上报就失败了,就会导致节点掉线,集群里面有controller manager的,它认为40%节点坏了,那么就将40%节点的pod驱逐,也即是集群40%pod受影响了,这是非常严重的问题。

这是伪故障,不是节点坏了,是控制面的组件坏了,那么kubelet误认为这些节点坏了就将pod迁走了,业务就会受到剧烈的影响。

完善的健康检查

即使API Server 采用多实例运行,也很难保证每一个实例都能一直正常地运行下去。因此,在负载均衡器处应该对每个实例进行健康状态检查。当某个实例处于不健康的状态时,负载均衡器应停止向这个实例分发请求。API Server 的健康状态检查是否准确将直接 影响到API Server 的高可用。如果出现误报,例如API Server 不能进行服务但状态却标记为健康,那么就会造成客户的某些请求不能及时响应。

API Server 是所有客户端访问 etcd 的入口,其生命周期应与 etcd 紧耦合。 API Server的早期版本对etcd 所做的健康检查只依赖 ping 命令。但是有这样的场景: etcd 处于假死状态,ping 的结果返回成功,但 etcd 的真实请求可能已经无法执行。 API Server 由于没有感知到etcd 的异常,所以会继续接收用户请求,但接收的请求无法被 etcd 处理,从而导致超时。
在后续的版本中,etcd 的健康检查被强化, API Server 的健康检查是基于 etcd 的真实 请求,如果 etcd 无法处理该请求,那么 API Server 会将自身状态置为 不健康 以避免继 续提供服务。
对于集群内部的客户端,应优先访问 API Server ClusterIP ,利用 kube-proxy 建立的
转发规则将流量送达 API Server 处。这样,当外部的负载均衡器出现问题时,不会影响集
群内部客户端访问 API Server ,也不会对集群内的客户端产生巨大影响。

 

 

 

Master节点出现网络分区


 网络分区,多的节点会有leader继续工作,两个分区都有leader,小的分区也就是老的leader它数据没有办法commit,因为得不到别人的确认,最后造成的就是左边就是无主的状态,左边的写入都是没有响应的,但是apiserver还是活着的,所以这些节点练到了apiserver,这又会导致连接到apiserver的这些节点掉线又掉线,因为状态汇报不上来,导致node lifecycle controller,那么就会将40%的pod清除掉,又会导致整个集群的动荡。

社区后面做了增强,也就是加了healthy-check,这个healthy check不仅仅要去看etcd的端口,他还要去真实的去看etcd是否存活着,如果etcd本身没有响应了,那么apiserver就自杀掉,这些节点就重新的连接到其他的apiserver上面,这样就能够恢复状态。

可以看到健康检查在微服务世界里面是多么的重要。一个应用如果你不配置好健康检查,那么可能引起灾难性的后果。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值