> 把配置抽成 include 片段,会弄坏每一条 CI 没在测的组装路径

Systems & Infrastructure Intermediate 2026-10-11 18:09 2026-10-11
#nginx #docker #ci #deployment #configuration #failure-modes

安全响应头被从 nginx.conf 里抽出来,变成 nginx/snippets/security-headers.conf, 主配置里用 6 条 include /etc/nginx/snippets/security-headers.conf; 引它。

// TABLE_OF_CONTENTS

结论先行

安全响应头被从 nginx.conf 里抽出来,变成 nginx/snippets/security-headers.conf, 主配置里用 6 条 include /etc/nginx/snippets/security-headers.conf; 引它。

这个仓库有三条把 nginx 配置装进容器的路径,重构只更新了其中一条:

组装路径 有没有带上 snippets/ 结果
Dockerfile(单容器镜像) 有:COPY nginx/snippets/ /etc/nginx/snippets/ 能起
docker-compose.yml 没有——只挂了 nginx.conf 和 docs/ 两个路径 容器起来立刻退出
.github/workflows/docker-publish.yml(内联 Dockerfile) 没有 发布的镜像起不来

nginx 在加载配置时解析 include,目标文件缺失是致命错误。 所以 make docker-up 拉起一个立刻退出的容器; 而发布出去的那个 web 镜像是 docker-compose.prod.yml 拉的, docker-compose.prod.yml 又是 deploy.sh 跑的——对外宣传的一行部署路径发布了一个起不来的容器。

CI 一直是绿的,因为它只 smoke test 了两个 Dockerfile* 镜像,从来没测 docker-compose.yml。

为什么

include 重构的危险在于它把一个文件内的依赖变成了文件间的依赖, 而"把文件送进容器"这件事在每条组装路径里是各自独立实现的: COPY 一次、volumes: 一次、workflow 里内联的 Dockerfile 再一次。 抽取片段时你只会看着主配置文件,看不见这三处。

两条可推广的判断:

  • 组装路径的数量 = 这次重构的风险面。 同一个 artifact 有几种装配方式(Dockerfile / compose / workflow 内联 / 手工部署脚本), 就有几处要同步,而其中被 CI 覆盖的往往只有一处。
  • "CI 绿"的覆盖面要按路径数去读,不是按 job 数。 这里 CI 覆盖 1/3 条路径,而被覆盖的恰好是不出问题的那条。

这和同一类的另一个 nginx 坑同源:一个 location 块里只要出现任意一条 add_header,就会丢弃 server 块继承来的全部头—— 两者都是"配置的组合规则和你脑子里的继承模型不一样"。

怎么用

  1. 抽 include 片段之前,先 grep 出所有组装路径。 grep -rln 'nginx.conf\|COPY nginx\|volumes:' . --include='*.yml' --include='Dockerfile*' 每一处都要么带上新目录,要么确认它不需要。
  2. 给每条部署路径一个 smoke test,哪怕只是"起来之后还活着"。 docker compose up -d && sleep 3 && docker compose ps 能发现这整类问题。 没被 smoke test 的那条路径,就是会在用户那里第一次运行的那条。
  3. 逐路径验证响应头,不要只验 /。 /、一个静态路径、一个用户上传路径分别 curl -I 一次——它们经常不一样。
  4. 宣传在 README 里的部署命令,必须有一次真实执行过的记录,否则它就是文档,不是路径。

出处

  • ~/Engineering/Mandelbrot-Renderer/PROJECT_ENGINEERING_GUIDE.md §0.2 / §0.3
  • 2026-10-11 核对现状:nginx/nginx.conf 中 6 条 include、nginx/snippets/security-headers.conf 存在、docker-compose.yml 的 volumes: 仅两条且不含 snippets、 Dockerfile:54 有 COPY nginx/snippets/、docker-publish.yml 中 grep 不到 snippets
  • 同一类配置组合陷阱:检查渲染产物,不是源码——Django 的 {# #} 只支持单行