11. 维护监控与告警

11.维护监控与告警

对于任何分布式系统而言,稳定运行离不开持续且有效的维护。etcd 作为一个高可用的键值存储,其维护工作主要围绕存储资源的管理展开。如果忽视这些维护任务,集群可能会面临性能下降、存储耗尽甚至服务不可用的风险。

etcd 的维护核心目标是确保 keyspace(键空间)的健康。随着时间的推移和数据的不断写入、更新、删除,etcd 内部存储引擎(基于 BoltDB)会积累大量的历史版本和碎片空间。如果不进行干预,这将导致两个主要问题:

  1. 性能退化:查询历史数据或进行常规操作时,需要扫描更多的无用数据,导致延迟增加。
  2. 存储耗尽:物理磁盘空间被无效数据占满,触发空间配额(Quota)告警,导致集群进入只读/只删的维护模式,严重影响业务。

因此,一套完善的维护流程通常包含以下几个关键环节:

  • 历史压缩(Compaction):清理不再需要的数据历史版本,逻辑上释放空间。
  • 碎片整理(Defragmentation):物理上回收被标记为删除的数据所占用的磁盘空间。
  • 空间配额管理:监控并确保数据库大小在预设的安全范围内。
  • 监控与告警:实时感知集群健康状态,提前发现问题。
  • 数据备份:通过快照(Snapshot)为灾难恢复提供保障。

在接下来的章节中,我们将深入探讨这些维护任务的具体操作和最佳实践。

压缩与碎片整理

etcd 的 MVCC(多版本并发控制)机制是其核心设计之一。每次更新一个键,etcd 并不会原地覆盖旧值,而是生成一个新的版本。这种设计带来了强大的事务能力和时间旅行查询(通过指定 revision 查询)的能力,但也带来了数据累积的问题。压缩和碎片整理就是为了解决这一问题而存在的两个互补操作。

历史压缩 (Compaction)

压缩是逻辑上清理旧版本数据的过程。它会告诉 etcd:“在这个修订号(revision)之前的所有历史版本数据,我都不再需要了。” 执行压缩后,这些旧版本的键值对将被标记为可回收,但它们占用的磁盘空间并不会立即减少。

压缩的主要作用是:

  1. 释放内存:etcd 会清除内存中关于被压缩版本的索引信息。
  2. 防止查询错误:当尝试查询一个已经被压缩的修订号时,etcd 会返回错误,而不是无限期地等待或返回错误的数据。
  3. 为碎片整理做准备:只有先压缩,后续的碎片整理才能回收空间。

自动压缩

对于大多数生产环境,推荐配置自动压缩,以避免人工遗忘。etcd 提供了 --auto-compaction-retention 参数。例如,设置为 1 表示 etcd 将每小时自动执行一次压缩,保留最近一小时的历史数据。

# 启动 etcd 时配置自动压缩,保留 1 小时的历史
$ etcd --auto-compaction-retention=1

手动压缩

你也可以使用 etcdctl 手动执行压缩。这在需要精确控制或进行临时维护时非常有用。

# 获取当前的修订号
$ rev=$(ETCDCTL_API=3 etcdctl endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*')

# 压缩到当前修订号
$ ETCDCTL_API=3 etcdctl compact $rev
compacted revision 1516

执行压缩后,任何尝试读取该修订号之前数据的操作都会失败。

# 尝试读取已被压缩的修订号 2 的数据
$ etcdctl get --rev=2 somekey
Error:  rpc error: code = 11 desc = etcdserver: mvcc: required revision has been compacted

碎片整理 (Defragmentation)

碎片整理是物理上回收磁盘空间的过程。在执行了历史压缩之后,数据在数据库文件中留下了“空洞”。碎片整理会重新组织数据库文件,将这些空洞占用的空间归还给操作系统。

重要警告:碎片整理是一个阻塞操作。在对一个 etcd 成员执行 defrag 命令时,该成员将暂时无法处理读写请求。因此,应谨慎安排碎片整理任务,最好在业务低峰期进行,并确保集群有其他健康节点可以提供服务。

对单个节点进行碎片整理

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

对整个集群进行碎片整理

为了方便操作,etcdctl 提供了 --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 工具直接对数据目录进行碎片整理,这不会影响正在运行的服务(因为服务已停止)。

$ etcdutl defrag --data-dir /var/lib/etcd/default.etcd

空间配额管理

为了防止 etcd 因为数据无限制增长而导致崩溃,etcd 引入了空间配额(Space Quota)机制。默认的配额大小是 2GB。当任何一个节点的数据库文件大小超过这个配额时,etcd 会触发一个名为 NOSPACE 的集群级告警。

一旦 NOSPACE 告警被触发,集群会进入一种特殊的维护模式:

  • 禁止写入:所有会增加数据库大小的写操作(如 put, txn)都将失败。
  • 允许读取和删除:读操作和能够减小数据库大小的删除操作仍然可以正常执行。

这种设计强制管理员必须处理存储空间问题,从而保护集群不会因为磁盘写满而发生不可预测的错误。

配置配额

你可以在启动 etcd 时通过 --quota-backend-bytes 参数来调整配额大小。例如,将其设置为 8GB:

$ etcd --quota-backend-bytes=$((8*1024*1024*1024))

触发与解除告警

我们可以通过一个简单的循环来模拟写满空间,从而触发告警:

# 持续写入数据直到触发 NOSPACE 错误
$ 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

此时,我们可以检查集群状态和告警列表:

# 检查节点状态,可以看到 DB SIZE 已经超过配额
$ 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 |
+----------------+------------------+-----------+---------+-----------+-----------+------------+

# 确认 NOSPACE 告警已被触发
$ ETCDCTL_API=3 etcdctl alarm list
memberID:13803658152347727308 alarm:NOSPACE

要解除告警并恢复集群正常运行,必须执行以下三步操作:

  1. 压缩历史数据:清理掉不再需要的旧版本,为碎片整理创造条件。
  2. 执行碎片整理:物理回收磁盘空间,使数据库文件大小低于配额。
  3. 解除告警:通知 etcd 清除 NOSPACE 状态。
# 1. 压缩到当前最新修订号
$ 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
compacted revision 1516

# 2. 对所有节点执行碎片整理
$ ETCDCTL_API=3 etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]

# 3. 解除告警
$ ETCDCTL_API=3 etcdctl alarm disarm
memberID:13803658152347727308 alarm:NOSPACE

# 4. 验证写入恢复正常
$ ETCDCTL_API=3 etcdctl put newkey 123
OK

监控指标

在监控系统中,关注以下两个指标对于空间管理至关重要:

  • etcd_debugging_mvcc_db_total_size_in_bytes (v3.4+ 为 etcd_mvcc_db_total_size_in_bytes):数据库文件的总物理大小,包括已分配但未使用的空间。
  • etcd_mvcc_db_total_size_in_use_in_bytes:数据库中实际被数据占用的空间大小。

当这两个指标的值都接近配额时,就需要立即执行压缩和碎片整理。

监控指标采集

etcd 暴露了丰富的 Prometheus 格式的监控指标,这是了解集群内部状态、进行性能调优和故障排查的最佳途径。通过采集这些指标,我们可以构建可视化的监控大盘和告警规则。

核心监控指标解读

etcd 的指标非常繁多,以下是一些在维护中最常关注的指标类别:

1. 存储相关指标 (Storage)

这些指标直接反映了数据引擎的健康状况。

  • etcd_debugging_mvcc_current_revision (Gauge): 当前存储的最新修订号。如果这个值长时间不增长,可能意味着写入停滞。
  • etcd_debugging_mvcc_compact_revision (Gauge): 上一次压缩操作完成的修订号。通过对比当前修订号,可以了解历史数据保留的范围。
  • etcd_debugging_mvcc_db_total_size_in_bytes (Gauge): 数据库文件总大小。这是触发 NOSPACE 告警的直接依据。
  • etcd_debugging_mvcc_db_compaction_pause_duration_milliseconds (Histogram): 碎片整理过程中暂停服务的时长。如果这个值的 p99 很高,说明碎片整理对业务影响较大,可能需要调整碎片整理策略或硬件。

2. 网络与 Raft 相关指标 (Network & Raft)

  • etcd_network_peer_round_trip_time_seconds (Histogram): 集群节点之间的网络延迟。高延迟会直接影响 Raft 共识的性能,可能导致提交延迟。
  • etcd_server_leader_changes_seen_total (Counter): 领导者变更次数。频繁的领导者变更通常意味着网络分区或节点故障,是高可用性的重大威胁。

3. 客户端请求相关指标 (Client)

  • etcd_grpc_proxy_cache_hits_total (Counter): gRPC 代理的缓存命中次数。
  • etcd_grpc_proxy_cache_misses_total (Counter): gRPC 代理的缓存未命中次数。
  • etcd_server_proposals_committed_total (Gauge): 已提交的提案总数。
  • etcd_server_proposals_pending (Gauge): 待处理的提案数。如果这个值持续很高,说明集群处理能力跟不上写入速度,可能存在性能瓶颈。

如何采集指标

通常,我们会通过一个 HTTP 端点来暴露这些指标。etcd 默认在客户端 API 端口(默认 2379)上提供 /metrics 路径。Prometheus 可以配置定期从这个端点拉取数据。

scrape_configs:
  - job_name: 'etcd'
    static_configs:
      - targets: ['etcd-host-1:2379', 'etcd-host-2:2379', 'etcd-host-3:2379']

健康检查机制

除了被动的监控告警,主动的健康检查是确保服务可用性的第一道防线。etcd 提供了多种方式进行健康检查。

1. 使用 etcdctl endpoint health

这是最直接的健康检查方式。它会向指定的 etcd 节点发起一个 gRPC 请求,检查其响应状态和集群一致性。

$ ETCDCTL_API=3 etcdctl endpoint health --cluster
http://127.0.0.1:2379 is healthy: successfully committed proposal: took = 2.015341ms
http://127.0.0.1:22379 is healthy: successfully committed proposal: took = 2.035143ms
http://127.0.0.1:32379 is healthy: successfully committed proposal: took = 2.055145ms

这个命令非常适合用于运维脚本或 CI/CD 流程中,用于在执行某些操作前确认集群状态。

2. 使用 etcdctl endpoint status

status 命令提供了更详细的节点信息,对于判断节点角色和数据同步状态非常有用。

$ ETCDCTL_API=3 etcdctl endpoint status --cluster --write-out=table
+----------------+------------------+---------+---------+-----------+-----------+------------+
|    ENDPOINT    |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX |
+----------------+------------------+---------+---------+-----------+-----------+------------+
| 127.0.0.1:2379 | 8214e898557c3d68 |   3.5.0 |   20 kB |      true |         2 |         15 |
| 127.0.0.1:22379 | 6a214e98557c3d68 |   3.5.0 |   20 kB |     false |         2 |         15 |
| 127.0.0.1:32379 | 7a214e98557c3d68 |   3.5.0 |   20 kB |     false |         2 |         15 |
+----------------+------------------+---------+---------+-----------+-----------+------------+

通过这个输出,我们可以快速判断:

  • IS LEADER:哪个节点是当前的领导者。
  • RAFT INDEX:所有节点的 Raft 索引是否一致,这是判断数据同步状态的关键。
  • DB SIZE:各节点的数据库大小是否均衡。

3. 生产环境中的健康检查

在容器化或云原生环境中(如 Kubernetes),etcd 的健康检查通常被集成到探针(Probes)中。

  • Liveness Probe (存活探针):用于判断 etcd 进程是否还在正常运行。可以使用简单的 etcdctl endpoint health 或者检查 2379 端口是否存活。
  • Readiness Probe (就绪探针):用于判断 etcd 节点是否准备好接收请求。这需要更严格的检查,例如检查节点是否是集群的 Follower 或 Leader,并且没有处于 NOSPACE 或 UNAVAILABLE 等异常状态。

Debug 端点使用

当监控和健康检查发现问题,或者遇到难以解释的性能瓶颈时,etcd 提供的 Debug 端点(Debug Endpoints)就是深入调查的利器。这些端点默认不开启,需要通过 --listen-client-urls 和 --listen-peer-urls 暴露给外部访问。

开启 Debug 端点

要启用 Debug 功能,需要在启动 etcd 时设置环境变量 GODEBUG=allocs=1 并暴露相应的端口。但更常用和安全的方式是利用 etcd 内置的 /debug/pprof/ 端点,它提供了标准的 Go runtime profiling 数据。

通常,我们会为 etcd 配置一个额外的、不对外公开的监听地址,专门用于调试。

# 示例:在启动时暴露一个用于调试的客户端 URL
$ etcd --listen-client-urls=http://127.0.0.1:2379,http://127.0.0.1:9999 --advertise-client-urls=http://127.0.0.1:2379,http://127.0.0.1:9999

使用 pprof 分析性能

Go 的 pprof 工具是分析 Go 程序性能的标准工具。通过访问 http://127.0.0.1:9999/debug/pprof/,你可以获取多种性能分析数据:

  • CPU Profile:获取 CPU 使用情况的火焰图,帮助定位消耗 CPU 最多的函数。
    # 采集 30 秒的 CPU profile
    $ go tool pprof http://127.0.0.1:9999/debug/pprof/profile?seconds=30
    
  • Heap Profile:分析内存分配情况,查找内存泄漏或内存占用过高的原因。
    $ go tool pprof http://127.0.0.1:9999/debug/pprof/heap
    
  • Goroutine Profile:查看当前的 Goroutine 数量和状态,用于排查死锁或 Goroutine 泄漏。
    $ go tool pprof http://127.0.0.1:9999/debug/pprof/goroutine
    

其他 Debug 端点

除了 pprof,etcd 还提供了一些其他有用的端点:

  • /version:打印 etcd 服务器的版本信息。
  • /metrics:如前所述,提供 Prometheus 格式的监控指标。
  • /health:一个简单的 HTTP 健康检查端点,返回 JSON 格式的健康状态。

通过结合使用这些 Debug 工具,运维人员可以像外科医生一样精确地诊断 etcd 集群的深层问题,从而进行有效的修复和优化。


本章详细介绍了 etcd 的日常维护、监控和健康检查机制。从逻辑上的历史压缩到物理上的碎片整理,再到空间配额的管理和核心监控指标的解读,这些构成了 etcd 稳定运行的基石。掌握这些技能,能够帮助我们主动地管理集群,而不是被动地响应故障。下一章,我们将聚焦于当这些维护措施未能阻止问题发生时,如何进行故障处理与灾难恢复。