Nginx容器化、反向代理与生产安全
把Nginx放进Docker,不只是执行 docker run -p 80:80 nginx。商业项目还必须回答:镜像入口脚本做了什么、Nginx为什么以前台方式运行、配置到底从哪里读取、宿主机端口如何到达容器、反向代理为什么不能写localhost、配置reload为什么不中断连接、日志为什么能被 docker logs 读取、证书如何更新,以及出现403、502、504时从哪一层开始查。
本页专门讲Nginx的容器化边界。Nginx事件模型、location匹配、负载均衡和限流的通用原理继续阅读 Nginx专栏。
学习目标
学完后应能:
- 画出镜像、Entrypoint、Nginx master/worker、Docker端口发布和请求处理链路。
- 区分镜像默认配置、Bind Mount、Volume、Compose
configs和Secret的适用范围。 - 使用只读配置、内部网络、健康检查和日志轮转启动Nginx。
- 解释容器中的localhost、服务名DNS、容器IP和宿主机IP分别表示什么。
- 完成静态资源、反向代理、WebSocket、TLS和真实客户端IP配置。
- 区分reload、restart、recreate和镜像升级的影响。
- 按证据链定位配置不生效、重启循环、403、404、502、504、TLS和磁盘问题。
- 说清Nginx容器的权限、只读根文件系统、临时目录、资源限制和高可用边界。
一、先理解完整请求链路
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返回客户端"]例如:
ports:
- "127.0.0.1:8080:80"三个端口概念不能混淆:
| 概念 | 示例 | 谁在使用 |
|---|---|---|
| 宿主机地址和端口 | 127.0.0.1:8080 | 浏览器或宿主机客户端 |
| 容器端口 | 80 | Nginx在自己的Network Namespace内监听 |
| 后端服务端口 | app:8080 | Nginx容器通过Compose网络访问应用 |
端口发布只完成宿主机到Nginx容器的转发,不会自动保证Nginx配置正确、后端可达或应用健康。
二、官方镜像启动时发生什么
先查看镜像声明,不要靠猜测:
docker image inspect nginx:stable-alpine \
--format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}} User={{json .Config.User}}'官方镜像通常使用类似下面的入口:
Entrypoint=["/docker-entrypoint.sh"]
Cmd=["nginx","-g","daemon off;"]完整过程:
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会认为容器已经结束。- 不同镜像版本的入口脚本行为可能变化,升级时要查看镜像说明和实际脚本,不能永久依赖某个旧版本细节。
查看运行中的实际命令:
docker inspect gateway-nginx \
--format 'Path={{.Path}} Args={{json .Args}}'
docker top gateway-nginx三、master、worker和PID 1
Nginx通常不是单进程模型:
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和信号设置错误会让优雅退出失效。
查看停止信号:
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/*.conf | http下的站点或反向代理配置 |
/usr/share/nginx/html | 默认静态资源目录 |
/var/cache/nginx | 缓冲和临时文件目录之一 |
/var/run或/run | PID等运行时文件 |
/var/log/nginx | 日志路径;官方镜像常把日志链接到stdout/stderr |
查看最终编译参数和完整配置:
docker exec gateway-nginx nginx -V
docker exec gateway-nginx nginx -T区别:
nginx -t:检查语法,并尝试打开配置中引用的文件。nginx -T:先检查,再把合并后的完整配置打印出来。- 只查看宿主机源配置不够,因为文件可能没有正确挂载,或者被其他配置覆盖。
五、挂载配置为什么容易出错
推荐挂载单个站点文件:
volumes:
- type: bind
source: ./gateway.conf
target: /etc/nginx/conf.d/default.conf
read_only: true检查最终挂载:
docker inspect gateway-nginx \
--format '{{range .Mounts}}{{println .Type .Source "->" .Destination "RW=" .RW}}{{end}}'常见错误:
- 宿主机源文件不存在,某些旧用法可能创建同名目录,最终出现“文件挂成目录”。
- 把空目录挂到
/etc/nginx,遮住镜像自带的全部配置。 - 只挂
nginx.conf,却忘了它还include /etc/nginx/conf.d/*.conf。 - 宿主机改了文件,但没有执行配置测试和reload。
- SELinux、UID/GID或只读权限让Nginx无法读取证书和配置。
- 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日志链路是:
flowchart TD
A["Nginx access_log与error_log"] --> B["stdout与stderr"]
B --> C["Docker日志驱动"]
C --> D["docker logs"]
C --> E["集中日志系统"]官方镜像常把:
/var/log/nginx/access.log -> /dev/stdout
/var/log/nginx/error.log -> /dev/stderr因此可以执行:
docker logs --timestamps --tail 200 gateway-nginx
docker logs --timestamps --since 10m -f gateway-nginx如果把宿主机空目录直接挂到 /var/log/nginx,可能遮住这些符号链接,导致日志不再进入Docker日志驱动。生产更推荐明确输出标准流,由日志平台采集,并设置日志驱动轮转:
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"日志轮转只控制容器日志文件大小,不等于集中检索、审计和长期保留。
八、静态资源怎么部署
学习环境可以挂载目录:
volumes:
- type: bind
source: ./html
target: /usr/share/nginx/html
read_only: true生产有两种常见方式:
| 方式 | 优点 | 风险与边界 |
|---|---|---|
| 构建进镜像 | 版本和镜像一起发布,可追踪、可回滚 | 每次资源变更要重新构建镜像 |
| 外部Volume/对象存储/CDN | 资源更新独立、适合大文件 | 版本一致性、缓存失效和权限要单独治理 |
前端静态站点Dockerfile示例:
FROM nginx:stable-alpine
COPY ./dist/ /usr/share/nginx/html/
COPY ./default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80COPY 进镜像前要确认构建上下文不包含 .env、源代码密钥、私钥或无关大文件。
SPA刷新404常用配置:
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}原理是先尝试真实文件和目录,不存在时内部转到 index.html,让前端路由接管。它不应无条件应用到 /api/,否则后端错误可能被错误地返回HTML页面。
九、容器间反向代理为什么不能写localhost
在Nginx容器中:
localhost = Nginx容器自己如果应用在另一个Compose服务中,应使用服务名:
location /api/ {
proxy_pass http://app:8080/;
}flowchart TD
A["gateway容器"] --> B["Docker内置DNS"]
B --> C["解析服务名app"]
C --> D["app容器IP:8080"]不要写死容器IP。容器重建后IP可能变化,而Compose服务名是稳定的服务发现入口。
9.1 proxy_pass尾部斜杠的影响
下面两个配置可能产生不同上游URI:
location /api/ {
proxy_pass http://app:8080;
}location /api/ {
proxy_pass http://app:8080/;
}是否保留或替换location匹配前缀与 proxy_pass URI写法有关。修改前要用具体请求验证:
/api/orders/1001并在后端日志确认最终收到的是 /api/orders/1001 还是 /orders/1001,不能只凭肉眼判断。
9.2 传递代理头
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。
排查:
docker network inspect <compose网络名>
docker exec gateway-nginx getent hosts app
docker exec gateway-nginx nginx -T极简镜像可能没有 getent、curl 或Shell。不要在生产容器临时安装一堆工具;可以使用同网络的受控诊断容器,或从宿主机和Docker元数据侧检查。
生产应结合当前Nginx版本支持的动态解析方式、Docker内置DNS地址和upstream写法进行压测。仅仅“服务名能ping通”不能证明Nginx会在IP变化后自动更新已有解析结果。
十一、WebSocket和长连接
WebSocket需要完成HTTP Upgrade:
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证书如何挂载和更新
证书与私钥应只读挂载:
volumes:
- type: bind
source: ./certs
target: /etc/nginx/certs
read_only: true配置示例:
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;
}
}更新流程:
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 配置测试
docker exec gateway-nginx nginx -t13.2 优雅重载
docker exec gateway-nginx nginx -s reload典型reload过程:
flowchart TD
A["master收到HUP"] --> B["读取并校验新配置"]
B --> C{"新配置是否可用"}
C -- "否" --> D["保留旧worker继续服务"]
C -- "是" --> E["启动新worker"]
E --> F["旧worker停止接新连接"]
F --> G["旧请求完成后退出"]13.3 Docker重启
docker compose restart gateway它停止再启动同一个容器,不会自动采用新镜像,也通常不会把Compose中新改的端口、环境和挂载重建进去。
13.4 按当前Compose模型重建
docker compose up -d --no-deps gateway镜像或容器创建配置变化时需要重建。发布前先执行 docker compose config,发布后再inspect最终容器。
十四、可运行Compose Demo
这个Demo使用两个Nginx容器:gateway 对外暴露,app 只在内部网络提供模拟业务响应。这样不依赖本机Java环境,也能验证Docker DNS和反向代理。
目录:
nginx-compose-lab/
├── compose.yaml
├── gateway.conf
└── app.conf14.1 后端 app.conf
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
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
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: truegateway 同时加入普通 frontend 网络和隔离的 backend 网络:宿主机发布端口经gateway进入,app只加入backend,不能被直接发布。internal: true 限制backend网络的外部连通性;真实网关如果还需要访问外部认证、OCSP或其他服务,可通过frontend或专门的受控出口网络访问,不能机械复制网络模型。
stable-alpine 便于学习运行,但它是可变Tag。生产发布应先在测试环境验证具体版本,并记录镜像Digest:
docker image inspect nginx:stable-alpine \
--format '{{index .RepoDigests 0}}'14.4 启动与验证
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/验证配置、日志、网络和挂载:
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}}'实验结束:
docker compose down十五、健康检查该检查什么
健康检查不是“进程存在检查”。分层设计:
| 层次 | 能证明什么 | 不能证明什么 |
|---|---|---|
nginx -t | 当前配置语法与引用文件可读取 | worker一定能处理真实请求 |
本地 /health | Nginx监听和location可响应 | 后端应用可用 |
| 代理健康接口 | Nginx到关键后端链路可用 | 所有业务和依赖都正常 |
| 外部探测 | DNS、LB、TLS、Nginx完整入口可用 | 内部每个组件根因 |
把所有下游都塞进单个健康检查会造成级联摘流;完全不检查后端又可能把失败实例留在流量中。应区分存活、就绪和外部业务探测,并按平台能力设计。
十六、资源限制与容量估算
16.1 worker连接数不是QPS
理论连接上限常被粗略表达为:
worker_processes × worker_connections但真实上限还受到:
- 宿主机和容器文件描述符限制。
- 每个代理请求可能同时占用客户端连接和上游连接。
- Keep-Alive和WebSocket持有时间。
- TLS内存与CPU。
- Buffer和临时文件。
- 后端连接池与容量。
因此不能把理论连接数直接当作QPS。
16.2 CPU限制
CPU quota会影响worker并行度、TLS握手、压缩和事件处理。若容器只分配1核却创建很多worker,可能增加调度开销。需要同时观察:
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
进一步加固时可以考虑:
read_only: true
tmpfs:
- /var/cache/nginx
- /var/run
- /tmp
security_opt:
- no-new-privileges:true但不能未经验证直接复制,因为镜像版本、PID路径、模板脚本和模块可能还需要其他可写目录。正确过程:
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 第一步:保存现场并确认对象
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 第二步:看启动日志和最终命令
docker logs --timestamps --tail 300 gateway-nginx
docker inspect gateway-nginx \
--format 'Image={{.Config.Image}} Path={{.Path}} Args={{json .Args}}'19.3 第三步:验证最终配置
docker exec gateway-nginx nginx -t
docker exec gateway-nginx nginx -T
docker inspect gateway-nginx --format '{{json .Mounts}}'19.4 第四步:逐跳验证网络
客户端
→ DNS
→ 宿主机80/443
→ Docker端口发布
→ Nginx容器监听
→ Docker DNS
→ 后端容器端口
→ 后端应用
→ 数据库/Redis/MQ每一跳分别判断:DNS失败、Connection refused、timeout、TLS错误还是HTTP状态码,不要把所有问题都叫“网络不通”。
19.5 第五步:资源与宿主机
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事件。
二十、常见问题逐项定位
| 现象 | 首要证据 | 常见根因 | 修复方向 |
|---|---|---|---|
| 容器Restarting | ps -a、logs、State | 配置语法错、文件不存在、端口或权限错误 | 保留日志,nginx -t,检查Mounts |
| 配置改了不生效 | nginx -T、Mounts | 改错文件、未挂载、未reload、被include覆盖 | 以容器最终配置为准 |
| 403 | error log、文件权限 | root/alias错误、目录无index、Nginx用户无权限 | 检查location、路径、UID/GID和SELinux |
| 404 | access/error log、URI | location匹配错误、SPA缺少try_files、proxy_pass改写URI | 用具体URI验证最终路径 |
| 502 | error log、DNS、TCP | 后端未启动、服务名/端口错、协议错、旧IP | 从Nginx网络逐跳验证 |
| 504 | error log、上游耗时 | 后端慢、线程池/连接池耗尽、下游超时 | 查后端链路,不只放大超时 |
| 日志为空 | ls -l /var/log/nginx、LogConfig | 写文件而非标准流,挂载遮住符号链接 | 恢复stdout/stderr并集中采集 |
| HTTPS失败 | nginx -t、外部TLS探测 | 私钥权限、证书链、SNI、过期、443未发布 | 校验证书、域名、链和端口 |
| reload失败 | error log、nginx -t | 新配置无效或引用文件不可读 | 保留旧worker,修复后再reload |
| 磁盘写满 | df -h、df -i、Docker日志 | 无轮转、请求体/代理临时文件、Dump | 控制日志和临时文件,清理前确认归属 |
二十一、商业场景:订单API网关发布
发布要求:
- Nginx配置和镜像版本进入代码评审与CI。
- CI先在同版本镜像执行
nginx -t,再做路由与安全头测试。 - 配置和证书使用受控只读挂载,私钥不进入镜像。
- 网关只把必要入口端口暴露给负载均衡,后端只加入内部网络。
- 发布先金丝雀,观察入口QPS、4xx/5xx、P95/P99、upstream响应时间和连接数。
- reload后验证新旧worker过渡、长连接、WebSocket和证书。
- 502升高时停止扩散,按DNS、TCP、协议、后端健康逐层定位。
- 回滚不仅恢复配置,还要确认后端路由、证书和应用版本兼容。
二十二、升级Nginx镜像
升级前记录:
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。
二十四、学习验收
不看答案完成:
- 画出浏览器到宿主机发布端口、Nginx容器和后端容器的完整路径。
- 从image inspect和container inspect证明Entrypoint、Cmd、PID 1和停止信号。
- 使用只读配置挂载启动Demo,并用Mounts和
nginx -T证明实际配置。 - 解释为什么服务名可用、localhost错误、容器IP不稳定。
- 修改proxy_pass尾部斜杠,证明后端实际收到的URI变化。
- 完成配置错误时
nginx -t失败、旧worker继续服务和修复后reload实验。 - 配置WebSocket代理头,并解释长连接对FD和优雅退出的影响。
- 完成证书只读挂载、更新、测试、reload和外部验证流程。
- 从日志、DNS、TCP和应用四层分别定位一次502与504。
- 设计非root、只读根文件系统、tmpfs和最小权限方案,并验证镜像写路径。
- 记录镜像Digest,完成新版本金丝雀验证和失败回退方案。
- 说明单个Nginx容器restart为什么不是入口高可用。
