为什么在NGINX的`proxy_pass`中变量无法工作?

7
为什么在 proxy_pass 中变量无法工作?
这样可以完美工作:
location /foo/ {
  proxy_pass http://127.0.0.1/;
}

这完全不起作用:

location /foo/ {
  set $FOO http://127.0.0.1/;
  proxy_pass $FOO;
  add_header x-debug $FOO;
}

我看到了x-header: http://127.0.0.1/,但结果是404,所以我不知道它代理到哪里,但它与第一个示例不完全相同。

源代码中解释了,在proxy_pass中使用变量将防止NGINX在上游主机不可用时出现启动错误。

更新:问题在于上游路径重写。我希望将/foo/blah重写为上游的/blah,并删除前缀/foo。使用静态主机/URI条目可以正常工作,但使用变量则不行。


你是否在你的nginx配置中添加了resolver指令? - Ivan Shatsky
我有点困惑,解析器是指DNS服务器的引用。这与解析变量有什么关系呢?事实上,我使用127.0.0.1(而不是localhost),所以我不需要DNS... - Marc
在OpenResty 1.17.8.2(基于nginx 1.17核心)的沙盒中测试过,对我来说完美无缺,无需任何额外的“resolver”。目前无法使用原始版本的nginx进行测试... - Ivan Shatsky
@IvanShatsky 这个问题是路径剥离。我们希望入站 /foo/bar/ 路由到上游 /bar/。 - Marc
1
确实,set $FOO http://127.0.0.1; proxy_pass $FOO/;也没有去掉/foo URI前缀。然而,你的最终解决方案有点过于复杂了,更简单的rewrite ^/foo(/.*) $1 break; proxy_pass http://$FOO;可以稍微更有效地完成同样的任务。 - Ivan Shatsky
2个回答

7

在@MSalters的大力帮助下,最终答案比我想象的复杂得多。原因是NGINX在处理变量时与静态输入的主机名不同 - 它甚至不使用相同的DNS机制。

主要问题是路径处理和前缀剥离在使用变量时不同。您必须自行剥离路径前缀。在我的原始示例中:

location /foo/ {
  set $FOO 127.0.0.1;
  rewrite /foo/(.*) /$1 break;
  proxy_pass http://$FOO/$1$is_args$args;
}

在我的例子中,我使用了一个IP地址,因此不需要解析器。但是,如果您使用主机名,则需要添加一个resolver来指定您的DNS IP。耸肩。

完全公开地说,我们在Kubernetes内部使用NGINX,因此它变得更加复杂。值得注意的特殊点包括:

  1. 添加一个resolver指令,并将其设置为集群DNS服务的IP地址(在我的情况下为10.43.0.10)。这是kube-system命名空间中kube-dns服务的ClusterIP。
  2. 即使您的NGINX位于相同的命名空间中,也必须使用FQDN,因为DNS显然只能解析FQDN。
location /foo/ {
  set $MYSERVICE myservice.mynamespace.svc.cluster.local;
  rewrite /foo/(.*) /$1 break;
  proxy_pass http://$MYSERVICE/$1$is_args$args;
  resolver 10.43.0.10 valid=10s;
}

注意:由于NGINX中的一个BUG(不幸的是,NGINX维护者并未承认),在URL中使用$1将会导致路径中包含空格时出现问题。因此,/foo%20bar/会被传递到上游服务器作为/foo bar/,并且会出现错误。


0
nginx.conf 中变量的想法是为了延迟计算。针对 nginx.conf 的配置有两个不同的解析时机:启动时和请求发生时。在第二次解析中,会填充基于请求的变量。
这个解析器并不十分智能,这种行为也没有被正式规定。你链接中的技巧是一个正确的黑客方法。
我在生产环境中使用的解决方法是 proxy-pass http://$FOO。字面字符串 http 和变量 $FOO 的连接确实发生在请求时,说明变量在proxy-pass中是有效的。为什么它无法通过纯替换运作,我不知道。由于这是一个未经记录的黑客方法,这可能会因版本而异。如果 nginx 本身更智能些那就太好了。
[编辑]

在某些情况下,无法确定要替换的请求 URI 的一部分: ... 当使用变量代理时: location /name/ { proxy_pass http://127.0.0.1$request_uri;} 在这种情况下,如果在指令中指定了 URI,则将其原样传递到服务器,以替换原始请求 URI。

如果一个变量存在,这种不同的行为在手册中有详细说明。您可以尝试使用rewrite指令,修改要发送的URI。


仍然无法使用那个解决方法 - 它会不同地代理并且不会删除路径前缀 /foo。没有等价物。尝试了 set $FOO 127.0.0.1/; proxy_pass http://$FOO;set $FOO 127.0.0.1; proxy_pass http://$FOO/;,包括斜杠和不包括斜杠。 - Marc
我已更新我的答案 - 问题不是它不工作,而是在代理之前剥离了“/foo”路径后,它的工作方式与以前不同。 - Marc
@Marc:请查看手册;这是一个有文档记录的怪异问题。 - MSalters
是的,这很奇怪。当我将主机名作为变量解析时,它无法解析,但是当我静态输入时,它可以正常解析。出现了“some.host 无法解析 (3: Host not found)” 的错误。如果我只输入“http://some.host”,它就可以正常工作。 - Marc
添加解析器似乎是解决问题的方法,因为当NGINX看到一个变量时,它不会执行普通的DNS。resolver 10.43.0.10 valid=10s;(适用于Kubernetes) - Marc
@Marc:啊。我的配置有resolver 127.0.0.11(Docker)。 - MSalters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接