我让 AI 参与维护 9 台 NixOS(上):为什么声明式系统适合人机协作

本文不是一篇“让 AI 帮我写了几个配置文件”的体验文,而是一份持续协作记录:我让 AI 参与维护一套真实运行的 NixOS 基础设施,经历需求讨论、代码审查、静态检查、测试、 金丝雀部署、全节点发布和线上告警处理。它确实提高了效率,也确实犯过一些只有人类知道 为什么不能犯的错误。

本文涉及的公开配置与实现:LokiSharp/nix-config。 真实凭据保存在独立私有仓库中,不包含在公开配置里。 写在前面 先说明两点。 第一,本文中的“AI”不是一个被接入生产环境后完全自主行动的机器人。它运行在我的工作区中, 能读取仓库、修改文件和执行检查;只有在我明确授权后,才会提交、连接节点或部署。敏感值由 SOPS 管理,我不会因为调试方便就把明文 secret 交给它。 第二,这篇文章本身也由 AI 参与整理。事实材料来自真实仓库、提交历史、部署输出、监控邮件 和我们之间的连续对话;章节结构和初稿由 AI 生成,我负责提供语境、纠正错误和决定哪些经验 值得公开。换句话说,文章的产生方式就是文章主题的一部分。 如果不想读完整连载,可以先记住下面五点:

NixOS 最适合 AI 的地方,不是 Nix 语法容易生成,而是系统状态能被求值、构建、测试、 比较和回滚; AI 最有价值的工作不是一次写出完整配置,而是持续做仓库搜索、约束翻译、机械重构、 故障调查和文档同步; nix flake check 通过只代表验证链的一层,真实部署仍然需要 Test 金丝雀、目标节点探针 和一段时间的监控; AI 会删除有意保留的模板、添加恒真测试、误解历史地址、把告警安静误当成故障解决, 所以权限边界和停止条件必须写清楚; 最可靠的合作方式是让人负责目标与授权,让 AI 负责搜索和执行,让 Nix 、Git 、测试、 Test 节点与监控分别提供不同层次的证据。

本文不会证明“NixOS 是唯一正确的发行版”,也不会证明“AI 已经能替代运维工程师”。我想 讨论的是一个更具体的问题:当系统本身可以被声明、检查和回滚时,我们是否能用一种比 “复制 AI 给出的命令并祈祷”更成熟的方式与它合作? 一、先说结论 过去一段时间,我一直在尝试让 AI 深度参与自己的 NixOS 配置仓库。 这里的“深度参与”不是让它生成一段 configuration.nix,然后由我复制粘贴;而是让它进入 仓库,阅读现有模块,理解主机模型,修改代码,运行格式化和静态检查,补测试,按照提交 规范拆分 commit ,先部署到 Test 节点,验证无误后再推向其他节点,最后继续读取 systemd 、 Prometheus 和 Alertmanager 的反馈。 目前这套仓库管理:

9 台 NixOS 节点; 1 台 nix-darwin 设备; 裸机服务器、虚拟机、测试节点以及多个不同供应商的 VPS ; 261 个 Nix 文件; 30 组 x86_64-linux 求值测试; Home Manager 、Colmena 、Disko 、impermanence 、sops-nix ; BIRD 、DN42 、ZeroTier 、Tailscale 、sing-box 、Caddy 、PostgreSQL 、Gitea 、Grafana 、 VictoriaMetrics 、Alertmanager 等服务; Btrfs 快照、scrub 、SMART 、mdraid 、coredump 和部署后健康检查。

我的结论是: NixOS 并不会让 AI 自动变得可靠,但它会把 AI 的大量错误提前变成可观察、可比较、可拒绝 的结果。 这两者的组合真正有价值的地方,不是“AI 会写 Nix”,而是 NixOS 把系统状态变成了代码, 又把代码变成了一套可以求值、构建、比较、回滚和部署的闭环。 如果使用普通发行版,AI 可能会告诉你执行十几条命令、修改五个路径下的配置、重启三个 服务。命令执行完以后,机器究竟处于什么状态,需要靠人记忆。 在 NixOS 中,更理想的合作方式是:

人描述目标、约束和不可接受的风险; AI 阅读仓库中的现有事实; AI 修改声明式配置; Nix 对配置进行求值; 测试检查跨节点约束; Test 节点承担真实运行验证; 监控系统提供部署后的长期反馈; 失败时回到上一个 generation 或上一个 commit 。

它仍然不是“按一下按钮全自动运维”,但已经很接近一种可审计的人机协作工程。 二、为什么偏偏是 NixOS

  1. AI 最擅长操作文本,而 NixOS 恰好把系统变成文本 AI 对当前机器里“曾经运行过哪些命令”没有天然记忆,也很难仅凭 /etc、数据库和服务 状态还原管理员过去几年的意图。 但它很擅长:

搜索代码; 比较相似模块; 找出重复结构; 根据类型和约束补全字段; 把手工流程整理成函数; 根据错误信息做局部修正; 为已经明确的规则生成测试。

NixOS 把软件包、用户、systemd unit 、内核参数、防火墙、文件系统、服务配置和部署元数据 都放进同一个表达式系统。这意味着 AI 不必先猜“这台机器可能被手动改过什么”,而可以从 仓库中得到一个相对完整的意图模型。 Nix 官方文档把 Nix 的典型使用场景概括为可复现开发环境和 Linux 机器的声明式定义; Flake 又提供了统一入口、输入锁定和标准化输出。对 AI 来说,这些恰好意味着更稳定的上下文: 依赖版本、主机输出和测试入口都在仓库里,而不是散落在聊天记录中。 2. Nix 的失败通常发生得比较早 传统运维脚本的典型风险是:前八步成功,第九步失败,机器停在一种很难描述的中间状态。 Nix 当然也可能在 activation 阶段失败,但大量问题会更早暴露:

语法错误在解析阶段暴露; option 类型错误在模块求值阶段暴露; 不存在的属性在 evaluation 阶段暴露; package 、unit 、secret 声明可以在构建阶段暴露; 跨节点重复地址可以通过纯求值测试暴露; Prometheus 规则可以通过 Promtool 暴露; Nushell 的错误数据形状可以通过类型签名和测试暴露。

这对 AI 特别重要。AI 最大的问题通常不是完全不会,而是“看起来很像对的”。越早让机器 检查它,越不需要依赖人类逐字阅读几千行差异。 3. 旧 generation 给试错留下了空间 NixOS 的代际模型让一次系统切换不会直接覆盖所有旧状态。只要启动链、磁盘和远程入口还 在,很多错误都可以切回上一代。 这并不意味着可以让 AI 随便部署。错误的防火墙、磁盘布局、SSH 配置和 secret 仍然可能 把人锁在门外。但是相较于不可追踪的命令历史,generation 至少让“刚才那次系统变更”有 一个清楚边界。 4. Flake 既是入口,也是边界 我的仓库使用 Flake 管理输入和输出。AI 可以从 flake.nix 开始理解:

使用哪些 nixpkgs 分支; 哪些输入是公开依赖; 哪个输入来自私有 secrets 仓库; 有哪些 NixOS 、Darwin 、package 、check 和 devShell 输出; CI 和本地执行的求值是否一致。

这里也有一个很实际的坑:Git 仓库中的 Flake 默认只看到已跟踪或已暂存的文件。 我曾经让 AI 新增一组测试。它运行 just test,所有测试都通过了,但报告里没有新测试。 原因不是测试写得好,而是新文件还没有进入 Git ,Flake 根本没看见它。 后来流程被修正成:

新增测试文件; 确认 Git 能看到它; 重新运行求值; 必须在测试报告中明确看到新测试名称; 才能说“新增测试通过”。

这是一个非常典型的例子:AI 会把“命令退出码为 0”理解为成功,而工程系统必须继续追问 “我们想测的东西真的参与测试了吗?” 三、我的仓库不是从一开始就这么整齐 这套配置最早也有大量常见问题:

主机元数据散落在不同文件; index 、地址和部署标签之间没有统一约束; 一些测试只是把实现重新抄一遍; 部署依靠单节点命令,没有强制 Test 金丝雀; 健康检查默认使用高权限账户; CI 只能求值,无法发现部分真实构建问题; 静态检查加入后,预留模板被误判为无用代码; 网络、日志和监控告警有不少历史噪声; 文档只覆盖局部目录,新接手者很难得到全局图。

AI 真正带来的改变不是一次“大重构”,而是把这些模糊的不舒服逐步变成具体问题,然后一项 一项处理。 这个过程持续了很多轮对话。很多时候,我只会问:

接下来还有什么可以优化?

AI 会先读代码,列出若干候选;我再问每一项的意义、代价和风险。确认以后,只改其中一项, 测试、提交、部署,再讨论下一项。 这种节奏比让 AI 一次生成一个“完美架构”可靠得多。 四、案例一:从“这个节点该用几号”到全局地址约束 我的多节点网络里,主机 index 不只是一个展示字段,它还参与生成多个内部网络地址、部署 标签和 DNS 记录。 一次讨论从一个很小的问题开始:某个节点应该使用哪个编号。 AI 先分析现有地址分配逻辑,又解释 /26 的地址范围、主机位和可用地址。我们讨论过是否 把整个序号换掉,最后给 OVH 节点选择了 7 。 如果只是修改一个数字,这件事没有太大意义。真正有价值的是随后补出的约束:

每个主机 index 全局唯一; 由 index 派生的各网络地址不能重复; 启用某网络时,相关字段必须完整; ZeroTier node ID 不能在两个主机上复用; DNS 记录必须覆盖所有应该出现的节点; 已退休节点不能继续残留在输出中。

最后,主机元数据从“方便写配置的 attrset”变成了一种小型数据模型。 AI 在这里的优势很明显:它很适合沿着字段依赖关系搜索所有消费者,然后生成跨节点测试。 但它也暴露了一个危险倾向:喜欢加“看起来更保险”的判断。 例如它曾经加入: benchmarkIPv4RoutesAbsent = lib.all (route: !lib.hasPrefix "198.19." route.target) routes;

问题是,我的 ZeroTier 正常地址使用的是 198.18..0/24,而 198.19.* 已经不再使用。 这条检查虽然会通过,却没有保护任何当前需求。它只是检查一个已经不存在的历史路径。 我追问:

为什么要加这个判断?这个是我 ZeroTier 的地址。

继续讨论以后,我们删掉了这类冗余测试。 这件事让我形成了一个很重要的判断标准: 测试不能只证明“某个字符串不存在”,它必须对应一个仍然存在的风险。 AI 很容易生成大量断言,但断言数量不等于保障强度。 五、案例二:SSH 不能只检查“服务开着” 我最不能接受的部署事故之一,是远程修改后把自己锁在服务器外面。 仓库原来有一个类似 public-ssh-port.nftablesEnabled 的检查。我没有同意删除它,反而要求:

这个还是检查吧,我不想被关在外面。是不是要再扩展一下,从各个角度保证 SSH 端口开放?

于是 SSH 保障不再只是检查一个布尔值,而是从多个层面验证:

OpenSSH 服务启用; 实际监听端口与主机元数据一致; 防火墙允许同一个端口; 公开暴露面期望值与配置一致; 部署健康元数据携带正确端口; Colmena 的目标端口没有走另一条遗留配置; 所有公网节点遵循同一约束。

这正是 NixOS 与 AI 配合得很舒服的地方。 如果用普通脚本,AI 可能给出: systemctl status sshd ss -lntp iptables -L

这些命令只能描述当前机器的一瞬间。 在 NixOS 中,我们可以把“SSH 服务、端口、防火墙、部署目标和测试期望必须一致”写成仓库 长期成立的不变量。以后改端口时,只要遗漏任何一个消费者,求值测试就会失败。 当然,这仍然不能完全替代外部探测。内部网络访问成功,不等于公网防火墙路径正确。因此 仓库还保留了从不受信网络执行 public exposure 检查的入口。 声明式测试和真实网络探测并不是二选一:前者检查意图一致性,后者检查现实世界。 六、案例三:让 Test 节点成为真正的发布闸门 最初的部署方式是 just :选一个节点,交给 Colmena 。 这种方式对人来说很直接,对 AI 来说却太自由。只要命令构造错一个目标,就可能直接修改 生产节点。 后来我们把发布流程改成固定顺序:

完整求值 Flake ; 执行部署流程自身的可执行测试; 只部署 Test-NixOS; 检查 Test 的 SSH 、主机名、systemd 、内核、audit 、ZeroTier 、BIRD 、DNS 和日志; Test 全部通过后,才部署剩余节点; 最后对所有节点执行健康检查。

现在完整发布入口是: just deploy-all

这里最关键的并不是命令变短,而是部署顺序从“聊天中的约定”变成了代码。 为了验证这个顺序,我们还给部署编排本身写了测试:

Test 部署失败时,其他节点不能开始; Test 健康检查失败时,其他节点不能开始; 中途某节点部署失败时,不能伪装成全量成功; 最终健康检查失败时,命令必须返回失败; SSH multiplexing 失效时允许重建连接; 认证失败和真实网络错误不能被错误地当作 multiplexing 问题重试。

有一次真实发布中,所有节点都成功 activation ,但最终健康检查发现 MoeDove-TPE -> Test-NixOS 的 SLK IPv6 在十个包里丢了两个,超过 10% 阈值。 发布命令因此返回失败。 AI 没有把它说成“部署失败”,也没有立刻重新部署全部节点,而是区分:

配置是否已经成功激活; 服务是否正常; 是哪一条健康检查失败; 同一节点的 IPv4 和另一条 IPv6 overlay 是否正常; 单独复测后丢包是否回落。

复测时,SLK IPv6 丢包降到 10%,按策略记录 warning ; Loki-Net IPv6 无丢包,节点健康 检查通过。 这类细节非常重要。一个好的自动化系统不应该只有“成功/失败”两个词,而应该保留失败发生 在哪一层。 上篇小结 这一篇先回答了“为什么是 NixOS”:AI 擅长处理文本和结构,NixOS 则把系统意图变成可以 求值、构建、测试和回滚的文本。主机编号、SSH 端口和部署顺序不再只是管理员脑中的约定, 而可以成为仓库中的长期约束。 但让 AI 能修改配置,并不等于应该给它无限权限。下一篇会继续写健康检查账户、secrets 预留模板、Nushell 类型、Btrfs 快照、磁盘监控、Alertmanager 告警和测试反例,并进一步 讨论怎样给 AI 下达运维任务、如何判断它提供的证据是否足够。

系列下一篇:《我让 AI 参与维护 9 台 NixOS (中):权限、故障案例与实战方法》

本篇参考

nix.dev:Nix 官方文档 nix.dev:Flakes 概念 NixOS Wiki:NixOS 概览与 generations