Skip to content

1.CRUD(v3)

etcd 版本为 3.4,可以ETCDCTL_API=3,或ETCDCTL_API=2,默认情况下用的就是v3了,可以不用声明ETCDCTL_API

数据库操作围绕对键值和目录的 CRUD (符合 REST 风格的一套操作:Create)完整生命周期的管理。

etcd 在键的组织上采用了层次化的空间结构(类似于文件系统中目录的概念),用户指定的键可以为单独的名字,如 myname,此时实际上放在根目录 / 下面,也可以为指定目录结构,如 cluster1/node2/testkey,则将创建相应的目录结构

提示:CRUD 即 Create, Read, Update, Delete,是符合 REST 风格的一套 API 操作

1.查看版本

[root@localhost etcd_data]# etcdctl version 
etcdctl version: 3.4.16
API version: 3.4
[root@localhost etcd_data]# etcdctl version 
etcdctl version: 3.4.16
API version: 3.4

2.查看成员

[root@localhost etcd_data]# etcdctl member list --write-out=table

3.插入数据

bash
#etcdctl put key value
[root@localhost etcd_data]# etcdctl put foo bar
OK

#或者
[root@localhost etcd_data]# etcdctl put demo 101 -w json

{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":5,"raft_term":3}}
#etcdctl put key value
[root@localhost etcd_data]# etcdctl put foo bar
OK

#或者
[root@localhost etcd_data]# etcdctl put demo 101 -w json

{"header":{"cluster_id":14841639068965178418,"member_id":10276657743932975437,"revision":5,"raft_term":3}}

4.查询

bash
[root@localhost etcd_data]# etcdctl get foo
foo
bar

#更新
[root@localhost etcd_data]#etcdctl put app demo2
[root@localhost etcd_data]# etcdctl get foo
foo
bar

#更新
[root@localhost etcd_data]#etcdctl put app demo2

5.删除数据

[root@localhost etcd_data]# etcdctl del foo
1

#删除所有
etcdctl del "" --prefix


ETCDCTL_API=3 etcdctl get "/registry/deployment" --prefix --keys-only | sed '/^\s$/d'
[root@localhost etcd_data]# etcdctl del foo
1

#删除所有
etcdctl del "" --prefix


ETCDCTL_API=3 etcdctl get "/registry/deployment" --prefix --keys-only | sed '/^\s$/d'

删除指定前缀的key

[root@localhost etcd_data]# etcdctl del --prev-kv --prefix name
[root@localhost etcd_data]# etcdctl del --prev-kv --prefix name

删除key

[root@localhost etcd_data]# etcdctl del key_name11
[root@localhost etcd_data]# etcdctl del key_name11

6.查看所有key

[root@localhost etcd_data]# etcdctl --prefix --keys-only=true get ""
foo
[root@localhost etcd_data]# etcdctl --prefix --keys-only=true get ""
foo

按key查询

[root@localhost etcd_data]#  etcdctl get key_name1
[root@localhost etcd_data]#  etcdctl get key_name1
  • 不显示key只限制values
[root@localhost etcd_data]#  etcdctl get --print-value-only key_name1
james
[root@localhost etcd_data]#  etcdctl get --print-value-only key_name1
james

按key前缀查找

[root@localhost etcd_data]# etcdctl get --prefix key_name
[root@localhost etcd_data]# etcdctl get --prefix key_name

更新key

put

# etcdctl get --prefix ""
avg_age
25
# etcdctl put avg_age 30
OK
# etcdctl get --prefix ""
avg_age
30
# etcdctl get --prefix ""
avg_age
25
# etcdctl put avg_age 30
OK
# etcdctl get --prefix ""
avg_age
30

7.警告

bash
#查看警告,如存储满时会切换为只读,产生 alarm
[root@localhost etcd_data]#etcdctl alarm list

#清除所有警告
[root@localhost etcd_data]#alarm disarm
#查看警告,如存储满时会切换为只读,产生 alarm
[root@localhost etcd_data]#etcdctl alarm list

#清除所有警告
[root@localhost etcd_data]#alarm disarm

2.restAPI

查看版本get:http://127.0.0.1:2379/version
# curl -s http://127.0.0.1:2379/version 

插入一个键值对put: http://10.110.30.183:2379/v2/keys/foo?value=bar

查询一个键get: http://10.110.30.179:2379/v2/keys/foo

查看集群的状态get:http://10.110.30.183:2379/v2/stats/store

查看全部键值get:http://10.110.30.183:2379/v2/keys


#查看etcd暴露出来的prometheus指标;在prometheus对其监控时使用
curl -L http://127.0.0.1:2379/metrics
查看版本get:http://127.0.0.1:2379/version
# curl -s http://127.0.0.1:2379/version 

插入一个键值对put: http://10.110.30.183:2379/v2/keys/foo?value=bar

查询一个键get: http://10.110.30.179:2379/v2/keys/foo

查看集群的状态get:http://10.110.30.183:2379/v2/stats/store

查看全部键值get:http://10.110.30.183:2379/v2/keys


#查看etcd暴露出来的prometheus指标;在prometheus对其监控时使用
curl -L http://127.0.0.1:2379/metrics

3.成员管理

1.查看成员

#查看默认端口
etcdctl member list --write-out=table

#查看非默认端口
etcdctl --endpoints=http://127.0.0.1:2379 member list --write-out=table
#查看默认端口
etcdctl member list --write-out=table

#查看非默认端口
etcdctl --endpoints=http://127.0.0.1:2379 member list --write-out=table

2.添加成员

bash
[root@localhost etcd_data]# etcdctl member add node2 --peer-urls=http://172.24.8.101:2380

Member  13e169ac57ee13d added to cluster cdf818194e3a8c32

ETCD_NAME="node2"
ETCD_INITIAL_CLUSTER="node2=http://172.24.8.101:2380,default=http://localhost:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.24.8.101:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"
[root@localhost etcd_data]# etcdctl member add node2 --peer-urls=http://172.24.8.101:2380

Member  13e169ac57ee13d added to cluster cdf818194e3a8c32

ETCD_NAME="node2"
ETCD_INITIAL_CLUSTER="node2=http://172.24.8.101:2380,default=http://localhost:2380"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.24.8.101:2380"
ETCD_INITIAL_CLUSTER_STATE="existing"

3.删除成员

etcdctl member list

etcdctl member remove id_name

4.更新成员peerURLS

etcdctl member update node2 --peer-urls=http://172.24.8.102:2380

4.v2

5.存储空间配额优化

https://etcd.io/docs/v3.4/op-guide/maintenance/

历史记录压缩

如果将etcd用作服务发现,每次服务注册和更新都可以看做一条新数据,日积月累,这些数据的量会导致etcd占用内存越来越大,直到etcd到达空间配额限制的时候,etcd的写入将会被静止,影响线上服务,定期删除历史记录就是避免这种情况

# 只保留一个小时的历史数据
$ etcd --auto-compaction-retention=1
# 只保留一个小时的历史数据
$ etcd --auto-compaction-retention=1

磁盘去碎片化

etcd官方是说在进行compaction操作之后,旧的revision被压缩,会产生内部的碎片,内部碎片是指空闲状态的,能被后端使用但是仍然消耗存储空间的磁盘空间。去碎片化实际上是将存储空间还给文件系统

$ etcdctl defrag
$ etcdctl defrag

存储空间配额

--quota-backend-bytes

存储空间配额可以理解为 ETCD 数据库大小,默认限制 2G(推荐最大 8G)。当数据写入耗尽存储空间时,ETCD 会引发整个集群范围的警告,该警告将会导致集群切换为维护模式,维护模式 仅接受键值读取和删除,不支持写入。

所以,在创建集群时候建议修改配额大小。但是这个配额值不宜过设置大,建议在主机内存的 60% - 70%

# 显式配置配额为16MB
$ etcd --quota-backend-bytes=$((16*1024*1024))
$ ETCDCTL_API=3 etcdctl --write-out=table endpoint status
# 显式配置配额为16MB
$ etcd --quota-backend-bytes=$((16*1024*1024))
$ ETCDCTL_API=3 etcdctl --write-out=table endpoint status

1.手动释放配额空间

  • 执行以下命令查看配额空间具体使用信息

ETCDCTL_API=3 etcdctl --write-out=table endpoint status

2.获取当前的修订版本

bash
# rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')

# 压缩所有旧的修订
ETCDCTL_API=3 etcdctl compact $rev

# 碎片整理,释放空间
ETCDCTL_API=3 etcdctl defrag

#集群释放
etcdctl defrag --cluster

# 解除警报(每个 ETCD 实例都要执行)
#通过执行etcdctl alarm list可以查看 etcd 的告警情况,如果存在告警,即使释放了 etcd 空间,etcd 也处于只读状态
ETCDCTL_API=3 etcdctl alarm disarm
# rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')

# 压缩所有旧的修订
ETCDCTL_API=3 etcdctl compact $rev

# 碎片整理,释放空间
ETCDCTL_API=3 etcdctl defrag

#集群释放
etcdctl defrag --cluster

# 解除警报(每个 ETCD 实例都要执行)
#通过执行etcdctl alarm list可以查看 etcd 的告警情况,如果存在告警,即使释放了 etcd 空间,etcd 也处于只读状态
ETCDCTL_API=3 etcdctl alarm disarm

3.历史版本清理(v3)

--auto-compaction-mode
--auto-compaction-retention
--auto-compaction-mode
--auto-compaction-retention

ETCD 会存储多版本数据,随着写入的主键增加,历史版本将会越来越多,并且 ETCD 默认不会自动清理历史数据。数据达到 --quota-backend-bytes 设置的配额值时就无法写入数据,必须要压缩并清理历史数据才能继续写入

所以,为了避免配额空间耗尽的问题,在创建集群时候建议默认开启 历史版本清理 功能。

  • 3.3.0 之前的版本

    3.3.0 之前的版本,只能按周期 periodic 来压缩。比如设置 --auto-compaction-retention=72h,那么就会每 72 小时进行一次数据压缩。

  • 3.3.0 之后的版本

    比如在 v3.3.0, v3.3.1 和 v3.3.2 中,可以通过 --auto-compaction-mode 设置压缩模式,可以选择 revision 或者 periodic 来压缩数据,默认为 periodic

Raft 日志保留

--snapshot-count 指定有多少条事务(transaction)被提交时,触发快照保存到磁盘。在存盘之前,Raft 条目将一直保存在内存中。从 v3.2 版本开始,--snapshot-count 条数从 10000 改为 100000,因此这将占用很大一部分内存资源。

如果节点总内存资源不多,或者是单 etcd 实例运行,则可以把 --snapshot-count 适当的缩减,比如设置为 --snapshot-count=50000

客户端必须向 etcd leader 发送请求吗?

Raft is leader-based, leader 处理所有需要一致性的客户机请求。但客户端不需要知道哪个节点是 leader,所有发送给跟随者的一致性请求都会自动转发给 leader。不需要协商一致的请求(例如,序列化读取)可以由任何集群成员处理

案例:

bash
#设置下启动大小
# set a very small 16MB quota
$ etcd --quota-backend-bytes=$((16*1024*1024))

#批量put
while [ 1 ]; do dd if=/dev/urandom bs=1024 count=1024  | ETCDCTL_API=3 etcdctl put key  || break; done

1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.0429034 s, 24.4 MB/s
{"level":"warn","ts":"2021-06-17T22:54:59.718-0400","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-7f280317-0920-4194-ac3f-bbdc9aa05467/127.0.0.1:2379","attempt":0,"error":"rpc error: code = ResourceExhausted desc = etcdserver: mvcc: database space exceeded"}
Error: etcdserver: mvcc: database space exceeded


[root@localhost etcd_data]#  etcdctl --write-out=table endpoint status
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------------------------------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX |             ERRORS             |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------------------------------+
| 127.0.0.1:2379 | 8e9e05c52164694d |  3.4.16 |  2.1 GB |      true |      false |         3 |       1766 |               1766 |  memberID:10276657743932975437 |
|                |                  |         |         |           |            |           |            |                    |                 alarm:NOSPACE  |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------------------------------+

#查看报警
[root@localhost etcd_data]# etcdctl alarm list
memberID:10276657743932975437 alarm:NOSPACE

#获取历史版本信息
[root@localhost etcd_data]# etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*'
1760

#压缩
[root@localhost etcd_data]# etcdctl compact 1760
compacted revision 1760

#清理碎片
[root@localhost etcd_data]# etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]

#关闭报警,之后才能正常写入
[root@localhost etcd_data]#etcdctl alarm disarm

#测试通过
$ ETCDCTL_API=3 etcdctl put newkey 123
#设置下启动大小
# set a very small 16MB quota
$ etcd --quota-backend-bytes=$((16*1024*1024))

#批量put
while [ 1 ]; do dd if=/dev/urandom bs=1024 count=1024  | ETCDCTL_API=3 etcdctl put key  || break; done

1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.0429034 s, 24.4 MB/s
{"level":"warn","ts":"2021-06-17T22:54:59.718-0400","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-7f280317-0920-4194-ac3f-bbdc9aa05467/127.0.0.1:2379","attempt":0,"error":"rpc error: code = ResourceExhausted desc = etcdserver: mvcc: database space exceeded"}
Error: etcdserver: mvcc: database space exceeded


[root@localhost etcd_data]#  etcdctl --write-out=table endpoint status
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------------------------------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX |             ERRORS             |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------------------------------+
| 127.0.0.1:2379 | 8e9e05c52164694d |  3.4.16 |  2.1 GB |      true |      false |         3 |       1766 |               1766 |  memberID:10276657743932975437 |
|                |                  |         |         |           |            |           |            |                    |                 alarm:NOSPACE  |
+----------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------------------------------+

#查看报警
[root@localhost etcd_data]# etcdctl alarm list
memberID:10276657743932975437 alarm:NOSPACE

#获取历史版本信息
[root@localhost etcd_data]# etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*'
1760

#压缩
[root@localhost etcd_data]# etcdctl compact 1760
compacted revision 1760

#清理碎片
[root@localhost etcd_data]# etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]

#关闭报警,之后才能正常写入
[root@localhost etcd_data]#etcdctl alarm disarm

#测试通过
$ ETCDCTL_API=3 etcdctl put newkey 123

6.调优

网络

跨数据中心时,需要调整心跳间隔和选举超时时间 默认的心跳时间是100ms,建议为round trip时间 默认选举超时是1000ms

--heartbeat-interval 心跳间隔,即 leader 通知member 并保证自己 leader 地位的心跳,默认是 100ms,这个应该设置为节点间的 RTT 时间

--election-timeout 选举超时时间,即 member 多久没有收到 leader 的回应,就开始自己竞选 leader,默认超时时间为 1s

磁盘 IO

磁盘 IO 也严重影响 etcd 的稳定性, etcd需要持久化数据,对磁盘速度很敏感,强烈建议对 ETCD 的数据挂 SSD。

另外,要确认机器上没有其他高 IO 操作,否则会影响 etcd 的 fsync,导致 etcd 丢失心跳,leader更换等。一般磁盘有问题时,报错的关键字类似于:

took too long (1.483848046s) to execute
 etcdserver: failed to send out heartbeat on time
took too long (1.483848046s) to execute
 etcdserver: failed to send out heartbeat on time

快照

etcd的存储分为内存存储和持久化(硬盘)存储两部分,内存中的存储除了顺序化的记录下所有用户对节点数据变更的记录外,还会对用户数据进行索引、建堆等方便查询的操作。而持久化则使用预写式日志(WAL:Write Ahead Log)进行记录存储。

在WAL的体系中,所有的数据在提交之前都会进行日志记录。在etcd的持久化存储目录中,有两个子目录。一个是WAL,存储着所有事务的变化记录;另一个则是snapshot,用于存储某一个时刻etcd所有目录的数据。通过WAL和snapshot相结合的方式,etcd可以有效的进行数据存储和节点故障恢复等操作。

既然有了WAL实时存储了所有的变更,为什么还需要snapshot呢?随着使用量的增加,WAL存储的数据会暴增,为了防止磁盘很快就爆满,etcd默认每10000条记录做一次snapshot,经过snapshot以后的WAL文件就可以删除。而通过API可以查询的历史etcd操作默认为1000条

客户端优化

etcd 的客户端应该避免一些频繁操作或者大对象操作,如:

  • put 时避免大 value,精简再精简(例如 k8s 中 crd 使用)
  • 避免创建频繁变化的 kv(例如 k8s 中 node 信息汇报),如 node-lease
  • 避免创建大量 lease,尽量选择复用(例如 k8s 中 event 数据管理)
  • 合理利用 apiserver 中的缓存,避免大量请求打到 etcd上,如集群异常恢复后大量 pod同步

7.leader

learner 是 etcd 3.4 版本中增加的新角色,类似于 zookeeper 的 observer, 不参与 raft 投票选举。通过这个新角色的引入,降低了加入新节点时给老集群的额外压力,增强了集群的稳定性。除此之外还可以使用它作为集群的热备或服务一些读请求。

举例,如果 etcd集群需要加入一个新节点,新加入的 etcd 成员因为没有任何数据,因此需要从 leader 那里同步数据,直到赶上领导者的日志为止。这样就会导致 leader 的网络过载,导致 leader 和 member 之间的心跳可能阻塞。然后就开始了新的leader选举,也就是说,具有新成员的集群更容易受到领导人选举的影响。领导者的选举以及随后向新成员的更新都容易导致一段时间的群集不可用,这种是不符合预期,风险也是很大的。

因此为了解决这个问题,raft 4.2.1 论文中介绍了一种新的节点角色:Learner。加入集群的节点不参与投票选举,只接收 leader 的 replication message,直到与 leader 保持同步为止。

learner 在网络分区等场景下的处理,可以详细参考:https://etcd.io/docs/v3.3.12/learning/learner/

7.扩容集群

如果该节点曾属于集群成员,那么删除etcd data目录,重启即可。可以通过以下方式查看是否是集群成员

text
ETCDCTL_API=2 etcdctl member list
ETCDCTL_API=2 etcdctl member list

如果该节点是一个新的节点,那么启动etcd时,

  1. 先在集群节点上执行 etcdctl member add new_node --peer-urls=xxx
  2. 设置环境变量ETCD_INITIAL_CLUSTERETCD_INITIAL_CLUSTER_STATE:分别指明了etcd的集群地址和集群状态。
  3. 启动这个new node

8.常见问题

如何选择etcd集群的节点数目?

首先要理解为什么需要增加节点数目?原因大致为:

  1. 提高etcd集群的可用性:3个节点能最大容忍一个节点不可用,5个节点则可以最大容忍2个节点不可用,节点数增加从概率角度上确实能提高集群的可用性。
  2. 提高读的吞吐:集群任何一个节点都能提供读的服务,增加节点类似于横向扩展了。

注意节点数并不是越多越好,因为etcd的一致性机制,每次写操作都需要同步到每个节点,节点数越多会降低写操作的吞吐。

在读写均存在的场景下,建议etcd集群有3个节点,如果读远大于写的场景下,可以考虑增加到5个节点

etcd内存、snapshot大小异常

大致原因有以下几点:

  1. etcd的key/value具有版本功能,频繁更新key/value会使得版本增加过快。
  2. raft log的太多了
  3. golang gc问题

解决办法有:

  1. 开启自动compact:启动参数增加--auto-compaction-retention=168
  2. 设置存储quota上限: 启动参数增加--quota-backend-bytes=8589934592
  3. 限制raft log:调小--snapshot-count=50000

3.4开始raft log默认是10w

etcd节点挂掉时间太长,重启失败咋办

集群没挂掉的情况下,直接删除data目录,重新加入集群处理

mvcc: database space exceeded 报错如何处理

etcd会保存key的历史版本,如果没有定期compact的话,就会耗尽etcd的存储空间。如果etcd的剩余空间很小的时候,就会报警、报错:mvcc: database space exceeded. 解决办法:

  1. compact etcd历史
  2. 清理每个etcd endpoint
  3. 消除报警
bash
recv=`ETCDCTL_API=3 etcdctl --endpoints=http://$addr endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*'`

ETCDCTL_API=3 etcdctl --endpoints=http://$addr compact $recv

ETCDCTL_API=3 etcdctl --endpoints=http://$addr defrag

ETCDCTL_API=3  etcdctl alarm disarm
recv=`ETCDCTL_API=3 etcdctl --endpoints=http://$addr endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*'`

ETCDCTL_API=3 etcdctl --endpoints=http://$addr compact $recv

ETCDCTL_API=3 etcdctl --endpoints=http://$addr defrag

ETCDCTL_API=3  etcdctl alarm disarm

https://hiddenpps.blog.csdn.net/article/details/113153693