ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

etcd-dump-logs 使用指南:解析 etcd WAL 日志与数据目录故障排查实战

etcd-dump-logs 使用指南:解析 etcd WAL 日志与数据目录故障排查实战 etcd-dump-logs 使用指南解析 etcd WAL 日志与数据目录故障排查实战【免费下载链接】etcdDistributed reliable key-value store for the most critical data of a distributed system项目地址: https://gitcode.com/GitHub_Trending/et/etcdetcd 的持久化可靠性来自两套底层文件以快照snapshot为周期的历史状态以及记录每个 Raft 提交条目的预写日志 WALWrite-Ahead Log。当集群出现成员变更异常、事务回放疑点、数据不一致或需要审计某个请求究竟在什么 term、什么 index 被落盘时直接阅读二进制 WAL 文件几乎不可能。etcd-dump-logs正是 etcd 官方提供的 WAL 日志转储与分析工具它位于仓库 tools/etcd-dump-logs 目录职责是从数据目录中读取快照与 WAL并将其中的 Raft 条目翻译成人类可读的文本、过滤出指定类型如事务、压缩、租约授权的条目、甚至把每条原始数据喂给外部解码器做二次解析。读完本文你将掌握该工具的安装方式、全部命令行参数的含义与默认值、各类条目的过滤语义以及如何借助源码与测试用例来验证你手中 WAL 文件的真实状态。工具定位从数据目录到可读日志etcd-dump-logs的文档自述非常简洁dumps the log from data directory。在 etcd 中log 指的是 Raft 复制状态机持久化下来的日志条目而这些日志存放在成员的数据目录data dir下。工具约定的数据目录结构如下data_dir/member ├── data_dir/member/snap └── data_dir/member/wal └── data_dir/member/wal/0000000000000000-0000000000000000.wal其中member/wal下是以{term}-{index}.wal命名的分段日志文件由 server/storage/wal 目录管理member/snap存放快照。从实现上看程序入口 tools/etcd-dump-logs/main.go 把dataDir/member/wal/作为默认 WAL 路径walDir()函数通过filepath.Join(dataDir, member, wal)拼出再用 server/storage/wal 的wal.OpenForRead打开日志。文档 tools/etcd-dump-logs/doc.go 将该工具定义为用于分析 etcd server 预写日志的程序这也是它的典型使用场景离线、只读地解剖某个 etcd 数据目录不影响运行中的集群。安装与运行三种方式任选在 etcd 源码根目录下执行go install即可把可执行文件安装到本机$ go install -v ./tools/etcd-dump-logs安装完成后可执行文件位于$GOPATH/bin。如果$GOPATH环境变量未设置则会被安装到$HOME/go/bin。若不确定实际安装位置可在 etcd 源码目录下运行下面这条命令查询并据此调整$PATH$ go list -f {{.Target}} ./tools/etcd-dump-logs如果不想真正安装也可以直接从源码目录运行效果等价$ go run ./tools/etcd-dump-logs注意该工具依赖仓库的多模块工程go.workgo install/go run均应在本仓库源码根目录内执行否则会因依赖模块如go.etcd.io/etcd/server/v3、go.etcd.io/raft/v3无法解析而失败。基本用法与帮助输出工具必须且只接受一个位置参数——数据目录。直接运行会打印详细帮助以下为当前源码版本对应的帮助内容$ etcd-dump-logs --help Usage: etcd-dump-logs [data dir] * Data dir is where the snapshots and WAL logs are located. The structure of the data dir should look like this: - data_dir/member - data_dir/member/snap - data_dir/member/wal - data_dir/member/wal/0000000000000000-0000000000000000.wal Flags: -end-index uint The index to stop dumping (exclusive) (default 18446744073709551615) -entry-type string If set, filters output by entry type. Must be one or more than one of: ConfigChange, Normal, Request, InternalRaftRequest, IRRRange, IRRPut, IRRDeleteRange, IRRTxn, IRRCompaction, IRRLeaseGrant, IRRLeaseRevoke, IRRLeaseCheckpoint (default Normal,ConfigChange) -raw Read the logs in the low-level form -start-index uint The index to start dumping (inclusive). If unspecified, dumps from the index of the last snapshot. -stream-decoder string The name of an executable decoding tool, the executable must process hex encoded lines of binary input (from etcd-dump-logs) and output a hex encoded line of binary for each input line -wal-dir string If set, dumps WAL from the informed path, rather than following the standard data_dir/member/wal/ location各参数要点整理如下参数类型默认值作用data dir位置参数必填存放member/snap与member/wal的数据目录-entry-typestringNormal,ConfigChange按条目类型过滤输出支持逗号分隔的多种类型-start-indexuint0起始转储 index含未指定时从最近一次快照的 index 开始-end-indexuint18446744073709551615即math.MaxUint64结束转储 index不含-rawboolfalse以底层原始形式读取日志-stream-decoderstring空指定一个可执行解码器逐条处理二进制输入并回写结果-wal-dirstring空指定 WAL 目录覆盖默认的data_dir/member/wal/值得注意的细节有两点-end-index默认值是 64 位无符号整数的最大值main.go 中直接由math.MaxUint64提供代表一直读到末尾而-start-index的默认值 0 只是占位是否真的从 index 0 开始取决于它是否出现在命令行上源码用flag.Visit探测该 flag 是否被显式设置见 main.go。另外若没有传入唯一的 data dir 参数程序会直接以Must provide>$ etcd-dump-logs /tmp/datadir Snapshot: empty Start dumping log entries from snapshot. WAL metadata: nodeID0 clusterID0 term0 commitIndex0 vote0 WAL entries: 34 lastIndex34 term index type data 1 1 conf methodConfChangeAddNode id2 2 2 conf methodConfChangeRemoveNode id2 ... 27 34 norm ??? Entry types (Normal,ConfigChange) count is : 34上面的conf表示成员配置变更ConfigChangenorm表示普通Normal条目无法反序列化为任何已知 protobuf 消息的 Normal 条目会以???呈现。按条目类型过滤-entry-type是最常用的过滤参数。命令格式为etcd-dump-logs -entry-type ENTRY_TYPE_NAME(S) [data dir]它支持的类型来自 main.go 中注册的过滤器集合。这些类型并非完全并列理解其语义分组有助于精确检索类型含义与匹配范围ConfigChange匹配 Raft 的EntryConfChange即节点添加/移除/更新/添加 Learner 等成员变更条目Normal默认组之一。展开为能解析为InternalRaftRequest的条目 ∪ 遗留 v2 请求 ∪ 无法解析的普通条目Request仅匹配遗留 v2 请求v2 API 时代的请求现已不支持输出为v2 requestInternalRaftRequest所有能反序列化为InternalRaftRequest的 Normal 条目IRRRange/IRRPut/IRRDeleteRange/IRRTxn分别匹配内部 Raft 请求中的 Range、Put、DeleteRange、Txn 四种操作IRRCompaction匹配压缩compaction请求注意physical:true表示物理删除历史版本IRRLeaseGrant/IRRLeaseRevoke/IRRLeaseCheckpoint匹配租约授权、吊销与检查点请求默认值Normal,ConfigChange覆盖了绝大多数常见条目。当用户只关心某类操作时可用逗号组合例如同时导出事务与成员变更。从实现上看evaluateEntrytypeFlag 会把字符串按,切分并查表获得一组EntryFilter谓词遇到无法识别的类型名会打印告警并忽略。下面是一个只过滤事务条目的例子$ etcd-dump-logs -entry-type IRRTxn /tmp/datadir Snapshot: empty Start dupmping log entries from snapshot. WAL metadata: nodeID0 clusterID0 term0 commitIndex0 vote0 WAL entries: lastIndex34 term index type data 7 13 norm ID:8 txn:success:request_delete_range:key:a range_end:k8s\000\n\025\n\002v1\022\017RangeAllocation\022#\n\022\n\000\022\000\032\000\\000*\0002\0008\000B\000z\000\022\01310.0.0.0/16\032\000\032\000\\000 failure:request_delete_range:key:a range_end:k8s\000\n\025\n\002v1\022\017RangeAllocation\022#\n\022\n\000\022\000\032\000\\000*\0002\0008\000B\000z\000\022\01310.0.0.0/16\032\000\032\000\\000 Entry types (IRRTxn) count is : 1再例如组合过滤配置变更与压缩条目$ etcd-dump-logs -entry-type ConfigChange,IRRCompaction /tmp/datadir Snapshot: empty Start dupmping log entries from snapshot. WAL metadata: nodeID0 clusterID0 term0 commitIndex0 vote0 WAL entries: lastIndex34 term index type data 1 1 conf methodConfChangeAddNode id2 2 2 conf methodConfChangeRemoveNode id2 2 3 conf methodConfChangeUpdateNode id2 2 4 conf methodConfChangeAddLearnerNode id3 8 14 norm ID:9 compaction:physical:true Entry types (ConfigChange,IRRCompaction) count is : 5示例中的dupmping为原文档笔误实际输出为 dumping。各类型的底层匹配逻辑源码中每种类型对应一个passXxx谓词统一签名func(entry *raftpb.Entry) (bool, string)。它们揭示了类型的真实划分passConfChange判断entry.Type raftpb.EntryConfChangepassInternalRaftRequest尝试把entry.Data反序列化为etcdserverpb.InternalRaftRequest成功即为真passRequest恰好相反——解析InternalRaftRequest失败说明这是 v2 遗留请求passUnknownNormal与passRequest条件相同但返回类型名UnknownNormal用于在默认过滤下兜底打印???各passIRRXxx则在成功反序列化的基础上继续检查对应字段是否为非空例如passIRRTxn要求rr.Txn ! nil见 main.go。输出时类型映射到不同打印器EntryPrinterInternalRaftRequest走 printInternalRaftRequest用 protobuf 的String()展示请求内容ConfigChange走 printConfChange展示method... id...UnknownNormal走printUnknownNormal打印???。打印内部请求时有一个安全细节如果请求是修改用户密码AuthUserChangePassword且密码非空输出会替换为value removed避免把敏感口令泄露到日志或终端。这一点在 测试生成的数据 与expectedoutput/listAll.output中均有体现。按 index 范围转储指定起始 index-start-index INDEX表示只打印从该 index 起含的 WAL 条目$ etcd-dump-logs -start-index 31 /tmp/datadir Start dumping log entries from index 31. WAL metadata: nodeID0 clusterID0 term0 commitIndex0 vote0 WAL entries: lastIndex34 term index type data 25 31 norm ID:26 auth_role_get:role:role3 26 32 norm ID:27 auth_role_grant_permission:name:role3 perm:permType:WRITE key:Keys range_end:RangeEnd 27 33 norm ID:28 auth_role_revoke_permission:role:role3 key:key range_end:rangeend 27 34 norm ??? Entry types () count is : 4实现上有个容易踩坑的细节wal.ReadAll()会从walsnap.Index之后开始读取因此代码在收到-start-index后会先把它减 1 再当作walsnap.Index从而保证输出恰好包含所请求的 index见 main.go。同时若从该 index 找不到快照wal.ErrSnapshotNotFound程序不会报错退出而是容忍跳过。起始与结束组合-start-index与-end-index一起使用时打印从 start含到 end不含区间内的条目$ etcd-dump-logs -start-index 930 -end-index 932 /tmp/datadir Start dumping log entries from index 930. WAL metadata: nodeID0 clusterID0 term5 commitIndex2448 vote0 WAL entries: 2 lastIndex931 term index type data 3 930 norm header:ID:11010058442592651283 put:key:key7 value:923 3 931 norm header:ID:6577953459306661672 put:key:key8 value:924 Entry types (Normal,ConfigChange) count is : 2注意上例中 WAL 总条目多于输出条数WAL entries: 2表示读取到该范围内恰好两条。-end-index的过滤逻辑在 main.go 中有明确注释——由于 WAL 中可能先出现前一个 term 里 index 大于等于 end的条目再出现当前 term 里 index 小于 end的条目因此遇到超范围条目只能continue跳过而不能提前break否则会漏掉合法区间内的条目。原始raw模式低层字节视角当需要越过 protobuf 语义层、直接查看 WAL 的物理记录结构时使用-raw$ etcd-dump-logs -raw /tmp/datadir原始模式的实现位于 raw.go。它会扫描 WAL 目录下所有.wal文件非.wal后缀文件会被告警忽略用wal.NewDecoderAdvanced(true, ...)逐条解码并区分输出以下几类记录CRC: n记录级校验值遇到 CRC 不匹配时会报告并尝试用后续 CRC 记录重新同步Metadata: ...WAL 元数据消息Snapshot: index:... term:...快照落盘记录HardState: ...Raft 硬状态term/vote/commitEntry: Term:... Index:... Type:... Data:...真正的条目其中Data采用与 v1 行为一致的八进制转义字符串输出raw.go中的printRec会把可打印字符原样输出、控制字符转义成\n/\r/\t其余按\%03o八进制转义。读取结束时若正常抵达文件尾部会打印EOF: All entries were processed.若最后一条记录不完整则打印ErrUnexpectedEOF: ...提示可能存在损坏记录。此外 raw 模式只支持-start-index与-wal-dir如果同时传入-entry-type、-stream-decoder等参数会直接报错退出见 main.go。外部解码器stream-decoder自定义二次解析etcd-dump-logs内置的 protobuf 渲染已经可读但对加密、压缩或私有格式的数据就无能为力了。-stream-decoder允许挂载一个外部可执行程序逐条解码etcd-dump-logs -stream-decoder EXECUTABLE_DECODER [data dir]工作方式如下对应 main.go 中的listEntriesType对每个被过滤出来的条目etcd-dump-logs把其原始entry.Data以十六进制编码 换行的形式写入解码器的标准输入解码器必须逐行读取并针对每一行输入输出一行十六进制编码的结果输出中解码器状态与解码数据分列显示。为了便于机器解析约定解码器输出格式为DECODER_STATUS|DECODED_DATA即用|分隔状态与数据若解码器输出不包含|则decoder_status固定为decoder output format is not right, print output anyway其全部输出都被当作decoded_data若包含多个|状态后还会附加(*WARNING: data might contain deliminator used by etcd-dump-logs)提示见 parseDecoderOutput。仓库 tools/etcd-dump-logs/testdecoder 提供了两个参考脚本decoder_correctoutputformat.sh按输入长度是否 ≥20 决定返回OK|...还是ERROR|...并借助tr 1234567890 abcdefghij把十六进制串中数字替换成字母模拟混淆化解码decoder_wrongoutputformat.sh直接回显经tr替换的行不输出状态|前缀用来演示格式异常时工具的行为。使用正确格式解码器的示例输出$ etcd-dump-logs -stream-decoder decoder_correctoutputformat.sh /tmp/datadir Snapshot: empty Start dupmping log entries from snapshot. WAL metadata: nodeID0 clusterID0 term0 commitIndex0 vote0 WAL entries: lastIndex34 term index type data decoder_status decoded_data 1 1 conf methodConfChangeAddNode id2 ERROR jhjaajjjahjbbbjj 3 2 norm noop OK jhjjabjjaajfbfgjfagdfhcjbbahgbbbfhfegibbcabbfhffbbbcbbfhfibbcaebbbgiffbbedgdbhjacbjjchjjdjjjdhjiejjjehjafjjjfhjjgjjjghjahjjajjhhjajj 3 3 norm methodQGET path/path1 OK jhjaabjdeadgdeedaajfbfgjfagdfhcabbacgbbbcjbbcabbcabbbcbbcbbbcaebbbccbbedgdbhjjcbjjchjjdjjjdhjiejjjehjafjjjfhjjgjjjghjahjjajjhhjajj 7 4 norm ID:8 txn:success:request_delete_range:key:a range_end:b failure:request_delete_range:key:a range_end:b OK jhjhcbadabjhaajfjajafaabjafbaajhaajfjajafaabjafb 8 5 norm ID:9 compaction:physical:true ERROR jhjicajbajja 9 6 norm ID:10 lease_grant:TTL:1 ID:1 ERROR jhjadbjdjhjaajja 12 7 norm ID:13 auth_enable: ERROR jhjdcbcejj 27 8 norm ??? ERROR cf Entry types () count is : 8而挂载错误格式解码器时decoder_status列会变为固定的告警文案$ etcd-dump-logs -stream-decoder decoder_wrongoutputformat.sh /tmp/datadir Snapshot: empty Start dupmping log entries from snapshot. WAL metadata: nodeID0 clusterID0 term0 commitIndex0 vote0 WAL entries: lastIndex34 term index type data decoder_status decoded_data 1 1 conf methodConfChangeAddNode id2 decoder output format is not right, print output anyway jhjaajjjahjbbbjj 3 2 norm noop decoder output format is not right, print output anyway jhjjabjjaajfbfgjfagdfhcjbbahgbbbfhfegibbcabbfhffbbbcbbfhfibbcaebbbgiffbbedgdbhjacbjjchjjdjjjdhjiejjjehjafjjjfhjjgjjjghjahjjajjhhjajj 3 3 norm methodQGET path/path1 decoder output format is not right, print output anyway jhjaabjdeadgdeedaajfbfgjfagdfhcabbacgbbbcjbbcabbcabbbcbbcbbbcaebbbccbbedgdbhjjcbjjchjjdjjjdhjiejjjehjafjjjfhjjgjjjghjahjjajjhhjajj 7 4 norm ID:8 txn:success:request_delete_range:key:a range_end:b failure:request_delete_range:key:a range_end:b decoder output format is not right, print output anyway jhjhcbadabjhaajfjajafaabjafbaajhaajfjajafaabjafb 8 5 norm ID:9 compaction:physical:true decoder output format is not right, print output anyway jhjicajbajja 9 6 norm ID:10 lease_grant:TTL:1 ID:1 decoder output format is not right, print output anyway jhjadbjdjhjaajja 12 7 norm ID:13 auth_enable: decoder output format is not right, print output anyway jhjdcbcejj 27 8 norm ??? decoder output format is not right, print output anyway cf Entry types () count is : 8从输出可见凡是原始数据长度不足 20 字节的短条目如空auth_enable、短compaction都会被示例解码器标记为ERROR这正是设计者用来演示解码状态随内容变化的典型素材。指定 WAL 目录-wal-dir允许跳过标准的data_dir/member/wal/布局直接指向一个自定义 WAL 路径。这在以下场景非常有用数据目录被手工整理过、WAL 被单独备份出来、或成员目录命名不符合规范。该参数在 main.go 中定义标准模式与 raw 模式均受支持代码会在walDir(dataDir)之前优先使用该值main.go。注意它只管 WAL快照目录仍按data_dir/member/snap/推断且snapDir()函数定义于 main.go。源码级实现要点与测试印证结合 main.go 可以把上述行为串成一条完整流水线解析 flag 与 data dir根据是否指定-raw分支标准分支进入 readUsingReadAll没有-start-index时先用latestSnapshot读取最近有效快照main.go并以快照为起点随后wal.OpenForReadReadAll得到 WAL 元数据、HardState 与条目切片打印表头后由 listEntriesType 完成过滤 → 打印 →可选喂解码器的循环最后统计命中数读取错误按场景分三类容忍-start-index场景下的wal.ErrSnapshotNotFound、设置了-end-index时的wal.ErrSliceOutOfRange离线较久后跟随 leader 快照回放导致的正常现象其余一律 fatal。测试代码 etcd-dump-log_test.go 覆盖了与文档逐条对应的 15 种参数组合——从不指定 entry-type到各种单类型、组合类型以及两种解码器脚本——每一种都与 expectedoutput 目录下的.output基准文件做逐字节比对空格被替换为_后再比较规避历史遗留的尾部空白问题。测试的 WAL 由mustCreateWALLog现场构造先用wal.Create建库再依次追加 4 条成员配置变更条目AddNode/RemoveNode/UpdateNode/AddLearnerNodeindex 1–4、22 条覆盖 Range/Put/DeleteRange/Txn/Compaction/LeaseGrant/LeaseRevoke/Alarm/各类 Auth 操作的InternalRaftRequest条目index 10–33恰好对应 README 示例中ID:5至ID:28最后补一条无法解析的?数据条目index 34由w.Save落盘。对照expectedoutput/listAll.output可以看到auth_user_change_password的密码被替换为value removedindex 34 的未知数据渲染为???v2 风格请求在测试数据中表现为noop、QGET、SYNC、DELETE、RANDOM等字符串这些method常量定义在 main.go属于 legacy v2 的请求打印逻辑。而 raw 模式的 raw_test.go 验证了逐条CRC、Metadata、Snapshot、Entry含八进制转义数据直到EOF的完整物理输出。注意TestEtcdDumpLogEntryType依赖同目录下已编译好的etcd-dump-logs二进制若二进制不存在测试会直接 skip常规测试脚本默认也不会触发它因此想复现文档示例时建议先在源码根目录go install ./tools/etcd-dump-logs或go build生成二进制再运行测试$ go build -o etcd-dump-logs ./tools/etcd-dump-logs go test ./tools/etcd-dump-logs/实战建议与注意事项综合文档与源码使用etcd-dump-logs时有几点值得记住先快照后日志未指定-start-index时工具默认从最近快照继续因此输出不会包含已被快照折叠掉的旧条目若要看完整历史需要显式给定起始 index并做好可能得到ErrSliceOutOfRange且日志只打印一部分的心理准备。类型名大小写敏感-entry-type的参数必须与帮助中的拼写完全一致如IRRTxn、ConfigChange否则会被当作无效类型忽略并告警。敏感信息保护修改密码类请求的密码字段输出前会被替换为value removed但其余请求体key、value、用户名、角色权限等会明文展示排查生产环境日志时仍需注意脱敏。raw 模式是二进制勘察利器当怀疑 WAL 文件本身损坏CRC 失败、记录截断时raw 模式能给出比语义层更接近底盘的诊断信息包括 CRC 同步、ErrUnexpectedEOF提示与精确的 term/index/八进制数据。组合使用覆盖审计需求例如审计该数据目录经历过的成员变更可用-entry-type ConfigChange定位某段 index 区间内所有事务可用-start-index/-end-index配IRRTxn需要自定义业务解码则挂-stream-decoder。作为一个离线、只读的诊断工具etcd-dump-logs不需要启动 etcd 进程也不会修改数据目录内容非常适合在集群异常、数据恢复演练或日志审计场景中作为第一手的取证工具使用。【免费下载链接】etcdDistributed reliable key-value store for the most critical data of a distributed system项目地址: https://gitcode.com/GitHub_Trending/et/etcd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表