Kubernetes 使用 Zuul 网关时上游 Tomcat 400 报错问题排查

在 Kubernetes 里,使用 Zuul 转发请求到上游的 Tomcat 服务时,Tomcat 报了一个很诡异的错误:

1
The host [zuul.host,zuul.host] is not valid

其中 zuul.host 是 Zuul 服务的域名。

看到这个报错时,有两个点很奇怪。一是 [zuul.host, zuul.host] 是怎么来的,为什么它重复了一遍。二是为什么 host 的值会是这个,应该是 Tomcat 服务的域名。

网上搜了一圈,没找到相关资料,只能一步步来挖了。

由于是 Tomcat 直接报错,未进入 Spring Boot 内部,配置的 Sentry 也没捕捉到该错误,只能通过添加 Tomcat 日志配置打印相应的 Header。

1
2
3
4
# application.properties
server.tomcat.basedir=logs/tomcat-logs
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t "%r" %s (%D ms) -Host (%{Host}i) -X-Forwarded-host (%{X-Forwarded-Host}i)

日志打印出了两个很有意思的值,Host 和 X-Forwarded-Host 的值是同样的,都为 [zuul.host, zuul.host]

1
2
Host: [zuul.host, zuul.host]
X-Forwarded-Host: [zuul.host, zuul.host]

这时可以确定,Tomcat 确实收到了一个诡异的请求,由于 Host Header 值无效导致 Tomcat 400 报错。但问题是,这请求是怎么来的呢?只能一步步往前追查,在 Zuul 服务中添加相应的日志配置。

1
2
// ZuulFilter.java
log.info("LOG_HEADER: {} Host-({}) X-Forwarded-Host-({})", request.getRequestURI(), request.getHeader("Host"), request.getHeader("X-Forwarded-Host"));
1
LOG_HEADER: /api/v1/login/app  Host-(zuul.host) X-Forwarded-Host-(zuul.host)

从日志中可以看出,Zuul 服务接收到的请求 Host 和 X-Forwarded-Host 都为 zuul.host,再加上 Zuul 默认情况下也会为转发的请求添加 X-Forwarded-Host,所以 Tomcat 收到的时候 X-Forwarded-Host 为两次的 zuul.host。现在已经知道 X-Forwarded-Host 的值是如何来的,但 Host 值的原因还没找到。

没其他线索的情况下,我本地往 Tomcat 服务发送了几次请求,X-Forwarded-Host 的值从零个到多个,发现了些许端倪。在 Tomcat 打印的日志中,Host 和 X-Forwarded-Host 的值都是一样的。而当请求的 X-Forwarded-Host 值零个或一个时,请求返回 200。X-Forwarded-Host 值大于一个时,请求返回 400。这时我有了一个猜想,难道 X-Forwarded-Host 的值会被复制到 Host 吗?

按照这个想法,找到了相关的 ISSUE,原来 Ingress 在开启 use-forwarded-headers 后,会将 X-Forwarded-Host 的值复制到 Host,但由于没有考虑 X-Forwarded-Host 为多个值的情况,导致 Host 不合法。这个问题已经在 ingress-nginx 0.24 版本被修复。

请求的整个流程,如下图所示。

找到了问题根源,那解决起来就简单了。有以下三种方法:

  1. 升级 ingress-nginx 至 0.24 及以上版本,从根本上解决这个问题。
  2. 配置 zuul.addProxyHeaders = false,使得 Zuul 不添加额外的 X-Forwarded-Host
  3. Zuul 直接使用 Tomcat 服务的 Service Name 进行访问,不经过 Ingress。

参考