在共享的 nginx 容器里加站点,怎么做到不中断

一台服务器上跑着好几个小业务,为了省资源,nginx 只起了一个容器, 所有站点的 server 块都放在同一个挂载目录里。这套结构平时很省事, 直到有一天我要加个自己的站点。

问题在哪

先看一下容器的挂载情况:

docker inspect nginx-server-1 --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}
{{end}}'
/root/logs/nginx              -> /var/log/nginx
/root/dockers/nginx/conf/nginx.conf -> /usr/local/openresty/nginx/conf/nginx.conf
/root/dockers/nginx/conf/conf.d     -> /etc/nginx/conf.d
/root/vhost/dist                    -> /etc/nginx/html/inventory/dist

配置目录是挂载的,所以加 server 块没问题 —— 直接把 .conf 文件丢进 /root/dockers/nginx/conf/conf.d/ 就行。

麻烦在于站点文件。 内容目录只挂了一个 /root/vhost/dist,那是给库存系统用的。 我要放自己的静态站,按常规思路应该在 compose 里加一行挂载:

volumes:
  - /root/vhost/funswe:/etc/nginx/html/funswe:ro   # 想加的

但加挂载必须重建容器,也就是 docker compose up -d, 这会把这个 nginx 背后的所有业务一起停几秒。 那几个业务是别人在用的,为加自己的站点让别人的服务抖一下,不太合适。

零中断的做法

思路很简单:既然不能加挂载点,那就用已有的挂载点。

  1. 把站点文件放到已经挂载的目录下的一个子目录里
  2. server 块的 root 指到这个子目录
  3. 用 nginx -s reload 让配置生效(不重建容器)
# 1. 站点文件放进已挂载的目录
mkdir -p /root/vhost/dist/funswe-site/assets
# 容器内对应路径:/etc/nginx/html/inventory/dist/funswe-site

# 2. server 块
cat > /root/dockers/nginx/conf/conf.d/funswe.com.conf <<'EOF'
server {
    listen 80;
    server_name funswe.com www.funswe.com;

    charset utf-8;
    root  /etc/nginx/html/inventory/dist/funswe-site;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

# 3. 先检查,再 reload
docker exec nginx-server-1 nginx -t
docker exec nginx-server-1 nginx -s reload
为什么 reload 不会中断: nginx 收到 reload 信号后,会用新配置启动新的 worker 进程, 等旧 worker 把手上的请求处理完再退出。整个过程监听 socket 一直是开着的, 所以正在处理的请求不会被打断,新请求直接由新 worker 接手。 这和 restart 是两回事。

两个细节

一定要先 nginx -t

配置写错了直接 reload,虽然 nginx 会拒绝并保留旧配置(不会把自己搞挂), 但你会看到一堆报错,还得多绕一圈。养成习惯:

docker exec nginx-server-1 nginx -t && \
  docker exec nginx-server-1 nginx -s reload

验证不能只看新站

加完以后我只测了新域名,全绿。但心里没底 —— 毕竟动的是共用组件。 于是把所有站点的 Host 都过了一遍:

for pair in "a.example.com:80" "b.example.com:8082" "c.example.com:8083"; do
  host="${pair%%:*}"; port="${pair##*:}"
  printf "%-24s -> %s\n" "$host" \
    "$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $host" http://127.0.0.1:$port/)"
done

结果发现有一个站点返回 404,当时就紧张了。查下去才放心:那是个 webhook 接收端点,不是网站,GET 根路径本来就该 404。

确认方式是把 nginx 摘掉,直连后端看:

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/
# 404 —— 后端自己就返回 404,与 nginx 无关

再看它的访问日志,正常业务是 POST /dingtalk/message, 一直返回 200,说明服务好好的。

排查别人的服务时,先分清楚「本来就坏」和「被我改坏」。 最省事的判断方法就是绕过中间层直连后端,对比两边的行为。

这个做法的代价

老实说这不是什么优雅方案 —— 站点文件寄居在别人项目的目录下面, 时间久了容易让人困惑。它只是在「不想动共用组件」这个约束下的折中。

正规做法还是给 nginx 加独立挂载,然后重建一次容器。等哪天有维护窗口, 我会把文件迁到 /root/vhost/funswe,挂载加上去,再删掉旧目录。

所以结论是:reload 是零中断的,重建容器不是。 能用前者就别用后者;实在必须重建,先想清楚影响面,挑个低峰时段。

← 返回首页