用 Docker Compose 复现上游端口错误与进程未启动,练习从 Nginx 日志定位 502

演练目标

环境准备

隔离边界:只使用本机 Docker Compose 创建的 lab-nginx 项目,端口绑定在 127.0.0.1:8080;不修改宿主机 Nginx,不连接任何生产上游。所有文件放在 /tmp/lab-nginx。

docker version --format '{{.ServerVersion}}' && docker compose version
mkdir -p /tmp/lab-nginx
cat > /tmp/lab-nginx/docker-compose.yml <<'EOF'
name: lab-nginx
services:
  app:
    image: nginx:1.27-alpine
  edge:
    image: nginx:1.27-alpine
    ports: ["127.0.0.1:8080:80"]
    volumes: ["./edge.conf:/etc/nginx/conf.d/default.conf:ro"]
    depends_on: [app]
EOF
cat > /tmp/lab-nginx/edge.conf <<'EOF'
server {
    listen 80;
    location / { proxy_pass http://app:80; }
}
EOF
docker compose -f /tmp/lab-nginx/docker-compose.yml up -d
for i in $(seq 1 30); do
  curl -s -o /dev/null http://127.0.0.1:8080/ && break
  sleep 2
done

注入故障

先停掉上游应用,制造“进程未启动”:

docker compose -f /tmp/lab-nginx/docker-compose.yml stop app
sleep 2
curl -s -o /dev/null -w 'stopped_code=%{http_code}\n' http://127.0.0.1:8080/

再恢复 app,把 upstream 端口从 80 改成 8080,模拟配置错误:

docker compose -f /tmp/lab-nginx/docker-compose.yml start app
sed -i 's|http://app:80|http://app:8080|' /tmp/lab-nginx/edge.conf
docker compose -f /tmp/lab-nginx/docker-compose.yml exec -T edge nginx -s reload
sleep 2
curl -s -o /dev/null -w 'bad_port_code=%{http_code}\n' http://127.0.0.1:8080/

观察现象

docker compose -f /tmp/lab-nginx/docker-compose.yml logs --no-color edge | tail -30
docker compose -f /tmp/lab-nginx/docker-compose.yml ps

预期 stopped_code=502、bad_port_code=502,响应首行是 HTTP/1.1 502 Bad Gateway。error log 中出现 connect() failed (111: Connection refused) while connecting to upstream;停 app 时 upstream 是 http://<ip>:80/,改端口后是 http://<ip>:8080/。access log 中对应请求的状态码为 502。

从 edge 容器内部确认上游是否可达:

docker compose -f /tmp/lab-nginx/docker-compose.yml exec -T edge \
  sh -c 'wget -qO- --timeout=2 http://app:80 | head -1'
docker compose -f /tmp/lab-nginx/docker-compose.yml exec -T edge \
  sh -c 'wget -qO- --timeout=2 http://app:8080' ; echo "exit=$?"

当前 app 已恢复时,第一条返回 Nginx 欢迎页,第二条连接失败,说明网络和 DNS 正常,问题在 upstream 端口。若 app 处于停止状态,两条都会失败,且 docker compose ps 显示 app 为 Exited。

判断与定位

  1. 先写假设:上游进程没启动、upstream 端口写错、DNS 解析失败、上游处理太慢,还是 Nginx 自身配置错误。
  2. 看 error log 的 upstream 地址和错误文本。Connection refused 表示 TCP 连接被拒绝,常见于进程未监听或端口错误;Operation timed out 表示连接或读取超时,通常返回 504。
  3. 从 Nginx 容器内部探测上游。容器名能解析、端口能连通,说明网络和 DNS 正常,问题在 upstream 配置或上游进程。
  4. 对照 docker compose ps。app 为 Exited 时是进程未启动;app 为 Up 但目标端口拒绝连接时是端口写错。
  5. 容易误判:把 502 一律当成“上游超时”。Nginx 对上游读超时通常返回 504,502 更多是连接失败、上游提前关闭或返回非法响应。另一个误判是只看 access log 的 502,不看 error log 中的 upstream 地址,导致改错端口。
  6. 如果 upstream 有多个 backend,还要确认是否只挂了其中一个、proxy_next_upstream 是否掩盖了部分失败。

修复

把 upstream 端口改回 80,重载 Nginx:

sed -i 's|http://app:8080|http://app:80|' /tmp/lab-nginx/edge.conf
docker compose -f /tmp/lab-nginx/docker-compose.yml exec -T edge nginx -s reload
sleep 2
curl -s -o /dev/null -w 'root_code=%{http_code}\n' http://127.0.0.1:8080/

root_code=200 表示恢复。生产环境还要检查 upstream 健康检查、连接池、超时参数和配置变更流程,避免单实例故障放大为 502。

清理与回滚

docker compose -f /tmp/lab-nginx/docker-compose.yml down -v --remove-orphans
rm -rf /tmp/lab-nginx

只删除本实验的 Compose 项目和 /tmp/lab-nginx;不要对宿主机 Nginx 或 /etc/nginx 执行修改。

复盘

延伸阅读

读完自测

  1. 上游端口写错和上游进程未启动都会返回 502,如何区分?
  2. 为什么上游读超时通常返回 504 而不是 502?
  3. 修改 Nginx 的 upstream 配置后,为什么必须 reload 或重启?
答案
  1. 在 Nginx 容器内探测上游端口,并结合 docker compose ps 判断进程状态;端口错误时进程在运行但目标端口不通。
  2. Nginx 能连上上游,但在 proxy_read_timeout 内没收到响应,属于网关超时,返回 504。
  3. Nginx 只在启动或 reload 时读取配置,直接改文件不会影响正在运行的 worker 进程。