如何在 .Net 核心中启用 nginx 反向代理以与 gRPC 一起使用?

Hel*_*123 12 c# nginx docker

我遇到了一个问题,我无法让 nginx 与 gRPC 一起正常工作。我正在使用 .Net core 3.1 来为支持 REST 和 gRPC 的 API 提供服务。

我正在使用以下 docker 图像:

  • .Net Core 3.1 (aspnet:3.1-alpine)
  • Nginx(nginx:最新)

客户端在本地运行,因为我只是通过 nginx 连接到 docker 容器(端口 8080 和 443 映射到主机)

我已经在 docker 容器中构建了 API 映像,并且正在使用 docker compose 来启动所有内容。

在 gRPC 方面,我的 API 相当简单:

app.UseEndpoints(endpoints =>
{
   endpoints.MapGrpcService<CartService>();
   endpoints.MapControllers();
});
Run Code Online (Sandbox Code Playgroud)

我在我的 API 前面有 nginx 作为反向代理,下面是我的 nginx 配置。但是 rpc 调用不起作用。我无法通过客户端连接到 gRPC 服务,它返回 502 请求。我得到一个2020/06/29 18:33:30 [error] 27#27: *3 upstream sent too large http2 frame: 4740180 while reading response header from upstream, client: 172.20.0.1. . 添加单独的 kestral 端点后(请参阅下面的 Edit1),我*1 upstream prematurely closed connection while reading response header from upstream在查看 Nginx 日志时收到。

服务器甚至从未收到实际请求,因为当我查看 docker 日志时,服务器端没有记录任何内容。

几乎没有关于如何通过 .Net 上的 docker 支持 gRPC 的文档,因此不确定如何继续。需要配置/启用什么比我要让它工作更进一步?请注意,API 的 REST 部分工作正常,没有问题。不确定是否需要将 SSL 一直传送到上游服务器(即 API 级别的 SSL)。

我在 Nginx 上看到的 gRPC 文档与我在下面看到的完全一样。在 Nginx 中启用了 http_v2_module,我可以通过响应协议验证它是否适用于 API 的非 gRPC 部分。

http {
    upstream api {
        server apiserver:5001;
    }
    upstream function {
        server funcserver:5002;
    }

    # redirect all http requests to https
    server {
        listen 80 default_server;
        listen [::]:80 default_server;
        return 301 https://$host$request_uri;
    }
    server {
        server_name api.localhost;
        listen 443 http2 ssl ipv6only=on;
        ssl_certificate /etc/certs/api.crt;
        ssl_certificate_key /etc/certs/api.key;
        location /CartCheckoutService/ValidateCartCheckout {
            grpc_pass grpc://api;
            proxy_buffer_size          512k;
            proxy_buffers              4 256k;
            proxy_busy_buffers_size    512k;
            grpc_set_header Upgrade $http_upgrade;
            grpc_set_header Connection "Upgrade";
            grpc_set_header Connection keep-alive;
            grpc_set_header Host $host:$server_port;
            grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            grpc_set_header X-Forwarded-Proto $scheme;
        }
        location / {
            proxy_pass http://api;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }
    }
    server {
        server_name func.localhost;
        listen 443 ssl;
        ssl_certificate /etc/certs/func.crt;
        ssl_certificate_key /etc/certs/func.key;
        location / {
            proxy_pass http://function;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection keep-alive;
            proxy_set_header Host $host:$server_port;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }
    }
    gzip on;
    gzip_vary on;
    gzip_proxied no-cache no-store private expired auth;
    gzip_types text/plain text/css application/json application/xml;
}
Run Code Online (Sandbox Code Playgroud)

编辑 1: 我还尝试为 REST/gRPC 启动单独的端点。从这文件,当不安全的请求进来,它会自动认为是HTTP1请求。我手动将 kestrel 配置为具有 2 个单独的端点、两个端口 - 一个用于 http1+http2,另一个用于 http2 请求。

services.Configure<KestrelServerOptions>(y =>
{
    y.ListenAnyIP(5010, o =>
    {
        o.Protocols = HttpProtocols.Http2;
        //o.UseHttps("./certs/backend.pfx", "password1");
    });

    y.ListenAnyIP(5001, o =>
    {
        o.Protocols = HttpProtocols.Http1AndHttp2;
    });
 });
Run Code Online (Sandbox Code Playgroud)

在 Nginx 中,我也创建了一个单独的条目:

upstream api {
        server apiserver:5001;
    }
    upstream grpcservice {
        server apiserver:5010;
    }
    upstream function {
        server funcserver:5002;
    }
Run Code Online (Sandbox Code Playgroud)

这也不起作用。我什至通过使 htt2 端点只接受 ssl 连接而不接受骰子来尝试上游 SSL。


编辑2

我也试过如下:

  • Nginx 中的上游 SSL - 即后端和反向代理之间的 SSL
  • 使用基于 Debian/Ubuntu 的镜像而不是 Alpine

他们都没有工作。


编辑 3:我终于能够完成这项工作:

location /CartCheckoutService/ValidateCartCheckout {
                grpc_pass grpc://api;
            }
Run Code Online (Sandbox Code Playgroud)

无论出于何种原因,nginx 唯一有效的配置是仅使用grpc_pass。它与代理通行证不同,不需要其他配置。我终于能够在不必执行上游 SSL 的情况下使其正常工作,而只需像我想的那样使用代理 - 在代理处终止 SSL。

我仍在寻找正式的解释,否则我会将我自己的解决方案标记为答案,因为我已成功对其进行了测试。

Hel*_*123 5

以下是有效的解决方案:

location /CartCheckoutService/ValidateCartCheckout {
                grpc_pass grpc://api;
            }
Run Code Online (Sandbox Code Playgroud)

使用 grpc 时,nginx 的唯一配置是仅使用grpc_pass。它与代理传递不同,并且不需要其他配置(即从请求中传递标头/协议/等)。我终于能够在不必执行上游 SSL 的情况下使其正常工作,而只需像我想的那样使用代理 - 在代理处终止 SSL。