etcd问题发散-灾备恢复

背景

线下有一套kubernetes集群,用于各种测试,某次在创建了一堆开源服务后,发现太乱了,于是想通过之前的某个版本的快照进行恢复,在快照恢复之后出现了比较奇怪的现象,同样一个kubectl get $resource的命令后,出现的返回均不一样,比如这样:

根据现象来看,像是kubectl 调用api-server 的时候出现了数据不一致的情况,由于这个环境的服务均是集群化/HA 部署的,所以根据这个现象,猜测可能存在以下两种情况:

  • api-server 的informer机制存在问题,导致了两台api-server之间获取的数据不一致。
  • etcd的集群出现了脑裂,即数据不一致,导致了读取到的数据不一致。

由于在固有印象里,etcd是基于raft算法进行强一致校验的,理论上不可能存在脑裂,所以先从api-server开始查,但是在将api-server 的高可用节点逐台关闭后,发现在api-server 单点工作的时候,依然会出现上述情况,所以问题只能是,etcd脑裂了。。。

判断故障点

假设我们发现了etcd出现了脑裂,那么该如何定位是哪个节点的数据不一致呢?如果在数据量较大的情况下,可以根据数据大小进行比对(我的环境数据量较小,从DB SIZE这看不出来)

[root@shcxgvm050 ~]# etcdctl endpoint status -w table
+---------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
|         ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |
+---------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
| https://10.184.4.238:2379 | 87dd5c835d9ec424 |  3.4.10 |  838 MB |     false |      false |        48 |    2006274 |            2006274 |        |
| https://10.184.4.239:2379 | 87155934c1e2784c |  3.4.10 |  838 MB |      true |      false |        48 |    2006274 |            2006274 |        |
| https://10.184.4.240:2379 | f90da8229f4e3c0f |  3.4.10 |  838 MB |     false |      false |        48 |    2006274 |            2006274 |        |
+---------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+

假设可能脑裂的数据量较小,从DB SIZE 层面看不出来,那么可以基于数据本身来进行查看,kubernetes访问etcd的时候,所有的读操作访问的都是follower,只有写操作会经过leader,那么可以分别访问每个etcd的endpoint,查询相同的数据,找到follower里哪个和leader的数据不一致。

[root@shcxgvm050 ~]# \etcdctl --endpoints=https://10.184.4.238:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/kubernetes/ssl/etcd.pem  --key=/etc/kubernetes/ssl/etcd-key.pem get /registry/leases/kube-node-lease --prefix -w=json | jq .
{
  "header": {
    "cluster_id": 16582195919756743000,
    "member_id": 7227708511869891000,
    "revision": 1240542,
    "raft_term": 36
  },
  "kvs": [
    {
      "key": "L3JlZ2lzdHJ5L2xlYXNlcy9rdWJlLW5vZGUtbGVhc2UvMTAuMTg0LjQuMjM4",
      "create_revision": 1240282,
      "mod_revision": 1240410,
      "version": 2,
      "value": "azhzAAofChZjb29yZGluYXRpb24uazhzLmlvL3YxEgVMZWFzZRK6AwqXAwoMMTAuMTg0LjQuMjM4EgAaD2t1YmUtbm9kZS1sZWFzZSIAKiQyMjdhMmRhZi1mY2ViLTRiNTgtYTc3NC1kMTM2OThiYWY0YjkyADgAQggI6KbgoAYQAGo+CgROb2RlGgwxMC4xODQuNC4yMzgiJDIwZmFkYmQ1LTQyNmEtNGI4My1hZDgzLWI5NzExN2JmMGYzYioCdjF6AIoB+gEKB2t1YmVsZXQSBlVwZGF0ZRoWY29vcmRpbmF0aW9uLms4cy5pby92MSIICOim4KAGEAAyCEZpZWxkc1YxOrgBCrUBeyJmOm1ldGFkYXRhIjp7ImY6b3duZXJSZWZlcmVuY2VzIjp7Ii4iOnt9LCJrOntcInVpZFwiOlwiMjBmYWRiZDUtNDI2YS00YjgzLWFkODMtYjk3MTE3YmYwZjNiXCJ9Ijp7fX19LCJmOnNwZWMiOnsiZjpob2xkZXJJZGVudGl0eSI6e30sImY6bGVhc2VEdXJhdGlvblNlY29uZHMiOnt9LCJmOnJlbmV3VGltZSI6e319fUIAEh4KDDEwLjE4NC40LjIzOBAoIgwIvKngoAYQ5tXGggMaACIA"
    },
    {
      "key": "L3JlZ2lzdHJ5L2xlYXNlcy9rdWJlLW5vZGUtbGVhc2UvMTAuMTg0LjQuMjM5",
      "create_revision": 170697,
      "mod_revision": 1237244,
      "version": 65537,
      "value": "azhzAAofChZjb29yZGluYXRpb24uazhzLmlvL3YxEgVMZWFzZRK6AwqXAwoMMTAuMTg0LjQuMjM5EgAaD2t1YmUtbm9kZS1sZWFzZSIAKiRjZGYzMDg2Mi05NzY0LTRkZTEtYWQyMy0xZGNkMjk3ODhlMGQyADgAQggIoNCSnwYQAGo+CgROb2RlGgwxMC4xODQuNC4yMzkiJDg3NGJkYWFlLTM0MjMtNGE3Zi1iNTYwLTA4YjFjZDcxMzYyZioCdjF6AIoB+gEKB2t1YmVsZXQSBlVwZGF0ZRoWY29vcmRpbmF0aW9uLms4cy5pby92MSIICKDQkp8GEAAyCEZpZWxkc1YxOrgBCrUBeyJmOm1ldGFkYXRhIjp7ImY6b3duZXJSZWZlcmVuY2VzIjp7Ii4iOnt9LCJrOntcInVpZFwiOlwiODc0YmRhYWUtMzQyMy00YTdmLWI1NjAtMDhiMWNkNzEzNjJmXCJ9Ijp7fX19LCJmOnNwZWMiOnsiZjpob2xkZXJJZGVudGl0eSI6e30sImY6bGVhc2VEdXJhdGlvblNlY29uZHMiOnt9LCJmOnJlbmV3VGltZSI6e319fUIAEh4KDDEwLjE4NC40LjIzORAoIgwIqcG7nwYQop+0xwMaACIA"
    },
    {
      "key": "L3JlZ2lzdHJ5L2xlYXNlcy9rdWJlLW5vZGUtbGVhc2UvMTAuMTg0LjQuMjQw",
      "create_revision": 170206,
      "mod_revision": 1237247,
      "version": 65587,
      "value": "azhzAAofChZjb29yZGluYXRpb24uazhzLmlvL3YxEgVMZWFzZRK6AwqXAwoMMTAuMTg0LjQuMjQwEgAaD2t1YmUtbm9kZS1sZWFzZSIAKiQ2MTQxMTdjMC03ZGVlLTQxOGUtYjc1Zi00Y2RlZjkyOWQ5NzAyADgAQggIyM2SnwYQAGo+CgROb2RlGgwxMC4xODQuNC4yNDAiJDQ2Zjk0MTU5LTY2ZjEtNDg2My1hMjI4LWM0OTc4NjM0Zjc0ZSoCdjF6AIoB+gEKB2t1YmVsZXQSBlVwZGF0ZRoWY29vcmRpbmF0aW9uLms4cy5pby92MSIICMjNkp8GEAAyCEZpZWxkc1YxOrgBCrUBeyJmOm1ldGFkYXRhIjp7ImY6b3duZXJSZWZlcmVuY2VzIjp7Ii4iOnt9LCJrOntcInVpZFwiOlwiNDZmOTQxNTktNjZmMS00ODYzLWEyMjgtYzQ5Nzg2MzRmNzRlXCJ9Ijp7fX19LCJmOnNwZWMiOnsiZjpob2xkZXJJZGVudGl0eSI6e30sImY6bGVhc2VEdXJhdGlvblNlY29uZHMiOnt9LCJmOnJlbmV3VGltZSI6e319fUIAEh4KDDEwLjE4NC40LjI0MBAoIgwIscG7nwYQ6PnOkAIaACIA"
    }
  ],
  "count": 3
}

我观察的是其中的mod_revisionmod_revision作用域为 key, 等于修改这个 key 时集群的 Revision, 只要这个 key 更新都会自增,也就是说,可以从这个字段判断value是否和其他endpoint一致,所以,从这就能找到,有问题的节点是etcd-01。

恢复方法

方式1:删除故障member, 然后重新添加该节点

算是比较粗暴的修复方法,就是将当前集群的问题member 从集群内剔除,然后删除节点信息,然后将该节点重新加入到集群,依靠etcd 自身的同步机制,将数据同步。但粗暴不代表无效,我是基于这个方法进行修复的。具体操作步骤如下:

查看当前member 清单
  • 如果发现问题节点是leader节点,则需要先手动更换leader
etcdctl member list -w table 
+------------------+---------+--------+---------------------------+---------------------------+------------+
|        ID        | STATUS  |  NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
+------------------+---------+--------+---------------------------+---------------------------+------------+
| 87155934c1e2784c | started | etcd02 | https://10.184.4.239:2380 | https://10.184.4.239:2379 |      false |
| 87dd5c835d9ec424 | started | etcd01 | https://10.184.4.238:2380 | https://10.184.4.238:2379 |      false |
| f90da8229f4e3c0f | started | etcd03 | https://10.184.4.240:2380 | https://10.184.4.240:2379 |      false |
+------------------+---------+--------+---------------------------+---------------------------+------------+

# 将当前leader指向member 87155934c1e2784c
etcdctl move-leader 87155934c1e2784c --endpoints 127.0.0.1:2379

删除异常member节点
# 删除member时需要注意,etcd的服务不要停止
etcdctl member remove 87dd5c835d9ec424
  • 当前整个etcd 的cluster内只存在了2个member, 此时要注意,2个节点的etcd是存在脑裂风险的,所以在这段时间内,需要尽量避免对etcd进行写操作。
etcdctl member list -w table 
+------------------+---------+--------+---------------------------+---------------------------+------------+
|        ID        | STATUS  |  NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
+------------------+---------+--------+---------------------------+---------------------------+------------+
| 87155934c1e2784c | started | etcd02 | https://10.184.4.239:2380 | https://10.184.4.239:2379 |      false |
| f90da8229f4e3c0f | started | etcd03 | https://10.184.4.240:2380 | https://10.184.4.240:2379 |      false |
+------------------+---------+--------+---------------------------+---------------------------+------------+

删除故障member的数据

由于剔除的节点是etcd01, 此后需要将etcd01作为新节点重新加入到集群内,所以需要删除etcd01内的数据,如有需要,可以提前进行快照备份。

# 根据配置文件找到存放wal和snapshot的目录,删除里面的member 目录
systemctl stop etcd
rm -rf /opt/etcd/member

重新加入节点

将该节点重新加入到集群内,加入后状态为unstart

etcdctl member add etcd01 --peer-urls="https://10.184.4.238:2380"
etcdctl member list -w table 
+------------------+---------+--------+---------------------------+---------------------------+------------+
|        ID        | STATUS  |  NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
+------------------+---------+--------+---------------------------+---------------------------+------------+
| 87155934c1e2784c | started | etcd02 | https://10.184.4.239:2380 | https://10.184.4.239:2379 |      false |
| 87dd5c835d9ec535 | unstart | etcd01 | https://10.184.4.238:2380 | https://10.184.4.238:2379 |      false |
| f90da8229f4e3c0f | started | etcd03 | https://10.184.4.240:2380 | https://10.184.4.240:2379 |      false |
+------------------+---------+--------+---------------------------+---------------------------+------------+

修改"新节点"的启动配置文件
# 将etcd启动配置文件内的--initial-cluster-state=new修改为--initial-cluster-state=existing
--initial-cluster-state=existing
systemctl start etcd
  • 查看服务,发现节点正常加入随后检查之前冲突点,确保数据一致
etcdctl member list -w table 
+------------------+---------+--------+---------------------------+---------------------------+------------+
|        ID        | STATUS  |  NAME  |        PEER ADDRS         |       CLIENT ADDRS        | IS LEARNER |
+------------------+---------+--------+---------------------------+---------------------------+------------+
| 87155934c1e2784c | started | etcd02 | https://10.184.4.239:2380 | https://10.184.4.239:2379 |      false |
| 87dd5c835d9ec535 | started | etcd01 | https://10.184.4.238:2380 | https://10.184.4.238:2379 |      false |
| f90da8229f4e3c0f | started | etcd03 | https://10.184.4.240:2380 | https://10.184.4.240:2379 |      false |
+------------------+---------+--------+---------------------------+---------------------------+------------+

方式2:数据恢复

方式1的方法需要重新新增节点,如果存在数据量较大,可以采用备份恢复的方式。

从集群的其他节点获取当前快照
# endpoint指向目前正常工作的节点
etcdctl --endpoints=https://10.184.4.240:2380 snapshot save snapshot.db

在故障节点将数据恢复
# 删除故障节点的数据目录,并恢复数据
rm -rf /opt/etcd
etcdctl snapshot restore snapshot.db --name etcd01 --initial-cluster etcd01=https://10.184.4.238:2379,etcd02=https://10.184.4.239:2379,etcd03=https://10.184.4.240:2379  --initial-cluster-token etcd-cluster --initial-advertise-peer-urls https://10.184.4.238:2380

修改配置,并重启etcd
# 将etcd启动配置文件内的--initial-cluster-state=new修改为--initial-cluster-state=existing
--initial-cluster-state=existing
systemctl start etcd

相比方法二来说,方法一的步骤比较简单,但实际操作的时候,可能会存在做完快照的同时有用户数据写入的情况,从而再一次导致Revision不一致,此外,之前提到奇数节点一旦发生故障,会将集群变成偶数节点,从而在短期内产生脑裂的风险,所以在发现故障后,要赶快进行修复。

发散

etcd为啥会数据不一致?

理论上etcd是强一致性的,但其实只能说raft算法是强一致性的,在算法的应用过程中也可能存在一定的bug, 具体bug原理可以参考etcd3Bug,这边简单阐述下可能触发bug的几个操作。

  • etcd开启了鉴权
  • 修改了etcd的核心配置,如压缩,心跳包等配置后发生了重启等

选举方法?

初始启动时,节点处于 follower 状态并被设定一个 election timeout,如果在这一时间周期内没有收到来自leader 的 heartbeat,节点将发起选举∶将自己切换为candidate 之后,向集群中其它 follower 节点发送请求,询问其是否选举自己成为Leader。
当收到来自集群中过半数节点的接受投票后,节点即成为 leader,开始接收保存client 的数据并向其它的 follower 节点同步日志。
如果没有达成一致,则candidate 随机选择一个等待间隔(150ms~300ms)再次发起投票,得到集群中半数以上follower 接受的 candidate 将成为 leader
换句话说,就是leader会定时对所有follower发送心跳包来确定自己的leader地址,这也导致了leader就重启之后,是有概率重新做leader的。

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值