$kernelink route --hydrate --safe

页面加载 /
跳到正文
auth://account/session

登录工作区

使用 Emlog 账户继续访问你的内容与互动记录。

忘记密码?

打开 Emlog 原生登录页

9.md
workspace / posts
~/posts/9.md 阅读中

Docker 部署 Nginx:配置挂载、反向代理和日志

Nginx 用 Docker 跑,镜像本身不用动,真正要定下来的是三件事:配置怎么挂、日志落到哪、容器和上游怎么互相找到。这三点定好了,后面加站点就是复制一个 conf 文件的事。

一、镜像和目录

用官方镜像,版本写死到小版本:

docker pull nginx:1.27

目录先建出来:

sudo mkdir -p /data/nginx/{conf.d,html,certs,logs}
宿主机目录 容器内路径 用途
/data/nginx/conf.d /etc/nginx/conf.d 站点配置,官方镜像默认会加载这里的 *.conf
/data/nginx/html /usr/share/nginx/html 静态文件
/data/nginx/certs /etc/nginx/certs 证书,自己约定的路径
/data/nginx/logs /var/log/nginx 访问日志和错误日志

二、先用 docker run 起一个

docker run -d --name nginx \
  -p 80:80 -p 443:443 \
  -v /data/nginx/conf.d:/etc/nginx/conf.d:ro \
  -v /data/nginx/html:/usr/share/nginx/html:ro \
  -v /data/nginx/certs:/etc/nginx/certs:ro \
  -v /data/nginx/logs:/var/log/nginx \
  -e TZ=Asia/Shanghai \
  --restart=always \
  nginx:1.27

conf.d、html、certs 都是只读挂载,容器不需要往里写东西。logs 必须可写。

跑起来先看默认页:

curl -I http://127.0.0.1/
docker exec nginx nginx -t

nginx -t 返回 syntax is ok 和 test is successful 两行才算配置能过,这个检查在改完配置后每次都要跑。

三、反向代理

在 /data/nginx/conf.d/app.conf 里写一个最简的反代:

upstream app_backend {
    server app:8080;
}

server {
    listen 80;
    server_name app.example.com;

    client_max_body_size 50m;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
    }
}

两个地方容易忽略:

  1. upstream 里写的 app 是容器名。Docker 内置 DNS 只在同一个自定义网络里解析容器名,Nginx 容器和 app 容器必须挂在同一个网络上,否则启动时报 host not found in upstream "app",整个 Nginx 起不来。
  2. proxy_set_header Host $host 不能省。默认透传的是 $proxy_host,也就是 upstream 名字,后端按 Host 做的域名路由、重定向、Cookie 域都会错。

改完配置,不用重启容器:

docker exec nginx nginx -t
docker exec nginx nginx -s reload

nginx -s reload 是给 master 进程发 HUP,平滑加载新配置,旧连接不断。先 -t 再 reload,配置写错时 -t 会拦下来,reload 不会让服务挂掉。

HTTPS 站点在同一个 server 块里加:

    listen 443 ssl;
    http2 on;
    ssl_certificate     /etc/nginx/certs/app.example.com.pem;
    ssl_certificate_key /etc/nginx/certs/app.example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;

证书文件放在 /data/nginx/certs 下,容器里按挂载路径引用。

静态文件单独放一个 server

server {
    listen 80;
    server_name static.example.com;
    root /usr/share/nginx/html;
    index index.html;

    location /assets/ {
        expires 7d;
        add_header Cache-Control "public";
    }
}

四、用 compose 管起来

/data/nginx/docker-compose.yml:

services:
  nginx:
    image: nginx:1.27
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    environment:
      TZ: Asia/Shanghai
    volumes:
      - /data/nginx/conf.d:/etc/nginx/conf.d:ro
      - /data/nginx/html:/usr/share/nginx/html:ro
      - /data/nginx/certs:/etc/nginx/certs:ro
      - /data/nginx/logs:/var/log/nginx

networks:
  default:
    name: web
    external: true

业务容器加入这个网络,就能用容器名互相访问:

docker network create web            # 只需建一次
docker network connect web app

用 compose 的话,在业务侧 compose 文件的网络段里写:

networks:
  default:
    name: web
    external: true

这样两边在同一个网络里,proxy_pass http://app:8080 才能解析。

五、日志

官方镜像里 /var/log/nginx/access.log 和 error.log 是指向 /dev/stdout、/dev/stderr 的软链,所以默认 docker logs nginx 能看到访问日志。一旦把宿主机目录挂到 /var/log/nginx,这两个软链被盖住,日志变成宿主机上的真实文件,docker logs 就空了。这不是故障,是挂载导致的必然结果。

用哪种方式,提前定:

方式 查看命令 轮转
不挂 logs 目录 docker logs -f nginx 由 Docker 的 max-size 控制
挂 logs 目录 tail -f /data/nginx/logs/access.log 自己做 logrotate

挂目录的情况下,加一份 logrotate:

/data/nginx/logs/*.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
    copytruncate
}

copytruncate 是因为容器里的 Nginx 不会收到 logrotate 的 USR1 信号,只能先复制再清空原文件。

要 JSON 格式日志,在 nginx.conf 里定义 log_format;conf.d 里的站点文件只能引用,不能定义全局 format,所以需要挂整个 nginx.conf,或者用 include 拆出来。

六、常见报错

现象 原因 处理
502 Bad Gateway,错误日志 connect() failed (111: Connection refused) 上游容器没起,或端口写错 docker ps 看上游,docker exec nginx getent hosts app 验 DNS
504 Gateway Time-out 后端处理慢,超过 proxy_read_timeout 调大超时,先确认后端本身耗时
413 Request Entity Too Large 上传超过 client_max_body_size,默认只有 1m 在 server 或 location 里调大
启动失败,host not found in upstream "app" 两个容器不在同一网络 建共用网络,或改用宿主机 IP
改了 conf 文件,容器里还是旧内容 单文件 bind mount 的 inode 问题 挂目录不挂单文件,重启容器
403 Forbidden,directory index of "/usr/share/nginx/html/" is forbidden 目录里没有 index 文件 放 index.html,或开 autoindex
docker logs 没输出 挂了 logs 目录,日志写到宿主机文件了 看 /data/nginx/logs
CentOS 上挂载后 403 或 Nginx 读不到文件 SELinux 拦了目录 挂载加 :z,或 setenforce 0 临时验证

七、备份和升级

Nginx 本身没有"数据",要备份的只有配置和证书:

sudo tar -czf /backup/nginx-$(date +%F).tar.gz -C /data nginx

升级镜像就是改 image: nginx:1.27 里的小版本,然后:

docker compose pull nginx
docker compose up -d nginx
docker exec nginx nginx -t

容器重建不影响挂载的配置和日志。升级前先备份配置,nginx -t 过了再放流量。

最后一条限制:client_max_body_size、proxy_read_timeout 这类参数没有全局最优值,得按实际业务的上传大小和后端耗时定,默认值在文件上传和大报表导出场景下基本都要调。

🤞 分享