最近一周一直在查 calico SDN 挂掉的问题, 过程十分艰辛, 回头想了想觉得可以好好反思一下.
太沉迷使用各种工具, 倒不是坏事, 只不过性价比太低了.
在开始怀疑 是自己 restart 服务导致的 之后, 就应该立刻开始进行集群的复刻和复现测试:
- 复刻: 完全相同的机器上, 使用完全相同的配置(除了端口和落盘目录等), 启动几乎等价的集群, 导入完全相同的数据
- 简化: 完全复刻客户端需要启动
calico-felix, 想到一台机器部署两个 felix 是需要技巧的, 不如先简单尝试一下 etcdctl 能否复现, 因为成本低, 就算不能也能快速回到正确的轨道进行完全复刻 - 半跨步: 先复刻客户端, 让复刻版本使用原版服务端, 测试, 然后也好做简化版的复刻
到最后希望是抛掉一切包装的东西, 比如 felix 使用 etcdv3 client, 那么我们应该最后用 etcdctl 复现或者 etcdv3 go client 复现; 网络中间有 etcd gateway, 如果它不是 cause 就应该把它扔掉; 最终得到一个最简可复现过程, 那么 bug 也几乎可以判死刑了.
当然人类难免出错, 我在复刻服务端的时候正好漏掉了最关键的东西导致没能复现, 不过稍后通过只复刻客户端并在简化后的客户端打断点看到了足够的信息让我发现了问题.
比方说刚拿到手的时候根本不可能知道集群发生了什么, 无法推测怎么才能复现, 尝试都没法尝试, 万一是集群放置时间太长导致 TCP 连接被 iptables conntrack drop 了呢, 没法下手的.
可以从几点下手:
- 日志: 如果有报错, 当然很好, 如果没有, 那就对比正常的日志和不正常的日志, 推测
少做了什么事情 - attach 进程, 当然这对编译时 flag 有要求, 不过万一可以 attach 的话, 结合源码去看
应该做什么事情却没做或者含糊不清的报错日志到底在说啥, 断点打上去查看变量 - 如果不能 attach, 那就
strace -fTp查看 syscalls, 如果难以下咽, 可以和一个正常的进程的strace来对比 - 外部信息还有很多, 进程 fds 里还有那些该有的连接吗, unix socket 都还在吗, 线程数量, goroutine 数量和正常的相比一致吗
- tcpdump 抓包能抓到推送或者响应吗; 如果顺利的话可以立刻把问题出在客户端还是服务端给划清; 如果是二进制流量, 那和正常进程的 segment 相比, length 大小一样吗
通过进程的外部和内部, 我们可以搜集到很多信息, 不过也可能被误导, 要及时回头纠错.
gdb manual 600 页, 然而最有用的依然是查看线程(go extension 下可以看 goroutine), 切换, 查看变量, 打断点.
查看另一端究竟发了什么过来, 发还是没发, 查看内存里的状态还对不对, 那个 map[int64]chan 是否保存着正确的东西, 如果不正确那么在什么时候改坏的, 我打个断点重新触发一下试试.
然而在分布事系统里这些都是奢望, 一 attach, 进程阻塞, 状态全变了, 我甚至觉得运行一个自己打日志后的二进制是最简单的; 其次可以通过 gdb -ex 来执行一次性命令并祈祷不会导致状态改变.
tracepoint 对 go 是奢望, 再见.
最后真的毫无办法, 进程不能 attach, 日志级别过高, 什么也分析不出来, 那么换掉 bin 吧, 换成自己编译的可以 attach 的 binary, 日志级别调高, 等待下一次 bug 降临.