1. 问题现象与初步分析
最近在将Spring Boot应用通过Docker Compose部署时,遇到了经典的502 Bad Gateway错误。这个HTTP状态码通常表示作为网关或代理的服务器从上游服务器收到了无效响应。在Nginx+Spring Boot的典型架构中,这意味着Nginx无法正确连接到后端的Spring Boot应用。
我遇到的场景是这样的:通过浏览器访问应用时,页面直接显示502错误,查看Nginx错误日志发现以下关键信息:
[error] 25#25: *1 connect() failed (111: Connection refused) while connecting to upstream这个错误表明Nginx无法连接到配置的上游服务器(即Spring Boot应用)。接下来我将详细记录整个排查过程,包括各种可能性验证和最终解决方案。
2. 环境配置检查
2.1 Docker Compose文件解析
首先检查docker-compose.yml文件配置,这是整个部署的核心。一个典型的Spring Boot + Nginx配置如下:
version: '3.8' services: app: image: my-springboot-app:latest container_name: springboot-app ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod networks: - app-network nginx: image: nginx:alpine container_name: nginx-proxy ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - app networks: - app-network networks: app-network: driver: bridge这里有几个关键点需要验证:
- 服务命名是否正确(app和nginx)
- 网络配置是否一致(都使用app-network)
- 端口映射是否正确(Spring Boot默认8080)
2.2 Nginx配置验证
Nginx的配置文件nginx.conf中,upstream配置需要特别注意:
upstream backend { server app:8080; } server { listen 80; location / { proxy_pass http://backend; proxy_set_header Host $host; } }常见问题包括:
- upstream中的服务名是否与docker-compose中一致(这里是app)
- 端口是否匹配Spring Boot应用端口(默认8080)
- proxy_pass的地址是否正确(http://backend)
3. 深度排查步骤
3.1 容器连通性测试
进入Nginx容器内部测试是否能访问Spring Boot应用:
docker exec -it nginx-proxy sh ping app curl http://app:8080/actuator/health如果ping通但curl失败,可能是:
- Spring Boot应用没有真正启动成功
- 应用监听地址配置有问题
3.2 Spring Boot应用日志检查
查看Spring Boot容器的日志:
docker logs -f springboot-app重点关注:
- 应用是否启动成功(看到Started Application in x seconds)
- 是否绑定到正确的端口(Tomcat started on port 8080)
- 是否有异常堆栈
3.3 网络配置验证
检查Docker网络配置:
docker network inspect app-network确认:
- 两个容器都在同一网络
- 有正确的IP分配
- 容器之间可以互相通信
4. 常见问题与解决方案
4.1 应用启动超时
在docker-compose.yml中,app服务可能启动较慢,而Nginx已经启动并尝试连接。解决方案:
app: # ...其他配置 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 10s retries: 5 nginx: # ...其他配置 depends_on: app: condition: service_healthy4.2 监听地址配置错误
Spring Boot默认只监听localhost,在容器中需要修改为:
server.address=0.0.0.0或者在application.yml中:
server: address: 0.0.0.04.3 端口冲突
确认主机端口没有被占用:
netstat -tuln | grep 8080如果冲突,可以修改docker-compose中的端口映射:
ports: - "8081:8080"同时更新Nginx配置中的端口。
5. 高级调试技巧
5.1 使用tcpdump抓包
在Nginx容器中安装tcpdump:
apk add tcpdump tcpdump -i eth0 port 8080 -vv观察是否有连接尝试以及响应情况。
5.2 调整Nginx超时设置
在Nginx配置中增加超时参数:
location / { proxy_pass http://backend; proxy_connect_timeout 60s; proxy_read_timeout 60s; proxy_send_timeout 60s; }5.3 使用Docker inspect检查
获取详细容器信息:
docker inspect springboot-app重点关注:
- NetworkSettings.Networks
- State.Running
- Config.ExposedPorts
6. 完整解决方案示例
经过以上排查,一个完整的解决方案通常包含以下步骤:
- 确保Spring Boot应用配置监听0.0.0.0
- 添加健康检查和服务依赖
- 验证网络连通性
- 检查端口映射
- 调整Nginx超时设置
最终可用的docker-compose.yml示例:
version: '3.8' services: app: image: my-springboot-app:latest container_name: springboot-app ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"] interval: 30s timeout: 10s retries: 5 networks: - app-network nginx: image: nginx:alpine container_name: nginx-proxy ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: app: condition: service_healthy networks: - app-network networks: app-network: driver: bridge对应的nginx.conf:
upstream backend { server app:8080; keepalive 32; } server { listen 80; location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_set_header Host $host; proxy_connect_timeout 60s; proxy_read_timeout 60s; } }7. 预防措施与最佳实践
- 始终配置健康检查:特别是对于有依赖关系的服务
- 明确网络配置:显式定义网络而不是依赖默认网络
- 日志收集:配置统一的日志收集(如ELK)方便排查
- 资源限制:为容器配置合理的资源限制避免OOM
- 版本固定:固定镜像版本而不是使用latest
在微服务架构下,还可以考虑:
- 使用服务发现替代硬编码地址
- 实现断路器模式(如Resilience4j)
- 配置完善的监控告警系统
8. 典型错误案例
8.1 案例一:Profile未激活
Spring Boot应用配置了生产环境特定的属性,但未激活profile:
# 错误:未指定profile docker run -p 8080:8080 my-springboot-app # 正确:激活prod profile docker run -p 8080:8080 -e SPRING_PROFILES_ACTIVE=prod my-springboot-app8.2 案例二:内存不足
JVM因内存不足崩溃,添加内存限制:
app: # ...其他配置 deploy: resources: limits: memory: 1G8.3 案例三:时区不一致
容器内时区与主机不一致导致证书过期等问题:
app: # ...其他配置 environment: - TZ=Asia/Shanghai