复盘

突然接到同事反馈说是线上某些接口偶尔会报400 Bad Request错误,去现场复现了下问题,习惯性的打开开发者工具查找错误细节,发现Query String ParametersCookie都异常的长,直觉告诉我跟这个应该有关系。

image-20200621131846643

查了下资料Nginx 400 Bad Request通常都是Request Header过大所引起的,结合我这里Query String ParametersCookie都特别长的情况,那应该就是这个问题没跑了。

于是顺着这个方向去寻找解决方法,网上的资料都提到了要调整下边两个参数,

  • client_header_buffer_size
  • large_client_header_buffers

于是看了下我最外层的Nginx(我这里的架构:LVS(公网)->Nginx->LVS(内网)->Kubernetes Ingress->Pod)的配置:

1
2
    client_header_buffer_size   32k;
    large_client_header_buffers 4 32k;

发现已经是加大过的了所以并没有立即无脑加大,而是想能不能从日志中分析一下具体报错的原因呢,于是打开error_log一顿狂grep,然而并没有什么发现。

后来看到了这个问题,原来是要调整error_log的级别,果断debug大法,本以为这下就找到错误原因呢,结果又一次扑空。


重新理了一下思路,如果真是Request Header过大导致了这层Nginx 返回的400 Bad Request的话 error_log中一定是能扑捉到具体错误原因,然后这里并没扑捉到说明400不是这层Nginx返回的,既然不是它那就要怀疑下一层了,这下焦点就到了Kuberneres ingress-nginx 控制器这里了,根据ingress-nginx文档查看了下client_header_buffer_sizelarge_client_header_buffers参数对应的选项和默认值:

name type default
client-header-buffer-size string “1k”
large-client-header-buffers string “4 8k”

默认值都不大,确实有超过限制的可能,于是适当的调大了相应值。

1
2
client-header-buffer-size: "32k"
large-client-header-buffers: "4 32k"

结果又一次扑空了,问题依旧,于是再次调整error_log 级别

1
error-log-level: "debug"

日志中还是一样没扑捉到具体错误原因,到这里一切迹象都把矛头指向了后端服务。


果断进到Pod中,本地访问了下接口,

image-20200621112445287

wow~200终于回来了,但是这和之前的推断矛盾了,难道400不是后端服务返回的?


既然本地访问是没问题的,那通过ingress-nginxsvc ip访问呢?

image-20200621112853466

结果毫无意外是400 Bad Request,不然就诡异了。

到这里问题似乎越来越近了,重新整理下思路,如果真是ingress-nginx的问题,那么错误日志中为什么扑捉不到相关信息呢,可见不是。


焦点回到后端服务上,这次我打算伪造一个假的头增大Request Header ,看看会发生什么?

image-20200621114837454

wow~,400 Bad Request了,证明了之前的推断确实是Request Header过大导致了后导服务返回了400了错误。


这时又有新的问题了:

为什么本机访问没问题,走ingress-nginx反向代理后就有问题了?

推断ingress-nginx反代时在请求后端服务时追加了Header内容导致长度达到了后台服务的限制从而报了400 Bad Request,果然谷歌找到了这个issue,原来从0.14.0开始nginx-ingress-controller增加了一个proxy-add-original-uri-header 参数且默认为true,翻译成对应Nginx参数就是:

1
proxy_set_header X-Original-URI         $request_uri;

它的作用是在反向代理请求后端服务时会将原始请求加到Header里然后带给后端服务,这就解释了为什么本机访问没问题走ingress-nginx后就有问题。原因找到了,解决也很简单了,修改

1
proxy-add-original-uri-header: "false"

然后应该生效就可以了,完整nginx-configuration ConfigMap:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: v1
data:
  allow-backend-server-header: "true"
  enable-underscores-in-headers: "true"
  error-log-level: notice
  forwarded-for-header: X-Real-IP
  generate-request-id: "true"
  ignore-invalid-headers: "true"
  log-format-upstream: $http_x_real_ip - $the_real_ip - [$the_real_ip] - $remote_user
    [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"
    $request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length
    $upstream_response_time $upstream_status $req_id $host
  max-worker-connections: "65536"
  proxy-body-size: 100m
  proxy-connect-timeout: "10"
  reuse-port: "true"
  server-tokens: "false"
  ssl-redirect: "false"
  use-forwarded-headers: "true"
  worker-cpu-affinity: auto
  large-client-header-buffers: "4 16k"
  proxy-add-original-uri-header: "false"
kind: ConfigMap
metadata:
  labels:
    app: ingress-nginx
  name: nginx-configuration
  namespace: kube-system

总结

  1. 问题虽然不复杂但是排查着实费了些功夫,如果将问题排查的经过倒过来先定位错误是否是服务返回的可能会少走很多弯路。

  2. 官方也意识到proxy-add-original-uri-header 参数默认为true可能是个问题,

This makes it configurable if a location adds an X-Original-Uri header to the backend request. Default is “true”, the current behaviour.

Our use case, we have some internal services where the URL can be quite long, though still quite below nginx’s large_client_header_buffers default limit. But by automatically adding the request URI as an additional header on the backend proxy request, a similar request header size limit from the backend web server, e.g. Jetty or another nginx, is exceeded.

所以从0.26.0开始参数的默认值改为了false

Change the default value for proxy-add-original-uri-header from true to false, introduced in #2353. This default adds a header that can trigger an error HTTP/1.1 431 Request Header Fields Too Large.

参考

https://stackoverflow.com/questions/12315832/how-to-fix-nginx-throws-400-bad-request-headers-on-any-header-testing-tools

https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/

https://github.com/kubernetes/ingress-nginx/blob/master/Changelog.md

https://github.com/kubernetes/ingress-nginx/pull/4604

https://github.com/kubernetes/ingress-nginx/pull/2353