在同一个 nginx 服务器块上公开多个 api uri
Expose multiple api uri on the same nginx server block
目标
我的目标是在同一个 nginx 服务器上设置多个后端 api 容器:
- http://localhost:80/api/account -> 调用 http://account-service:9000/
- http://localhost:80/api/cart -> 调用 http://cart-service:9000/
- http://localhost:80/api/order -> 调用 http://order-service:9000/
- http://localhost:80/api/product -> 调用 http://product-service:9000/
- ...
我的后端容器基于 php:7.2-fpm(symfony 托管在每个 apache 容器上)并且它们没有任何名为 [=13 的路由=](我不想在我的后端创建一些无用的 parent 路由)。
所以,我的要求很简单,例如,我希望在调用时能够做到这一点:
- http://localhost:80/api/account/profile
我的后端 帐户容器 提供这个 uri :
- http://account-service:9000/profile
到目前为止我尝试了什么
rewrite
(对 fastcgi 参数没有帮助)
- 使用
proxy_pass
设置 upstream
服务器
- 调整
fastcgi_param REQUEST_URI
(没有任何成功)
alias
(禁止访问)
会议
这是我的 nginx.conf :
...
server {
server_name ~.*;
client_max_body_size 50m;
location / {
try_files $uri /index.php$is_args$args;
}
# work if i want to serve account-service on http://localhost:80/
# location ~ ^/index\.php(/|$) {
# fastcgi_pass account-service:9000;
# fastcgi_buffers 16 16k;
# fastcgi_buffer_size 32k;
# fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
# include fastcgi_params;
# }
# my last attempt with alias
location ~* ^/api/account {
alias /;
index index.php;
location ~ index\.php(/|$) {
fastcgi_pass account-service:9000;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
fastcgi_intercept_errors on;
include fastcgi_params;
}
}
}
...
docker-compose.yml :
nginx:
image: nginx:1.15.3-alpine
restart: on-failure
volumes:
- "./build/nginx/default.conf:/etc/nginx/nginx.conf:ro"
- "./logs/nginx:/var/log/nginx"
ports:
- "80:80"
depends_on:
- account-service
- account-db
- cart-service
- cart-db
- order-service
- order-db
- product-service
- product-db
account-service:
env_file:
- config/account.env
build: apps/account-service
restart: on-failure
expose:
- "9000"
volumes:
- "./apps/account-service:/usr/src/app"
depends_on:
- account-db
cart-service:
...
P.S: 我知道你可以将 nginx conf 分成多个服务器块,监听不同的 port/hostname,但这不是我想在这里实现的。
调整fastcgi_param REQUEST_URI
是什么意思?如果您尝试在包含 fastcgi_params
文件之前将某些自定义值设置为 REQUEST_URI
,则 fastcgi_params
设置的值将覆盖您的任何调整:
fastcgi_pass service:9000;
fastcgi_param REQUEST_URI /some/path;
include fastcgi_params;
# REQUEST_URI passed as the real request URI
但是这个会按预期工作:
fastcgi_pass service:9000;
include fastcgi_params;
fastcgi_param REQUEST_URI /some/path;
# REQUEST_URI passed as "/some/path"
尝试使用 rewrite
更改此设置将不起作用,因为 REQUEST_URI
fastcgi 参数在 fastcgi_params
文件中设置为 $request_uri
内部 nginx 变量值,并且该变量不会被 rewrite
指令规则改变,它是一个 $uri
指令规则。
这是最简单的解决方案:
server {
...
location ~ ^/api(/(?:account|cart|order|product)/.*) {
# strip "/api" part from the URI and search for the new location block
rewrite ^ last;
}
location /account {
# strip "/account" part from the URI and continue processing within the current location block
rewrite ^/account(.*) break;
# include default fastcgi parameters first
include fastcgi_params;
# all our tweakings goes after it
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# use the rewrited $uri variable instead of the default $request_uri
# $uri variable does not include query arguments, so add them manually if they exists
fastcgi_param REQUEST_URI $uri$is_args$args;
fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
fastcgi_intercept_errors on;
fastcgi_pass account-service:9000;
}
location /cart {
rewrite ^/cart(.*) break;
...
fastcgi_pass cart-service:9000;
}
location /order {
rewrite ^/order(.*) break;
...
fastcgi_pass order-service:9000;
}
location /product {
rewrite ^/product(.*) break;
...
fastcgi_pass product-service:9000;
}
}
可以使用高级 nginx 技术极大地优化此解决方案:
server {
...
# This is a very important one!
# Since we are using variables for backend name, we need a resolver to resolve it at the runtime
# Docker default internal resolver is 127.0.0.11
resolver 127.0.0.11;
location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
include fastcgi_params;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# note we are using the $path variable here instead of the $uri one
fastcgi_param REQUEST_URI $path$is_args$args;
# assuming this path is the same within all the backend services
fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
fastcgi_intercept_errors on;
# using $api variable as part of backend container name
fastcgi_pass $api-service:9000;
}
}
请注意,我们需要一个新的 resolver
指令,因为我们使用变量来指定后端名称。您可以阅读更多详细信息 here, and the resolver address for docker taken from 答案。
更新@2022.05.18
实际上 是 一种无需附加 resolver
指令即可使上述配置工作的方法。为此,我们需要将每个服务定义为上游。这样 nginx 将只需要在启动时将使用的服务名称解析为 docker 容器 IP 地址,从而消除所有内部 DNS 流量并使整个配置的性能更高:
upstream account {
server account-service:9000;
}
upstream cart {
server cart-service:9000;
}
upstream order {
server order-service:9000;
}
upstream product {
server product-service:9000;
}
完成后,上面给出的配置的最后一行可以更改为
fastcgi_pass $api;
和 resolver
指令可以安全地从 nginx 配置中删除。
如果您的脚本路径因不同的 API 后端容器而异,您可以使用额外的 map
块从 $api
变量值中获取脚本路径:
map $api $script {
account /usr/src/app/public/index.php;
cart /some/other/path;
...
}
server {
...
location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
...
fastcgi_param SCRIPT_FILENAME $script;
...
}
}
目标
我的目标是在同一个 nginx 服务器上设置多个后端 api 容器:
- http://localhost:80/api/account -> 调用 http://account-service:9000/
- http://localhost:80/api/cart -> 调用 http://cart-service:9000/
- http://localhost:80/api/order -> 调用 http://order-service:9000/
- http://localhost:80/api/product -> 调用 http://product-service:9000/
- ...
我的后端容器基于 php:7.2-fpm(symfony 托管在每个 apache 容器上)并且它们没有任何名为 [=13 的路由=](我不想在我的后端创建一些无用的 parent 路由)。
所以,我的要求很简单,例如,我希望在调用时能够做到这一点:
- http://localhost:80/api/account/profile
我的后端 帐户容器 提供这个 uri :
- http://account-service:9000/profile
到目前为止我尝试了什么
rewrite
(对 fastcgi 参数没有帮助)- 使用
proxy_pass
设置 - 调整
fastcgi_param REQUEST_URI
(没有任何成功) alias
(禁止访问)
upstream
服务器
会议
这是我的 nginx.conf :
...
server {
server_name ~.*;
client_max_body_size 50m;
location / {
try_files $uri /index.php$is_args$args;
}
# work if i want to serve account-service on http://localhost:80/
# location ~ ^/index\.php(/|$) {
# fastcgi_pass account-service:9000;
# fastcgi_buffers 16 16k;
# fastcgi_buffer_size 32k;
# fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
# include fastcgi_params;
# }
# my last attempt with alias
location ~* ^/api/account {
alias /;
index index.php;
location ~ index\.php(/|$) {
fastcgi_pass account-service:9000;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
fastcgi_intercept_errors on;
include fastcgi_params;
}
}
}
...
docker-compose.yml :
nginx:
image: nginx:1.15.3-alpine
restart: on-failure
volumes:
- "./build/nginx/default.conf:/etc/nginx/nginx.conf:ro"
- "./logs/nginx:/var/log/nginx"
ports:
- "80:80"
depends_on:
- account-service
- account-db
- cart-service
- cart-db
- order-service
- order-db
- product-service
- product-db
account-service:
env_file:
- config/account.env
build: apps/account-service
restart: on-failure
expose:
- "9000"
volumes:
- "./apps/account-service:/usr/src/app"
depends_on:
- account-db
cart-service:
...
P.S: 我知道你可以将 nginx conf 分成多个服务器块,监听不同的 port/hostname,但这不是我想在这里实现的。
调整fastcgi_param REQUEST_URI
是什么意思?如果您尝试在包含 fastcgi_params
文件之前将某些自定义值设置为 REQUEST_URI
,则 fastcgi_params
设置的值将覆盖您的任何调整:
fastcgi_pass service:9000;
fastcgi_param REQUEST_URI /some/path;
include fastcgi_params;
# REQUEST_URI passed as the real request URI
但是这个会按预期工作:
fastcgi_pass service:9000;
include fastcgi_params;
fastcgi_param REQUEST_URI /some/path;
# REQUEST_URI passed as "/some/path"
尝试使用 rewrite
更改此设置将不起作用,因为 REQUEST_URI
fastcgi 参数在 fastcgi_params
文件中设置为 $request_uri
内部 nginx 变量值,并且该变量不会被 rewrite
指令规则改变,它是一个 $uri
指令规则。
这是最简单的解决方案:
server {
...
location ~ ^/api(/(?:account|cart|order|product)/.*) {
# strip "/api" part from the URI and search for the new location block
rewrite ^ last;
}
location /account {
# strip "/account" part from the URI and continue processing within the current location block
rewrite ^/account(.*) break;
# include default fastcgi parameters first
include fastcgi_params;
# all our tweakings goes after it
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# use the rewrited $uri variable instead of the default $request_uri
# $uri variable does not include query arguments, so add them manually if they exists
fastcgi_param REQUEST_URI $uri$is_args$args;
fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
fastcgi_intercept_errors on;
fastcgi_pass account-service:9000;
}
location /cart {
rewrite ^/cart(.*) break;
...
fastcgi_pass cart-service:9000;
}
location /order {
rewrite ^/order(.*) break;
...
fastcgi_pass order-service:9000;
}
location /product {
rewrite ^/product(.*) break;
...
fastcgi_pass product-service:9000;
}
}
可以使用高级 nginx 技术极大地优化此解决方案:
server {
...
# This is a very important one!
# Since we are using variables for backend name, we need a resolver to resolve it at the runtime
# Docker default internal resolver is 127.0.0.11
resolver 127.0.0.11;
location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
include fastcgi_params;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# note we are using the $path variable here instead of the $uri one
fastcgi_param REQUEST_URI $path$is_args$args;
# assuming this path is the same within all the backend services
fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
fastcgi_intercept_errors on;
# using $api variable as part of backend container name
fastcgi_pass $api-service:9000;
}
}
请注意,我们需要一个新的 resolver
指令,因为我们使用变量来指定后端名称。您可以阅读更多详细信息 here, and the resolver address for docker taken from
更新@2022.05.18
实际上 是 一种无需附加 resolver
指令即可使上述配置工作的方法。为此,我们需要将每个服务定义为上游。这样 nginx 将只需要在启动时将使用的服务名称解析为 docker 容器 IP 地址,从而消除所有内部 DNS 流量并使整个配置的性能更高:
upstream account {
server account-service:9000;
}
upstream cart {
server cart-service:9000;
}
upstream order {
server order-service:9000;
}
upstream product {
server product-service:9000;
}
完成后,上面给出的配置的最后一行可以更改为
fastcgi_pass $api;
和 resolver
指令可以安全地从 nginx 配置中删除。
如果您的脚本路径因不同的 API 后端容器而异,您可以使用额外的 map
块从 $api
变量值中获取脚本路径:
map $api $script {
account /usr/src/app/public/index.php;
cart /some/other/path;
...
}
server {
...
location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
...
fastcgi_param SCRIPT_FILENAME $script;
...
}
}