这是三部分中的最后一部分。第一部分里,我把个人服务拆到了它们自己的双节点集群上。第二部分里,我读了编排器的源码,修了两个缺陷,它们随下一个版本发布了。这一部分讲的是我安装那个版本之后发生的事。
它用三种互不相干的方式弄坏了集群。没有一个共享同一个根因。下面按我遇到它们的顺序来说。
第一次故障:glibc 版本不匹配
我把官方发布二进制复制到第二个节点,服务随即进入崩溃循环:
/usr/local/bin/orca: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by orca)
这个节点运行 Ubuntu 20.04,自带 glibc 2.31。官方构建链接的是更新的版本,所以二进制在那里根本无法加载。控制器节点更新一些,顺利接受了这个版本。旧节点不行。
解决办法是停止在那个节点上使用官方二进制,改用一个针对它的 glibc 链接出来的构建。在一个匹配的容器里编译就能做到:
docker run --rm -v "$PWD":/src -w /src rust:1-bullseye sh -c 'apt-get update && apt-get install -y protobuf-compiler && cargo build --release'
rust:1-bullseye 是 Debian 11,glibc 2.31,所以生成的二进制能在 2.31 的节点上运行。
教训是:最老的主机为每一个预构建二进制设定了 glibc 下限。要么为那个下限构建,要么淘汰那台主机。我现在有了一条固定规则:那个节点上的任何编排器升级都需要容器构建。我也多了一个替换这台机器的全新理由。
第二次故障:容器组达到上限
有了可用的二进制之后,单元仍然拒绝启动,这次失败方式不同:
Main PID: ... (code=exited, status=219/CGROUP)
systemd 的退出码 219 意味着它无法为服务设置容器组。这根本不是二进制的问题。这个节点是一个 Virtuozzo 容器,在那个环境里 systemd 的容器组层级有一个硬上限:
$ cat /sys/fs/cgroup/systemd/cgroup.subgroups_limit100
然后是计数:
$ find /sys/fs/cgroup/systemd -type d | wc -l101
已经到顶了。systemd 连一个额外的服务容器组都创建不了,所以任何新单元都会以 219 失败,不管二进制能不能用。
是什么把它填满的,这才是最有意思的部分。有两个来源。第一,前两次故障造成的数小时崩溃循环搅动了大量单元。第二,也是更大的一个,泄漏的 SSH 会话作用域。每一次 SSH 登录都会创建一个 session-*.scope 容器组,而这个 systemd 版本在注销后从不清理它们。在实际上没有任何用户登录的情况下,101 个容器组目录里有 96 个完全不持有进程。我自己为了调试这台机器而反复登录,一直在悄悄消耗这个预算。
恢复方法是移除那些没有进程的会话作用域,清除失败状态,然后启动:
for d in /sys/fs/cgroup/systemd/session-*.scope; do if [ -z "$(cat "$d/cgroup.procs")" ]; then rmdir "$d" fidonesystemctl reset-failedsystemctl start orca
清理之后,容器组目录数从 101 降到了个位数,服务终于能正常启动了。但故事还没结束。
第三次故障:一个占位符缺陷
服务启动后,集群仍然没有恢复。日志里反复出现同一个错误,指向一个配置项。那个配置项在上一版本里被标记为可选,但新版本里它的默认值是一个占位符,而不是实际值。集群在启动时读取这个占位符,试图连接一个不存在的地址,然后失败。
修复方法是把这个配置项显式设置为正确的值。设置之后,集群才真正恢复。
热门跟贴