使用 Docker 和 Nginx 实现反向代理:统一端口路由多服务的完整指南
场景需求
假设我们有一个 Spring Boot 应用(app.jar
),需要启动多个实例,分别运行在 8080
和 8081
端口,并通过 Nginx 反向代理实现以下功能:
- 外部通过
80
统一端口访问服务。 - 根据 URL 路径前缀转发到不同端口的服务:
- 请求
/service1/xxx
转发到8080
端口。 - 请求
/service2/xxx
转发到8081
端口。
- 请求
准备工作
- 安装 Docker 和 Docker Compose(官方安装文档)。
- 将
app.jar
文件放置在工作目录中(例如~/projects/myapp
)。 - 确保
app.jar
在启动时可接受外部请求(如配置server.port=8080
)。
步骤 1:创建 Docker 网络
为了提高容器间通信的安全性,创建一个自定义网络:
<BASH>
docker network create nginx-proxy-network
步骤 2:编写服务镜像的 Dockerfile
创建文件 Dockerfile
,内容如下:
<DOCKERFILE>
FROM openjdk:17-alpine
# 复制 jar 包到容器内
COPY app.jar /app/app.jar
# 暴露端口(实际端口通过启动命令指定)
EXPOSE ${PORT}
# 启动命令,通过环境变量指定端口和配置文件
CMD ["sh", "-c", "java -jar /app/app.jar --server.port=${PORT} --spring.profiles.active=${PROFILE}"]
步骤 3:启动多个服务容器
启动两个服务实例,分别映射到宿主机的 8080
和 8081
端口:
<BASH>
# 构建镜像
docker build -t myapp-service .
# 启动服务实例1(端口 8080)
docker run -d --name service1 \
-e PORT=8080 \
-e PROFILE=dev \
--network nginx-proxy-network \
-p 8080:8080 \
myapp-service
# 启动服务实例2(端口 8081)
docker run -d --name service2 \
-e PORT=8081 \
-e PROFILE=test \
--network nginx-proxy-network \
-p 8081:8081 \
myapp-service
步骤 4:编写 Nginx 配置文件
创建文件 nginx.conf
,配置路由规则:
<NGINX>
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
# 路由到 service1(端口 8080)
location /service1/ {
proxy_pass http://service1:8080/; # 使用 Docker 容器名直接访问
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 路由到 service2(端口 8081)
location /service2/ {
proxy_pass http://service2:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
步骤 5:启动 Nginx 容器
<BASH>
docker run -d --name nginx-proxy \
-v $(pwd)/nginx.conf:/etc/nginx/nginx.conf \
--network nginx-proxy-network \
-p 80:80 \
nginx:alpine
步骤 6:测试验证
发起请求测试路由是否正确:
<BASH>
# 访问 service1
curl http://localhost/service1/hello
# 访问 service2
curl http://localhost/service2/hello
核心原理
- Docker 网络:所有容器加入同一网络,通过容器名(如
service1
)直接通信,无需暴露外网端口。 - Nginx 代理:
location /service1/
匹配以/service1/
开头的请求。proxy_pass
将请求转发到对应容器的内部端口。
- 解耦访问入口:前端只需调用
80
端口,后端服务的真实端口对外隐藏。
配置优化建议
1. 静态资源与动态请求分离
<NGINX>
location /static/ {
root /var/www/html;
expires 30d;
}
location /api/ {
proxy_pass http://backend;
}
2. 负载均衡
若单个服务需多实例,可配置负载均衡:
<NGINX>
upstream backend {
server service1:8080;
server service2:8081;
}
location / {
proxy_pass http://backend;
}
3. 健康检查
在服务容器中添加健康检查端点:
<DOCKERFILE>
HEALTHCHECK --interval=30s --timeout=10s \
CMD curl -f http://localhost:${PORT}/actuator/health || exit 1
完整架构图
+----------------+
| Frontend |
+----------------+
|
| 80 端口
v
+----------------+ +----------------+ +----------------+
| Nginx Proxy | --service1->| Service1:8080 | | Service2:8081 |
| (Docker) | --service2->| (Docker) | | (Docker) |
+----------------+ +----------------+ +----------------+
常见问题排查
- Nginx 返回 502 Bad Gateway
- 检查服务容器是否运行正常:
docker logs service1
。 - 确认 Nginx 配置中的容器名和端口是否正确。
- 检查服务容器是否运行正常:
- 请求路径缺失
- 检查
proxy_pass
末尾是否包含/
:<NGINX>
# 正确写法(转发时会保留路径) proxy_pass http://service1:8080/;
- 检查
- 跨容器网络不通
- 确认所有容器加入同一网络:
docker network inspect nginx-proxy-network
。
- 确认所有容器加入同一网络:
总结
通过 Docker 和 Nginx 的配合,我们实现了:
- 多服务实例的隔离部署。
- 统一入口的路由管理。
- 灵活且安全的服务扩展。
此方案适用于微服务架构、多环境测试(如 Dev/Test 环境共存)等场景。在实际生产中,可进一步结合 CI/CD 和监控工具,打造稳健的容器化服务生态。