节点根目录被打满导致的ETCD憨批修复记录

背景

事情发生在 UAT 环境的其中一台 Controller 节点,节点根目录被打满,同时 etcd 数据没有落盘到独立的磁盘中,导致 etcd 憨批,节点出现 notready

etcd-error-log

修复过程

参考了各种网络资料,最终形成如下修复手段:

  1. 移除 statis pod yaml,从而停止坏掉的 etcd pod
  2. 通过 etcdctl member remove 移除坏掉的 etcd 实例
  3. 备份数据目录并移除
  4. 通过 etcdctl member add 添加新实例,记录 etcdctl 输出的配置信息
  5. 通过裸起容器的方式,启动 etcd 容器,启动需要用到的参数,参考 statis pod yaml 和第 4 步输出的配置信息
  6. 启动后会与 leader 进行数据的同步,可以通过 etcdctl endpoint status -w table 查看状态
  7. 如果同步成功则可以停止 etcd 容器,将 statis pod yaml 放回对应的目录中,集群修复

具体的操作命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# stop issue etcd pod
mv /etc/kubernetes/manifests/etcd.yaml .

# init etcdctl command envs
export endpoints="https://10.82.69.10:2379,https://10.82.69.11:2379,https://10.82.69.12:2379,https://10.82.69.19:2379,https://10.66.10.83:2379"
export cacert="/etc/kubernetes/pki/etcd/ca.crt"
export cert="/etc/kubernetes/pki/etcd/peer.crt"
export key="/etc/kubernetes/pki/etcd/peer.key"

# sample: e member list -w table
alias e="etcdctl --endpoints $endpoints --cacert $cacert --cert $cert --key $key"

# or use this one
eval $(kubectl get nodes -owide|grep -E "etcd|control-plane" |awk '{printf "https://"$6":2379,"}'|awk '{gsub(",$","");print "export ETCDCTL_ENDPOINTS=\""$1"\""}') && export ETCDCTL_CACERT=/etc/kubernetes/ssl/etcd/ca.crt && export ETCDCTL_CERT=/etc/kubernetes/ssl/etcd/peer.crt && export ETCDCTL_KEY=/etc/kubernetes/ssl/etcd/peer.key

# remove issue etcd member
etcdctl member remove $issue_etcd_id

# delete etcd data
rm -rf /var/lib/etcd/*

# member add
etcdctl member add wcn-gduvm-mwdcm1 --peer-urls=https://10.82.69.10:2380

# start a temporary etcd pod to restore
nerdctl run -d --name restore_etcd \
-v /etc/kubernetes/ssl/etcd:/etc/kubernetes/ssl/etcd \
-v /var/lib/etcd:/var/lib/etcd \
--network=host \
-e ETCD_NAME="wcn-gduvm-mwdcm1" \
-e ETCD_INITIAL_CLUSTER="wcn-gduvm-mwdcm2=https://10.82.69.11:2380,wcn-gduvm-mwdcm1=https://10.82.69.10:2380,wcn-gduvm-mwdcm3=https://10.82.69.12:2380" \
-e ETCD_INITIAL_ADVERTISE_PEER_URLS="https://10.82.69.10:2380" \
-e ETCD_INITIAL_CLUSTER_STATE="existing" \
--entrypoint=etcd 10.82.49.238/quay.io/coreos/etcd:v3.5.6 --advertise-client-urls=https://10.82.69.10:2379 --auto-compaction-retention=8 --cert-file=/etc/kubernetes/ssl/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/etcd --election-timeout=5000 --experimental-initial-corrupt-check=true --experimental-watch-progress-notify-interval=5s --heartbeat-interval=250 --key-file=/etc/kubernetes/ssl/etcd/server.key --listen-client-urls=https://127.0.0.1:2379,https://10.82.69.10:2379 --listen-metrics-urls=http://127.0.0.1:2381 --listen-peer-urls=https://10.82.69.10:2380 --metrics=basic --peer-cert-file=/etc/kubernetes/ssl/etcd/peer.crt --peer-client-cert-auth=true --peer-key-file=/etc/kubernetes/ssl/etcd/peer.key --peer-trusted-ca-file=/etc/kubernetes/ssl/etcd/ca.crt --snapshot-count=10000 --trusted-ca-file=/etc/kubernetes/ssl/etcd/ca.crt

# wait for the etcd pod running, if use kubectl and etcdctl to see that both node and member are restored, we can stop it
nerdctl stop restore_etcd

# start etcd pod
mv ./etcd.yaml /etc/kubernetes/manifests/etcd.yaml

etcd leader选举

etcd 是基于 raft 算法进行选举,而 raft 是一种管理日志一致性的协议,将系统中的角色分为三个

  1. leader: 接受客户端的请求,并向 follower 发送同步请求日志
  2. follower: 接收 leader 同步的日志
  3. candidate: 候选者角色,在选举过程中发挥作用

leader 选举

  1. raft 是通过心跳机制来触发 leader 的选举,每一个实例(例如 etcd pod)启动后都会初始化为一个 follower,leader 则会周期性的向所有 follower 发送心跳包,在 etcd 的编排中就能看到相关的参数

etcd编排

  1. 如果 follower 如果在选举超时时间内没有收到 leader 的心跳包,就会等待一个随机的时间,然后发起 leader 选举。每个 follower 都有一个时钟,这个时钟是一个随机的值,集群中谁的时钟先跑完,那么就由谁来发起 leader 选举

  2. 该 follower 会将当前的任期(term) + 1 然后转化为 candidate,先给自己投票然后向集群中的其他 follower 发送 RequestVote RPC

那么最终的结果会有三种:

  1. 自己赢得了最多的票数,成为 leader
  2. 收到了 leader 的消息,表示已经有其他服务抢先成为了 leader
  3. 没有服务获得最高票数,即选举失败,会等待选举时间超时后进行下一次选举

在 raft 协议中,所有的日志条目都只会是 leader 往 follower 写入,且 leader 的日志只增不减,所以能被选举成为 leader 的节点,一定包含了所有已经提交的日志条目