Nginx负载均衡+Docker Compose下.NET Core应用无法访问问题排查
问题描述
尝试搭建Nginx负载均衡转发至两个.NET Core应用(api1、api2)的系统,应用已全环境启用Swagger和WeatherForecast端点,可通过单独URL(如http://localhost:8081/swagger/index.html)访问Swagger,但无法通过负载均衡URL(如http://localhost:81/api/api1/swagger)访问。Nginx日志显示尝试读取本地静态文件失败:
nginx | 2025/02/26 23:49:25 [error] 32#32: *3 open() "/usr/share/nginx/html/api/api1/swagger" failed (2: No such file or directory), client: 172.19.0.1, server: localhost, request: "GET /api/api1/swagger HTTP/1.1", host: "localhost:81" nginx | 172.19.0.1 - - [26/Feb/2025:23:49:25 +0000] "GET /api/api1/swagger HTTP/1.1" 404 153 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0" "-"
核心原因
Nginx请求转发路径处理异常
当前location /api/api1的配置中,proxy_pass http://api1/的结尾斜杠会替换请求路径中的/api/api1部分,但日志显示请求未被转发至后端服务,而是被Nginx当作静态文件处理,说明请求匹配逻辑未生效(可能是配置加载问题或路径匹配规则不符合预期)。同时,直接访问/swagger需要应用自动重定向到/swagger/index.html,若Nginx未传递必要头信息,重定向会失效。Swagger未适配Nginx前缀路径
.NET Core应用不知道自身通过/api/api1前缀暴露,Swagger生成的资源路径(JS、CSS、文档地址)均以根路径/开头,浏览器会直接请求http://localhost:81/swagger/...而非http://localhost:81/api/api1/swagger/...,这些请求被Nginx当作静态文件返回404。端口配置可能不匹配
mcr.microsoft.com/dotnet/aspnet:8.0镜像默认监听80端口,若应用未通过代码或环境变量修改为监听8080,Docker Compose映射的8081:8080会失效,Nginx访问api1:8080会失败。
修复步骤
1. 修正Nginx配置
调整location规则并添加必要的请求头:
upstream api1 { server api1:8080; } upstream api2 { server api2:8080; } server { listen 80; location /api/api1/ { proxy_pass http://api1/; 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; } location /api/api2/ { proxy_pass http://api2/; 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; } }
- 给location路径添加结尾斜杠,确保匹配所有前缀请求;
- 传递
Host等头信息,让后端应用正确识别请求上下文。
2. 配置.NET Core应用的BasePath
修改Program.cs,适配Nginx的前缀路径:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // 设置应用基础路径,api2改为"/api/api2" app.UsePathBase("/api/api1"); app.UseSwagger(c => { c.PreSerializeFilters.Add((swaggerDoc, httpReq) => { swaggerDoc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{httpReq.PathBase}" } }; }); }); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/api/api1/swagger/v1/swagger.json", "API1 V1"); c.RoutePrefix = string.Empty; // 允许直接通过/api/api1访问Swagger UI }); app.UseHttpsRedirection(); app.MapGet("/weatherforecast", () => { /* 原有逻辑 */ }); app.Run();
UsePathBase指定应用的访问前缀;- 修改Swagger配置,确保文档地址和UI路径正确适配前缀。
3. 确认应用监听端口
确保应用监听容器内的8080端口,可二选一配置:
- 在Program.cs中添加:
builder.WebHost.UseUrls("http://*:8080"); - 或在Dockerfile中添加环境变量:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 ENV ASPNETCORE_URLS=http://+:8080 WORKDIR /api1 COPY --from=build ./api1/out . ENTRYPOINT ["dotnet", "api1.dll"]
4. 重启服务
执行以下命令重新构建并启动服务:
docker-compose down docker-compose up --build
之后可通过http://localhost:81/api/api1直接访问Swagger UI。
内容的提问来源于stack exchange,提问作者Daniele Tentoni

