我让 AI 参与维护 9 台 NixOS(中):权限、故障案例与实战方法

发布于

> 上一篇讨论了 NixOS 为什么适合与 AI 协作,以及怎样把主机地址、SSH 安全和 Test 金丝雀发布变成机器可验证的约束。这一篇进入更接近真实运维的部分:权限、secrets、存储、告警和测试中的失败案例,以及如何向 AI 描述任务、如何判断验证证据。

本文涉及的公开配置与实现:[LokiSharp/nix-config](https://github.com/LokiSharp/nix-config)。真实凭据保存在独立私有仓库中,不包含在公开配置里。

## 七、案例四:健康检查不应该等于给 AI 一个 root shell
为了让 AI 能部署后验证节点,最简单的办法是允许它 SSH 到 root,然后运行任意命令。

我认为这不是一个好边界。

现在健康检查通过专用普通用户连接。这个用户:

- 不属于 `wheel`;
- 不是 Nix trusted user ;
- 不负责部署;
- 不能运行任意 sudo 命令。

只有少数确实需要权限的检查通过一个 Nix 生成的固定 helper:
```
deployment-health-root
```

它只接受有限动作:

- 读取 audit 状态;
- 读取当前启动中的高优先级 journal ;
- 查询 BIRD 控制套接字;
- 查询 ZeroTier 状态;
- 以 postgres 用户执行只读的 `SELECT 1`。

调用使用 `sudo -n`,不会询问密码,也不能把参数变成任意 shell 命令。

这套设计并不是为了防止 AI“作恶”,而是为了减少错误的作用面。AI 和人类都会输错命令,最可靠的权限模型不是相信操作者永远正确,而是让错误命令没有足够权限造成更大事故。

## 八、案例五:AI 很擅长整理重复模块,但必须防止它删掉“有意无用”的东西
仓库引入 Nix formatter、Statix 和 Deadnix 后,AI 做了一轮全仓库清理:

- 格式化 Nix 文件;
- 修复 `inherit` 等静态分析建议;
- 删除未使用的参数和声明;
- 把检查加入 CI 和 Git hooks 。

大多数修改都很好,但有一次它删除了 secrets 目录中的未使用模板。

从 Deadnix 的角度看,那些声明确实没有消费者。

从我的角度看,它们是故意保留的权限模板,用来提醒未来新增 secret 时选择正确的 owner、group 和 mode。

我问:
> secrets 里没有使用的模板为什么要删?

最后我们恢复了这些模板,并明确了不同安全级别:

- 普通服务 secret ;
- 仅服务账户可读;
- root-only ;
- 模板文件与单值 secret 的不同权限;
- 哪些预留参数不能因为“当前没有使用”而删除。

后来我又继续追问:
> NOTE: the args not used in this file CAN NOT be removed! 这些修改是?

这件事非常能代表 AI 协作的边界。

静态分析知道变量是否被引用,却不知道某个例子是不是组织知识。AI 如果只追求“零 warning”,会把代码整理得更干净,同时把未来维护者需要的语义一起擦掉。

解决办法不是停用 Deadnix,而是:

1. 明确哪些文件承担模板或接口职责;
2. 使用工具支持的忽略方式;
3. 在注释中说明为什么保留;
4. 把“不得删除的占位符”视为仓库契约;
5. 清理后检查 Git 历史中是否还有类似误删。

工具负责发现可疑点,人负责定义什么叫“无用”。

## 九、案例六:从 Nushell 的 `any` 到可维护的部署 API
仓库的部署和健康检查逻辑主要写在 `utils.nu`,目前超过一千行。

Nushell 相比传统 shell 的优势,是管道中传递的不只是文本,还可以是 list、record、table 和带类型的值。对于 AI 来说,这一点非常有帮助,因为函数契约可以直接写进代码:
```
def check-host [
host: record
since_minutes: int
]: nothing -> int {
# ...
}
```

我曾经要求 AI:
> 可以帮我把 nu 脚本的 any 类型都补全么?

第一轮完成后,我又让它继续检查是否还有其他 `any`。

这个过程不是单纯追求类型洁癖。部署脚本里的类型可以提前暴露:

- 期望主机列表却得到字符串;
- `exit_code` 可能不存在;
- 网络地址可能是 null ;
- closure 的输入输出约定不一致;
- 从 JSON 读取的数据形状发生变化;
- 函数原本返回 nothing,却在某个分支返回 record。

传统 shell 中,这些错误经常在某个远端命令执行到一半才出现。Nushell 至少能把一部分错误提前到本地解析和测试阶段。

不过,类型也不能乱补。为了消灭 `any` 而写一个不真实的精确类型,反而会迫使代码用大量转换绕过类型系统。正确做法是先观察真实数据形状,再收紧边界。

## 十、案例七:让存储维护从“我记得要做”变成系统能力
我后来把关注点从网络转向存储:
> 接下来做 Btrfs 定期维护和磁盘健康检查吧。

一开始的问题是“哪些节点需要加”,最后形成的是一组自动派生能力:

- 只要最终配置中存在 Btrfs 文件系统,就启用定期 scrub;
- 同一设备上的多个子卷只 scrub 一次;
- 每日检查 Btrfs device error counters;
- `/persistent` 每小时创建本地快照;
- Server 的 `/data/apps` 额外创建应用快照;
- 快照只有来源变化时才创建;
- 按小时、天、周、月分层保留;
- 快照成功时间和数量导出为 Prometheus 指标;
- 快照目录未挂载、快照缺失、任务长时间未成功都会告警;
- 物理盘节点启用 smartd 和 smartctl exporter;
- OVH 的 mdraid1 额外启用 mdadm collector 和降级告警。

这里有几个很适合 AI 的工作:

- 扫描所有 `fileSystems`,找出哪些挂载点其实位于同一个 Btrfs 设备,避免重复 scrub;
- 从主机元数据筛选需要物理盘监控的节点,自动生成 VictoriaMetrics targets;
- 为跨模块关系补测试。例如:
- 启用 `diskHealth` 的节点必须同时启用 smartd 和 exporter;
- 监控中心的 scrape targets 必须覆盖所有启用节点;
- Btrfs 来源和快照目录必须在同一设备;
- timer 必须进入部署健康检查清单;
- OVH 必须显式启用 mdadm collector。
- 把最终策略写成文档。现在仓库中有一份完整的存储、快照和磁盘健康说明,记录执行周期、保留策略、空间代价、手动检查命令和单文件恢复方法。

这件事也让我更清楚地看到,AI 不只是代码生成器。它很适合把“实现、测试、运维命令和文档”同时保持一致。

但仍然有一个必须由人强调的事实:

**同盘 Btrfs 快照不是备份。**

快照能帮助恢复误删和错误配置,不能抵抗整盘损坏、机器丢失或攻击者同时删除来源与快照。如果不明确说出威胁模型,AI 很容易在“已经有快照”以后写出过度乐观的结论。

## 十一、案例八:从一封烦人的告警邮件追到 Nix 自己的崩溃
监控系统上线以后,我收到过不少告警:

- journald coredump ;
- CPU iowait ;
- 磁盘写延迟;
- systemd oneshot 失败;
- Btrfs 快照异常;
- node-exporter 不可达。

其中 `Lycheen-US-SLC` 最麻烦。它的供应商虚拟磁盘会周期性出现高延迟,小型同步写入也可能阻塞几秒。结果是:

1. iowait 升高;
2. journald 写日志受阻;
3. 服务错误产生更多日志;
4. journald 自己崩溃;
5. coredump 和磁盘延迟同时告警。

如果只处理表面现象,可以不断提高阈值、删除 coredump 或禁用规则。但我们最后做的是:

- 普通 journal 改为 volatile;
- 限制 RuntimeMaxUse 和单文件大小;
- audit 和外部 coredump 继续持久化;
- 对高噪声代理日志降低级别;
- journald 告警只针对最新 15 分钟的真实崩溃;
- coredump 指标改为扫描外部文件,避免监控脚本反过来读取并加重 journal;
- iowait 必须持续超过阈值 5 分钟才 firing 。

最近又出现了另一个例子:`VM-NixOS` 上有两次 `nix-daemon 2.34.7` coredump 。

AI 读取 Alertmanager 后发现,这是当时唯一 firing 的告警。继续查看 coredump,确认:

- 两次都是 `SIGABRT`;
- 异常为 `nix::Interrupted: interrupted by the user`;
- 堆栈位于 `TunnelLogger::enqueueMsg`、`ignoreExceptionExceptInterrupt` 和线程池;
- 主 daemon 后续仍然接受连接;
- 这不是机器持续离线,而是两个 worker 进程在客户端中断路径触发了 Nix bug 。

这时又发现一个告警设计问题:旧规则只要 24 小时内存在任何普通应用 coredump,就持续 firing,并每 4 小时重复发信。

于是规则被拆成两层数据:

- 24 小时计数继续用于 Grafana 和历史分析;
- 最新应用 coredump 时间戳用于实时告警;
- 只有最新崩溃发生后的 15 分钟内 firing;
- 超过时间窗口自然 resolved,不删除证据。

这个例子很好地说明了监控中的一个原则:

**保留事件历史,与持续呼叫人处理,是两件不同的事。**

AI 很适合追踪指标、规则、采集脚本和邮件之间的链路;人需要决定什么值得在凌晨持续打扰。

## 十二、案例九:测试不是越多越好
AI 特别喜欢补测试,因为测试看起来是“提高质量”最直接的方式。

但我的仓库曾经出现过几类没有意义的测试:

### 1. 恒真测试
表达式只是把实现中的值重新组合一遍,再和自己比较。它永远不会因为真实回归失败。

### 2. 不可达分支测试
上游类型已经排除了某种状态,下游还在断言“不会出现该状态”。这增加阅读成本,却没有新增保障。

### 3. 原样快照测试
把某台主机的整份元数据抄进 expected。任何合理修改都会让测试失败,但失败并不说明系统不安全,只说明文本变了。

### 4. 历史幽灵测试
检查一个已经废弃的地址段、路由或兼容字段不存在。新维护者会误以为它仍然是重要需求。

后来我们逐个讨论这些测试:
> 这个检查有什么意义?

> 有必要改成可以执行的测试?

> 删除吧。

最终保留的测试更偏向不变量:

- 地址和 ID 全局唯一;
- 字段之间依赖完整;
- 安全选项最终值正确;
- 防火墙 forward chain 语义正确;
- SSH 端口在各消费者之间一致;
- 部署顺序不可绕过;
- 必检 unit 真实存在;
- 监控 targets 覆盖元数据声明;
- 规则的时间窗口和严重级别正确。

下一步我还准备引入 `promtool test rules`,用模拟时间序列验证:

- node-exporter 连续离线五分钟才告警;
- 短暂失败不会告警;
- iowait 必须持续超过阈值;
- coredump 在第十四分钟告警、第十五分钟恢复。

现在已有的 Promtool 检查只证明 YAML 和 PromQL 语法正确;行为测试才证明规则按我们理解的时间线工作。

## 十三、它和 Ansible、脚本、容器编排有什么不同
写到这里,可能会有一个自然的问题:
> 这些事情用 Ansible、Shell、Terraform 或容器不也能做吗?

当然能。NixOS 并没有垄断声明式运维,AI 也可以操作任何一种工具。这里真正值得比较的,不是哪一种语法更漂亮,而是 AI 所面对的“系统边界”有多完整。

### 1. 和 Shell 脚本相比
Shell 的优势是普及、直接,几乎每台 Linux 都能运行。遇到一次性问题时,让 AI 写几条诊断命令通常是最快的。

问题在于,Shell 默认描述的是操作过程:
```
apt install nginx
cp nginx.conf /etc/nginx/nginx.conf
systemctl restart nginx
```

这段脚本没有自然回答:

- nginx 原来是什么版本;
- 配置文件之前由谁管理;
- 第三步失败后前两步是否需要撤销;
- 重复执行是否得到相同结果;
- 谁保证另一段脚本不会覆盖同一个文件;
- 半年后怎样从机器状态还原当时的意图。

AI 很容易继续在这个过程上叠加命令。第一次是三行,出错后补一个 `sed`,再出错补一个 `chmod`,最终形成一段只有当前聊天上下文才能解释的操作历史。

NixOS 更倾向于描述结果:
```
services.nginx = {
enable = true;
recommendedProxySettings = true;
};
```

它没有消灭底层操作,但把“如何从旧状态切到新状态”交给模块系统和 activation 处理。AI 的主要修改对象因此是期望状态,而不是一串只执行一次的补丁。

我仍然大量使用命令行脚本,尤其是部署编排、探测和故障调查。区别是,这些脚本不负责偷偷创造系统事实。长期状态尽量回到 Nix 模块中,脚本负责验证它。

### 2. 和 Ansible 相比
Ansible 同样可以把运维写入 Git,也有幂等 module、inventory、check mode 和丰富生态。如果团队已经在使用 Ansible,没有必要为了 AI 立刻迁移。

我感受到的区别主要有三点。

第一,Ansible playbook 通常描述从当前状态走向目标状态的步骤; NixOS 模块先合并成一份最终配置,再构建切换结果。AI 修改 Nix 时,可以对最终 option 值做求值,不必只推测任务执行后的状态。

第二,Nix 包依赖、服务配置和系统配置共享同一套表达式与 store。Ansible 经常还要依赖目标机软件源、Python 环境和外部模板。两者都能固定版本,但 Nix 的输入锁定更接近默认工作方式。

第三,NixOS generation 是系统级结果。Ansible 可以自己实现回滚,但每个 role 是否留下可恢复状态,要由作者设计。

反过来说,Ansible 对现有异构 Linux、网络设备和命令式 API 的覆盖明显更广。NixOS 最舒服的场景,是你真的愿意让目标主机成为 NixOS,并把大部分状态纳入声明。

### 3. 和 Docker、Kubernetes 相比
容器解决的是应用交付边界,不等于主机操作系统管理。

我可以用容器运行 Grafana、数据库或 Web 应用,但仍然要处理:

- 内核和启动参数;
- 文件系统、磁盘、RAID 和 SMART;
- SSH、防火墙和路由;
- systemd 与宿主机 timer;
- 用户、权限和 secrets;
- node-exporter 与宿主机日志;
- 容器运行时本身。

NixOS 可以负责宿主机,也可以声明容器。两者不是替代关系。

Kubernetes 的控制回路和声明式 API 同样很适合 AI,因为资源有 schema、有状态、有事件。但对我这类十台左右、包含路由和存储职责的个人基础设施,引入一套集群控制面未必比直接管理 NixOS 更简单。

一个朴素的选择标准是:

- 如果主要问题是跨大量异构设备执行任务,优先考虑 Ansible;
- 如果主要问题是编排大规模容器工作负载,优先考虑 Kubernetes;
- 如果希望把主机从 bootloader 到服务都作为一个可求值结果管理,NixOS 很有吸引力;
- 如果只是修一台临时机器,Shell 可能已经足够。

AI 不会改变这些工具的适用范围,只会放大它们原有的结构。边界清楚的系统,AI 更容易得到可靠反馈;边界模糊的系统,AI 只会更快地产生更多变化。

## 十四、一次完整修改到底怎样发生
前面的案例按主题拆开讲,可能仍然显得抽象。下面用一次典型的监控修改,还原我和 AI 的实际协作节奏。

### 1. 告警到达
我把 Alertmanager 邮件贴进对话:
```
alertname = HostCpuHighIowait
instance = 198.18.0.5:9100
severity = warning
VALUE = 11.45
```
一开始我只知道它经常报警,不知道是磁盘真的濒临损坏、供应商虚拟化噪声,还是阈值太敏感。

一个不够谨慎的 AI 很可能直接建议把阈值从 10% 改成 20%,或者在 Alertmanager 中 silence 这台机器。这样邮件确实少了,但没有回答实际问题。

### 2. 先建立时间线
我要求继续监控并检查具体原因。AI 依次收集:

- 当前 iowait;
- 一段时间内的峰值和持续时间;
- `sda` 的写延迟;
- SMART 是否报告介质错误;
- 内核是否有 I/O reset;
- journald coredump 的发生时间;
- 同时段有哪些 timer、snapshot 或更新任务;
- 节点的虚拟化和磁盘类型;
- 最近配置切换时间。

这些信息的价值不在单个数值,而在顺序:
```
磁盘写延迟升高
-> iowait 短时超过 10%
-> 日志写入阻塞
-> journald worker 崩溃
-> coredump 与 iowait 告警一起出现
```
如果时间顺序相反,结论可能完全不同。例如某个服务先疯狂刷日志,才把磁盘打满,那么应该处理服务日志,而不是先怪供应商磁盘。

### 3. 把“事实”和“推断”分开
AI 在调查中需要明确区分:

- 事实:某时刻的指标、journal 行、unit 状态、coredump 堆栈;
- 推断:高延迟可能导致 journald 阻塞;
- 尚未证明:磁盘硬件已经损坏;
- 反证:SMART 没有介质错误,内核没有 reset,延迟会自行恢复。

这个区分非常重要。AI 的自然语言很流畅,很容易把“符合现象的解释”写成“已经确认的根因”。

在我的节点上,最后更合理的描述是:供应商提供的虚拟磁盘存在周期性高延迟,触发短时 iowait;没有足够证据证明物理盘即将损坏,但 journald 的写入模式确实放大了影响。

### 4. 先处理放大器,再调整告警
我们没有先关告警,而是减少故障放大:

- 将普通 journal 放在内存;
- 限制 journal 总量和单文件大小;
- 保留 audit 与外部 coredump 的持久证据;
- 减少高频代理日志;
- 避免监控采集再次扫描重型 journal 路径。

完成这些以后,再把 iowait 规则改成必须连续超过阈值五分钟。

这不是简单的“提高容忍度”。瞬时峰值在这台虚拟机上没有明确行动价值,而持续五分钟的高 iowait 仍然值得处理。告警定义应当对应可执行动作。

### 5. 本地验证
修改后不是立即部署。AI 先检查:
```
just fmt
statix check .
deadnix --fail .
just test
nix flake check --all-systems --no-build --show-trace
```
如果涉及 Prometheus 规则,还运行 Promtool 语法检查。

这里要警惕一种很常见的假成功:命令通过,但目标文件没有进入 Flake source;或者测试只检查规则能解析,没有检查 `for: 5m` 的行为。退出码是证据之一,不是全部证据。

### 6. 审查差异与提交
我会让 AI 解释:

- 哪些文件改变;
- 每个变化如何对应告警链路;
- 是否影响所有节点还是单个节点;
- 是否有阈值调整;
- 是否删除了采集证据;
- 如何回滚。

然后把不同语义拆成独立提交。这样如果日志策略有效、告警窗口不合适,可以只回退后一项。

提交信息不追求花哨,重点是让半年后的自己可以搜索:
```
fix(logging): reduce journald pressure on slow disks
fix(monitoring): require sustained high iowait
```

### 7. Test 与目标节点
公共模块发生改变时,先部署 Test 。Test 能确认模块求值、activation、unit 和基本健康。

但 Test 没有同一种慢磁盘,所以它不能证明 Lycheen 的问题已经消失。随后仍需部署真实目标,观察:

- journald 是否稳定;
- timer 是否成功;
- 指标是否继续采集;
- iowait 峰值是否缩短;
- Alertmanager 是否自然 resolved;
- 新规则会不会漏掉真正的持续故障。

这也说明 Test 节点的边界:它是发布闸门,不是生产环境的完美复制品。

### 8. 把结论写回系统
一次调查如果只停留在聊天里,下次会从头再来。

因此最终成果至少应该进入以下一种载体:

- 模块注释记录节点例外的原因;
- 测试记录不能破坏的约束;
- runbook 记录调查命令;
- commit 记录决策;
- 监控规则记录时间窗口;
- 文档记录已知代价。

AI 在这里最有价值的不是记住对话,而是帮助把对话压缩成仓库可以长期保存的事实。

## 十五、AI 最适合承担什么角色
经过这些实践,我认为 AI 在 NixOS 仓库中最适合五种角色。

### 1. 仓库考古
它可以快速回答:

- 这个 option 在哪些节点被覆盖;
- 某个地址由哪些字段派生;
- 一个 service 的 secret 从哪里进入;
- 某个 systemd unit 为什么出现在健康检查中;
- 哪些测试依赖某个旧字段;
- 最近几个 commit 是否误删过类似占位符。

这类工作人也能做,但在几百个文件中反复搜索很消耗注意力。

### 2. 约束翻译器
人说:
> 所有公网节点的 SSH 端口必须一致地传到防火墙、Colmena 和健康检查。

AI 可以把它翻译成:

- 数据模型;
- 模块配置;
- 求值断言;
- expected fixture;
- 文档。

### 3. 机械重构者
例如:

- 把主机元数据迁移到统一结构;
- 为 Nushell 函数补类型;
- 拆分大型模块;
- 批量修复格式和 Statix 建议;
- 把重复 required units 改成模块自声明。

只要边界明确,AI 做机械变更的效率很高。

### 4. 故障调查助手
它可以把 Alertmanager 标签、PromQL、systemd、journal、coredump、磁盘指标和最近部署串成一条时间线。

但要注意,“调查”不等于“立刻修改”。我通常会先让它解释原因,再决定是否授权修复。

### 5. 文档维护者
系统改完以后,AI 很适合根据真实代码更新:

- 根 README;
- 新主机流程;
- VPS 安装说明;
- 金丝雀发布说明;
- 安全基线;
- 快照与磁盘健康手册;
- 告警处理 runbook。

这比让文档长期依赖人的记忆更现实。

## 十六、我怎样给 AI 下达运维任务
同一个模型,用不同方式提出任务,结果差距很大。

最危险的指令通常很短:
> 优化一下。

它没有说明优化目标、风险边界、验证方式和是否允许部署。AI 只能自行填空,而它填入的目标往往是“代码更整洁”“warning 更少”“配置更统一”,未必是“线上更安全”。

我现在更倾向于分阶段给任务。

### 1. 只调查,不修改
```
检查这个告警的具体原因。
先读取当前规则、指标来源、节点日志和最近提交。
区分已确认事实、推断和未知项。
不要修改配置,不要清空告警,不要重启服务。
最后列出按风险排序的处理方案。
```
这种写法适合线上故障。它明确禁止 AI 为了快速得到“绿色”而改变状态。

### 2. 讨论一个设计
```
分析当前 SSH 端口检查覆盖了哪些层次。
目标是避免部署后把管理员锁在门外。
分别说明求值测试、Test 节点验证和外部网络探测能发现什么。
先讨论,不修改。
```
要求它解释覆盖范围,比直接说“多加几个测试”更容易得到有意义的设计。

### 3. 实施一个已经确认的方案
```
按刚才确定的方案修改。
只改 SSH 安全检查,不重构无关模块。
保留 secrets 中的预留模板和未使用接口参数。
运行格式化、Statix、Deadnix、仓库测试和 Flake 求值。
确认新增测试实际出现在报告中。
按 Conventional Commits 提交,但先不要部署。
```
这里把“不要做什么”写清楚,通常比重复强调“仔细一点”更有效。

### 4. 金丝雀部署
```
将刚才的 commit 先部署到 Test-NixOS。
检查 SSH、failed units、内核、网络、BIRD、DNS 和高优先级日志。
如果有异常就停止,不要继续其他节点。
如果全部通过,再部署其余受影响节点并执行最终健康检查。
报告 activation 成功和健康检查成功时要分开描述。
```
这条指令授予了明确范围内的部署权限,也规定了停止条件。

### 5. 审查 AI 自己的修改
```
重新查看最近几个提交。
寻找以下问题:
1. 为通过静态检查而删除了有意保留的占位符;
2. 新测试恒真、重复实现或检查已废弃路径;
3. 文档与最终配置不一致;
4. 新模块没有被任何主机导入;
5. 测试文件没有进入 Flake source;
6. commit 混入不相关修改。
只报告有证据的问题。
```
让 AI 第二次审查自己非常有用。第一次工作时,它的注意力集中在完成方案;第二次可以换成审查者视角。

### 6. 一些我会刻意避免的表达
我尽量不说:
- “自动修好所有问题”;
- “你看着办”;
- “没问题就全部部署”,但没有定义什么叫没问题;
- “清空告警”,但没有区分 silence、resolved 和删除历史;
- “把无用代码都删了”,但没有说明预留接口;
- “所有节点都一样配置”,但没有检查硬件差异。

这些话对人类同事也很模糊,只是人类可能会根据长期共同经验主动追问。AI 往往会选择一个最可能的解释并继续执行。

提示词不是魔法咒语。真正有效的是把工程权限、终止条件和可验证结果写清楚。

## 十七、怎样判断 AI 给出的结果是否可信
我不会根据回答语气判断可信度。AI 说“已经全面验证”没有意义,必须看它提供的证据落在哪一层。

### 第一层:文本证据
- 找到了相关实现;
- 引用了准确文件和 option;
- 差异与需求一致;
- 没有修改无关文件。

这一层只能证明代码看起来合理。

### 第二层:静态证据
- formatter 没有差异;
- Statix 和 Deadnix 通过;
- Nushell 能解析;
- YAML、JSON、PromQL 能解析;
- Git diff 没有空白错误。

静态工具擅长发现局部问题,不理解运行语义。

### 第三层:求值证据
- Nix 模块成功合并;
- option 类型正确;
- assertions 成立;
- 所有 host 输出可以求值;
- 新测试确实被 Flake 纳入。

这一层仍然不会启动服务。

### 第四层:构建证据
- derivation 可以实际构建;
- 引用的 package 存在;
- 生成文件符合预期;
- CI 代表性构建覆盖 Server、VPS 和桌面。

构建成功也不能证明真实 secret、网络和磁盘可用。

### 第五层:金丝雀证据
- Test activation 成功;
- SSH 没有中断;
- 必需 unit 正常;
- 实际探针通过;
- 新 generation 可启动;
- 高优先级日志没有新异常。

这证明配置能在一台真实机器上工作。

### 第六层:目标环境证据
- 受影响节点 activation 成功;
- 硬件或供应商特有路径有效;
- overlay 与公网路径连通;
- 数据库和应用真实响应;
- 没有新增 firing。

这一层才接近“上线成功”。

### 第七层:时间证据
- timer 按预定周期再次成功;
- 快照保留策略经过轮转;
- 告警能 firing 也能 resolved;
- 磁盘趋势没有继续恶化;
- 服务经过重启和下一次升级仍正常。

很多运维结论需要时间才能成立。刚部署五分钟就说“问题彻底解决”,通常证据不足。

我希望 AI 最终报告类似:
```
本地静态检查和全仓库求值通过;
Test-NixOS activation 与健康检查通过;
Lycheen-US-SLC 已成功切换,当前无 failed unit;
新的 iowait 规则已加载,但是否消除短时误报仍需观察一个告警周期。
```
而不是一句:
```
全部修复完成,一切正常。
```
前一种说法保留了证据边界,也告诉我下一步该观察什么。

## 中篇小结
从健康检查账户到告警规则,这一篇处理的其实是同一个问题:AI 看到的是代码和输出,但人类必须告诉它哪些语义不能从“有没有被引用”“命令是否成功”或“告警是否消失”中直接推导。

最小权限限制错误的作用面,快照和监控提供运行反馈,有意义的测试把经验固化成约束;清楚的任务边界与分层证据,则决定了什么时候可以继续部署。它们共同组成的不是无人值守系统,而是一个允许 AI 参与、又能及时拒绝错误的工程环境。

下一篇会集中讨论剩余风险:仓库为什么不等于现实,哪些副作用无法随 generation 回滚,为什么 CI 不应该默认触碰节点,以及个人维护者应当如何控制复杂度。

> 系列下一篇:《我让 AI 参与维护 9 台 NixOS(下):协作流程、证据边界与风险》

### 本篇参考
- [sops-nix:声明式 secrets 管理](https://github.com/Mic92/sops-nix)
- [Prometheus:告警规则单元测试](https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/)

---

原文链接:[点击查看](https://www.v2ex.com/t/1231220)

评论

暂无评论。

0.068980s