维护

定期的 etcd 集群维护指南

概述

etcd 集群需要定期维护以保持可靠性。根据 etcd 应用程序的需求,这种维护通常可以自动化执行,并且不会导致停机或显著的性能下降。

所有 etcd 维护都管理由 etcd 键空间消耗的存储资源。如果未能充分控制键空间大小,将受到存储空间配额的保护;如果 etcd 成员的存储空间不足,配额将触发集群范围的警报,使系统进入有限操作的维护模式。为了避免写入键空间时空间不足,必须压缩 etcd 键空间的历史记录。通过碎片整理 etcd 成员,可以回收存储空间。最后,定期对 etcd 成员状态进行快照备份,可以在因操作错误导致的任何意外逻辑数据丢失或损坏时恢复数据。

Raft 日志保留

etcd --snapshot-count 配置了在内存中保存的已应用 Raft 条目数量,直到压缩。当 --snapshot-count 达到设定值时,服务器首先将快照数据持久化到磁盘,然后截断旧条目。当一个慢速跟随者请求早于压缩索引的日志时,领导者会发送快照,迫使跟随者覆盖其状态。

较高的 --snapshot-count 会在内存中保留更多的 Raft 条目,直到快照,从而导致反复更高的内存使用量。由于领导者会保留最新的 Raft 条目更长时间,慢速跟随者有更多时间赶上领导者的快照。--snapshot-count 是在较高内存使用量和更好的慢速跟随者可用性之间的权衡。

从 v3.2 开始,--snapshot-count 的默认值已从 10,000 更改为 100,000

从性能角度来看,--snapshot-count 大于 100,000 可能会影响写入吞吐量。内存中对象数量较多会减慢Go GC 标记阶段 runtime.scanobject,并且不频繁的内存回收会使分配变慢。性能会根据工作负载和系统环境而有所不同。然而,一般来说,过于频繁的压缩会影响集群的可用性和写入吞吐量。过于不频繁的压缩也会对 Go 垃圾收集器造成太大压力。有关更多研究成果,请参阅https://www.slideshare.net/mitakeh/understanding-performance-aspects-of-etcd-and-raft

历史压缩:v3 API 键值数据库

由于 etcd 保留了其键空间的确切历史记录,因此应定期压缩该历史记录,以避免性能下降和最终的存储空间耗尽。压缩键空间历史记录会丢弃所有关于在给定键空间修订之前被取代的键的信息。这些键所占用的空间随后可用于对键空间的额外写入。

键空间可以通过 etcd 的基于时间窗口的历史保留策略自动压缩,也可以通过 etcdctl 手动压缩。etcdctl 方法提供了对压缩过程的细粒度控制,而自动压缩则适用于只需要键历史记录一段时间的应用程序。

etcdctl 启动的压缩过程如下:

# compact up to revision 3
$ etcdctl compact 3

早于压缩修订版本的修订版本将变得不可访问:

$ etcdctl get --rev=2 somekey
Error:  rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted

自动压缩

etcd 可以通过 --auto-compaction-* 选项设置为自动压缩键空间,周期为小时数:

# keep one hour of history
$ etcd --auto-compaction-retention=1

v3.0.0v3.1.0 使用 --auto-compaction-retention=10 每 10 小时对 v3 键值存储进行一次定期压缩。压缩器仅支持定期压缩。压缩器每 5 分钟记录一次最新的修订版本,直到达到第一个压缩周期(例如 10 小时)。为了保留上一个压缩周期的键值历史记录,它使用从每 5 分钟收集的修订记录中获取的、在压缩周期之前的最后一个修订版本。当 --auto-compaction-retention=10 时,压缩器使用 10 小时前获取的最新修订版本 100 进行压缩。如果压缩成功或请求的修订版本已经被压缩,则重置周期计时器,并使用新的历史修订记录重新开始(例如,重新开始修订收集和压缩下一个 10 小时周期)。如果压缩失败,将在 5 分钟后重试。

v3.2.0 压缩器每小时运行一次。压缩器仅支持定期压缩。压缩器继续每 5 分钟记录一次最新的修订版本。每小时,它使用从每 5 分钟收集的修订记录中获取的、在压缩周期之前的最后一个修订版本。也就是说,每小时,压缩器会丢弃在压缩周期之前创建的历史数据。压缩周期的保留窗口移动到下一个小时。例如,当每小时写入 100 条记录且 --auto-compaction-retention=10 时,v3.1 每 10 小时压缩修订版本 1000、2000 和 3000,而 v3.2.x、v3.3.0、v3.3.1 和 v3.3.2 每小时压缩修订版本 1000、1100 和 1200。如果压缩成功或请求的修订版本已经被压缩,则重置周期计时器并从历史修订记录中移除已使用的压缩修订版本(例如,从之前收集的修订版本开始下一次修订收集和压缩)。如果压缩失败,将在 5 分钟后重试。

v3.3.0v3.3.1v3.3.2 中,--auto-compaction-mode=revision --auto-compaction-retention=1000 每 5 分钟自动在 "最新修订版本" - 1000 上进行压缩(当最新修订版本为 30000 时,在修订版本 29000 上进行压缩)。例如,--auto-compaction-mode=periodic --auto-compaction-retention=72h 每 7.2 小时自动进行 72 小时保留窗口的压缩。例如,--auto-compaction-mode=periodic --auto-compaction-retention=30m 每 3 分钟自动进行 30 分钟保留窗口的压缩。定期压缩器继续每给定压缩周期的 1/10 记录最新的修订版本(例如,当 --auto-compaction-mode=periodic --auto-compaction-retention=10h 时,每 1 小时记录一次)。每给定压缩周期的 1/10,压缩器使用在压缩周期之前的最后一个修订版本来丢弃历史数据。压缩周期的保留窗口每给定压缩周期的 1/10 移动一次。例如,当每小时写入 100 条记录且 --auto-compaction-retention=10 时,v3.1 每 10 小时压缩修订版本 1000、2000 和 3000,而 v3.2.x、v3.3.0、v3.3.1 和 v3.3.2 每小时压缩修订版本 1000、1100 和 1200。此外,当每分钟写入 1000 条记录时,v3.3.0、v3.3.1 和 v3.3.2 使用 --auto-compaction-mode=periodic --auto-compaction-retention=30m 每 3 分钟以更细的粒度压缩修订版本 30000、33000 和 36000。

--auto-compaction-retention=10h 时,etcd 首先等待 10 小时进行第一次压缩,然后每小时(10 小时的 1/10)进行一次压缩,如下所示:

0Hr  (rev = 1)
1hr  (rev = 10)
...
8hr  (rev = 80)
9hr  (rev = 90)
10hr (rev = 100, Compact(1))
11hr (rev = 110, Compact(10))
...

无论压缩是否成功,此过程都会在给定的压缩周期的每 1/10 重复一次。如果压缩成功,它将从历史修订记录中删除已压缩的修订。

v3.3.3中,--auto-compaction-mode=revision --auto-compaction-retention=1000会自动在"最新修订" - 1000上每 5 分钟执行一次Compact(当最新修订为 30000 时,在修订 29000 上进行压缩)。以前,--auto-compaction-mode=periodic --auto-compaction-retention=72h会在每 7.2 小时自动执行一次Compact,保留 72 小时的时间窗口。现在,Compact每 1 小时执行一次,但仍然保留 72 小时的时间窗口。以前,--auto-compaction-mode=periodic --auto-compaction-retention=30m会在每 3 分钟自动执行一次Compact,保留 30 分钟的时间窗口。现在,Compact每 30 分钟执行一次,但仍然保留 30 分钟的时间窗口。周期性压缩器在给定周期小于 1 小时时,每压缩周期记录最新的修订,或在给定压缩周期大于 1 小时时,每 1 小时记录最新的修订(例如,--auto-compaction-mode=periodic --auto-compaction-retention=24h时为 1 小时)。对于每个压缩周期或 1 小时,压缩器使用在压缩周期之前获取的最后修订来丢弃历史数据。压缩周期的保留窗口在每个给定的压缩周期或每小时移动。例如,当每小时写入量为 100 且--auto-compaction-mode=periodic --auto-compaction-retention=24h时,v3.2.xv3.3.0v3.3.1v3.3.2每 2.4 小时压缩修订 2400、2640 和 2880,而v3.3.3或更高版本每 1 小时压缩修订 2400、2500 和 2600。此外,当--auto-compaction-mode=periodic --auto-compaction-retention=30m且每分钟写入量约为 1000 时,v3.3.0v3.3.1v3.3.2每 3 分钟压缩修订 30000、33000 和 36000,而v3.3.3或更高版本每 30 分钟压缩修订 30000、60000 和 90000。

碎片整理

压缩键空间后,后端数据库可能会出现内部碎片。任何内部碎片都是后端可以使用的空闲空间,但仍占用存储空间。压缩旧修订会导致etcd在后端数据库中留下间隙,从而产生内部碎片。碎片化的空间可供etcd使用,但对主机文件系统不可用。换句话说,删除应用程序数据不会回收磁盘上的空间。

碎片整理过程会将这些存储空间释放回文件系统。碎片整理是按成员逐个进行的,以避免整个集群范围内的延迟峰值。

要对etcd成员进行碎片整理,请使用etcdctl defrag命令:

$ etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]

请注意,对活动成员进行碎片整理会阻止系统在重建状态时读取和写入数据

请注意,碎片整理请求不会在集群中复制。也就是说,该请求仅应用于本地节点。请在--endpoints标志或--cluster标志中指定所有成员以自动查找所有集群成员。

对与默认端点关联的集群中的所有端点运行碎片整理操作:

$ etcdctl defrag --cluster
Finished defragmenting etcd member[http://127.0.0.1:2379]
Finished defragmenting etcd member[http://127.0.0.1:22379]
Finished defragmenting etcd member[http://127.0.0.1:32379]

要在etcd未运行时直接对其数据目录进行碎片整理,请使用以下命令:

$ etcdutl defrag --data-dir <path-to-etcd-data-dir>

空间配额

etcd中设置空间配额可以确保集群可靠运行。如果没有空间配额,当键空间变得过大时,etcd可能会出现性能下降,或者存储空间耗尽,导致集群行为不可预测。如果任何成员的键空间后端数据库超过了空间配额,etcd会触发一个全集群警报,使集群进入维护模式,该模式下只接受键读取和删除操作。只有在释放足够的键空间并整理后端数据库,并清除空间配额警报后,集群才能恢复正常运行。

默认情况下,etcd设置了适合大多数应用程序的保守空间配额,但也可以通过命令行以字节为单位进行配置:

# set a very small 16 MiB quota
$ etcd --quota-backend-bytes=$((16*1024*1024))

可以通过循环触发空间配额:

# fill keyspace
$ while [ 1 ]; do dd if=/dev/urandom bs=1024 count=1024  | ETCDCTL_API=3 etcdctl put key  || break; done
...
Error:  rpc error: code = 8 desc = etcdserver: mvcc: database space exceeded
# confirm quota space is exceeded
$ ETCDCTL_API=3 etcdctl --write-out=table endpoint status
+----------------+------------------+-----------+---------+-----------+-----------+------------+
|    ENDPOINT    |        ID        |  VERSION  | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+----------------+------------------+-----------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | bf9071f4639c75cc | 2.3.0+git | 18 MB   | true      |         2 |       3332 |
+----------------+------------------+-----------+---------+-----------+-----------+------------+
# confirm alarm is raised
$ ETCDCTL_API=3 etcdctl alarm list
memberID:13803658152347727308 alarm:NOSPACE

移除过多的键空间数据并整理后端数据库将使集群回到配额限制内:

# get current revision
$ rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')
# compact away all old revisions
$ ETCDCTL_API=3 etcdctl compact $rev
compacted revision 1516
# defragment away excessive space
$ ETCDCTL_API=3 etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]
# disarm alarm
$ ETCDCTL_API=3 etcdctl alarm disarm
memberID:13803658152347727308 alarm:NOSPACE
# test puts are allowed again
$ ETCDCTL_API=3 etcdctl put newkey 123
OK

指标etcd_mvcc_db_total_size_in_use_in_bytes表示历史压缩后的实际数据库使用情况,而etcd_debugging_mvcc_db_total_size_in_bytes则显示包括等待整理的空闲空间在内的数据库大小。后者仅在前者接近其值时增加,这意味着当这两个指标都接近配额时,需要进行历史压缩以避免触发空间配额。

etcd_debugging_mvcc_db_total_size_in_bytes从 v3.4 版本开始被重命名为etcd_mvcc_db_total_size_in_bytes

注意:对于 Put/Txn/LeaseGrant 请求,可能会收到ErrGRPCNoSpace错误,但写入请求仍然可能在后端成功执行,因为etcd在 API 层和内部 Apply 层检查空间配额,而 Apply 层只会触发NOSPACE警报而不阻止事务继续进行。

快照备份

定期对etcd集群进行快照可以作为etcd键空间的持久备份。通过对etcd成员的后端数据库定期进行快照,可以将etcd集群恢复到已知的良好状态。

使用etcdctl进行快照:

$ etcdctl snapshot save backup.db
$ etcdutl --write-out=table snapshot status backup.db
+----------+----------+------------+------------+
|   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
+----------+----------+------------+------------+
| fe01cf57 |       10 |          7 | 2.1 MB     |
+----------+----------+------------+------------+

最后更新于 2025 年 6 月 3 日:递归地将 v3.6 的内容复制到 v3.7(a90b2a6)