Blog

用 Ansible 批量配置实验室 Linux

机器超过三台后,手工 SSH 改配置会开始出错:这台装了 fail2ban,那台忘了;时区有的对有的不对。三月把常见初始化收成 Ansible playbook。这篇不按「从零安装 Ansible」写,而按 inventory 怎么长成今天这样写——演化史比最终文件更有用。

史前:只有一个 hosts 文件和希望

最早 inventory 只有三行 IP,用户名写在命令行 -u,主机名全是 10.8.0.x。跑通 ping 模块的那天很开心,第二天就忘了哪台是 NAS 哪台是实验机。第一课:没有可读名称的 inventory 等于没有文档。

[lab]
10.8.0.10
10.8.0.20
10.8.0.5

密钥登录必须先搞定。密码登录批量是灾难片开场——密钥失效时宁可停,不要把密码写进 shell 历史。ansible_host 与 WireGuard 内网地址对齐后,我习惯从笔记本经隧道批量操作,避免在公网 IP 上直接撒 playbook。

第一阶段:分组与 ansible_host

把「显示名」和「连哪」分开,心理负担立刻下降:

[lab]
lab-core ansible_host=10.8.0.10
nas-app  ansible_host=10.8.0.20
vps-1    ansible_host=10.8.0.5

[lab:vars]
ansible_user=veritas
ansible_python_interpreter=/usr/bin/python3

[lab:vars] 消灭了重复的 -u 与解释器路径。随后出现 [lab:children] 的念头:把 nas、vps、workstation 拆开,因为安全基线相同、但 docker 与备份角色并不相同。演化的动力永远是:某次 --limit 打错范围,或某 role 不该在某台跑。

也试过把主机变量拆到 host_vars/:时区统一、hostname 各异、是否启用 docker 用布尔值。inventory 本体变瘦,差异集中到文件树里,code review 时更好看「这台到底特在哪」。坏处是新手克隆仓库后要多理解一层目录约定,所以 README 里画了「新机器改哪几个文件」。

第二阶段:role 长出来,inventory 开始长标签

常用 role 保持无聊:

  • common:时区、hostname、基础包、vim
  • security:ssh 硬化、fail2ban、ufw 骨架
  • docker:引擎与当前用户组
  • node_exporter:监控代理

无聊才能在多台机器上重复。inventory 侧开始加 labels 式的组:[docker_hosts]、[monitored],host 可以重复出现在多组。比在 playbook 里写一长串 when: inventory_hostname in ... 清晰。敏感变量进 ansible-vault,vault 密码不与登录密码相同。

第三阶段:check、limit、tags 成为肌肉记忆

ansible-playbook -i inventory site.yml --check
ansible-playbook -i inventory site.yml --limit lab-core
ansible-playbook -i inventory site.yml --tags security
ansible-playbook -i inventory site.yml

先 check 再 apply。生产变更加 --limit,别一次打全家。play 加 tags,方便只跑 security。handlers 重启 sshd 要谨慎,确认不会踢光所有会话——曾在实验机上把自己锁在「新配置未生效、旧会话已断」的夹缝里,后来改成显式确认窗口。

inventory 文件本身也进 Git,但生产密钥与 vault 密文的处理要干净;明文 secret 进库是演化史上的黑暗时代,只发生一次就够。

边界:第三次才写进 role

一次性实验继续手动。会重复第三次的再写 role。把临时实验写进强制 apply 的角色,三个月后你会在毫不知情时「被收敛」掉实验环境——端口又被 ufw 收掉、测试用户又被删掉。为此 inventory 里出现过 [lab_unmanaged] 或跳过组:声明「这台暂时不收敛」,比口头记住可靠。

自动化的回报是:新机器半小时进监控、进备份、进同一安全基线。这种一致性,比酷炫任务本身更重要。新机器入编流程后来也固化了:先 SSH 密钥与 sudo,再写入 inventory 对应组,先 --limit 跑 common 与 security,确认无误再加 docker 或 exporter。inventory 里多一行注释「用途 / 负责人 / 是否可强收敛」,三个月后的你会感谢现在的你。

inventory 从三行 IP 长成带组、带变量、带例外清单的小地图——地图还在长,但至少不会再靠记忆分辨 10.8.0.20 到底是谁。演化还会继续:也许下一步是拆成多环境 inventory,或把部分事实改由动态脚本生成;原则不变——名称可读、范围可 limit、例外可声明、秘密不进明文仓库。

一次真实的「半失败」批量

有回我想给五台机器统一装 qemu-guest-agent 和改时区。playbook 很短,--check 也过了,正式跑时三台成功、两台失败:一台 Python 解释器路径不对(精简容器式 LXC),一台 SSH 变成只允许密钥且我临时用了密码参数。

处理方式不是「再 -vvv 扫一遍世界」,而是:

  1. 用 --limit 只重跑失败主机
  2. 给 LXC 那组单独 ansible_python_interpreter
  3. 密码参数从命令行环境变量进,不进仓库

批量工具会放大小疏忽。家庭实验室的 Ansible 价值,经常是把成功的步骤固化,而不是追求一次编排一百台。

密钥与敏感变量

group_vars 里的密码我改用 Ansible Vault 或干脆不进 git,机器上留 vars.local.yml(权限 600)。曾经把 SMTP 应用密码写进明文 role,推到私有 Gitea 也觉得「还好是私有」——后来还是轮换了。私有不等于泄露无代价。

handler 重启服务要克制:sshd 配置改完 reload 即可的不要 restart,防火墙规则错误叠加重启可能把自己踢出去——改网络相关 play 时保留一个活跃会话,和改 sshd 同一纪律。

← Back to Blog