Skip to content

Nginx容器化、反向代理与生产安全

把Nginx放进Docker,不只是执行 docker run -p 80:80 nginx。商业项目还必须回答:镜像入口脚本做了什么、Nginx为什么以前台方式运行、配置到底从哪里读取、宿主机端口如何到达容器、反向代理为什么不能写localhost、配置reload为什么不中断连接、日志为什么能被 docker logs 读取、证书如何更新,以及出现403、502、504时从哪一层开始查。

本页专门讲Nginx的容器化边界。Nginx事件模型、location匹配、负载均衡和限流的通用原理继续阅读 Nginx专栏

学习目标

学完后应能:

  1. 画出镜像、Entrypoint、Nginx master/worker、Docker端口发布和请求处理链路。
  2. 区分镜像默认配置、Bind Mount、Volume、Compose configs 和Secret的适用范围。
  3. 使用只读配置、内部网络、健康检查和日志轮转启动Nginx。
  4. 解释容器中的localhost、服务名DNS、容器IP和宿主机IP分别表示什么。
  5. 完成静态资源、反向代理、WebSocket、TLS和真实客户端IP配置。
  6. 区分reload、restart、recreate和镜像升级的影响。
  7. 按证据链定位配置不生效、重启循环、403、404、502、504、TLS和磁盘问题。
  8. 说清Nginx容器的权限、只读根文件系统、临时目录、资源限制和高可用边界。

一、先理解完整请求链路

mermaid
flowchart TD
    A["浏览器请求宿主机IP:8080"] --> B["宿主机监听与Docker端口发布"]
    B --> C["DNAT到Nginx容器IP:80"]
    C --> D["Nginx master管理worker"]
    D --> E["worker匹配server与location"]
    E --> F{"请求类型"}
    F -- "静态资源" --> G["读取容器内html或挂载文件"]
    F -- "反向代理" --> H["通过Docker DNS解析后端服务名"]
    H --> I["连接后端容器端口"]
    I --> J["响应经Nginx返回客户端"]

例如:

yaml
ports:
  - "127.0.0.1:8080:80"

三个端口概念不能混淆:

概念示例谁在使用
宿主机地址和端口127.0.0.1:8080浏览器或宿主机客户端
容器端口80Nginx在自己的Network Namespace内监听
后端服务端口app:8080Nginx容器通过Compose网络访问应用

端口发布只完成宿主机到Nginx容器的转发,不会自动保证Nginx配置正确、后端可达或应用健康。

二、官方镜像启动时发生什么

先查看镜像声明,不要靠猜测:

bash
docker image inspect nginx:stable-alpine \
  --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}} User={{json .Config.User}}'

官方镜像通常使用类似下面的入口:

text
Entrypoint=["/docker-entrypoint.sh"]
Cmd=["nginx","-g","daemon off;"]

完整过程:

mermaid
flowchart TD
    A["docker compose up"] --> B["Docker创建namespace、cgroup、网络和挂载"]
    B --> C["执行docker-entrypoint.sh"]
    C --> D["入口脚本处理受支持的初始化脚本和模板"]
    D --> E["exec nginx -g daemon off"]
    E --> F["Nginx读取nginx.conf和conf.d"]
    F --> G["master创建worker"]
    G --> H["worker进入事件循环并接收请求"]

关键点:

  • Entrypoint脚本不是Nginx本身,它完成镜像约定的初始化后,再 exec 最终命令。
  • exec 会让Nginx替换Shell进程,使Nginx master成为容器PID 1。
  • daemon off; 让Nginx保持前台运行。容器生命周期由PID 1决定,若Nginx转为后台且前台入口退出,Docker会认为容器已经结束。
  • 不同镜像版本的入口脚本行为可能变化,升级时要查看镜像说明和实际脚本,不能永久依赖某个旧版本细节。

查看运行中的实际命令:

bash
docker inspect gateway-nginx \
  --format 'Path={{.Path}} Args={{json .Args}}'
docker top gateway-nginx

三、master、worker和PID 1

Nginx通常不是单进程模型:

mermaid
flowchart TD
    A["容器PID 1:master"] --> B["读取与校验配置"]
    A --> C["绑定监听端口"]
    A --> D["管理worker生命周期"]
    A --> E["接收TERM、QUIT、HUP等信号"]
    D --> F["worker 1事件循环"]
    D --> G["worker 2事件循环"]
    F --> H["处理大量连接"]
    G --> H
  • master负责读取配置、打开日志和监听端口、创建worker以及处理信号。
  • worker使用事件驱动模型处理连接,一个worker不是只能处理一个请求。
  • worker_processes auto; 通常按可见CPU决定worker数量,但仍应在目标镜像和cgroup环境中验证。
  • 容器停止时,Docker向PID 1发送停止信号。PID 1和信号设置错误会让优雅退出失效。

查看停止信号:

bash
docker image inspect nginx:stable-alpine \
  --format 'StopSignal={{json .Config.StopSignal}}'
docker inspect gateway-nginx \
  --format 'StopSignal={{json .Config.StopSignal}} StopTimeout={{.HostConfig.StopTimeout}}'

生产停止流程应是:先从上游负载均衡摘流,再发送优雅停止信号,等待现有请求结束,超过宽限期才强制终止。

四、配置文件从哪里读取

官方镜像常见路径:

路径用途
/etc/nginx/nginx.conf主配置,包含全局、events和http块
/etc/nginx/conf.d/*.confhttp下的站点或反向代理配置
/usr/share/nginx/html默认静态资源目录
/var/cache/nginx缓冲和临时文件目录之一
/var/run/runPID等运行时文件
/var/log/nginx日志路径;官方镜像常把日志链接到stdout/stderr

查看最终编译参数和完整配置:

bash
docker exec gateway-nginx nginx -V
docker exec gateway-nginx nginx -T

区别:

  • nginx -t:检查语法,并尝试打开配置中引用的文件。
  • nginx -T:先检查,再把合并后的完整配置打印出来。
  • 只查看宿主机源配置不够,因为文件可能没有正确挂载,或者被其他配置覆盖。

五、挂载配置为什么容易出错

推荐挂载单个站点文件:

yaml
volumes:
  - type: bind
    source: ./gateway.conf
    target: /etc/nginx/conf.d/default.conf
    read_only: true

检查最终挂载:

bash
docker inspect gateway-nginx \
  --format '{{range .Mounts}}{{println .Type .Source "->" .Destination "RW=" .RW}}{{end}}'

常见错误:

  1. 宿主机源文件不存在,某些旧用法可能创建同名目录,最终出现“文件挂成目录”。
  2. 把空目录挂到 /etc/nginx,遮住镜像自带的全部配置。
  3. 只挂 nginx.conf,却忘了它还 include /etc/nginx/conf.d/*.conf
  4. 宿主机改了文件,但没有执行配置测试和reload。
  5. SELinux、UID/GID或只读权限让Nginx无法读取证书和配置。
  6. Windows和Docker Desktop环境中,路径共享、换行符和大小写行为与Linux不同。

挂载会发生“覆盖视图”,不是把两个目录自动合并。目标路径原来存在的镜像内容会被挂载源遮住,卸载或重建容器后才重新可见。

六、为什么不应该使用privileged

Nginx提供HTTP反向代理通常不需要特权容器。--privileged 会显著扩大设备、Capability和宿主机攻击面,不能用它掩盖文件权限或低端口问题。

权限问题应按原因解决:

问题正确方向
配置无法读取检查Mounts、文件权限、UID/GID和SELinux标签
无法写缓存目录创建可写Volume/tmpfs并设置正确所有权
非root不能绑定80改监听8080,或只授予必要的低端口Capability并评估风险
证书Permission denied最小只读权限让Nginx用户可读,不开放整个目录写权限

也不要用 chmod 777 作为通用修复。它会扩大无关用户权限,并掩盖部署模型错误。

七、日志为什么能被docker logs读取

Docker日志链路是:

mermaid
flowchart TD
    A["Nginx access_log与error_log"] --> B["stdout与stderr"]
    B --> C["Docker日志驱动"]
    C --> D["docker logs"]
    C --> E["集中日志系统"]

官方镜像常把:

text
/var/log/nginx/access.log -> /dev/stdout
/var/log/nginx/error.log  -> /dev/stderr

因此可以执行:

bash
docker logs --timestamps --tail 200 gateway-nginx
docker logs --timestamps --since 10m -f gateway-nginx

如果把宿主机空目录直接挂到 /var/log/nginx,可能遮住这些符号链接,导致日志不再进入Docker日志驱动。生产更推荐明确输出标准流,由日志平台采集,并设置日志驱动轮转:

yaml
logging:
  driver: json-file
  options:
    max-size: "20m"
    max-file: "5"

日志轮转只控制容器日志文件大小,不等于集中检索、审计和长期保留。

八、静态资源怎么部署

学习环境可以挂载目录:

yaml
volumes:
  - type: bind
    source: ./html
    target: /usr/share/nginx/html
    read_only: true

生产有两种常见方式:

方式优点风险与边界
构建进镜像版本和镜像一起发布,可追踪、可回滚每次资源变更要重新构建镜像
外部Volume/对象存储/CDN资源更新独立、适合大文件版本一致性、缓存失效和权限要单独治理

前端静态站点Dockerfile示例:

dockerfile
FROM nginx:stable-alpine

COPY ./dist/ /usr/share/nginx/html/
COPY ./default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

COPY 进镜像前要确认构建上下文不包含 .env、源代码密钥、私钥或无关大文件。

SPA刷新404常用配置:

nginx
location / {
    root /usr/share/nginx/html;
    try_files $uri $uri/ /index.html;
}

原理是先尝试真实文件和目录,不存在时内部转到 index.html,让前端路由接管。它不应无条件应用到 /api/,否则后端错误可能被错误地返回HTML页面。

九、容器间反向代理为什么不能写localhost

在Nginx容器中:

text
localhost = Nginx容器自己

如果应用在另一个Compose服务中,应使用服务名:

nginx
location /api/ {
    proxy_pass http://app:8080/;
}
mermaid
flowchart TD
    A["gateway容器"] --> B["Docker内置DNS"]
    B --> C["解析服务名app"]
    C --> D["app容器IP:8080"]

不要写死容器IP。容器重建后IP可能变化,而Compose服务名是稳定的服务发现入口。

9.1 proxy_pass尾部斜杠的影响

下面两个配置可能产生不同上游URI:

nginx
location /api/ {
    proxy_pass http://app:8080;
}
nginx
location /api/ {
    proxy_pass http://app:8080/;
}

是否保留或替换location匹配前缀与 proxy_pass URI写法有关。修改前要用具体请求验证:

text
/api/orders/1001

并在后端日志确认最终收到的是 /api/orders/1001 还是 /orders/1001,不能只凭肉眼判断。

9.2 传递代理头

nginx
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;

应用只有在信任当前代理层时才能使用这些头。客户端可以自行伪造请求头,不能不配置可信代理范围就把任意 X-Forwarded-For 当真实IP。

十、Docker DNS和服务重建

Nginx解析upstream名称的时机与配置写法、版本和resolver配置有关。若Nginx只在启动或reload时解析一次,而后端容器重建后IP变化,可能继续访问旧IP并产生502。

排查:

bash
docker network inspect <compose网络>
docker exec gateway-nginx getent hosts app
docker exec gateway-nginx nginx -T

极简镜像可能没有 getentcurl 或Shell。不要在生产容器临时安装一堆工具;可以使用同网络的受控诊断容器,或从宿主机和Docker元数据侧检查。

生产应结合当前Nginx版本支持的动态解析方式、Docker内置DNS地址和upstream写法进行压测。仅仅“服务名能ping通”不能证明Nginx会在IP变化后自动更新已有解析结果。

十一、WebSocket和长连接

WebSocket需要完成HTTP Upgrade:

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;

    location /ws/ {
        proxy_pass http://app:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 60s;
    }
}

如果缺少Upgrade头,普通HTTP可能正常而WebSocket握手失败。超时也不能无限放大;需要结合心跳周期、连接数、worker连接上限、文件描述符和上游容量设计。

十二、TLS证书如何挂载和更新

证书与私钥应只读挂载:

yaml
volumes:
  - type: bind
    source: ./certs
    target: /etc/nginx/certs
    read_only: true

配置示例:

nginx
server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass http://app:8080;
    }
}

更新流程:

mermaid
flowchart TD
    A["证书平台签发新证书"] --> B["写入受控Secret或宿主机目录"]
    B --> C["在容器中执行nginx -t"]
    C --> D{"配置和证书是否有效"}
    D -- "否" --> E["保留旧证书并告警"]
    D -- "是" --> F["nginx -s reload"]
    F --> G["从外部验证证书链、域名和到期时间"]

私钥不能构建进公共镜像、提交到Git或输出到日志。只替换宿主机证书文件但不reload,旧worker可能继续使用旧证书。证书更新后还要从真实入口检查SNI、完整证书链和负载均衡各实例是否一致。

十三、reload、restart和recreate有什么区别

13.1 配置测试

bash
docker exec gateway-nginx nginx -t

13.2 优雅重载

bash
docker exec gateway-nginx nginx -s reload

典型reload过程:

mermaid
flowchart TD
    A["master收到HUP"] --> B["读取并校验新配置"]
    B --> C{"新配置是否可用"}
    C -- "否" --> D["保留旧worker继续服务"]
    C -- "是" --> E["启动新worker"]
    E --> F["旧worker停止接新连接"]
    F --> G["旧请求完成后退出"]

13.3 Docker重启

bash
docker compose restart gateway

它停止再启动同一个容器,不会自动采用新镜像,也通常不会把Compose中新改的端口、环境和挂载重建进去。

13.4 按当前Compose模型重建

bash
docker compose up -d --no-deps gateway

镜像或容器创建配置变化时需要重建。发布前先执行 docker compose config,发布后再inspect最终容器。

十四、可运行Compose Demo

这个Demo使用两个Nginx容器:gateway 对外暴露,app 只在内部网络提供模拟业务响应。这样不依赖本机Java环境,也能验证Docker DNS和反向代理。

目录:

text
nginx-compose-lab/
├── compose.yaml
├── gateway.conf
└── app.conf

14.1 后端 app.conf

nginx
server {
    listen 8080;
    server_name _;

    location /health {
        access_log off;
        default_type text/plain;
        return 200 "app healthy\n";
    }

    location / {
        default_type application/json;
        return 200 '{"service":"order-api","status":"ok"}';
    }
}

14.2 网关 gateway.conf

nginx
upstream order_backend {
    server app:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name _;

    location = /health {
        access_log off;
        default_type text/plain;
        return 200 "gateway healthy\n";
    }

    location /api/ {
        proxy_pass http://order_backend/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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_connect_timeout 3s;
        proxy_read_timeout 15s;
        proxy_send_timeout 15s;
    }
}

访问 /api/ 时,location前缀会因 proxy_pass 末尾 / 被替换,后端收到 /

14.3 compose.yaml

yaml
name: nginx-proxy-lab

services:
  app:
    image: nginx:stable-alpine
    command:
      - nginx
      - -g
      - daemon off;
    volumes:
      - type: bind
        source: ./app.conf
        target: /etc/nginx/conf.d/default.conf
        read_only: true
    expose:
      - "8080"
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1:8080/health | grep -q 'app healthy'"]
      interval: 10s
      timeout: 3s
      start_period: 10s
      retries: 5
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

  gateway:
    image: nginx:stable-alpine
    command:
      - nginx
      - -g
      - daemon off;
    volumes:
      - type: bind
        source: ./gateway.conf
        target: /etc/nginx/conf.d/default.conf
        read_only: true
    ports:
      - "127.0.0.1:8080:80"
    networks:
      - frontend
      - backend
    depends_on:
      app:
        condition: service_healthy
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O - http://127.0.0.1/health | grep -q 'gateway healthy'"]
      interval: 10s
      timeout: 3s
      start_period: 10s
      retries: 5
    stop_grace_period: 30s
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: "20m"
        max-file: "5"

networks:
  frontend:
  backend:
    internal: true

gateway 同时加入普通 frontend 网络和隔离的 backend 网络:宿主机发布端口经gateway进入,app只加入backend,不能被直接发布。internal: true 限制backend网络的外部连通性;真实网关如果还需要访问外部认证、OCSP或其他服务,可通过frontend或专门的受控出口网络访问,不能机械复制网络模型。

stable-alpine 便于学习运行,但它是可变Tag。生产发布应先在测试环境验证具体版本,并记录镜像Digest:

bash
docker image inspect nginx:stable-alpine \
  --format '{{index .RepoDigests 0}}'

14.4 启动与验证

bash
docker compose config
docker compose pull
docker compose up -d --wait
docker compose ps -a
curl -i http://127.0.0.1:8080/health
curl -i http://127.0.0.1:8080/api/

验证配置、日志、网络和挂载:

bash
docker compose exec gateway nginx -t
docker compose exec gateway nginx -T
docker compose logs --timestamps --tail 200 gateway
docker network inspect nginx-proxy-lab_backend
docker inspect nginx-proxy-lab-gateway-1 --format '{{json .Mounts}}'

实验结束:

bash
docker compose down

十五、健康检查该检查什么

健康检查不是“进程存在检查”。分层设计:

层次能证明什么不能证明什么
nginx -t当前配置语法与引用文件可读取worker一定能处理真实请求
本地 /healthNginx监听和location可响应后端应用可用
代理健康接口Nginx到关键后端链路可用所有业务和依赖都正常
外部探测DNS、LB、TLS、Nginx完整入口可用内部每个组件根因

把所有下游都塞进单个健康检查会造成级联摘流;完全不检查后端又可能把失败实例留在流量中。应区分存活、就绪和外部业务探测,并按平台能力设计。

十六、资源限制与容量估算

16.1 worker连接数不是QPS

理论连接上限常被粗略表达为:

text
worker_processes × worker_connections

但真实上限还受到:

  • 宿主机和容器文件描述符限制。
  • 每个代理请求可能同时占用客户端连接和上游连接。
  • Keep-Alive和WebSocket持有时间。
  • TLS内存与CPU。
  • Buffer和临时文件。
  • 后端连接池与容量。

因此不能把理论连接数直接当作QPS。

16.2 CPU限制

CPU quota会影响worker并行度、TLS握手、压缩和事件处理。若容器只分配1核却创建很多worker,可能增加调度开销。需要同时观察:

bash
docker stats --no-stream gateway-nginx
docker inspect gateway-nginx \
  --format 'NanoCpus={{.HostConfig.NanoCpus}} CpuQuota={{.HostConfig.CpuQuota}} CpuPeriod={{.HostConfig.CpuPeriod}}'

16.3 内存和临时文件

内存消耗来自连接状态、请求头、代理Buffer、TLS、缓存元数据、模块等。大响应或请求体还可能落到临时文件目录。若使用只读根文件系统,需要为必要目录提供受控可写tmpfs或Volume,否则会出现Permission denied。

十七、只读根文件系统和非root

进一步加固时可以考虑:

yaml
read_only: true
tmpfs:
  - /var/cache/nginx
  - /var/run
  - /tmp
security_opt:
  - no-new-privileges:true

但不能未经验证直接复制,因为镜像版本、PID路径、模板脚本和模块可能还需要其他可写目录。正确过程:

mermaid
flowchart TD
    A["盘点启动与运行写路径"] --> B["把配置和证书只读挂载"]
    B --> C["为必要临时目录提供tmpfs"]
    C --> D["使用非root或最小Capability"]
    D --> E["启动、reload、TLS和大请求测试"]
    E --> F["验证日志、健康检查和优雅退出"]

非root镜像常改为监听8080,再由Docker发布宿主机80/443。不要只改 user 字段而不检查配置读取、PID、缓存目录和低端口权限。

十八、502与504为什么不同

18.1 502 Bad Gateway

表示Nginx作为代理没有获得有效上游响应,常见原因:

  • 服务名解析失败。
  • 后端容器未运行。
  • 后端端口写错。
  • 应用只监听127.0.0.1。
  • 连接被拒绝或上游提前关闭。
  • HTTP代理到了HTTPS端口,或反过来。
  • 后端重建后Nginx仍使用旧解析结果。

18.2 504 Gateway Timeout

表示Nginx连接或等待上游响应超过超时,常见原因:

  • 慢SQL或锁等待。
  • Java线程池、数据库连接池耗尽。
  • 下游HTTP/RPC超时。
  • 大查询或大文件处理。
  • 网络丢包或防火墙静默丢弃。
  • 超时时间短于业务合理处理时长。

不能看到504就把 proxy_read_timeout 无限增大。超时只是失败边界,根因可能在后端容量和依赖链路。

十九、生产故障排查Runbook

19.1 第一步:保存现场并确认对象

bash
docker context show
docker compose ps -a
docker inspect gateway-nginx \
  --format 'Status={{.State.Status}} Exit={{.State.ExitCode}} OOM={{.State.OOMKilled}} Health={{if .State.Health}}{{.State.Health.Status}}{{end}}'

19.2 第二步:看启动日志和最终命令

bash
docker logs --timestamps --tail 300 gateway-nginx
docker inspect gateway-nginx \
  --format 'Image={{.Config.Image}} Path={{.Path}} Args={{json .Args}}'

19.3 第三步:验证最终配置

bash
docker exec gateway-nginx nginx -t
docker exec gateway-nginx nginx -T
docker inspect gateway-nginx --format '{{json .Mounts}}'

19.4 第四步:逐跳验证网络

text
客户端
→ DNS
→ 宿主机80/443
→ Docker端口发布
→ Nginx容器监听
→ Docker DNS
→ 后端容器端口
→ 后端应用
→ 数据库/Redis/MQ

每一跳分别判断:DNS失败、Connection refused、timeout、TLS错误还是HTTP状态码,不要把所有问题都叫“网络不通”。

19.5 第五步:资源与宿主机

bash
docker stats --no-stream gateway-nginx
docker top gateway-nginx
docker inspect gateway-nginx \
  --format 'Memory={{.HostConfig.Memory}} Pids={{.HostConfig.PidsLimit}} Ulimits={{json .HostConfig.Ulimits}}'

同时检查宿主机CPU、内存、文件描述符、磁盘容量、inode、网络和Docker daemon事件。

二十、常见问题逐项定位

现象首要证据常见根因修复方向
容器Restartingps -a、logs、State配置语法错、文件不存在、端口或权限错误保留日志,nginx -t,检查Mounts
配置改了不生效nginx -T、Mounts改错文件、未挂载、未reload、被include覆盖以容器最终配置为准
403error log、文件权限root/alias错误、目录无index、Nginx用户无权限检查location、路径、UID/GID和SELinux
404access/error log、URIlocation匹配错误、SPA缺少try_files、proxy_pass改写URI用具体URI验证最终路径
502error log、DNS、TCP后端未启动、服务名/端口错、协议错、旧IP从Nginx网络逐跳验证
504error log、上游耗时后端慢、线程池/连接池耗尽、下游超时查后端链路,不只放大超时
日志为空ls -l /var/log/nginx、LogConfig写文件而非标准流,挂载遮住符号链接恢复stdout/stderr并集中采集
HTTPS失败nginx -t、外部TLS探测私钥权限、证书链、SNI、过期、443未发布校验证书、域名、链和端口
reload失败error log、nginx -t新配置无效或引用文件不可读保留旧worker,修复后再reload
磁盘写满df -hdf -i、Docker日志无轮转、请求体/代理临时文件、Dump控制日志和临时文件,清理前确认归属

二十一、商业场景:订单API网关发布

发布要求:

  1. Nginx配置和镜像版本进入代码评审与CI。
  2. CI先在同版本镜像执行 nginx -t,再做路由与安全头测试。
  3. 配置和证书使用受控只读挂载,私钥不进入镜像。
  4. 网关只把必要入口端口暴露给负载均衡,后端只加入内部网络。
  5. 发布先金丝雀,观察入口QPS、4xx/5xx、P95/P99、upstream响应时间和连接数。
  6. reload后验证新旧worker过渡、长连接、WebSocket和证书。
  7. 502升高时停止扩散,按DNS、TCP、协议、后端健康逐层定位。
  8. 回滚不仅恢复配置,还要确认后端路由、证书和应用版本兼容。

二十二、升级Nginx镜像

升级前记录:

bash
docker exec gateway-nginx nginx -V
docker inspect gateway-nginx --format 'ImageID={{.Image}} ConfiguredImage={{.Config.Image}}'
docker image inspect nginx:stable-alpine --format '{{json .RepoDigests}}'

升级需要验证:

  • Release Notes和废弃指令。
  • 已编译模块、OpenSSL和TLS行为。
  • Entrypoint脚本与模板处理。
  • 配置语法、location路由和proxy_pass URI。
  • DNS重解析、长连接和WebSocket。
  • 日志格式与监控采集。
  • CPU、内存、延迟和错误率。

不要对运行容器只执行pull然后restart。pull只下载新镜像,已有容器仍引用创建时的镜像内容;要通过受控重建使用新镜像,并保留旧Digest用于回退。

二十三、面试标准回答

Docker中Nginx启动全过程是什么

Docker根据镜像创建namespace、cgroup、网络和挂载,执行镜像Entrypoint;入口脚本完成受支持的初始化后,用exec启动 nginx -g 'daemon off;'。Nginx master成为PID 1,读取nginx.conf和conf.d,绑定容器端口并创建worker。外部请求先到宿主机发布端口,经DNAT进入容器,worker匹配server和location,再返回静态资源或通过Docker DNS访问后端服务。

为什么Nginx容器要daemon off

容器生命周期取决于PID 1。Nginx若转入后台,前台入口进程退出后Docker会认为容器结束。daemon off让master保持前台并成为容器主进程,便于Docker管理日志、状态和停止信号。

为什么容器反向代理不能写localhost

每个容器有独立Network Namespace,Nginx容器中的localhost只指向Nginx容器自身。后端在另一个Compose服务中时应通过共同网络和服务名访问,例如app:8080;不能写死容器IP,因为重建后IP可能变化。

Nginx配置修改后为什么不一定生效

可能改错宿主机文件、挂载目标错误、空目录遮住镜像配置、include覆盖,或只改文件未reload。排查要inspect最终Mounts,再在容器内用 nginx -T 查看合并配置、nginx -t检查,成功后才reload,并从真实入口验证。

Nginx 502和504怎么区分

502表示代理没有获得有效上游响应,常见于DNS、端口、连接拒绝、协议错误和上游提前关闭;504表示连接或读取上游响应超时,常见于慢SQL、线程池或连接池耗尽、下游慢和网络丢包。都应先看Nginx error log,再逐跳验证上游,不能只重启或无限调大超时。

reload和restart有什么区别

reload让master校验新配置并启动新worker,旧worker处理完已有连接后退出,适合配置平滑生效;Docker restart停止再启动同一容器,会中断进程,而且不会自动采用新镜像或Compose中新建配置。镜像、端口、环境或挂载变化通常需要受控recreate。

二十四、学习验收

不看答案完成:

  1. 画出浏览器到宿主机发布端口、Nginx容器和后端容器的完整路径。
  2. 从image inspect和container inspect证明Entrypoint、Cmd、PID 1和停止信号。
  3. 使用只读配置挂载启动Demo,并用Mounts和 nginx -T证明实际配置。
  4. 解释为什么服务名可用、localhost错误、容器IP不稳定。
  5. 修改proxy_pass尾部斜杠,证明后端实际收到的URI变化。
  6. 完成配置错误时 nginx -t 失败、旧worker继续服务和修复后reload实验。
  7. 配置WebSocket代理头,并解释长连接对FD和优雅退出的影响。
  8. 完成证书只读挂载、更新、测试、reload和外部验证流程。
  9. 从日志、DNS、TCP和应用四层分别定位一次502与504。
  10. 设计非root、只读根文件系统、tmpfs和最小权限方案,并验证镜像写路径。
  11. 记录镜像Digest,完成新版本金丝雀验证和失败回退方案。
  12. 说明单个Nginx容器restart为什么不是入口高可用。

关联知识点