1、错误的返回-503 起因是ingress-nginx有人提了一个issue:https://github.com/kubernetes/ingress-nginx/issues/11742 ,使用limit_except时没有得到预期的403,而是得到了503,首先看下怎么复现
首先得有一个kubernetes环境,启动一个服务节点foo,然后安装ingress-nginx,ingress的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: foo-ingress namespace: default annotations: nginx.ingress.kubernetes.io/configuration-snippet: limit_except GET { deny all; } nginx.ingress.kubernetes.io/server-snippet: | location =/ { return 403; } spec: ingressClassName: nginx rules: - host: http: paths: - path: /foo pathType: Prefix backend: service: name: foo-service port: number: 8080
这个配置的含义是,只允许get请求请求foo,其余请求一律为403,比如一个POST请求
1 curl -X POST http://172.xx.xx.xx:32080/foo
按照正常思维来说应该返回403,但是返回了503?
1 2 3 4 5 6 7 <html> <head><title>503 Service Temporarily Unavailable</title></head> <body> <center><h1>503 Service Temporarily Unavailable</h1></center> <hr><center>nginx</center> </body> </html>
难道是nginx/openresty的问题吗?于是在openresty快速测试一下,配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 server { listen 5678 ; server_name ZJfans.com; location / { limit_except GET { deny all; } return 200 ; } location = / { return 403 ; } location = /foo { limit_except GET { deny all; } return 200 ; } }
测试的结果,post确实返回了403,所以openresty本身没有问题,那么问题就出在ingress-nginx
2、ingress的nginx.conf配置 现在来看下ingress生成的nginx.conf有什么特殊的,只看重点
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 29 30 31 32 33 34 35 36 location = /foo { set $namespace "default" ; set $ingress_name "foo-ingress" ; set $service_name "foo-service" ; set $service_port "8080" ; set $location_path "/foo" ; set $global_rate_limit_exceeding n; rewrite_by_lua_block { lua_ingress.rewrite({ force_ssl_redirect = false , ssl_redirect = true , force_no_ssl_redirect = false , preserve_trailing_slash = false , use_port_in_redirects = false , global_throttle = { namespace = "" , limit = 0 , window_size = 0 , key = { }, ignored_cidrs = { } }, }) balancer.rewrite() plugins.run() } set $proxy_upstream_name "default-foo-service-8080" ; limit_except GET { deny all; } proxy_pass http://upstream_balancer; proxy_redirect off ; }
测试了一下,我发现balancer.rewrite()是问题的关键,这其实是balancer初始化的一个实例,那么我们来看看这块代码
3、balancer.lua 可以看到这里确实返回了503,那么为什么get_balancer()会失败呢?
1 2 3 4 5 6 7 function _M.rewrite () local balancer = get_balancer() if not balancer then ngx.status = ngx.HTTP_SERVICE_UNAVAILABLE return ngx.exit (ngx.status ) end end
现在来看看get_balancer的实现,其实就是,没有获取到balancer返回了nil
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 local function get_balancer () if ngx.ctx.balancer then return ngx.ctx.balancer end local backend_name = ngx.var.proxy_upstream_name local balancer = balancers[backend_name] if not balancer then return nil end return balancerend
现在来看为什么没有获取到balancer,首先使用ngx.say输出一下backend_name,我发现是一个 ”-“,这其实很奇怪,因为nginx.conf是这么设置的
1 set $proxy_upstream_name "default-foo-service-8080" ;
照理ngx.var.proxy_upstream_name可以取到的值是default-foo-service-8080,那为什么会变成 - ?这又到了openresty的问题了,这时我们需要研究一下limit_except的源码
4、limit_except代码 简单研究一下,直接来看关键的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void ngx_http_update_location_config (ngx_http_request_t *r) { ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->method & clcf->limit_except) { r->loc_conf = clcf->limit_except_loc_conf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); } }
关键在于r->loc_conf = clcf->limit_except_loc_conf;这里切换了r的loc,配置进行了更新,在 Nginx 中,r->loc_conf = clcf->limit_except_loc_conf;
这行代码用于处理特定请求方法时的配置切换。具体来说,当客户端请求的 HTTP 方法不在 limit_except
指定的允许范围内时,Nginx 会将该请求的 loc_conf
(location 配置)切换到为该方法配置的 limit_except_loc_conf
。
举个具体的例子:
假设有如下配置:
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 server { listen 5678 ; server_name ZJfans.com; location / { limit_except GET { deny all; } return 200 ; } location = / { return 403 ; } location /foo { set $proxy_upstream_name "default-foo-service-8080" ; rewrite_by_lua_block { local upstream_name = ngx.var.proxy_upstream_name ngx.log(ngx.INFO, "Proxy upstream name: " , upstream_name) } limit_except GET { deny all; } } }
在这个配置中:
配置结构 :
clcf->limit_except
保存了限制条件,即允许的 HTTP 方法,这里是 GET
和 POST
。
clcf->limit_except_loc_conf
是一个指向 limit_except
条件下的特定 location 配置结构的指针。
请求处理 :
当一个请求到达 /foo
路径时,Nginx 首先会根据请求的方法来检查 clcf->limit_except
中的配置。
如果请求方法是 GET
,则继续使用原始的 loc_conf
处理请求,执行 rewrite_by_lua_block
等其他配置。
如果请求方法是其他方法(如 DELETE
或 POST
),代码中的 if (r->method & clcf->limit_except)
条件会成立,Nginx 会将 r->loc_conf
切换到 clcf->limit_except_loc_conf
,即对应限制方法的特定配置。这时,Nginx 可能会直接拒绝请求或应用不同的配置。
具体行为 :
当请求方法是 GET
或时,r->loc_conf
保持为原始的配置,执行 rewrite_by_lua_block
,可以打印 proxy_upstream_name
。
当请求方法是 POST
,r->loc_conf
切换为 limit_except_loc_conf
,此时不再执行 rewrite_by_lua_block
,直接应用 deny all
,返回 403 错误。
所以如果请求方法是 POST
,ngx.var.proxy_upstream_name根本就获取不到值,因为此时配置更新为limit_except的loc,在locatioon设置的变量都无法访问到,nginx会报错using uninitialized
,此时问题已经定位到了,这是nginx的限制
再深入一下,如果把set $proxy_upstream_name “default-foo-service-8080”;设置为server级别呢,是否可以获取到这个变量?
答案是可以的,因为现在只是替换了location级别的配置,server级别的配置并没有更新,可以正常获取
5、验证–调试balancer.lua 5.1、”-“ 从哪里来的 来看nginx.conf的配置,server里面设置了默认值,所以 - 就是从这里来的
5.2、balancers结构 回到balancer.lua,我对balancers的结构体挺好奇的,于是打印一下,看下具体的配置
1、查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ocal function get_balancer () if ngx.ctx.balancer then return ngx.ctx.balancer end local backend_name = ngx.var.proxy_upstream_name backend_name = "default-foo-service-8080" local balancer = balancers[backend_name] if balancer then local balancer_json = cjson.encode(balancer) ngx.header["Content-Type" ] = "application/json" ngx.status = ngx.HTTP_OK ngx.say(balancer_json) end if not balancer then return nil end
请求/响应
1 2 3 curl -X POST http://172.xx.xx.xx:32080/foo {"traffic_shaping_policy":{"weight":0,"cookie":"","headerPattern":"","headerValue":"","header":"","weightTotal":0},"instance":{"nodes":{"10.233.xx.xx:5678":1},"gcd":1,"only_key":"10.233.xx.xx:5678","cw":1,"last_id":"10.233.xx.xx:5678","max_weight":1}}
所以如果正常能取到proxy_upstream_name的值,也是能取到balancer的
6、如何修复 按照我的初步想法,简单粗暴,在proxy_upstream_name没有被重新赋值时,直接返回403。因为使用了limit_except 后,location设置的proxy_upstream_name将无法被获取,肯定为 - ,所以可以认为只要是 -,就是使用了limit_except?返回403也比较合理。
唯一的问题是,会不会有其他指令,也会导致这个情况,但是他需要返回其他的状态码,这个暂时还没想到有,nginx的指令很多,需要慢慢确认
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 local function get_balancer () if ngx.ctx.balancer then return ngx.ctx.balancer end local backend_name = ngx.var.proxy_upstream_name if backend_name == '-' then ngx.status = ngx.HTTP_FORBIDDEN return ngx.exit (ngx.status ) end local balancer = balancers[backend_name] if not balancer then return nil end return balancerend
–
已经向社区反馈该问题的进展,结果后续会更新