Etcd 基础知识

📌 学习笔记:本文为早期学习与实践阶段整理的技术笔记,内容梳理自官方文档与网络公开资料,主要用于知识沉淀与命令速查。

本文整合了 Etcd 的基础知识体系,包括其核心简介、常用命令行工具 etcdctl 的操作、性能调优指南,以及 Etcd 在 Kubernetes 中的应用与数据管理。

1. etcd简介

简介

etcd 是一款分布式存储中间件,使用 Go 语言编写,并通过 Raft 一致性算法处理和确保分布式一致性,解决了分布式系统中数据一致性的问题

etcd 的核心架构

  1. etcd Server : 对外接收和处理客户端的请求
  2. gRPC Server : 面向客户端的接入层(对应 2379 端口、v3 API),把请求分发给 KV、Watch、Lease、Cluster 等 service,并通过 gRPC-gateway 兼容 HTTP/JSON 客户端。而 etcd 节点之间(peer,2380 端口)的 raft 消息与日志复制走的是 rafthttp 的 HTTP 流,不是 gRPC
  3. MVCC : 多版本控制,etcd 的存储模块,键值对的每一次操作行为都会被记录存储,这些数据底层存储在 BoltDB 数据库中
  4. Snapshot、WAL : WAL 预写式日志,etcd 中的数据提交前都会记录到日志。 Snapshot 快照,以防 WAL 日志过多,用于存储某一时刻 etcd 的所有数据。Snapshot 和 WAL 相结合,etcd 可以有效地进行数据存储和节点故障恢复等操作。
  5. Raft 模块 : 实现分布式集群的一致性

MVCC:四个版本号和两个清理动作

“每一次操作行为都会被记录”这句话背后有一套版本号体系,理解它才用得动 watch 回放和历史查询。etcd 里有四个版本号,分工完全不同:

版本号 作用域 含义
revision 全局 整个 keyspace 的逻辑时钟,任何一次写操作(含删除)都让它 +1,全局单调递增
create_revision 单个 key 这个 key 被创建时的 revision,key 被删掉重建会变
mod_revision 单个 key 这个 key 最后一次被修改时的 revision
version 单个 key 这个 key 被修改过多少次,创建时为 1,删除后重建归 1
1
2
3
4
etcdctl put /a 1 && etcdctl put /b 2 && etcdctl put /a 3
etcdctl get /a -w json
# {"kvs":[{"key":"/a","create_revision":2,"mod_revision":4,"version":2,"value":"3"}], ...}
# ↑ 全局 revision 已经走到 4 了,但 /a 自己只改过 2 次

mod_revision 的典型用途是乐观并发控制——读的时候记下它,写的时候用事务断言”它没变过”,变了就说明有人插队:

1
2
3
4
etcdctl txn <<'EOF'
mod("/a") = "4"
put /a 5
EOF

按 revision 读历史,是 MVCC 最直接的收益:

1
2
etcdctl get /a --rev=2        # 读 revision 2 那一刻的值,拿到 1 而不是 3
etcdctl watch /a --rev=2 # 从 revision 2 开始把之后的所有变更重放一遍

watch --rev 这个能力是 Kubernetes 各种 Informer 断线重连后能”续上”的基础:记住上次处理到哪个 revision,重连时从那里继续,不用重新 list 全量。

但历史不是永久的,compact 会把它砍掉。

1
2
etcdctl compact 100          # 丢弃 revision 100 之前的所有历史版本
etcdctl get /a --rev=2 # Error: etcdserver: mvcc: required revision has been compacted

那句 required revision has been compacted 是运维 etcd 时最常见的报错之一,K8s 里表现为 apiserver 日志刷 too old resource version,客户端不得不重新 list 全量。

compact 和 defrag 是两件不同的事,都得做:

作用 不做的后果
compact 丢弃旧版本,逻辑上腾出空间 历史无限累积,撑爆 --quota-backend-bytes(默认 2GB),集群进入只读的 NOSPACE 告警状态
defrag 整理 BoltDB 文件碎片,把空间还给文件系统 db 文件大小一直不降,compact 完了 du 看着没变小

生产上 compact 应该交给 etcd 自己(--auto-compaction-mode=periodic --auto-compaction-retention=1h),defrag 需要逐个节点手工做(它会短暂阻塞该节点的读写,所以别同时对所有节点执行):

1
2
etcdctl --endpoints=<node1> defrag        # 一台一台来,做完确认 health 再下一台
etcdctl endpoint status -w table # 看 DB SIZE 有没有降下来

lease 与分布式锁

lease(租约)是带 TTL 的对象,key 可以挂在 lease 上,lease 过期时挂在它上面的所有 key 一起被删除——服务注册与发现就靠这个做健康摘除。

1
2
3
etcdctl lease grant 60                       # → lease 694d71...
etcdctl put --lease=694d71... /svc/a "1.2.3.4:80"
etcdctl lease keep-alive 694d71... # 客户端定期续约,进程挂了就不续了

有一点值得知道:lease 是走 raft 共识的对象,不是各节点本地的定时器。所以 leader 切换后新 leader 会重新开始计时——这意味着一次 leader 选举可能让所有 lease 的实际存活时间比 TTL 更长,靠 lease 做故障检测时不能假设”TTL 到了立刻就删”。

基于 lease + 事务可以做分布式锁,etcdctl 直接内置了:

1
etcdctl lock mylock -- sh -c 'echo 拿到锁了; sleep 5'

它的实现思路值得知道:在 /mylock/ 前缀下用自己的 lease id 建一个 key,然后看这个前缀下 create_revision 最小的是不是自己——是就拿到锁,不是就 watch 前一个 key 等它消失。这样不会出现”惊群”(只有下一个候选者被唤醒),而 lease 保证了持锁进程崩溃后锁能自动释放。

版本选择

etcd 目前有 V2.x 和 V3.x 两个大版本

主流版本 V3.x。

安装

三节点部署, etcd 和 etcdctl 文件 在 /usr/bin 目录下

前置条件

节点 ip

1
2
3
172.20.40.173
172.20.40.196
172.20.40.107
生成 etcd 的server 证书

当前和 k8s 共同部署,k8s 文件均相同, 在 /etc/kubernetes/ssl/ 目录下

1
2
3
/etc/kubernetes/ssl/ca.pem
/etc/kubernetes/ssl/ca-key.pem
/etc/kubernetes/ssl/ca-config.json

/etc/kubernetes/ssl/ca-config.json 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@test-173 test]# cat /etc/kubernetes/ssl/ca-config.json
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}

etcd 使用的 etcd-csr.json 需要修改 IP , 在 /etc/etcd/ssl 目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@test-173 ssl]# pwd
/etc/etcd/ssl

[root@test-173 ssl]# cat etcd-csr.json
{
"CN": "etcd",
"hosts": [
"127.0.0.1",
"172.20.40.173" # 需要每个节点对应修改
],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "HangZhou",
"L": "XS",
"O": "k8s",
"OU": "System"
}
]
}

在每个节点的 的 /etc/etcd/ssl 目录下执行, 生成 etcd.csr , etcd-key.pem , etcd.pem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@test-107 ssl]# pwd
/etc/etcd/ssl

[root@test-107 ssl]# ls
etcd-csr.json

[root@test-173 ssl]# cfssl gencert -ca=/etc/kubernetes/ssl/ca.pem -ca-key=/etc/kubernetes/ssl/ca-key.pem -config=/etc/kubernetes/ssl/ca-config.json -profile=kubernetes /etc/etcd/ssl/etcd-csr.json | cfssljson -bare etcd
2021/04/06 13:48:22 [INFO] generate received request
2021/04/06 13:48:22 [INFO] received CSR
2021/04/06 13:48:22 [INFO] generating key: rsa-2048
2021/04/06 13:48:23 [INFO] encoded CSR
2021/04/06 13:48:23 [INFO] signed certificate with serial number 518875750718489889129533277175826954994664517958
2021/04/06 13:48:23 [WARNING] This certificate lacks a "hosts" field. This makes it unsuitable for
websites. For more information see the Baseline Requirements for the Issuance and Management
of Publicly-Trusted Certificates, v.1.1.6, from the CA/Browser Forum (https://cabforum.org);
specifically, section 10.2.3 ("Information Requirements").
cfssl gencert -ca=/etc/kubernetes/ssl/ca.pem -ca-key=/etc/kubernetes/ssl/ca-key.pem -config=/etc/kubernetes/ssl/ca-config.json -profile=kubernetes /etc/etcd/ssl/etcd-csr.json | cfssljson -bare etcd

[root@test-107 ssl]# ls
etcd.csr etcd-csr.json etcd-key.pem etcd.pem
创建 etcd.service 启动文件

每个节点创建文件,对应修改
eg: 172.20.40.107 名字为 etcd3

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
[root@test-107 ssl]# cat /etc/systemd/system/etcd.service
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target
Documentation=https://github.com/coreos

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
ExecStart=/usr/bin/etcd \
--name=etcd3 \
--cert-file=/etc/etcd/ssl/etcd.pem \
--key-file=/etc/etcd/ssl/etcd-key.pem \
--peer-cert-file=/etc/etcd/ssl/etcd.pem \
--peer-key-file=/etc/etcd/ssl/etcd-key.pem \
--trusted-ca-file=/etc/kubernetes/ssl/ca.pem \
--peer-trusted-ca-file=/etc/kubernetes/ssl/ca.pem \
--initial-advertise-peer-urls=https://172.20.40.107:2380 \
--listen-peer-urls=https://172.20.40.107:2380 \
--listen-client-urls=https://172.20.40.107:2379,http://127.0.0.1:2379 \
--advertise-client-urls=https://172.20.40.107:2379 \
--initial-cluster-token=etcd-cluster-0 \
--initial-cluster=etcd1=https://172.20.40.173:2380,etcd2=https://172.20.40.196:2380,etcd3=https://172.20.40.107:2380 \
--initial-cluster-state=new \
--data-dir=/mnt/etcd_data
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

[root@test-107 ssl]# systemctl daemon-reload && systemctl enable --now etcd.service

验证, 当前正常

客户端交互端口 : 2379

peer通信端口 : 2380

peer : 同一集群的另一个 member

1
2
3
4
5
6
7
8
9
10
[root@test-107 ssl]# etcdctl --endpoints=http://localhost:2379  member list  # 查看集群成员
d20c6ab8456f5fd, started, etcd1, https://172.20.40.173:2380, https://172.20.40.173:2379, false
71c9147302874bdf, started, etcd3, https://172.20.40.107:2380, https://172.20.40.107:2379, false
ce0a9eb4851e47ac, started, etcd2, https://172.20.40.196:2380, https://172.20.40.196:2379, false

[root@test-107 ssl]# ETCDCTL_API=3 /usr/bin/etcdctl --endpoints=https://172.20.40.196:2379,https://172.20.40.107:2379,https://172.20.40.173:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem endpoint health # 查看节点健康

https://172.20.40.107:2379 is healthy: successfully committed proposal: took = 26.66478ms
https://172.20.40.173:2379 is healthy: successfully committed proposal: took = 30.31433ms
https://172.20.40.196:2379 is healthy: successfully committed proposal: took = 37.859764ms
etcd.service 部分参数说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--name:etcd 集群中的节点名,这里可以随意,方便区分且不重复即可。

--listen-client-urls:监听用于客户端通信的 url,同样可以监听多个。

--advertise-client-urls:建议使用的客户端通信 url,该值用于 etcd 代理或 etcd 成员与 etcd 节点通信。

--listen-peer-urls:监听用于节点之间通信的 url,可监听多个,集群内部将通过这些 url 进行数据交互(如选举、数据同步等)。

--initial-advertise-peer-urls:建议用于节点之间通信的 url,节点间将以该值进行通信。

--initial-cluster-token: etcd-cluster-0,节点的 token 值,设置该值后集群将生成唯一 ID,并为每个节点也生成唯一 ID。当使用相同配置文件再启动一个集群时,只要该 token 值不一样,etcd 集群就不会相互影响。

--initial-cluster:集群中所有的 initial-advertise-peer-urls 的合集。

--initial-cluster-state:new,新建集群的标志。

升级

仅需 替换二进制文件 etcd, etcdctl

备份恢复

在任一正常节点执行如下操作

eg:

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
[root@test-173 test]# export ETCDCTL_API=3; /usr/bin/etcdctl --endpoints=172.20.40.107:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem snapshot save /root/backup/etcd/snapshot-20210406-143852.db


{"level":"info","ts":1617691138.681973,"caller":"snapshot/v3_snapshot.go:119","msg":"created temporary db file","path":"/root/backup/etcd/snapshot-20210406-143852.db.part"}
{"level":"info","ts":"2021-04-06T14:38:58.698+0800","caller":"clientv3/maintenance.go:200","msg":"opened snapshot stream; downloading"}
{"level":"info","ts":1617691138.698508,"caller":"snapshot/v3_snapshot.go:127","msg":"fetching snapshot","endpoint":"172.20.40.107:2379"}
{"level":"info","ts":"2021-04-06T14:38:58.990+0800","caller":"clientv3/maintenance.go:208","msg":"completed snapshot read; closing"}
{"level":"info","ts":1617691138.9978282,"caller":"snapshot/v3_snapshot.go:142","msg":"fetched snapshot","endpoint":"172.20.40.107:2379","size":"10 MB","took":0.315710239}
{"level":"info","ts":1617691139.000513,"caller":"snapshot/v3_snapshot.go:152","msg":"saved","path":"/root/backup/etcd/snapshot-20210406-143852.db"}
Snapshot saved at /root/backup/etcd/snapshot-20210406-143852.db


[root@test-173 test]# export ETCDCTL_API=3; /usr/bin/etcdctl --endpoints=172.20.40.107:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem snapshot save /root/backup/etcd/snapshot-20210406-143852.db


{"level":"info","ts":1617691138.681973,"caller":"snapshot/v3_snapshot.go:119","msg":"created temporary db file","path":"/root/backup/etcd/snapshot-20210406-143852.db.part"}
{"level":"info","ts":"2021-04-06T14:38:58.698+0800","caller":"clientv3/maintenance.go:200","msg":"opened snapshot stream; downloading"}
{"level":"info","ts":1617691138.698508,"caller":"snapshot/v3_snapshot.go:127","msg":"fetching snapshot","endpoint":"172.20.40.107:2379"}
{"level":"info","ts":"2021-04-06T14:38:58.990+0800","caller":"clientv3/maintenance.go:208","msg":"completed snapshot read; closing"}
{"level":"info","ts":1617691138.9978282,"caller":"snapshot/v3_snapshot.go:142","msg":"fetched snapshot","endpoint":"172.20.40.107:2379","size":"10 MB","took":0.315710239}
{"level":"info","ts":1617691139.000513,"caller":"snapshot/v3_snapshot.go:152","msg":"saved","path":"/root/backup/etcd/snapshot-20210406-143852.db"}
Snapshot saved at /root/backup/etcd/snapshot-20210406-143852.db


### 恢复
systemctl stop etcd.service

cd /root/backup/etcd/ && \
ETCDCTL_API=3 /use/bin/etcdctl snapshot restore snapshot-20210406-143852.db\
--name etcd-3 \
--initial-cluster etcd1=https://172.20.40.173:2380,etcd2=https://172.20.40.196:2380,etcd3=https://172.20.40.107:2380 \
--initial-cluster-token etcd-cluster-0 \
--initial-advertise-peer-urls https://172.20.40.107:2380"

具体可以参考 之前编写的 ansible 文件

用 kubeasz 安装

1
etcdctl --endpoints=https://172.20.19.14:2379,https://172.20.19.9:2379,https://172.20.19.17:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/kubernetes/ssl/etcd.pem --key=/etc/kubernetes/ssl/etcd-key.pem endpoint health

etcd 网关与 gRPC-Gateway

etcd 网关通常用于 etcd 集群的门户,是一个简单的 TCP 代理,将客户端请求转发到 etcd 集群,对外屏蔽了 etcd 集群内部的实际情况,在集群出现故障或者异常时,可以通过 etcd 网关进行切换;

gRPC-Gateway 则是对于 etcd 的 gRPC 通信协议的补充,有些语言的客户端不支持 gRPC 通信协议,此时就可以使用 gRPC-Gateway 对外提供的 HTTP API 接口。通过 HTTP 请求,实现与 gRPC 调用协议同样的功能。


2. etcdctl

使用 v3 版本,export ETCDCTL_API=3

常用操作

etcdctl 支持命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@test-173 test]#  etcdctl -h
NAME:
etcdctl - A simple command line client for etcd3.

USAGE:
etcdctl [flags]

VERSION:
3.4.13

API VERSION:
3.4

...

数据库操作

数据库操作基本围绕着对键值和目录的 CRUD 操作及其对应的生命周期管理。 etcd 这些操作符合 REST 风格的一套 API 操作

键操作

键操作包括最常用的增删改查操作,包括 PUT、GET、DELETE 等命令。

PUT 设置或者更新某个键的值

1
2
3
4
5
6
[root@test-173 test]# etcdctl put /test/foo1 "Hello world"
OK
[root@test-173 test]# etcdctl put /test/foo2 "Hello world2"
OK
[root@test-173 test]# etcdctl put /test/foo3 "Hello world3"
OK

GET 获取指定键的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@test-173 test]#  etcdctl get /test/foo1
/test/foo1
Hello world

[root@test-173 test]# etcdctl get /test/foo2 --hex # 返回 十六进制格式
\x2f\x74\x65\x73\x74\x2f\x66\x6f\x6f\x32
\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x32

[root@test-173 test]# etcdctl get /test/foo3 --hex --print-value-only # 返回 十六进制格式,只获取值
\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x33

[root@test-173 test]# etcdctl get /test/foo1 /test/foo3 --print-value-only # 获取 [/test/foo1, /test/foo3)
Hello world
Hello world2

[root@test-173 test]# etcdctl get --prefix --limit=2 /test/foo --print-value-only # 获取 前缀为 /test/foo 的两个值
Hello world
Hello world2

DELETE 删除一个键或者特定范围的键

1
2
3
4
5
6
7
8
9
10
11
12
[root@test-173 test]# etcdctl del /test/foo1  /test/foo2  # 删除 [/test/foo1, /test/foo2)
1 # 删除了一个健
[root@test-173 test]# etcdctl get /test/foo1
[root@test-173 test]# etcdctl get /test/foo2
/test/foo2
Hello world2

root@test-173 test]# etcdctl del --prev-kv /test/foo2 # 删除键并返回值
1
/test/foo2
Hello world2
[root@test-173 test]# etcdctl get /test/foo2
watch 键值对的改动

watch 监测一个或多个键值的变化。v3 的 etcdctl watch 是一条长连接的流式推送:命令执行后就一直挂着,这个 key(或前缀)每发生一次变更都会打印一个事件,直到客户端自己断开为止,不会收到第一个事件就退出——「输出一次就退出」是 v2 的行为。所以下面的 watch 窗口里能看到两次 PUT 事件

1
[root@test-173 ~]# etcdctl watch foo1

新开窗口

1
2
3
4
5
6
[root@test-173 ~]# etcdctl put foo1 Hello watch
OK
[root@test-173 ~]# etcdctl put foo bar
OK
[root@test-173 ~]# etcdctl put foo1 bar1
OK

查看 watch 窗口

1
2
3
4
5
6
7
[root@test-173 ~]# etcdctl watch foo1
PUT
foo1
Hello
PUT
foo1
bar1
lease(租约)

一旦租约的 TTL 到期,租约就会过期并且所有附带的键都将被删除

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
41
[root@test-173 test]# etcdctl lease grant 100  # 授予租约,TTL 为 100 秒
lease 75fd78a5bc8db873 granted with TTL(100s)

[root@test-173 test]# etcdctl put --lease=75fd78a5bc8db873 foo1 bar # 附加键 foo1 到租约 75fd78a5bc8db873
OK
[root@test-173 test]# etcdctl get foo1
foo1
bar
[root@test-173 test]# etcdctl get foo1 # 两分钟后,发现 已没有值

[root@test-173 test]# etcdctl put --lease=75fd78a5bc8db873 foo1 bar # 租约已失效
{"level":"warn","ts":"2021-04-06T15:14:17.315+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-71d3b0ce-8010-4407-ad17-71c5f1970309/127.0.0.1:2379","attempt":0,"error":"rpc error: code = NotFound desc = etcdserver: requested lease not found"}


[root@test-173 test]# etcdctl lease grant 1000
lease 75fd78a5bc8dbb9b granted with TTL(1000s)
[root@test-173 test]# etcdctl put --lease=75fd78a5bc8dbb9b foo1 bar
OK
[root@test-173 test]# etcdctl get foo1
foo1
bar
[root@test-173 test]# etcdctl lease revoke 75fd78a5bc8dbb9b # 撤销租约
lease 75fd78a5bc8dbb9b revoked
[root@test-173 test]# etcdctl get foo1 # 撤销租约将删除所有附带的 key


## 刷新租约
[root@test-173 test]# etcdctl lease keep-alive 75fd78a5bc8dbe37 # 一直刷新
lease 75fd78a5bc8dbe37 keepalived with TTL(100)
lease 75fd78a5bc8dbe37 keepalived with TTL(100)
lease 75fd78a5bc8dbe37 keepalived with TTL(100)
lease 75fd78a5bc8dbe37 keepalived with TTL(100)

## 新开一个端口
root@test-173 ~]# etcdctl put --lease=75fd78a5bc8dbe37 foo1 bar
OK
[root@test-173 ~]# etcdctl lease timetolive 75fd78a5bc8dbe37 # 获取有关租赁信息
lease 75fd78a5bc8dbe37 granted with TTL(100s), remaining(68s)

[root@test-173 ~]# etcdctl lease timetolive --keys 75fd78a5bc8dbe37 # 获取哪些 key 绑定了租赁信息:
lease 75fd78a5bc8dbe37 granted with TTL(100s), remaining(91s), attached keys([foo1])

3. etcd调优

使用内存模式

⚠️ 这个办法只适合基准测试或者数据明确可丢的场景,不要用在生产上。etcd 的线性一致性建立在 WAL 先 fsync 到持久设备这个前提上,把 data-dir 放到 tmpfs 之后这个前提就不成立了:节点一重启数据全清,放在 k8s 里就等于 apiserver 的全部数据没了。三节点集群平时靠其他副本还能重建一个节点,但一旦断电或者批量重启同时丢掉多数派,raft 就没法自愈,连已经 commit 的数据也会丢。而下面把挂载写进 /etc/fstab,意味着这是重启后依然生效的常态配置,而不是临时压测——真要压测,挂完别写 fstab。

生产环境想解决同一个问题,方向是换 NVMe/SSD 把 fsync 延迟压下来,配合调 --heartbeat-interval、--election-timeout、--snapshot-count,并且至少保留定时 snapshot save 的备份。

未安装时

1
2
3
mkdir /var/lib/etcd
mount -t tmpfs -o size=10G,mode=0755 tmpfs /var/lib/etcd
echo "tmpfs /var/lib/etcd tmpfs defaults,size=10G 0 0" >> /etc/fstab

已经安装了 etcd, 先备份,操作完再恢复

将 etcd 的流量排在第一优先级

2379 2380 端口流量

1
2
3
4
5
6
netDevice="ens33"
tc qdisc add dev $netDevice root handle 1: prio bands 3
tc filter add dev $netDevice parent 1: protocol ip prio 1 u32 match ip sport 2380 0xffff flowid 1:1
tc filter add dev $netDevice parent 1: protocol ip prio 1 u32 match ip dport 2380 0xffff flowid 1:1
tc filter add dev $netDevice parent 1: protocol ip prio 2 u32 match ip sport 2379 0xffff flowid 1:1
tc filter add dev $netDevice parent 1: protocol ip prio 2 u32 match ip dport 2379 0xffff flowid 1:1

4. etcd与k8s

获取 Kubernetes 存储在 etcd 中的 keys

1
2
3
4
5
6
7
8
9
10
11
12
[root@test-173 ~]# ETCDCTL_API=3 etcdctl get / --prefix --keys-only  |grep -Ev "^$"
/calico/ipam/v2/assignment/ipv4/block/170.56.144.192-26
/calico/ipam/v2/assignment/ipv4/block/170.56.170.64-26
/calico/ipam/v2/assignment/ipv4/block/170.56.27.0-26
/calico/ipam/v2/assignment/ipv4/block/170.56.73.192-26
/calico/ipam/v2/handle/k8s-pod-network.006edea2a1463ad925976290e5a0fea9d87e3188731566465ec8459f20b54a9d
/calico/ipam/v2/handle/k8s-pod-network.0397054208d1bce46053df4814d1006f14ae0dd98280555aa5b35d1b8b02c06e
/calico/ipam/v2/handle/k8s-pod-network.070dee87e37e685f2f6adac7ea38a54bb2326ac1c5d56bcae3b9b46086ff5c2b
/calico/ipam/v2/handle/k8s-pod-network.0fad3aa32d7c9bfe0392577fc44a9f7ad9482c9914e0a93b1b94363cd1173585
## 略

[root@test-173 ~]# ETCDCTL_API=3 etcdctl --endpoints=https://172.20.40.196:2379,https://172.20.40.107:2379,https://172.20.40.173:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem get / --prefix --keys-only |grep -Ev "^$" # 同样结果

查看键值

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
[root@test-107 ~]# ETCDCTL_API=3 etcdctl --endpoints=https://172.20.40.196:2379,https://172.20.40.107:2379,https://172.20.40.173:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem  get /registry/services/endpoints/monitoring/grafana  # 查看键值

/registry/services/endpoints/monitoring/grafana
k8s

v1 Endpoints⚌
⚌
grafana
monitoring"*$c5bca02d-dcde-4638-88fa-d32f1606cd722⚌⚌߃Z&
pp.kubernetes.io/componentgrafanaZ!
app.kubernetes.io/namegrafanaZ,
app.kubernetes.io/part-ofkube-prometheusZ"
app.kubernetes.io/version7.4.3bM
0endpoints.kubernetes.io/last-change-trigger-time2021-04-26T18:01:18+08:00z⚌⚌
kube-controller-managerUpdatev⚌FieldsV1:⚌
⚌{"f:metadata":{"f:annotations":{".":{},"f:endpoints.kubernetes.io/last-change-trigger-time":{}},"f:labels":{".":{},"f:app.kubernetes.io/component":{},"f:app.kubernetes.io/name":{},"f:app.kubernetes.io/part-of":{},"f:app.kubernetes.io/version":{}}},"f:subsets":{}}⚌
⚌
170.56.73.220_
Pod
172.20.40.173fana-665447c488-245vl"$db4da82f-36ed-49b9-8003-197a61e8ed97*11359764:"
http⚌TCP"


[root@test-107 ~]# ETCDCTL_API=3 etcdctl --endpoints=https://172.20.40.196:2379,https://172.20.40.107:2379,https://172.20.40.173:2379 --cacert=/etc/kubernetes/ssl/ca.pem --cert=/etc/etcd/ssl/etcd.pem --key=/etc/etcd/ssl/etcd-key.pem get /registry/namespaces/default

/registry/namespaces/default
k8s

v1 Namespace⚌
⚌
default"*$fb03c357-9393-11eb-898b-fad65d90ce002⚌⚌⚌Z$
field.cattle.io/projectIdp-zrh2mZ
istio-injectionenabledb⚌
cattle.io/status⚌{"Conditions":[{"Type":"ResourceQuotaInit","Status":"True","Message":"","LastUpdateTime":"2021-04-14T05:51:05Z"},{"Type":"InitialRolesPopulated","Status":"True","Message":"","LastUpdateTime":"2021-04-14T05:51:05Z"}]}b,
field.cattle.io/projectIdc-hxllk:p-zrh2mb1
)lifecycle.cattle.io/create.namespace-authtruer#controller.cattle.io/namespace-authz


kubernetes
Active"

键类型

这些键定义了集群中所有资源的配置和状态:

1
2
3
4
5
6
7
8
9
10
Nodes
Namespaces
ServiceAccounts
Roles and RoleBindings, ClusterRoles / ClusterRoleBindings
ConfigMaps
Secrets
Workloads: Deployments, DaemonSets, Pods, …
Cluster's certificates
The resources within each apiVersion
The events that bring the cluster in the current state

元数据资源在 etcd 中的存储格式由前缀、资源类型、namespace 和具体资源名组成

补

Kubernetes 集群使用了事务 Txn 接口防止并发创建、更新被覆盖等问题。当执行完 BeforeCreate 策略后,API Server 会调用 Storage 模块的 Create 接口写入资源。Storage.Create 接口调用底层存储模块 etcd3,将 user Deployment 资源对象写入 etcd。

Kubernetes 使用 watch 机制获取数据变化的事件,etcd watch 机制提供了流式推送机制,相比于定时轮询减少了高昂的查询开销,方便 API Server 实现数据的监听。服务器端的 store 对象利用 etcd 的 watch 机制,当 watch 机制触发时,数据的变化信息将封装成 event 对象并打包发送出去,客户端则通过不停地监听尝试读取 event chunk。

需要注意的是,Kubernetes 社区提供了通用的Informer 组件,实现了客户端与 API Server 之间的资源和事件同步。Informer 机制的 Reflector 封装了 Watch、List 操作,结合本地 Cache、Indexer,控制器加载完初始状态数据后,接下来的其他操作只需从本地缓存读取,极大降低了 API Server 和 etcd 的压力。