客户端使用insecure模式时Ingress Nginx无法代理gRPC的原因
问题背景
- 请求链路:
go-client --> ingress-nginx --> grpc pod - 部署环境为私有网络,未采购公网可信CA签发证书,采用自签名证书配置Ingress TLS规则。测试过程中,加载自签证书的客户端可正常发起gRPC调用,使用
insecure.NewCredentials()配置传输凭证的客户端调用失败,返回400 Bad Request,对应Nginx访问日志记录为:
"PRI * HTTP/2.0" 400
- 可正常运行的客户端代码:
cert, _ := credentials.NewClientTLSFromFile("./example.pem", "example.com") conn, err := grpc.DialContext( ctx, "example.com:443", grpc.WithTransportCredentials(cert), grpc.WithBlock(), )
- 调用失败的客户端代码:
conn, err := grpc.DialContext( ctx, "example.com:443", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithBlock(), )
- 本次使用的Ingress配置如下:
apiVersion: networking.k8s.io/v1 kind: Ingress spec: ingressClassName: nginx tls: - hosts: example.com secretName: example-tls rules: - host: example.com http: paths: - path: /foo/bar pathType: Prefix backend: service: grpc-svc port: name: grpc-port
核心结论
1. insecure参数的准确含义
很多人会误以为insecure.NewCredentials()的作用是「走TLS但跳过服务端证书校验」,这个认知完全错误:
insecure.NewCredentials()返回的传输凭证会完全关闭TLS加密层,客户端直接通过明文HTTP/2协议向服务端发起通信,整个传输过程没有TLS握手、没有证书校验、流量全明文。- 真正的「TLS下跳过证书校验」是独立的配置,仍然会走完整TLS握手流程,只是不校验服务端证书的信任链、域名匹配关系,示例代码如下:
import "crypto/tls" import "google.golang.org/grpc/credentials" // 注意:该配置存在中间人攻击风险,生产环境不推荐使用 creds := credentials.NewTLS(&tls.Config{ InsecureSkipVerify: true, }) conn, err := grpc.DialContext( ctx, "example.com:443", grpc.WithTransportCredentials(creds), grpc.WithBlock(), )
2. 400报错的根因
当前Ingress配置了TLS规则,Nginx的443端口会优先等待客户端发起TLS ClientHello报文,完成TLS握手后才会处理上层HTTP/2流量:
- 正常工作的代码加载了自签证书作为根信任锚,客户端会正常发起TLS握手,用本地的自签证书完成服务端身份校验,TLS隧道建立后再传输gRPC的HTTP/2流量,Nginx可以正常处理并转发给后端Pod。
- 使用
insecure.NewCredentials()的客户端完全不发起TLS握手,直接向443端口发送明文的HTTP/2连接前言(内容就是PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n)。Nginx在443端口没收到预期的TLS握手报文,会把收到的内容当明文HTTP/1.x请求解析,而PRI不是HTTP/1.x支持的请求方法,因此直接返回400 Bad Request,和日志记录完全吻合。
修复方案
三选一即可,优先推荐第一种:
- 保持现有Ingress TLS配置不变,客户端加载自签CA证书(即第一段正常工作的代码写法)发起TLS连接,是私有环境下的最稳妥方案。
- 如果一定要用跳过证书校验的TLS连接,使用上文给出的
InsecureSkipVerify: true的凭证配置,不要用insecure.NewCredentials()。 - 如果确实需要走明文gRPC,需要给Ingress Nginx配置明文gRPC监听(默认使用80端口),同时去掉对应路由的TLS配置,并开启Nginx的h2c支持,生产环境不推荐该方案。
内容的提问来源于stack exchange,提问作者caibirdme
相关产品推荐
相关产品推荐

