# 记一次循环依赖导致的运维小事故

## 背景

每次没有 UPS 或 UPS 容量不够用的倒闸对于运维来说都是一次鸡飞狗跳。这次很不幸，鸡飞狗跳终于轮到了我，还好花了一个半小时还是解决了。在这里做个简单的复盘。

## 现象与排查

首先介绍一下现象：停电恢复之后，服务器开起来，但是无法从互联网连接内网的网关（即主网关）。还好，之前考虑到网关的重要性，做了备份，走内网的备用网关顺利进入了内网，然后发现主网关即使 IP 地址是对的，也连不上互联网。虽然通过 keepalived 的配置，主网关和备用网关会通过虚拟 IP 给内网机器提供一个高可用的默认网关，但由于 keepalived 只是检测了机器是否开机，并没有判断能否正常访问互联网，所以 keepalived 总会选择优先级更高的主网关，导致虚拟 IP 指向主网关，因而内网的机器都连不上互联网，还得继续尝试修复主网关。

主网关运行在 ESXi 的虚拟机里，于是进入 ESXi 管理网页，看看它的网络情况。这个虚拟机的网络用的并不是 ESXi 的普通网络，而是通过 vCSA 配置的基于 DS 的 LACP。看了几个不同的 ESXi，发现问题都集中在 LACP 上。而 ESXi 是没法配置 DS 的，所以就先去看了 vCSA。连上 vCSA 以后一看，所有的 ESXi 都掉线了。原来，之前为了方便配置，vCSA 都是通过域名连接 ESXi 的，而域名就需要有 DNS 服务器解析，然而主网关连不上互联网，也就连不上配置好的互联网的 DNS 服务器，于是 vCSA 无法配置 ESXi，然后 ESXi 的部分虚拟机就会断网，正好主网关又在被断网的虚拟机里面。这就形成了一个循环依赖。

既然找到了问题，那就需要打破循环依赖：把主网关在 keepalived 里的优先级调低，让备用网关上位。结果这时候发现一个小的问题：备用网关的 NAT 忽然不工作了。排查了一下，发现是因为 `net.ipv4.ip_forward = 1` 写在了 `/etc/sysctl.conf` 文件里，而 Debian 升级 Trixie 以后，这个文件已经不会被应用了，而要把内容写到 `/etc/sysctl.d/*.conf` 里面去，通过 `/usr/lib/systemd/systemd-sysctl --cat-config` 来确认是否持久化成功。由于主网关一直工作得很好，备用网关很久都没有做 NAT 了，导致这个问题一直没有被发现。

修好以后，vCSA 就能找回 ESXi 了，然后通过 vCSA 再重新配置一下 ESXi 的 DS LACP 网卡，然后一切就恢复了。

## 反思

虽然事故解决了，但这个过程中暴露了很多问题：

- 首先就是循环依赖：ESXi 的 DS LACP 依赖 vCSA，vCSA 依赖 DNS，DNS 依赖主网关，主网关通过 DS LACP 访问互联网。如果没有准备好备用网关，且备用网关恰好没有用 DS LACP 因此不受影响，那么修复起来就更麻烦了。解决循环依赖的办法也很简单，对于一些重要的虚拟机（如网关），它所依赖的功能越简单越好。
- 备用网关的 NAT 功能在升级 Debian 版本后，因为没有仔细阅读 sysctl 的[变化](https://www.debian.org/releases/trixie/release-notes/issues.en.html#etc-sysctl-conf-is-no-longer-honored)而失效了，升级后缺少对功能的检查。
- keepalived 只判断机器是否在线，但没有判断机器是否可以正常连接互联网、承担起网关的职能。
- ESXi + vCSA 这类闭源软件，修复起来还是比较痛苦的，很多内部的工作原理并不清楚，可调试性也比较差，以后还是会谨慎选择。
