美文网首页
2023.02.28 故障复盘 | 一条配置让我们崩了 7 小时

2023.02.28 故障复盘 | 一条配置让我们崩了 7 小时

作者: 初心不变_叶子 | 来源:发表于2023-05-02 08:52 被阅读0次

    两个月前,我们发生了一次严重事故,直接导致本年度的服务可用率下降至 99.93%。

    故障发生 4 天后,我们完成了定位,并在接下来的两天进行了修复和重新评估。

    这篇文章在一个月前就打算写了,由于各种事情,拖到现在才动笔。

    那让我们把时间倒回到 2023 年 2 月 28 日,看看当时到底发生了什么。

    故障发现与临时处理

    18:16,我照常打开 Grafana 仪表盘,准备查看一个容器的资源占用趋势,发现页面白屏,于是我查看了 服务状态面板,同样是白屏状态。简书小工具集和之前上线的 2022「落格」也无法访问。

    18:23,尝试 SSH 登录服务器失败,通过另一台服务器尝试跳板登录失败。

    18:50,打开阿里云控制台,查看资源占用信息。

    图中可见,自当日 12:52 起,服务器磁盘读 IO 明显上升,达到 2800 IOPS 后下降,稳定在 2300 IOPS。

    (后经数据比对,实际故障发生时间为 13:05)

    CPU 使用率上升到 25% 稳定,网络流量同时归零(只统计外网流量,不包含内部网卡)。

    为了阻止可能的恶意攻击,我们取消了其它服务器的跳板连接权限,只保留通过内网连接的通道。

    再次确认故障属实后,我们对服务器执行了重启,此时时间为 18:54。

    重启后服务器恢复正常,故障于 18:58 结束,历时 6 小时 53 分钟。

    故障定位

    排除恶意攻击

    查看 SSH 登录日志:

    last --time-format iso | less
    

    故障期间日志摘录如下:(数据经过脱敏处理)

    xxx     pts/xx       100.xx.xxx.xx    2023-03-27T06:21:09+08:00 - 2023-03-27T06:29:50+08:00  (00:08)
    xxx     pts/xx       100.xx.xxx.xx    2023-03-26T08:59:30+08:00 - 2023-04-09T18:51:34+08:00 (14+09:52)
    xxx     pts/xx       100.xx.xxx.xx    2023-03-26T05:58:14+08:00 - 2023-03-26T05:58:25+08:00  (00:00)
    

    在故障发生前,没有异常登录记录,全部连接请求均来自内网(100.xx.xx.xx)。

    查询审计日志,结果相同。

    查看公钥列表:

    vim /root/.ssh/authorized_keys
    

    公钥列表为空,修改时间为 2022 年 12 月。

    检查阿里云服务器防火墙配置,仅开放了对外服务所需的 80 端口和 443 端口,其余端口均处于关闭状态,外网尝试连接 SSH 22 端口,MongoDB 27017 端口失败,与预期结果一致。

    检查告警记录,无异常。

    查看网络连接情况:

    netstat -tulp
    

    无异常程序信息,无异常打开端口。

    由于 Grafana 服务正在测试中,其向公网暴露了一个 Web 服务。我们使用的 Grafana 为最新稳定版,当时共有两个账户,均设置了强密码。服务本身使用 Docker 容器运行,未开启特权模式,未通入 docker.sock 文件。

    我们回溯了故障发生前该容器的网络通信记录,结合 Grafana 自身日志,判断该时间段内无用户登录。

    为保证该服务安全,我们在事后临时禁用了使用频率较低的账号,并为所有账号修改了密码。

    至此,可基本排除恶意攻击可能。

    排除近期服务变更导致异常

    我们的所有服务均在容器内运行,并应用了资源限制,最大程度降低单服务故障影响其它服务的风险。

    回溯故障发生前的 CPU 占用率如下:

    可见 CPU iowait 占用较高:

    故障结束后,我们对该指标进行了重点监测,未再出现该前兆表现。

    (后经确认,该现象为某服务特定情况下的 Bug 导致,现已修复)

    回溯故障发生前内存占用:

    (红色、橙色为 user 内存占用警戒线,并非所有内存占用类别的警戒线)

    内存未见明显异常。

    故障发生前 1m/5m/15m load 均在 0.2 以下,服务器处于低负载状态。

    硬盘 IO、网络 IO、硬盘占用、文件描述符使用率等指标均正常。

    观察故障发生前的最后一个数据点,未发现明显异常。

    由于当时容器监测基础设施尚未建设完成,我们无法采集到各服务容器的 CPU / 内存等信息,但可以通过查看网桥的流量记录判断容器间通信情况,经查,容器间通信正常,无流量激增。

    至此,可推断本次故障并非线上服务导致,也与近期正在建设的可观测性平台无关。

    (在此期间,我们还通过工单联系了阿里云工程师,询问能否获取故障期间的 CPU 占用和网络请求信息,但得到了否定回复。)

    日志分析

    各服务自身的日志分析较为简单,且涉及一些内部数据,此处略过不表。

    为了避免日志文件被自动轮转清除,我们先对 /var/log 目录进行备份。

    接下来,查看故障发生前 3 小时的系统日志:

    journalctl --since "2023-02-28 10:00:00"
    

    在故障发生时点附近,我们注意到如下日志信息:

    Feb 28 13:19:16 server systemd-resolved[420]: Grace period over, resuming full feature set (UDP+EDNS0) for DNS server 100.100.100.100.
    Feb 28 13:20:08 server systemd-resolved[420]: Using degraded feature set UDP instead of UDP+EDNS0 for DNS server 100.100.100.100.
    

    也正是这两条日志,让我们找到了最终的故障原因。

    故障原因

    Tailscale 是一个 SD-WAN(软件定义局域网)服务,可将不同网络环境中的设备连接到私有网段,并在设备间建立直接或中转连接。我们通过该服务实现对服务器的安全访问。

    systemd-resolved 是 systemd 的一个服务,用于 DNS 解析。

    Tailscale 中,加入尾网(Tailnet,Tailscale 组成的虚拟局域网)的设备将被自动分配一个 100.xx.xx.xx 的私有 IP 地址,用于设备间互访。

    而为了简化互访过程,Tailscale 支持在每台设备上运行一个小型 DNS 服务器,用户可在管理面板定义每台设备的名称,之后以类似域名的方式直接访问,这一功能称为 MagicDNS。

    MagicDNS 只为尾网设备对应的名称提供 DNS 解析,它将接管系统 DNS 服务器,所有与尾网无关的 DNS 请求将被转发到外部 DNS 服务器上,具体转发目标可通过管理面板控制。

    在本次故障期间,MagicDNS 处于启用状态,对于非尾网的 DNS 解析请求,我们将其转发到 223.5.5.5(阿里云公共 DNS)和 1.1.1.1(Cloudflare DNS)上,而对于尾网请求,转发到 100.100.100.100(MagicDNS 服务器)。这一设置将会覆盖设备自身的 DNS 解析配置。

    在故障服务器的 /etc/systemd/resolved.conf 文件中,我们发现了这样一行配置:

    [Resolve]
    DNSSEC=true
    

    DNSSEC(域名系统安全扩展)是用于对 DNS 数据进行身份验证的协议。具体来说,它会对每一条 DNS 数据进行数字签名,但不会进行加密。

    systemd-resolved 的 Arch Wiki 页面 中,关于这条设置有一段警告:

    systemd-resolved may disable DNSSEC after a few unsuccessful validations. If the DNSSEC option is set to true, then DNS resolution will stop working entirely. See systemd issue 9867.

    systemd-resolved 可能会在几次验证失败后禁用 DNSSEC。如果 DNSSEC 选项设置为 true ,则 DNS 解析将完全停止工作。请参阅 systemd 问题 9867

    而 Tailscale 并不支持 DNSSEC。我们可以通过 Arch Wiki 上给出的命令来验证:

    ~ ❯ resolvectl query does-not-exist.sscreator.com
    does-not-exist.sscreator.com: resolve call failed: 'does-not-exist.sscreator.com' not found
    
    ~ ❯ resolvectl query tools.sscreator.com
    tools.sscreator.com: 8.210.85.3                -- link: tailscale0
    
    -- Information acquired via protocol DNS in 5.7ms.
    -- Data is authenticated: no; Data was acquired via local or encrypted transport: no
    -- Data from: cache network
    

    因此,最终的故障原因如下:

    tailscaled 将不属于尾网的 DNS 请求转发给 systemd-resolved,而后者由于网络波动,多次解析失败,触发了回退机制,其目标正是 Tailscale 管理面板设置的 100.100.100.100,即 MagicDNS 服务器的地址。

    systemd-resolved 收到的请求再次被转发给 tailscaled,由于前者的错误配置,虽然 Tailscale 不支持 DNSSEC,但 systemd-resolved 并未继续回退到内置的备用 DNS(Cloudflare、Quad9 和 Google),而是不断尝试向 MagicDNS 服务器发起请求,最终导致了环路。

    而 IO 打满可能是两者的日志记录或配置文件读取机制所致。

    由于 MagicDNS 服务器处于高负载状态,尾网的虚拟网卡 tailscale0 也被 DNS 解析数据包占用,SSH 无法连接到服务器。

    DNS 解析失败导致请求无法正常被路由到容器,因此出现了服务不可用,同理,各 Exporter 容器无法被正确路由,导致 Prometheus 无法采集到相关数据。

    本次故障的直接原因在于 Fallback 后的 DNS 服务器引发了网络环路,根本原因在于 systemd-resolved 配置错误。

    解决方案

    定位问题后,我们立刻修改了相关配置文件,禁用 DNSSEC 校验。考虑到我们强制指定了可信的 DNS 服务器,且阿里云服务器会自动将 223.5.5.5 路由到机房内部的 DNS 节点,这一变更并不会对服务器安全性造成影响。

    同时,我们在之后的基础设施建设中完善了容器监测机制,以更快速地定位到容器间网络通信的问题。

    反思

    本次故障中,从发现故障到采取临时解决措施,耗时 38 分钟,不足预期。

    Tailscale 默认开启 MagicDNS 功能,且我们在使用前在其它服务器上进行了较充分的测试,但没有模拟外网波动使系统 DNS 解析器触发回退的场景。

    2023.02.18 的内部故障,也是由于 DNS 配置错误,导致了服务状态面板的数据不可靠。

    DNS 是现代网络的基础设施,DNS 运作原理和相关技术是每位运维的必修课。

    在使用 Tailscale 的过程中,我们大大提升了服务器的安全性,简化了部署流程,关于它的经验,留待之后的「技术说」系列再讲吧。

    我们一直以 99.97% 的可用率为目标,并在内部推行 99.99% 可用率相关的最佳实践。

    我们为所有申报后经核实受到影响的简友发放了 300 简书贝补偿。

    对于合作方,我们也将遵守服务可用性承诺。本次故障中,我们对合作方进行了损失评估,并赔付了等额简书贝或服务时长。

    非常感谢你能看到这里,也希望你能继续支持我们的产品和服务。

    差点被送进往生堂的 开发组 敬上
    2023.05.03

    相关文章

      网友评论

          本文标题:2023.02.28 故障复盘 | 一条配置让我们崩了 7 小时

          本文链接:https://www.haomeiwen.com/subject/ngyajdtx.html