把 nginx 配置写成代码:从人工到 GitOps
当 nginx 配置多到没人敢动,就该考虑把它工程化了。一份从"裸配置 + sed"迁移到 GitOps 流水线的实践笔记。
某次事故复盘时,我们数了一下线上 nginx 配置文件:387 个 server 块,1600 多个 location,散落在 12 台机器上。最大的一个文件 2400 行,最后修改时间在三年前,原作者已经离职。这种状态下,没有人敢动它,但每个人都怕它出事。
这就是"配置债"。它不出现在代码评审里,却每天都在拖累团队。我们花了一个季度把它从"人工 + sed"迁移到 GitOps 流水线。
债务是怎么积累的
每一条配置都是某个时刻"合理的选择":临时给一个新域名加 vhost;为某个大促改 rewrite 规则;修复某个 502 加一个 proxy_next_upstream;对接某个新上游加一段 health check。每一条都"小而合理",但合起来就是一坨没人能完整复述的配置。配置债的可怕在于:它不会让系统崩,只会让系统"改不动"。
配置债的本质是"决策的上下文丢失"。改动还在,但为什么这么改已经没人记得。
第一步:把配置纳入 Git
所有 nginx 配置文件必须存放在 Git 仓库,禁止直接在机器上 vim。这是基础纪律。仓库结构:templates/ 放 jinja2 模板,inventory/ 放每台机器的变量,playbooks/ 放部署脚本,tests/ 放测试。把"配置文件"变成"数据 + 模板"。这样 vhost 之间的差异显式化,不再是 1700 行里找 3 行不同。
第二步:用模板抽象重复
387 个 vhost 里,80% 的内容是重复的(SSL 配置、日志格式、安全头)。抽成模板后,每个 vhost 只剩"它独有的部分"——10 行 YAML 替代 80 行 conf。更重要的是,"配置差异"变成了"数据差异",可被 review。
vhosts:
- server_name: api.example.com
upstream: api_backend
ssl_cert: /etc/nginx/ssl/api.example.com/fullchain.cer
extra_locations:
- path: /health
return: 200 "ok"
第三步:CI 校验
每次 PR 都跑:语法检查 nginx -t;语义检查所有 upstream 是否存在、SSL 证书是否存在;安全检查是否启用 HSTS、是否禁用弱密码套件;回归检查与 master 的渲染结果 diff,关键 location 不能消失。这一步把"事故"拦在合并之前。我们统计:CI 上线后,配置相关事故下降 80%。
第四步:灰度部署
配置变更必须灰度。我们用 Ansible 的 serial 参数控制:10% 一批,每批渲染 → 测试 → reload → 验证。任何一批失败,自动停。
- hosts: nginx_web
serial: "10%"
tasks:
- template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
notify: reload nginx
- command: nginx -t
- uri: url=https://localhost/health
until: r.status == 200
retries: 3
第五步:回滚是默认能力
Git 仓库本身就是回滚机制。每次部署都打 tag,回滚就是 git checkout && ansible-playbook。这比"备份原配置再覆盖"靠谱得多。
非技术但很关键的事
- 所有变更必须走 PR,包括紧急变更。紧急性通过"快速 review 通道"解决,不能跳过流程。
- 每个 PR 必须有"为什么改"的描述,不只是"改了什么"。
- 配置 owner 制度:每个 vhost 有明确 owner,PR review 至少包含 owner。
把 nginx 配置工程化,看起来是"工具问题",本质是"组织问题"。它要求团队接受"任何变更都要走流程"的纪律。这种纪律一旦建立,受益的不只是 nginx——它会扩散到所有基础设施。