
做运维这么多年我越来越觉得 etcd 属于那种“平时没存在感出事要救命”的组件。Kubernetes 集群的状态、微服务的注册发现、分布式锁底层基本都会落到 etcd 上。版本到了 3.5.15稳定性和性能比早期版本成熟很多但数据备份与恢复这件事依然得靠运维自己把关什么时候打快照、怎么校验、真把集群搞挂了能不能快速拉起来。这篇文章就把我在 Linux 环境下维护 etcd 3.5.15 集群时沉淀下来的备份恢复流程完整写出来包括数据落盘机制、快照命令、脚本化、三种故障场景的处理以及恢复后的验证。适合正在维护 etcd 集群或者刚接手一套没有文档的 etcd 集群的运维同学。1. 先弄清 etcd 3.5.15 的数据是怎么落盘的再谈备份很多事故不是没备份而是备份了不会恢复或者更惨——备份方法从一开始就是错的。要避免这种局面第一步不是急着敲命令而是理解 etcd 的数据存储方式。1.1 数据目录下到底有什么etcd 本质上是一个强一致性的键值存储系统数据以多副本形式存放在集群各节点的数据目录里。它并不是“一个文件就存了整个数据库”而是由多种文件协作完成持久化。启动参数里的--data-dir决定了数据目录位置通常默认是/var/lib/etcd/default.etcd如果是 kubeadm 部署的 Kubernetes一般放在/var/lib/etcd下面。3.5.x 版本的数据目录内部大致包含member/snap存放已经压缩过的历史快照文件。member/wal预写日志按 segment 文件保存记录所有写操作。member/db当前状态机对应的 bolt 数据库文件是整个 etcd 数据最核心的文件。理解这个结构很重要。etcd 的数据并不是一口气写进 DB 的而是先写 WAL再在内存维护 raft 状态机并定期做快照压缩。如果只盯着某一个文件去拷贝很容易漏掉关键部分恢复出来的数据也不可用。1.2 snapshot、WAL、MVCC 与压缩的关系etcd 内部是典型的 MVCC 机制每次 put/delete 操作都会产生一个新的修订版本 revision这些历史版本默认不会立刻删除。为了控制磁盘占用etcd 会定期执行 compaction把某个 revision 之前的历史版本丢弃。另外raft 层还会在 WAL 积累到一定数量后触发 snapshot把当前状态机的数据落到db文件里同时清理旧 WAL。这里有一个非常关键的结论通过备份只能拿到“某个一致时刻”的数据拿不到所有历史时刻。compaction 一旦执行旧版本数据就是被真正丢弃了任何备份都救不回来。所以备份策略里有一条常见建议不要把自动压缩周期调得太激进也不要为了省空间把--snapshot-count调得过小。3.5.15 版本里如果同时开了自动压缩和大量高频写入数据保留窗口会比想象中短得多这时再谈备份恢复就要先想清楚“恢复到什么时间点”是可接受的。1.3 备份的本质拿到一个“一致的时刻”etcd 的备份尤其是用快照工具做的备份核心目标就是拿到集群在某个时刻逻辑一致的状态。一个节点的db文件在运行过程中是持续被写入的直接拷贝它很可能拷贝到一半文件处于中间状态校验失败。这种情况我见过不止一次rsync 一个正在运行的三节点 etcd 数据目录看着文件在增长拷贝完以为万事大吉恢复时才发现根本起不来。而etcdctl snapshot save这类工具是从 raft 层拿一份一致性快照背后由 leader 协调完成不会得到“写了一半”的数据。这也是为什么标题里的操作看起来简单真正理解原理之后你才会明白为什么不能用粗暴方式代替。2. 备份方案选型为什么不是“把目录打包”就完事我在排查别人给过的备份脚本时见过至少三种“自创”方案直接 tar 数据目录、用 rsync 同步/var/lib/etcd、以及用etcdctl做快照。前面两种不是完全不能用但有很严格的前提条件不少同学不看条件直接拷最后恢复时才发现文件不完整。2.1 三种常见备份手段对比我用一个表格列一下实际运维里常用的方法方案是否需要停写一致性适用场景tar/rsync 直接拷贝数据目录建议停止写入低存在 WAL/DB 不同步风险单节点临时容灾或已下线节点在每个节点分别备份 WAL db不需要中等要处理 raft 状态匹配对一致性要求不高的测试环境etcdctl snapshot save不需要高基于 raft 的一致性快照生产环境首选真正生产环境我只会用etcdctl snapshot save其他方案顶多作为补充。如果你非要在某个节点上直接拷贝文件应当在 etcd 停止服务后拷贝而不是运行中拷贝否则大概率得到一份没法正常 restore 的数据。2.2 选 etcdctl snapshot 的核心理由etcdctl snapshot成为事实标准是因为它在备份时走了 etcd 自己的一致性协议。它会与集群里的端点建立连接从 raft 状态机里拿一份完整快照快照文件包含了当时的全部 key-value、所有 revision 信息和必要元数据。恢复时再通过snapshot restore把文件还原成一份新的数据目录。它还有一个容易被忽略的好处备份不需要停止服务对线上读写的影响通常很小。只要集群本身不是处于极端负载下几秒钟到几十秒就能打完一个快照。这和“停机拷贝”完全是两种运维体验对业务连续性要求高的环境尤其重要。2.3 集群级灾备节点级快照之外还要做什么如果你有 3 个 etcd 节点只对其中一个节点做了快照够不够如果这个节点上的数据已经落后于 leader快照可能不是最新。更稳妥的做法是用--endpoints同时指定多个节点或者至少指定当前 leader 所在地址。命令执行时 etcdctl 会自己找到合适的成员去拉数据。换句话说备份动作应该是“集群级”的不是“某个节点局部状态”。除此之外还要把集群的静态配置一起备份成员地址、initial-cluster节点列表、证书文件、启动参数。否则快照是有了但恢复的时候连集群成员关系都拼不回来只能对着报错发愣。3. 动手打快照环境变量、命令参数与定时任务这一部分直接给可抄的作业。假设 etcd 是 3.5.15运行在 Linux 上数据端口 2379peer 端口 2380。我以最常见的非 TLS 集群为例再补充启用 TLS 时要注意的写法。3.1 命令前必须确认的几件事打快照之前先确认环境变量和访问方式。etcd 3.5 的 etcdctl 已经默认走 v3 API但在脚本或手动操作里我习惯性加上ETCDCTL_API3避免历史遗留的 v2 环境变量干扰。然后确认能连上 endpointexport ETCDCTL_API3 etcdctl --endpointshttp://127.0.0.1:2379 endpoint health如果输出显示127.0.0.1:2379 is healthy说明连通性没问题。生产环境如果启用了证书需要把证书路径一起带上去etcdctl --endpointshttps://10.0.0.1:2379 \ --cacert/etc/etcd/ca.pem \ --cert/etc/etcd/server.pem \ --key/etc/etcd/server-key.pem \ endpoint health注意证书参数写错时命令大概率会卡在 TLS 握手阶段报错信息可能只有一句context deadline exceeded。这时候先不要怀疑网络先去检查证书路径和文件权限。3.2 手动快照与校验确认健康之后就可以打快照了mkdir -p /backup/etcd etcdctl --endpointshttp://10.0.0.1:2379,http://10.0.0.2:2379,http://10.0.0.3:2379 \ snapshot save /backup/etcd/etcd-snapshot-$(date %Y%m%d%H%M%S).db执行成功后命令行会提示Snapshot saved at ...。注意 endpoints 里多写几个成员。3.5.x 的 etcdctl 在 snapshot save 时会从给定端点里找可用成员拉取如果一个节点不可达且刚好被选中命令可能直接失败或长时间超时所以至少给出两个健康端点。打完快照别急着走立刻做校验etcdctl --endpointshttp://127.0.0.1:2379 \ snapshot status /backup/etcd/etcd-snapshot-20250101120000.db -w table输出里能看到哈希值、修订版本、key 数量和数据库大小。如果哈希值无法计算或者命令报错说 snapshot file is corrupted这份备份就不能算有效备份必须排查重打。这里有个实操技巧我会顺手用sha256sum给快照文件再生成一个校验文件方便后面传到异地备份服务器时做完整性比对。3.3 备份脚本和保留轮转手动打快照只能应对临时需求生产环境要的是定时任务。我通常会把快照和校验写进同一个脚本然后在 crontab 中调度#!/bin/bash set -euo pipefail BACKUP_DIR/backup/etcd ENDPOINTShttp://10.0.0.1:2379,http://10.0.0.2:2379,http://10.0.0.3:2379 KEEP_DAYS7 mkdir -p $BACKUP_DIR SNAP_FILE${BACKUP_DIR}/etcd-snapshot-$(date %Y%m%d-%H%M%S).db etcdctl --endpoints$ENDPOINTS snapshot save $SNAP_FILE etcdctl snapshot status $SNAP_FILE -w table find $BACKUP_DIR -name etcd-snapshot-*.db -mtime $KEEP_DAYS -delete echo $(date %Y-%m-%d %H:%M:%S) backup finished: $SNAP_FILE /var/log/etcd-backup.log这里有一个我看过很多人踩的坑只写 snapshot save不写校验过了两星期才发现备份文件一直生成失败。crontab 定时任务的标准输出和错误输出如果不重定向到日志失败原因也很容易丢失。所以脚本里一定要做状态校验并且把输出写到固定日志文件里。同时检查一下备份目录所在磁盘的剩余空间快照文件通常几十 MB 到几个 GB 不等如果磁盘写满快照会静默失败或者生成半个文件。4. 恢复实操全集群重建、单节点故障和数据误删怎么处理恢复并没有一套命令走天下的方案。现实中的故障大概可以分成三类整个集群数据都没了、某一个节点挂掉但其他节点还活着、以及逻辑误删。先按场景拆开你才知道该不该用 snapshot restore。4.1 全集群丢失用 snapshot restore 重建如果三台机器全部无法恢复数据目录也损坏这时要做的不是直接启动 etcd而是用之前打好的快照重建。先把快照文件拷贝到一台机器上然后逐个节点执行 restore。先看单个节点etcdctl snapshot restore /backup/etcd/etcd-snapshot-20250101120000.db \ --name etcd-1 \ --initial-cluster etcd-1http://10.0.0.1:2380,etcd-2http://10.0.0.2:2380,etcd-3http://10.0.0.3:2380 \ --initial-cluster-token etcd-restore-cluster \ --initial-advertise-peer-urls http://10.0.0.1:2380 \ --data-dir /var/lib/etcd-restore恢复命令会根据快照生成一份全新的数据目录并把当前节点的 member id、集群 id 都重设。三个节点都执行 restore 之后再把 etcd 启动参数里的>etcdctl member list -w table etcdctl member remove 节点ID然后清空坏节点的数据目录重新以新成员身份加入集群。新节点启动后raft 协议会从 leader 同步全量数据到新成员无需手动 restore 快照。这个流程比 restore 命令更安全也是生产环境处理单节点故障的标准思路。4.3 数据误删能不能用快照找回确有需要时快照可以找回误删的数据但代价是时间点丢失。快照是某一时刻的完整状态不是数据库 binlog没有“恢复到误删前一刻”的能力。比如你 2 点打的快照3 点误删了一大堆 key然后用快照恢复那 2 点到 3 点之间的正常写入也会一起丢掉。所以在执行恢复前先确认几个问题误删影响范围有多大、上层服务能不能接受短暂停写、快照中数据落后到什么程度。如果只删了几个 key优先用etcdctl get去查当前集群是否还有副本残留或者把旧快照单独 restore 到一个临时目录再用 get 把 key 捞出来写回而不是直接全量覆盖线上集群。等真正决定要全量回滚时再走 4.1 的流程。5. 恢复完成后的验证与翻车点快照 restore 成功只是第一步真正危险的是恢复完成后业务发现了异常而你检查了半天才发现问题不在应用而在 etcd 状态不干净。恢复后必须按固定顺序验证并且敏锐识别几个经典翻车点。5.1 endpoint health、member list 和读写验证起完 etcd 之后先检查健康状态etcdctl --endpointshttp://127.0.0.1:2379 endpoint health --cluster etcdctl --endpointshttp://127.0.0.1:2379 member list -w table etcdctl --endpointshttp://127.0.0.1:2379 endpoint status -w tableendpoint status 表格中的DB SIZE应该与快照文件大小在同一量级RAFT INDEX和RAFT APPLIED INDEX差距过大说明数据正在同步等追平后再进一步操作。光看状态还不够必须做一次真实读写验证。放一个带时间前缀的测试 key再取出来etcdctl put /health-check $(date %s) etcdctl get /health-check写不进去大概率是数据目录权限或者磁盘空间问题。如果写能成功但有成员读不到多半是成员之间 raft 状态还没追上稍等片刻再看。5.2 鉴权、证书和静态 Pod 场景下的恢复注意如果集群开启了 RBAC 和 root 用户快照恢复后的数据里包含原来的用户和角色定义这点通常不丢。但有个细节容易被忽略恢复出来的新集群是“全新”集群元数据客户端证书的信任关系如果依赖集群 ID 或成员身份可能需要重新处理尤其在很多 Kubernetes 静态 Pod 的部署里。在 K8s 部署中apiserver 的 etcd 连接串、证书路径、静态 Pod manifest 通常由 kubeadm 生成。恢复 etcd 数据目录后必须确认 etcd Pod 挂载的>