kubernetes底层数据存放在etcd中,这里记录一下学习的笔记。
说明
- Etcd 是 CoreOS 推出的分布式一致性键值存储,用于共享配置和服务发现
- Etcd 支持集群模式部署,从而实现自身高可用
- 本文以
CentOS-7.6
和etcd-v3.3.10
为例
etcd安装
二进制文件安装
下载
1 | 下载并解压 |
配置
创建用户
1 | groupadd -r etcd |
创建目录
1 | mkdir -p /var/lib/etcd /etc/etcd/ |
配置文件
创建配置文件etcd.config.yaml,内容如下
1 | # This is the configuration file for the etcd server. |
创建服务文件
使用systemd托管etcd的服务
1 | cat > /usr/lib/systemd/system/etcd.service <<EOF |
运行etcd
1 | chown -R etcd:etcd /var/lib/etcd /etc/etcd |
验证etcd服务
1 | etcdctl cluster-health |
etcd集群部署
构建集群的方式
静态发现
预先已知 Etcd 集群中有哪些节点,在启动时直接指定好 Etcd 的各个 node 节点地址
动态发现
通过已有的 Etcd 集群作为数据交互点,然后在扩展新的集群时实现通过已有集群进行服务发现的机制
DNS动态发现
通过 DNS 查询方式获取其他节点地址信息
节点信息
这里只提供静态发现部署etcd集群的流程IP地址 | 主机名 | CPU | 内存 |
---|---|---|---|
172.16.80.201 | etcd1 | 4 | 8G |
172.16.80.202 | etcd2 | 4 | 8G |
172.16.80.203 | etcd3 | 4 | 8G |
静态发现部署etcd集群
创建配置文件
etcd1
1 | # This is the configuration file for the etcd server. |
etcd2
1 | # This is the configuration file for the etcd server. |
etcd3
1 | # This is the configuration file for the etcd server. |
启动etcd集群
1 | for NODE in 172.16.80.201 172.16.80.202 172.16.80.203;do |
检查etcd集群
1 | export ETCDCTL_API=2 |
SSL/TLS加密
此段翻译自官方文档
etcd支持自动TLS、客户端证书身份认证、客户端到服务器端以及对等集群的加密通信
生成证书
为方便起见,这里使用
CFSSL
工具生成证书
下载CFSSL
1 | mkdir ~/bin |
创建工作目录
1 | mkdir ~/cfssl |
创建默认配置文件
1 | cfssl print-defaults config > ca-config.json |
证书类型介绍
- 客户端证书用于服务器验证客户端身份
- 服务器端证书用于客户端验证服务器端身份
- 对等证书由etcd集群成员使用,同时使用客户端认证和服务器端认证
配置CA
修改ca-config.json
说明
expiry
定义过期时间,这里的43800h为5年usages
字段定义用途signing
代表可以用于签发其他证书key encipherment
代表将密钥加密server auth
client auth
1 | { |
配置证书请求
修改ca-csr.json
,可以根据自己的需求修改对应字段
1 | { |
生成CA证书
运行以下命令生成CA证书
1 | cfssl gencert -initca ca-csr.json | cfssljson -bare ca - |
生成以下文件
1 | ca-key.pem |
- ca-key.pem为CA的私钥,请妥善保管
- csr文件为证书请求文件,可以删除
生成服务器端证书
1 | cfssl print-defaults csr > server.json |
修改server.json
的CN和hosts字段,names
字段按需修改
说明
- hosts字段为列表,服务器端需要将自己作为客户端访问集群,可以使用
hostname
或者IP地址
的形式定义hosts
1 | { |
创建服务器端证书和私钥
1 | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server.json | cfssljson -bare server |
生成以下文件
1 | server-key.pem |
生成客户端证书
1 | cfssl print-defaults csr > client.json |
修改client.json
,客户端证书不需要hosts字段,只需要CN字段设置为client
1 | ... |
创建客户端证书和私钥
1 | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client client.json | cfssljson -bare client |
生成以下文件
1 | client-key.pem |
生成对等证书
1 | cfssl print-defaults csr > member1.json |
修改member1.json
的CN字段和hosts字段
1 | ... |
创建member1
的证书和密钥
1 | cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=peer member1.json | cfssljson -bare member1 |
生成以下文件
1 | member1-key.pem |
对于多个member需要重复此操作,用于生成相对应的对等证书
验证证书
1 | openssl x509 -in ca.pem -text -noout |
示例1、客户端使用HTTPS传输数据给服务器端
准备CA证书ca.pem
,密钥对server.pem
server-key.pem
启动服务器端
启动参数如下
1 | etcd --name infra0 \ |
客户端使用HTTPS访问服务器端
使用curl
加载CA证书测试HTTPS连接
1 | curl --cacert /path/to/ca.pem \ |
示例2、客户端使用客户端证书作为身份验证访问服务器端
在示例1的基础上,需要客户端证书client.pem
和client-key.pem
启动服务器端
启动参数如下,这里比示例1多了client-cert-auth
和truested-ca-file
1 | etcd --name infra0 \ |
重复示例1的访问
1 | curl --cacert /path/to/ca.crt https://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar -v |
此命令结果会提示被服务器端拒绝
1 | ... |
使用客户端证书访问服务器端
1 | curl --cacert /path/to/ca.pem \ |
命令结果包含以下信息
- 身份认证成功
1 | ... |
示例3、在集群中传输安全和客户端证书
这里需要为每个member配备对应的member证书,操作步骤见生成证书部分
假设有2个member,这两个member都已生成对应的证书(member1.pem
、member1-key.pem
、member2.pem
、member2-key.pem
)
etcd 成员将组成一个集群,集群中成员之间的所有通信将使用客户端证书进行加密和验证。
etcd的输出将显示其连接的地址使用HTTPS。
启动服务器端
从https://discovery.etcd.io/new获取discovery_url作为启动集群的发现服务
发现服务可以在内网环境搭建,详见github地址
1 | DISCOVERY_URL=$(curl https://discovery.etcd.io/new) |
member1
1 | etcd --name infra1 \ |
member2
1 | etcd --name infra2 \ |
示例4、自动自签名
对于只需要加密传输数据而不需要身份验证的场景,etcd支持使用自动生成的自签名证书加密传输数据
启动服务器端
1 | DISCOVERY_URL=$(curl https://discovery.etcd.io/new) |
member1
1 | etcd --name infra1 \ |
member2
1 | etcd --name infra2 \ |
注意
由于自签名证书不会进行身份认证,因此curl会返回错误,因此需要添加-k
参数禁用证书链检查
etcd维护操作
查看所有的key
1 | export ETCDCTL_API=3 |
请求最大字节数
max-request-bytes
限制请求的大小,默认值是1572864
,即1.5M。在某些场景可能会出现请求过大导致无法写入的情况,可以调大到10485760
即10M。
快照条目数量调整
--snapshot-count
:指定有多少事务(transaction)被提交时,触发截取快照保存到磁盘。从v3.2开始,--snapshot-count
的默认值已从10000更改为100000。
注意
此参数具体数值可以通过根据实际情况调整
过低会带来频繁的IO压力,影响集群可用性和写入吞吐量。
过高则导致内存占用过高以及会让etcd的GC变慢
历史数据压缩(针对v3的API)
由于etcd保存了key的历史记录,因此能通过MVCC机制获取多版本的数据,需要定期压缩历史记录避免性能下降和空间耗尽。
到达上限阈值时,集群将处于只读和只能删除key的状态,无法写操作。
历史数据压缩只是针对数据的历史版本进行清理,清理之后只能读取到清理点之后的历史版本
手动压缩
清理revision为3之前的历史数据
1 | export ETCDCTL_API=3 |
清理之后,访问revision3之前的数据会提示不存在
1 | export ETCDCTL_API=3 |
自动压缩
启动参数中添加--auto-compaction-retention=1
即为每小时压缩一次
碎片整理(针对v3的API)
在数据压缩操作之后,旧的revision被压缩,会产生内部碎片,这些内部碎片可以被etcd使用,但是仍消耗磁盘空间。
碎片整理就是将这部分空间释放出来。
1 | export ETCDCTL_API=3 |
空间配额
etcd通过--quota-backend-bytes
参数来限制etcd数据库的大小,以字节为单位。
默认是2147483648
即2GB,最大值为8589934592
即8GB。
容量限制见官方文档
数据备份(针对v3的API)
快照备份
通过快照etcd集群可以作备份数据的用途。
可以通过快照备份的数据,将etcd集群恢复到快照的时间点。
1 | export ETCDCTL_API=3 |
检查快照状态
1 | etcd --write-out=table snapshot status /path/to/snapshot.db |
基于快照的定期备份脚本
1 | !/bin/sh |
etcd镜像集群(针对v3的API)
通过mirror-maker实时做镜像的方式同步数据,如果出现主机房服务挂了可以通过切换域名的形式切换到灾备机房;这个过程中数据是可以保持一致的。
提前部署好两套etcd集群之后,可以在主集群上面运行以下命令
1 | export ETCDCTL_API=3 |
make-mirror的输出为30s一次,程序为前台运行,可以通过nohup >/path/to/log 2>&1 &的方式扔到后台运行
etcd监控
debug endpoint
启动参数中添加
--debug
即可打开debug模式,etcd会在http://x.x.x.x:2379/debug
路径下输出debug信息。由于debug信息很多,会导致性能下降。
/debug/pprof
为go语言runtime的endpoint,可以用于分析CPU、heap、mutex和goroutine利用率。
这里示例为使用go命令获取etcd最耗时的操作
1 | go tool pprof http://127.0.0.1:2379/debug/pprof/profile |
metrics endpoint
每个etcd节点都会在/metrics
路径下输出监控信息,监控软件可以通过此路径获取指标信息
具体的metrics信息可以参看官方文档
--listen-metrics-urls
定义metrics的location。--metrics
可以定义basic
和extensive
这里通过curl
命令来获取metrics信息
1 | curl http://127.0.0.1:2379/metrics |
health check
这里通过curl
命令来获取health信息,返回结果为json
1 | curl http://127.0.0.1:2379/health |
返回结果如下
1 | { |
对接Prometheus
配置文件
HTTP
1 | global: |
HTTPS
1 | global: |
监控告警
使用Alertmanager进行监控告警
监控指标展示
使用Grafana读取Prometheus的数据展示监控数据,Dashboard模板
etcd故障处理
leader节点故障
- leader节点故障,etcd集群会自动选举出新的leader。
- 故障检测模型是基于超时的,因此选举新的leader节点不会在旧的leader节点故障之后立刻发生。
- 选举leader期间,集群不会处理写入操作。选举期间的写入请求会进入队列等待处理,直至选出新的leader节点。
- 已经发送给故障leader但尚未提交的数据可能会丢失。这是因为新的leader节点有权对旧leader节点的数据进行修改
- 客户端会发现一些写入请求可能会超时,没有提交的数据会丢失。
follower节点故障
- follower故障节点数量少于集群节点的一半时,etcd集群是可以正常工作的。
- 例如3个节点故障了1个,5个节点故障了2个
- follower节点故障后,客户端的etcd库应该自动连接到etcd集群的其他成员。
超过半数节点故障
- 由于Raft算法的原理所限,超过半数的集群节点故障会导致etcd集群进入不可写入的状态。
- 只要正常工作的节点超过集群节点的一半,那么etcd集群会自动选举leader节点并且自动恢复到健康状态
- 如果无法修复多数节点,那么就需要走灾难恢复的操作流程
网络分区
- 由于网络故障,导致etcd集群被切分成两个或者更多的部分。
- 那么占有多数节点的一方会成为可用集群,少数节点的一方不可写入。
- 如果对等切分了集群,那么每个部分都不可用。
- 这是因为Raft一致性算法保证了etcd是不存在脑裂现象。
- 只要网络分区的故障解除,少数节点的一方会自动从多数节点一方识别出leader节点,然后恢复状态。
集群启动失败
只有超过半数成员启动完成之后,集群的bootstrap才会成功。
Raft一致性算法保证了集群节点的数据一致性和稳定性,因此对于节点的恢复,更多的是恢复etcd节点服务,然后恢复数据
新的集群
可以删除所有成员的数据目录,然后重新走创建集群的步骤
已有集群
这个就要看无法启动的节点是数据文件损坏,还是其他原因导致的。
这里以数据文件损坏为例。
- 寻找正常的节点,使用
etcdctl snapshot save
命令保存出快照文件 - 将故障节点的数据目录清空,使用
etcdctl snapshot restore
命令将数据恢复到数据目录 - 使用
etcdctl member list
确认故障节点的信息 - 使用
etcdctl member remove
删除故障节点 - 使用
etcdctl member add MEMBER_NAME --peer-urls=http://member:2379
重新添加成员 - 修改etcd启动参数
--initial-cluster-state=existing
启动故障节点的etcd服务
etcd灾难恢复
这里的灾难恢复,只能恢复v2或者v3的数据,不能同时恢复v2和v3。
两套API是相互隔离的。
针对v3的API
etcd v3的API提供了快照和恢复功能,可以在不损失快照点数据的情况下重建集群
快照备份数据
1 | ETCDCTL_API=3 etcdctl --endpoints http://member1:2379,http://member2:2379,http://member3:2379 snapshot save /path/to/snapshot.db |
恢复集群
- 恢复etcd集群,只需要快照文件
db
即可。 - 使用
etcdctl snapshot restore
命令还原数据时会自动创建新的etcd数据目录。 - 恢复过程会覆盖快照文件里面的一些metadata(特别是member id和cluster id),该member会失去之前的id。覆盖metadata可以防止新成员无意中加入现有集群。
- 从快照中恢复集群,必须以新集群启动。
- 恢复时可以选择验证快照完整性hash。
- 使用
etcdctl snapshot save
生成的快照,则具有完整性hash - 如果是直接从数据目录拷贝数据快照,则没有完整性hash,需要使用
--skip-hash-check
跳过检查
- 使用
恢复节点数据
- 这里假定原有的集群节点为member1、member2、member3
在member1、member2、member3上分别恢复快照数据
1 | ETCDCTL_API=3 etcdctl snapshot restore snapshot.db \ |
启动etcd集群
在member1、member2、member3上分别启动集群
1 | etcd \ |
针对v2的API
备份数据
1 | ETCDCTL_API=3 |
清理数据目录
1 | rm -rf /path/to/data-dir |
恢复数据
1 | mv /path/to/backup_data_dir /path/to/data-dir |
启动etcd集群
启动参数需要添加--force-new-cluster
1 | etcd --data-dir /path/to/data-dir \ |
etcd版本升级
这里可以参考etcd的升级文档
etcd的FAQ
摘自Frequently Asked Questions (FAQ)
客户端是否需要向etcd集群的leader节点发送请求
- leader节点负责处理所有需要集群共识的请求(例如写请求)。
- 客户端不需要知道哪个节点是leader,follower节点会将所有需要集群共识的请求转发给leader节点。
- 所有节点都可以处理不需要集群共识的请求(例如序列化读取)。
listen-client-urls、listen-peer-urls、advertise-client-urls、initial-advertise-peer-urls的区别
listen-client-urls
和listen-peer-urls
指定etcd服务端用于接收传入连接的本地地址,要监听所有地址,请指定0.0.0.0作为监听地址。advertise-client-urls
和initial-advertise-peer-urls
指定etcd的客户端及集群其他成员访问etcd服务的地址,此地址必须要被外部访问,因此不能设置127.0.0.1或者0.0.0.0等地址。
为什么不能通过更改listen-peer-urls或者initial-advertise-peer-urls来更新etcdctl member list中列出的advertise peer urls
- 每个member的advertise-peer-urls来自初始化集群时的
initial-advertise-peer-urls
参数 - 在member启动完成后修改listen-peer-urls或者initial-advertise-peer-urls参数不会影响现有的advertise-peer-urls,因为修改此参数需要通过集群仲裁以避免出现脑裂
- 修改
advertise-peer-url
请使用etcd member update
命令操作
系统要求
- etcd会将数据写入磁盘,因此高性能的磁盘会更好,推荐使用SSD
- 默认存储配额为2GB,最大值为8GB
- 为了避免使用swap或者内存不足,服务器内存至少要超过存储配额
为什么etcd需要奇数个集群成员
- etcd集群需要通过大多数节点仲裁才能将集群状态更新到一致
- 仲裁为(n/2)+1
- 双数个集群成员并不比奇数个节点容错性强
集群容错性列表
Cluster Size | Majority | Failure Tolerance |
---|---|---|
1 | 1 | 0 |
2 | 2 | 0 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 3 | 2 |
6 | 4 | 2 |
7 | 4 | 3 |
8 | 5 | 3 |
9 | 5 | 4 |
集群最大节点数量
- 理论上没有硬性限制,一般不超过7个节点
- 建议5个节点,5个节点可以容忍2个节点故障下线,在大多数情况下已经足够
- 更多的节点可以提供更好的可用性,但是写入性能会有影响
部署跨数据中心的etcd集群是否合适
- 跨数据中心的etcd集群可以提高可用性
- 数据中心之间的网络延迟可能会影响节点的election
- 默认的etcd配置可能会因为网络延迟频繁选举或者心跳超时,需要调整对应的参数
为什么etcd会因为磁盘IO延迟而重新选举
- 这是故意设计的
- 磁盘IO延迟是leader节点存活指标的一部分
- 磁盘IO延迟很高导致选举超时,即使leader节点在选举间隔内能处理网络信息(例如发送心跳),但它实际上是不可用的,因为它无法及时提交新的提议
- 如果经常出现因磁盘IO延迟而重新选举,请关注一下磁盘或者修改etcd时间参数
etcd性能压测
这里参考官方文档
性能指标
- 延迟
- 完成操作所需的时间
- 吞吐量
- 一段时间内完成的总操作数量
通常情况下,平均延迟会随着吞吐量的增加而增加。
etcd使用Raft一致性算法完成成员之间的数据同步并达成集群共识。
集群的共识性能,尤其是提交延迟,主要受到两个方面限制。
- 网络IO延迟
- 磁盘IO延迟
提交延迟的构成
- 成员之间的网络往返时间RTT
- 同一个数据中心内部的RTT是ms级别
- 跨数据中心的RTT就需要考虑物理限制和网络质量
fdatasync
数据落盘时间- 机械硬盘
fdatasync
延迟通常在10ms左右 - 固态硬盘则低于1ms
- 机械硬盘
其他延迟构成
- 序列化etcd请求需要通过etcd后端boltdb的MVVC机制来完成,通常会在10ms完成。
- etcd定期将最近提交的请求快照,然后跟磁盘上的快照合并,这个操作过程会导致延迟出现峰值。
- 正在进行的数据压缩也会影响到延迟,所以要跟业务错开
benchmark跑分
etcd自带的benchmark命令行工具可以用来测试etcd性能
写入请求
1 | # 假定 HOST_1 是 leader, 写入请求发到 leader |
序列化读取
1 | # Single connection read requests |
etcd性能调优
参考官方文档
etcd默认配置是基于同一个数据中心,网络延迟较低的情况。
对于网络延迟较高,那么就需要优化心跳间隔和选举超时时间
时间参数(time parameter)
延迟不止有网络延迟,还可能受到节点磁盘IO影响。
每一次超时设置应该包括请求发出到响应成功的时间。
心跳间隔(heartbeat interval)
leader节点通知各follower节点自己的存活信息。
最佳实践是通过ping命令获取RTT最大值,然后设置为RTT的0.5~1.5倍。
默认是100ms。
选举超时(election timeout)
follower节点在多久之后没收到leader节点的心跳信息,就开始选举新leader节点。
默认是1000ms。选举超时应该设置为至少是RTT的10倍,以避免网络出现波动导致重新选举。
快照(snapshot)
etcd会将所有变更的key追加写入到wal日志文件中。
一行记录一个key的变更,因此日志会不断增长。
为避免日志过大,etcd会定期做快照。
快照操作会保存当前系统状态并移除旧的日志。
snapshot-count
参数控制快照的频率,默认是10000,即每10000次变更会触发一次快照操作。
如果内存使用率高并且磁盘使用率高,可以尝试调低这个参数。
磁盘
etcd集群对磁盘IO延迟非常的敏感。
etcd需要存储变更日志、快照等操作,可能会导致磁盘IO出现很高的fsync
延迟。
磁盘IO延迟高会导致leader节点心跳信息超时、请求超时、重新选举等。
- etcd所使用的磁盘与系统盘分开
- data目录和wal目录分别挂载不同的磁盘
- 有条件推荐使用SSD固态硬盘
使用
ionice
调高etcd进程的IO优先级(这个针对etcd数据目录在系统盘的情况)1
ionice -c2 -n0 -p `pgrep etcd`
网络
如果leader节点接收来自客户端的大量请求,无法及时处理follower的请求,那么follower节点处理的请求也会因此出现延迟。
具体表现为follower会提示sending buffer is full。
可以通过调高leader的网络优先级或者通过流量管控机制来提高对follower的请求响应。