Nginx 动态upstream引入变量的导致proxy_pass转发规则产生异常的神坑

大多数L7用的是Nginx,而一般Nginx upstream 配置是静态的不会变动,这其中有两种常用情况

  1. 如AWS之类的云服务厂商提供ELB域名,域名解析的IP随时会变动
  2. 对于服务运行在容器里的,例如Docker、LXC、LXD等,一个常见的现象就是容器ip的变更,例如扩容缩容,迁移等等。

Nginx使用upstream时,

  1. 里面如果填写的是域名,那么Nginx会在请求DNS后把对应的IP信息缓存起来,后续的请求就一直用缓存的IP。直到下次reload的时候才会再次查询domain,不想经常reload,需要通过set设置一个变量变量实现
  2. upstream直接配置的容器ip,扩容缩容,迁移ip变更,Nginx upstream不能随之变更,官方没有Dynamic Upstream,一般有多种解决方案:
    • 编写lua模块动态处理
    • upstream在文件中定义,该文件随时更新,而每次更新触发Nginx reload,且需要通过set设置一个变量变量实现。至于文件如果变动可通过多种语言,多种方法实现

所以不管upstream是域名还是动态IP,通过set设置一个变量是经常的行为,但恰恰是因为引入了这个变量,导致出现了一个proxy_pass转发规则与平时不一样的深坑。

问题描述

正常情况(静态upstream)下

想象一下,我们有一个包含以下内容的Nginx配置:

location /foo/ {
    proxy_pass http://127.0.0.1:8080;
}

当你向站点请求 /foo/bar/baz时,Nginx会将请求转发到

http://127.0.0.1:8080/foo/bar/baz

如果配置看起来像这样:

location /foo/ {
    # 注意尾部的斜线                 ↓
    proxy_pass http://127.0.0.1:8080/;
}

然后,Nginx将剥离在location指令中指定的URI部分,并将其余部分传递给上游服务器。因此,对 /foo/bar/baz 的请求将转发到:

http://127.0.0.1:8080/bar/baz

引入变量的情况

当我们使用变量作为proxy_pass的参数时,上面显示的带有尾部的斜杠行为会发生改变。假设我们有以下配置:

set $upstream_endpoint http://service-xxxxxxxx.elb.amazonaws.com/;
location /foo/ {
    proxy_pass $upstream_endpoint;
}

当请求 /foo/bar/baz时,转发的给后端请求将变为 / 而不是预期的 /bar/baz。于是变为

http://127.0.0.1:8080/

坑就在此

解决办法

解决方法是upstream删除尾部斜杠 / ,然后像这样手动 rewrite:

set $upstream_endpoint http://service-xxxxxxxx.elb.amazonaws.com/;
location /foo/ {
    rewrite ^/foo/(.*) /$1 break;
    proxy_pass $upstream_endpoint;
}

然后,当你向/foo/bar/baz发出请求时,upstream将像我们想要的那样获得对/bar/baz的请求。

如果不知道这个坑的话,是无论如何也想不到的我真实的经历过,后端研发要求上线新接口,转发至新的后端,我按照一般的思路配置好,后端研发说无论怎样请求后端都会转发到根 / 下

反反复复来去几次,甚至最后额外另搞了一个域名,另开了一个Nginx作为后端测试,把截图甩了过去。。。说你后端写的就是有问题呀

前端发出的请求
后端收到的请求

做测试时想的是把一切都简化,控制变量法抛离一切的不可能,最后剩下的那个就是有问题的。没想到控制变量法,出问题的就是变量(双关)。。。

发表评论

电子邮件地址不会被公开。 必填项已用*标注