nginx 反向代理 - 如何为多个应用程序提供服务
nginx reverse proxy - how to serve multiple apps
我正在尝试使用 nginx 构建一个反向代理,以使我的项目中的所有 Is 都可以从单个地址访问。
对于单个服务,下面的配置没有问题
/etc/nginx/sites-enabled/reverse-proxy.conf
server {
listen 80;
listen [::]:80;
location / {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
}
所以当我在浏览器中调用服务器的 ip x.x.x.x
时,我看到 Consul UI 和 URL 显示 x.x.x.x/ui/dc1
。除此之外,我看到 UI 成功请求资产文件。
我的问题;是否有可能在同一台服务器上有两个托管不同的服务,只是在不同的位置引用它们?例如,如果我想包含 Vault UI 那么我会想到做这样的事情:
server {
listen 80;
listen [::]:80;
location /consul {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
location /vault {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://vault:8200;
}
}
但是我不确定这是否可以通过这种方式完成。我得到的最远的是打开 Consul UI 没有找到所有其他子请求(即加载资产)。
更新
我认为我的问题是我错误地使用了 location
和 proxy_pass
观察第一个配置(有效)
server {
listen 80;
listen [::]:80;
location / {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
}
如果我查看 curl 命令 curl localhost -L -vvvv
* Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:24:38 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 39
< Connection: keep-alive
< Location: /ui/
<
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost/ui/'
* Found bundle for host localhost: 0x557b754549e0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /ui/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:24:38 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 7806
< Connection: keep-alive
< Accept-Ranges: bytes
< Last-Modified: Fri, 10 Jul 2020 07:37:44 GMT
<
<!DOCTYPE html>
<html lang="en" class="ember-loading">
...
而且我已经可以看到 html。但是,如果我将 conf 文件更改为:
server {
listen 80;
listen [::]:80;
location /consul/ {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
}
然后尝试像 curl localhost/consul -L -vvvv
那样调用它,我得到以下信息:
* Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /consul HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:32:35 GMT
< Content-Type: text/html
< Content-Length: 178
< Location: http://localhost/consul/
< Connection: keep-alive
<
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost/consul/'
* Found bundle for host localhost: 0x55ba7959f9e0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /consul/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:32:35 GMT
< Content-Length: 0
< Connection: keep-alive
如果有任何关于此问题的想法,我将不胜感激
你是对的,你用错了location
和proxy_pass
。当您使用
location /vault {
proxy_pass http://vault:8200;
}
构造,您正在将 URI 传递到上游 as-is,而您很可能想从中删除 /vault
前缀。为此,您应该使用这个:
location /vault/ {
proxy_pass http://vault:8200/;
}
您可以阅读更多关于第一个和第二个的区别here。然而,这仍然会阻止资产正确加载。
这个问题——如何在某些 URI 前缀下代理某些 webapp——在 Whosebug 上被反复询问。唯一正确的方法是让你的代理应用仅通过相对 URLs 请求其资产(考虑 assets/script.js
而不是 /assets/script.js
)或使用正确的前缀(/vault/assets/script.js
).一些 well-written 应用程序能够检测它们是否在这样的 URI 前缀下使用,并在生成资产 link 时使用它,一些应用程序允许通过一些设置来指定它,但有些不适合对于这样的用途。如果不满足这些要求,webapp 将无法工作的原因非常明显 - 任何未以 /vault
开头的 URL 都不会匹配您的 location /vault/ { ... }
块,并将通过 main location
阻止。所以最好的方法是修复你的网络应用程序,但是如果你真的不能,可以使用几种解决方法。
一些 Web 框架已经使用相对 URL 构建了它们的 Web 应用程序,但在 index.html
的头部使用了 <base href="/">
。例如,React 或 Angular 使用这种方法。如果您的 webapp 根目录中有这样一行 index.html
,只需将其更改为 <base href="/vault/">
.
使用基于 HTTP Referer
header 值的条件路由。这种方法对于用于加载资产的单页应用程序非常有效,但如果 Web 应用程序包含多个页面,则这种方法将不起作用,正确的上游检测逻辑会在第一次从一个页面跳转到另一个页面后中断。这是一个例子:
map $http_referer $prefix {
~https?://[^/]+/vault/ vault;
# other webapps prefixes could be defined here
# ...
default base;
}
server {
# listen port, server name and other global definitions here
# ...
location / {
try_files "" @$prefix;
}
location /vault/ {
# proxy request to the vault upstream, remove "/vault" part from the URI
proxy_pass http://vault:8200/;
}
location @vault {
# proxy request to the vault upstream, do not change the URI
proxy_pass http://vault:8200;
}
location @base {
# default "root" location
proxy_pass http://consul:8500;
}
}
更新@2022.02.19
这是使用条件重写的另一种可能方法:
server {
# listen port, server name and other global definitions here
# ...
if ($http_referer ~ https?://[^/]+/vault/)
# rewrite request URI only if it isn't already started with '/vault' prefix
rewrite ^((?!/vault).*) /vault;
}
# locations here
# ...
}
使用 ngx_http_sub_module
中的 sub_filter
指令重写响应 body 中的 link。这是最丑陋的一个,但仍然可以用作最后一个可用选项。这种方法具有明显的性能影响。重写模式应根据您的上游响应 body 确定。通常那种类型的配置看起来像
location /vault/ {
proxy_pass http://vault:8200/;
sub_filter_types text/css application/javascript;
sub_filter_once off;
sub_filter 'href="/' 'href="/vault/';
sub_filter "href='/" "href='/vault/";
sub_filter 'src="/' 'src="/vault/';
sub_filter "src='/" "src='/vault/";
sub_filter 'url("/' 'url("/vault/';
sub_filter "url('/" "url('/vault/";
sub_filter "url(/" "url(/vault/";
}
更新@2022.02.19
ServerFault 的相关线程:How to handle relative urls correctly with a nginx reverse proxy。
在 JavaScript 代码上使用 sub_filter
的可能注意事项:Nginx as reverse proxy to two nodejs app on the same domain.
我正在尝试使用 nginx 构建一个反向代理,以使我的项目中的所有 Is 都可以从单个地址访问。 对于单个服务,下面的配置没有问题
/etc/nginx/sites-enabled/reverse-proxy.conf
server {
listen 80;
listen [::]:80;
location / {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
}
所以当我在浏览器中调用服务器的 ip x.x.x.x
时,我看到 Consul UI 和 URL 显示 x.x.x.x/ui/dc1
。除此之外,我看到 UI 成功请求资产文件。
我的问题;是否有可能在同一台服务器上有两个托管不同的服务,只是在不同的位置引用它们?例如,如果我想包含 Vault UI 那么我会想到做这样的事情:
server {
listen 80;
listen [::]:80;
location /consul {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
location /vault {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://vault:8200;
}
}
但是我不确定这是否可以通过这种方式完成。我得到的最远的是打开 Consul UI 没有找到所有其他子请求(即加载资产)。
更新
我认为我的问题是我错误地使用了 location
和 proxy_pass
观察第一个配置(有效)
server {
listen 80;
listen [::]:80;
location / {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
}
如果我查看 curl 命令 curl localhost -L -vvvv
* Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:24:38 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 39
< Connection: keep-alive
< Location: /ui/
<
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost/ui/'
* Found bundle for host localhost: 0x557b754549e0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /ui/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:24:38 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 7806
< Connection: keep-alive
< Accept-Ranges: bytes
< Last-Modified: Fri, 10 Jul 2020 07:37:44 GMT
<
<!DOCTYPE html>
<html lang="en" class="ember-loading">
...
而且我已经可以看到 html。但是,如果我将 conf 文件更改为:
server {
listen 80;
listen [::]:80;
location /consul/ {
resolver 127.0.0.1;
allow "x.x.x.x";
deny all;
proxy_pass http://consul:8500;
}
}
然后尝试像 curl localhost/consul -L -vvvv
那样调用它,我得到以下信息:
* Trying 127.0.0.1:80...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /consul HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:32:35 GMT
< Content-Type: text/html
< Content-Length: 178
< Location: http://localhost/consul/
< Connection: keep-alive
<
* Ignoring the response-body
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost/consul/'
* Found bundle for host localhost: 0x55ba7959f9e0 [serially]
* Can not multiplex, even if we wanted to!
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /consul/ HTTP/1.1
> Host: localhost
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: nginx/1.18.0 (Ubuntu)
< Date: Fri, 10 Jul 2020 16:32:35 GMT
< Content-Length: 0
< Connection: keep-alive
如果有任何关于此问题的想法,我将不胜感激
你是对的,你用错了location
和proxy_pass
。当您使用
location /vault {
proxy_pass http://vault:8200;
}
构造,您正在将 URI 传递到上游 as-is,而您很可能想从中删除 /vault
前缀。为此,您应该使用这个:
location /vault/ {
proxy_pass http://vault:8200/;
}
您可以阅读更多关于第一个和第二个的区别here。然而,这仍然会阻止资产正确加载。
这个问题——如何在某些 URI 前缀下代理某些 webapp——在 Whosebug 上被反复询问。唯一正确的方法是让你的代理应用仅通过相对 URLs 请求其资产(考虑 assets/script.js
而不是 /assets/script.js
)或使用正确的前缀(/vault/assets/script.js
).一些 well-written 应用程序能够检测它们是否在这样的 URI 前缀下使用,并在生成资产 link 时使用它,一些应用程序允许通过一些设置来指定它,但有些不适合对于这样的用途。如果不满足这些要求,webapp 将无法工作的原因非常明显 - 任何未以 /vault
开头的 URL 都不会匹配您的 location /vault/ { ... }
块,并将通过 main location
阻止。所以最好的方法是修复你的网络应用程序,但是如果你真的不能,可以使用几种解决方法。
一些 Web 框架已经使用相对 URL 构建了它们的 Web 应用程序,但在
index.html
的头部使用了<base href="/">
。例如,React 或 Angular 使用这种方法。如果您的 webapp 根目录中有这样一行index.html
,只需将其更改为<base href="/vault/">
.使用基于 HTTP
Referer
header 值的条件路由。这种方法对于用于加载资产的单页应用程序非常有效,但如果 Web 应用程序包含多个页面,则这种方法将不起作用,正确的上游检测逻辑会在第一次从一个页面跳转到另一个页面后中断。这是一个例子:map $http_referer $prefix { ~https?://[^/]+/vault/ vault; # other webapps prefixes could be defined here # ... default base; } server { # listen port, server name and other global definitions here # ... location / { try_files "" @$prefix; } location /vault/ { # proxy request to the vault upstream, remove "/vault" part from the URI proxy_pass http://vault:8200/; } location @vault { # proxy request to the vault upstream, do not change the URI proxy_pass http://vault:8200; } location @base { # default "root" location proxy_pass http://consul:8500; } }
更新@2022.02.19
这是使用条件重写的另一种可能方法:
server { # listen port, server name and other global definitions here # ... if ($http_referer ~ https?://[^/]+/vault/) # rewrite request URI only if it isn't already started with '/vault' prefix rewrite ^((?!/vault).*) /vault; } # locations here # ... }
使用
ngx_http_sub_module
中的sub_filter
指令重写响应 body 中的 link。这是最丑陋的一个,但仍然可以用作最后一个可用选项。这种方法具有明显的性能影响。重写模式应根据您的上游响应 body 确定。通常那种类型的配置看起来像location /vault/ { proxy_pass http://vault:8200/; sub_filter_types text/css application/javascript; sub_filter_once off; sub_filter 'href="/' 'href="/vault/'; sub_filter "href='/" "href='/vault/"; sub_filter 'src="/' 'src="/vault/'; sub_filter "src='/" "src='/vault/"; sub_filter 'url("/' 'url("/vault/'; sub_filter "url('/" "url('/vault/"; sub_filter "url(/" "url(/vault/"; }
更新@2022.02.19
ServerFault 的相关线程:How to handle relative urls correctly with a nginx reverse proxy。
在 JavaScript 代码上使用 sub_filter
的可能注意事项:Nginx as reverse proxy to two nodejs app on the same domain.