Docker Compose 部署 Ongrid 登录 502 Bad Gateway 排障实录

问题背景

在部署 Ongrid 平台(Docker Compose 架构)时,访问 Web 界面登录页面正常,但点击登录后接口返回 502 Bad Gateway,无法进入系统。

环境信息

  • 部署方式:Docker Compose
  • 服务组件:nginx(前端反代 + 静态资源)、ongrid(后端 API)、MySQL、Prometheus、Grafana 等
  • 网络模式:Docker bridge 网络(ongrid_default)

排查过程

1. 确认 502 来源

通过 curl 测试 API 接口,返回 nginx 的 502 页面:

<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx</center>
</body>
</html>

说明 nginx 正常运行,但反向代理到后端 ongrid 服务时失败。

2. 检查 Docker 容器状态

$ docker ps
NAMES               STATUS
ongrid              Up 6 minutes
ongrid-nginx        Up 9 minutes
ongrid-mysql        Up 6 minutes (healthy)
...

所有容器均在运行,ongrid 容器状态为 Up,日志也显示 API 在 :8080 端口正常监听:

{"level":"INFO","msg":"http server listening","listener":"api","addr":":8080"}

3. 检查 nginx 错误日志(关键!)

$ docker logs ongrid-nginx 2>&1 | grep error
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://127.0.1.1:8080/api/v1/auth/login"

发现关键问题:nginx 将 upstream ongrid:8080 解析到了 127.0.1.1,而不是 Docker 网络中 ongrid 容器的实际 IP 172.18.0.10

4. 验证 DNS 解析

在 nginx 容器内手动测试 DNS 解析:

$ docker exec ongrid-nginx getent hosts ongrid
172.18.0.10       ongrid

DNS 解析正常!从容器的角度 ongrid 可以正确解析到 172.18.0.10。但 nginx 用的却是 127.0.1.1

5. 对比容器启动时间

ongrid-nginx  启动时间: 07:27:22
ongrid        启动时间: 07:29:48

根因找到了!nginx 比 ongrid 早启动了约 2 分半钟。nginx 在启动时解析 upstream 中的域名 ongrid,此时 ongrid 容器还未启动,Docker DNS 无法解析该名称,nginx 缓存了一个错误的解析结果,之后一直使用这个过期地址。

根因分析

这是 nginx + Docker DNS 缓存的经典问题

  • nginx 的 upstream 块中的域名只在启动时解析一次,解析结果会被永久缓存
  • Docker Compose 的 depends_on 只保证启动顺序,不保证 DNS 记录立即可用
  • 当 nginx 启动时 ongrid 还未注册到 Docker DNS,解析失败后 nginx 不会重试
  • 即使 ongrid 后来启动了,nginx 仍然使用缓存的错误地址

解决方案

方案一:快速修复 — 重启 nginx 容器

最简单的方法,重启 nginx 让它重新解析 DNS:

$ docker restart ongrid-nginx

重启后 nginx 在所有服务已运行的状态下重新解析 ongrid:8080,502 问题立即解决。

方案二:根治 — nginx 配置动态 DNS 解析

在 nginx 配置中添加 resolver 指令,让 nginx 使用 Docker 内置 DNS 并定期刷新:

http {
    # 使用 Docker 内置 DNS 服务器,valid=5s 表示每 5 秒刷新缓存
    resolver 127.0.0.11 valid=5s;

    upstream ongrid_backend {
        server ongrid:8080;
        keepalive 16;
    }

    server {
        location /api/ {
            # 使用变量触发动态解析,不走 upstream 缓存
            set $ongrid_upstream http://ongrid:8080;
            proxy_pass $ongrid_upstream;
            ...
        }
    }
}

关键点:

  • resolver 127.0.0.11 valid=5s:使用 Docker 内置 DNS,5 秒刷新一次缓存
  • set $ongrid_upstream http://ongrid:8080; proxy_pass $ongrid_upstream;:通过变量引用触发 nginx 动态解析,而不是在启动时静态解析

方案三:docker-compose 健康检查 + 依赖

在 docker-compose.yml 中给 ongrid 添加健康检查,确保 nginx 在 ongrid 完全就绪后再启动:

ongrid:
  ...
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
    interval: 5s
    timeout: 3s
    retries: 10
    start_period: 30s

nginx:
  ...
  depends_on:
    ongrid:
      condition: service_healthy

这样 nginx 只在 ongrid 健康检查通过后才会启动,避免了 DNS 解析不到的问题。

最终效果

执行 docker restart ongrid-nginx 后,立即验证:

$ curl -sk https://172.16.11.15/healthz
ok

$ curl -sk -X POST https://172.16.11.15/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@ongrid.local","password":"xxx"}'
# 返回 JWT Token,登录成功

502 问题彻底解决!

总结

Docker Compose 环境下 nginx 502 问题,排障思路:

  1. 先看 nginx error log,找到 upstream 实际连接的地址
  2. 对比容器启动时间,确认是否存在 DNS 解析时序问题
  3. 容器内验证 DNS,排除网络问题
  4. 对症下药:重启是应急方案,配置 resolver + 变量代理是根治方案

这个问题在所有使用 nginx upstream + Docker DNS 的场景中都很常见,希望对你有帮助!

发表回复

后才能评论