[TOC]

ETCD 应急方案

​ETCD 常见 FAQ​

基本概念

  1. etcd 是一个分布式的、可靠的 key-value 存储系统,它用于存储分布式系统中的关键数据
  2. 一个 etcd 集群,通常会由 3 个或者 5 个节点组成,多个节点之间通过 Raft 一致性算法的完成分布式一致性协同,算法会选举出一个主节点作为 leader,由 leader 负责数据的同步与数据的分发。当 leader 出现故障后系统会自动地选取另一个节点成为 leader,并重新完成数据的同步。客户端在多个节点中,仅需要选择其中的任意一个就可以完成数据的读写,内部的状态及数据协同由 etcd 自身完成。
  3. 在 etcd 整个架构中,有一个非常关键的概念叫做 quorum,quorum 的定义是 (n+1)/2,也就是说超过集群中半数节点组成的一个团体,在 3 个节点的集群中,etcd 可以容许 1 个节点故障,也就是只要有任何 2 个节点可用,etcd 就可以继续提供服务。同理,在 5 个节点的集群中,只要有任何 3 个节点可用,etcd 就可以继续提供服务,这也是 etcd 集群高可用的关键。
  4. 在允许部分节点故障之后继续提供服务,就需要解决一个非常复杂的问题:分布式一致性。在 etcd 中,该分布式一致性算法由 Raft 一致性算法完成,这个算法本身是比较复杂的有机会再详细展开,这里仅做一个简单的介绍以方便大家对其有一个基本的认知。Raft 一致性算法能够工作的一个关键点是:任意两个 quorum 的成员之间一定会有一个交集(公共成员),也就是说只要有任意一个 quorum 存活,其中一定存在某一个节点(公共成员),它包含着集群中所有的被确认提交的数据。正是基于这一原理,Raft 一致性算法设计了一套数据同步机制,在 Leader 任期切换后能够重新同步上一个 quorum 被提交的所有数据,从而保证整个集群状态向前推进的过程中保持数据的一致。

常见故障与方案

Etcd 节点无法启动

  1. etcd一般以静态pod方式运行,先通过“journalctl -u kubelet”检查该节点kubelet是否运行正常
  2. 如果kubelet正常,查看kubelet日志看是否有pod无法启动日志信息
  3. 通过kubectl logs -f -n kube-system etcd-$HOSTNAME,检查etcd启动日志是否有错误信息,如该节点与集群其他etcd节点网络是否正常互通,该节点etcd数据库目录(一般为/var/lib/etcd)是否正常
  4. 该节点etcd数据目录是否损坏,如日志显示数据目录损坏,可以参考备份与恢复的方法,用备份文件恢复该节点数据目录

Kube-api无法连接etcd集群

  1. 检查kube-apisever的yaml配置,确认kube-api与etcd连接的网络正常,kube-apiserver节点ping etcd的ip能通,且telnet也是通的
  2. kube-api连接etcd集群的证书是否过期,证书是否配置正确(证书需由 /etc/kubernetes/pki/etcd/ca.crt签发)
  3. 检查etcd集群本身是否正常

Ectd Timeout

选举时间调优

(https://etcd.io/docs/v3.2/tuning/) etcd 中的默认设置应该适用于平均网络延迟较低的本地网络上的安装。但是,当跨多个数据中心或通过具有高延迟的网络使用 etcd 时,心跳间隔和选举超时设置可能需要调整。 每个请求和响应都可能受到领导者和追随者上慢速磁盘的影响。这些超时中的每一个都表示从请求到另一台机器成功响应的总时间。 底层分布式共识协议依赖于两个独立的时间参数,以确保节点在一个停止或离线时能够移交领导权

  1. *Heartbeat Interval*:这是领导者通知追随者它仍然是领导者的频率,该参数应围绕成员之间的往返时间进行设置。默认情况下,etcd 使用​​100ms​​心跳间隔。
  2. *Election Timeout*:此超时是跟随者节点在尝试成为领导者之前将持续多长时间而没有听到心跳。默认情况下,etcd 使用​​1000ms​​选举超时。
快照时间调优
  1. etcd 将所有关键更改附加到日志文件。该日志会永远增长,并且是对密钥所做的每次更改的完整线性历史记录。完整的历史记录适用于轻度使用的集群,但频繁使用的集群会携带大量日志。
  2. 为了避免日志过大,etcd 会定期创建快照。这些快照为 etcd 提供了一种通过保存系统的当前状态并删除旧日志来压缩日志的方法
  3. etcd 将在每 10,000 次更改后生成快照。如果 etcd 的内存使用率和磁盘使用率过高,可设置降低快照阈值
磁盘与网络
  1. etcd 集群对磁盘延迟非常敏感。由于 etcd 必须将提案保存到其日志中,因此来自其他进程的磁盘活动可能会导致较长的​​fsync​​延迟。结果是 etcd 可能会错过心跳,导致请求超时和临时领导者丢失。当赋予高磁盘优先级时,etcd 服务器有时可以与这些进程一起稳定运行。
  2. 在 Linux 上,etcd 的磁盘优先级可以配置为​​ionice​​ :
$ sudo ionice -c2 -n0 -p `pgrep etcd`
  1. 如果 etcd leader 有大量并发客户端请求,可能会因为网络拥塞而延迟处理 follower peer 请求。这表现为跟随节点上的发送缓冲区错误消息:
dropped MsgProp to 247ae21ff9436b2d since streamMsg's sending buffer is full
dropped MsgAppResp to 247ae21ff9436b2d since streamMsg's sending buffer is full
  1. 这些错误可以通过将 etcd 的对等流量优先于其客户端流量来解决。在 Linux 上,可以使用流量控制机制对对等流量进行优先级排序:
tc qdisc add dev eth0 root handle 1: prio bands 3
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip sport 2739 0xffff flowid 1:1
tc filter add dev eth0 parent 1: protocol ip prio 2 u32 match ip dport 2739 0xffff flowid 1:1

Etcd 集群损坏且无备份

  1. 停掉全部节点的etcd进程
mv /etc/kubernetes/manifests/etcd.yaml /tmp   # 适用kubelet静态pod启动etcd情况
  1. 备份全部etcd节点的数据目录 cp -rp /var/lib/etcd /data
  2. 选取一个节点的/var/lib/etcd/member/snap/db作为备份文件,重建etcd集群。这个与etcd 恢复的区别在于:数据是从目录复制而来,没有完整性的 hash,所以在使用恢复命令,etcdctl snapshot restore命令生成恢复数据目录时,须加--skip-hash-check参数
  3. 数据可能会产生丢失,或无法恢复

ETCD warning “apply entries took too long”

  1. 在大多数 etcd 成员同意提交请求后,每个 etcd 服务器将请求应用到其数据存储并将结果持久化到磁盘,应用请求通常需要不到 50 毫秒。如果平均应用持续时间超过 100 毫秒,etcd 将警告条目应用时间过长。
  2. 通常这个问题是由慢速磁盘引起的。磁盘可能在 etcd 和其他应用程序之间发生争用.
  3. 第二个最常见的原因是 CPU 不足。如果监控机器的 CPU 使用率显示使用率很高,则可能没有足够的计算能力用于 etcd。将 etcd 移至专用机器,增加进程资源隔离 cgroup,或将 etcd 服务器进程重新调整为更高的优先级通常可以解决问题。

超出数据库空间 mvcc: database space exceeded

etcd 中的​​多版本并发控制数据模型保留了键空间的准确历史记录。​​​如果不定期压缩此历史记录(例如,通过设置​​--auto-compaction​​​),etcd 最终将耗尽其存储空间。如果 etcd 存储空间不足,它会发出空间配额警报以保护集群免受进一步写入。只要发出警报,etcd 就会以 error 响应写入请求​​mvcc: database space exceeded​​。 从低空间配额警报中恢复:

  1. ​压缩​​etcd 的历史。
  2. 对每个 etcd 端点进行​​碎片整理。​
  3. ​解除​​警报。

Etcd 备份与恢复

还原
# 使用docker 为底层的,请使用docker 命令
# 每台的master节点 IP 和主机名需要修改
# 停止集群
mv /etc/kubernetes/manifests /etc/kubernetes/manifests.bak
# 备份原有etcd数据
mv /u01/local/kube-system/etcd /u01/local/kube-system/etcd-`date '+%Y%m%d-%H:%M:%S'`

#还原第一台master
nerdctl -n k8s.io run --rm \
-v '/tmp:/tmp' \ # 指定备份至本地的 /tmp 目录的数据
-v '/u01/local/kube-system:/u01/local/kube-system' \
-v '/etc/kubernetes/pki/etcd:/etc/kubernetes/pki/etcd' \
--env ETCDCTL_API=3 \
'docker.kedacom.com:15000/etcd:3.5.0-0' \
/bin/sh -c "etcdctl snapshot restore \
/tmp/etcd-snapshot-.db \
--name node-vdua \
--endpoints=10.165.16.5:2379 \
--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://10.165.16.5:2380 \
--initial-cluster=node-vdua=https://10.165.16.5:2380,node-5zv8=https://10.165.16.6:2380,node-2iup=https://10.165.16.7:2380 \
--data-dir=/u01/local/kube-system/etcd \
--skip-hash-check=true"
nerdctl  -n k8s.io run --rm \
-v '/tmp:/tmp' \
-v '/u01/local/kube-system:/u01/local/kube-system' \
-v '/etc/kubernetes/pki/etcd:/etc/kubernetes/pki/etcd' \
--env ETCDCTL_API=3 \
'docker.kedacom.com:15000/etcd:3.5.0-0' \
/bin/sh -c "etcdctl snapshot restore \
/tmp/etcd-snapshot-.db \
--name node-5zv8 \
--endpoints=10.165.16.6:2379 \
--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://10.165.16.6:2380 \
--initial-cluster=node-5zv8=https://10.165.16.6:2380,node-vdua=https://10.165.16.5:2380,node-2iup=https://10.165.16.7:2380 \
--data-dir=/u01/local/kube-system/etcd \
--skip-hash-check=true"

============================================================

nerdctl -n k8s.io run --rm \
-v '/tmp:/tmp' \
-v '/u01/local/kube-system:/u01/local/kube-system' \
-v '/etc/kubernetes/pki/etcd:/etc/kubernetes/pki/etcd' \
--env ETCDCTL_API=3 \
'docker.kedacom.com:15000/etcd:3.5.0-0' \
/bin/sh -c "etcdctl snapshot restore \
/tmp/etcd-snapshot-.db \
--name node-2iup \
--endpoints=10.165.16.7:2379 \
--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://10.165.16.7:2380 \
--initial-cluster=node-2iup=https://10.165.16.7:2380,node-5zv8=https://10.165.16.6:2380,node-vdua=https://10.165.16.5:2380 \
--data-dir=/u01/local/kube-system/etcd \
--skip-hash-check=true"

#恢复集群
mv /etc/kubernetes/manifests.bak /etc/kubernetes/manifests
备份
nerdctl  -n k8s.io run --rm \
-v '/tmp:/tmp' \
-v '/etc/kubernetes/pki/etcd:/etc/kubernetes/pki/etcd' \
--env ETCDCTL_API=3 \
'docker.kedacom.com:15000/etcd:3.5.0-0' \
/bin/sh -c "etcdctl snapshot save \
/tmp/etcd-snapshot-134.db \ # 备份至本地的 /tmp 目录下
--endpoints=10.165.16.5:2379 \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key \
--cacert=/etc/kubernetes/pki/etcd/ca.crt"

巡检脚本示例

下面的脚本利用 curl 命令,调用 etcd 的 endpoint 的 healt h接口,逐个检查是否返回 true

#!/bin/bash
ETCD_YAML="/etc/kubernetes/manifests/etcd.yaml"
ETCD_ENDPOINTS=`cat ${ETCD_YAML}|grep -i "initial-cluster=" |awk -F'=|,' '{print $3,$5,$7}'|sed -e "s/2380/2379/g"`
ETCD_CA="/etc/kubernetes/pki/etcd/ca.crt"
ETCD_KEY="/etc/kubernetes/pki/etcd/peer.key"
ETCD_CRT="/etc/kubernetes/pki/etcd/peer.crt"
for i in $ETCD_ENDPOINTS
do
curl --key ${ETCD_KEY} --cert ${ETCD_CRT} --cacert ${ETCD_CA} $i/health |grep true
if [ $? -eq 0 ]; then
echo -e "\033[32m etcd node $i is checked health\033[0m"
else
echo -e "\033[31m etcd node $i is checked failed\033[0m"
fi
done

​工具推荐​

  • ​lucas​​ - 一个基于 Web 的 kubernetes etcd3.0+ 集群的键值查看器。
# Simply use docker command     
docker run -d -p 8088:8080 -v /etc/kubernetes/pki/etcd/:/etc/kubernetes/pki/etcd/ -e CA_FILE=/etc/kubernetes/pki/etcd/ca.crt -e CERT_FILE=/etc/kubernetes/pki/etcd/server.crt -e KEY_FILE=/etc/kubernetes/pki/etcd/server.key -e ENDPOINTS="https://192.168.213.129:2379" registry.cn-hangzhou.aliyuncs.com/ringtail/lucas:0.0.2

# use yaml
kubectl create -f kubernetes-deployment.yaml (Add master node deployment affinity)