Docker 中的 NGINX 缓存 IP 地址并传送错误 Content-Type
NGINX in Docker caching IP addresses and delivering wrong Content-Type
总结:
我想通过使用 proxy_pass
中的变量(如 this official Nginx article in the section "Setting the Domain Name in a Variable" 中的建议)使 NGINX(不是 NGINX Plus)re-resolve 来自 DNS 名称的 IP 地址。但是当我这样做时,它不会 set/forward 正确的 Content-Type
header,而是始终使用 text/html
,.css
,.js
等。文件。
我的设置:
我在单独的 Docker 容器中有一个前端和一个后端服务 运行ning(将在生产环境中部署到 OpenShift)。还有第三个容器 运行s nginx:latest
(从今天起为 v1.19.9)并充当反向代理,将对 /my-app
的调用转发到前端和 /my-app/api
到后端容器。 NGINX 反向代理使用它们的 DNS 名称将它们设置为上游服务器。
所有三个容器 运行 都在同一个自定义 Docker 网络中,因此解析本身工作正常 - 但仅限于(重新)启动 NGINX 时。
问题:
当前端或后端容器重新启动时,它可能会获得一个新的 IP 地址。由于 NGINX 缓存了 IP 地址,所以我在调用它们时得到 502
。我希望 NGINX 更频繁地 re-resolve IP 地址,就像 NGINX Plus 一样。当我尝试使用 NGINX re-resolve DNS 名称时,Content-Type
的问题就是这样出现的。
配置:
这是我的 NGINX 配置(仅简化为相关内容):
index index.html index.htm;
upstream upstream_frontend {
server frontend:8080;
}
upstream upstream_backend {
server backend:8000;
}
server {
listen 8080;
root /usr/share/nginx/html;
try_files $uri$args $uri$args/ $uri $uri/ /index.html =404;
rewrite ^/my-app$ $scheme://$http_host/my-app/ permanent;
location /my-app {
resolver 127.0.0.11 ipv6=off valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_header Content-Type;
# Attempt #1, NOT working: using a variable and an upstream server setup
set $frontend_var "http://upstream_frontend/";
proxy_pass http://$frontend_var;
# Attempt #2, NOT working: using a variable and directly using the container name
# set $frontend_var "http://frontend:8080/";
# proxy_pass $frontend_var;
# Attempt #3, working fine: using NO variable and an upstream server setup, , but no DNS re-resolving happening :-(
# proxy_pass http://upstream_frontend/;
# Attempt #4, working fine: using NO variable and directly using the container name, but no DNS re-resolving happening :-(
# proxy_pass http://frontend:8080/;
}
location /my-app/api {
resolver 127.0.0.11 ipv6=off valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
# Attempt #1, NOT working: using a variable and an upstream server setup
set $backend_var "http://upstream_backend/api";
proxy_pass http://$backend_var;
# Attempt #2, NOT working: using a variable and directly using the container name
# set $backend_var "http://backend:8000/api";
# proxy_pass http://$backend_var;
# Attempt #3, working fine: using NO variable and an upstream server setup, , but no DNS re-resolving happening :-(
# proxy_pass http://upstream_backend/api;
# Attempt #4, working fine: using NO variable and directly using the container name, but no DNS re-resolving happening :-(
# proxy_pass http://backend:8000/api;
}
}
(请注意 proxy_pass
末尾的 location /my-app {...}
和 /
,因为前端是从 /my-app
提供的,而前端容器本身,也由 NGINX 提供,直接在 ...:8080/
上运行,没有 /my-app
上下文路径。我们对监听 :8000
的后端容器有相同的设置,但是当调用被转发时为此,我们正在从它们中删除 /my-app
,以便它们直接到达位于 ...:8000/api
的容器。)
NGINX 在端口 9000
上 运行ning,当我使用上面的方法打开 http://localhost:9000/my-app 时 Attempt #1 or Attempt #2 .css
, .js
, image files etc. are all served with Content-Type: text/html
, which prevents the browser properly呈现页面,并在调试器窗格中显示如下消息:
The stylesheet http://localhost:9000/my-app/static/css/main.df1d2133.chunk.css was not loaded because its MIME type, ”text/html“ is not ”text/css"
起初我以为 proxy_pass_header Content-Type;
会解决这个问题,但这也无济于事。
然后我也从this NGINX bug ticket那里了解到”在proxy_pass中使用变量时,如果指定了URI,则原样传递给服务器,替换原来的请求 URI。”。这似乎是我在这里遇到的 Content-Type
问题的可能原因。
此外,正如您在配置中看到的那样,我设置了 Docker DNS IP:resolver 127.0.0.11 ipv6=off valid=1s;
。我也试过将它设置在 location
块之外,这也没有帮助。
问题:
那么我如何在 NGINX (Open-Source) 和 中设置正确的 Content-Type
DNS re-resolving?我没有使用上游服务器,所以我可以摆脱它们,如果这是可能修复的一部分。
P.S.:
正如其他 Whosebug 评论中所建议的那样,我不能根据文件夹、文件名等添加单独的 location
块来“追溯”修复 Content-Type
,因为这是一个不断发展的项目,我很担心需要我定期将此类修补程序添加到 NGINX 配置中。
编辑:
我发布的配置包含在 NGINX 的 Docker 容器的配置文件中,如果您想知道一些“缺失”的设置:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# This line includes my configuration above
include /etc/nginx/conf.d/*.conf;
}
这不是 Content-Type
的问题。您使用 proxy_pass
不正确。正如您已经注意到的:
When using variables in proxy_pass, if URI is specified, it is passed to the server as is, replacing the original request URI.
您当前有以下工作配置,但您想用一个变量替换它以强制进行 DNS 解析:
location /my-app {
proxy_pass http://frontend:8080/;
}
最简单的解决方案是使用 rewrite...break
对 URI 进行更改,并从变量中删除结尾的 /
。
例如:
location /my-app {
rewrite ^/my-app/?(.*)$ / break;
set $frontend_var "frontend:8080";
proxy_pass http://$frontend_var;
}
或者,使用 正则表达式 location
.
捕获原始请求的其余部分
例如:
location ~ ^/my-app/?(.*)$ {
proxy_pass http://frontend:8080/;
}
这是我的最终工作设置。再次感谢,@richard-smith!
请注意...:[=19=]
- 我没有使用
upstream
块,因为我正在将它部署到 OpenShift。 Pods(~= 服务器或容器)前面有“服务”的概念,这基本上就是 upstream
在 Nginx 中所做的,
- 我重新排序了
location
块,所以更具体的块 /my-app/api
会先被击中,否则会失败,
- 我必须添加
$isargs$args
部分,否则 URL 构造不正确并且会失败,特别是对于对我的后端的调用,
- 对于 OpenShift,我必须在
proxy_pass
上添加 .<PROJECT>.svc.cluster.local
,否则解析器将无法解析服务。
这是相关设置,对于剩余的(默认)Nginx 配置,请参阅我在上面初始问题的编辑中发布的配置。
index index.html index.htm;
server {
listen 8080;
root /usr/share/nginx/html;
try_files $uri$args $uri$args/ $uri $uri/ /index.html =404;
rewrite ^/my-app$ $scheme://$http_host/my-app/ permanent;
# Backend
location ~ ^/my-app/api/?(.*)$ {
# Use this for Docker...
# resolver 127.0.0.11 ipv6=off valid=1s;
# ... or use this for OpenShift
# resolver dns-default.openshift-dns.svc.cluster.local ipv6=on valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_header Content-Type;
proxy_pass http://backend.<PROJECT>.svc.cluster.local:8000/api/$is_args$args;
}
# Frontend
location ~ ^/my-app/?(.*)$ {
# Use this for Docker...
# resolver 127.0.0.11 ipv6=off valid=1s;
# ... or use this for OpenShift
# resolver dns-default.openshift-dns.svc.cluster.local ipv6=on valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_header Content-Type;
proxy_pass http://frontend.<PROJECT>.svc.cluster.local:8080/$is_args$args;
}
}
总结:
我想通过使用 proxy_pass
中的变量(如 this official Nginx article in the section "Setting the Domain Name in a Variable" 中的建议)使 NGINX(不是 NGINX Plus)re-resolve 来自 DNS 名称的 IP 地址。但是当我这样做时,它不会 set/forward 正确的 Content-Type
header,而是始终使用 text/html
,.css
,.js
等。文件。
我的设置:
我在单独的 Docker 容器中有一个前端和一个后端服务 运行ning(将在生产环境中部署到 OpenShift)。还有第三个容器 运行s nginx:latest
(从今天起为 v1.19.9)并充当反向代理,将对 /my-app
的调用转发到前端和 /my-app/api
到后端容器。 NGINX 反向代理使用它们的 DNS 名称将它们设置为上游服务器。
所有三个容器 运行 都在同一个自定义 Docker 网络中,因此解析本身工作正常 - 但仅限于(重新)启动 NGINX 时。
问题:
当前端或后端容器重新启动时,它可能会获得一个新的 IP 地址。由于 NGINX 缓存了 IP 地址,所以我在调用它们时得到 502
。我希望 NGINX 更频繁地 re-resolve IP 地址,就像 NGINX Plus 一样。当我尝试使用 NGINX re-resolve DNS 名称时,Content-Type
的问题就是这样出现的。
配置:
这是我的 NGINX 配置(仅简化为相关内容):
index index.html index.htm;
upstream upstream_frontend {
server frontend:8080;
}
upstream upstream_backend {
server backend:8000;
}
server {
listen 8080;
root /usr/share/nginx/html;
try_files $uri$args $uri$args/ $uri $uri/ /index.html =404;
rewrite ^/my-app$ $scheme://$http_host/my-app/ permanent;
location /my-app {
resolver 127.0.0.11 ipv6=off valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_header Content-Type;
# Attempt #1, NOT working: using a variable and an upstream server setup
set $frontend_var "http://upstream_frontend/";
proxy_pass http://$frontend_var;
# Attempt #2, NOT working: using a variable and directly using the container name
# set $frontend_var "http://frontend:8080/";
# proxy_pass $frontend_var;
# Attempt #3, working fine: using NO variable and an upstream server setup, , but no DNS re-resolving happening :-(
# proxy_pass http://upstream_frontend/;
# Attempt #4, working fine: using NO variable and directly using the container name, but no DNS re-resolving happening :-(
# proxy_pass http://frontend:8080/;
}
location /my-app/api {
resolver 127.0.0.11 ipv6=off valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
# Attempt #1, NOT working: using a variable and an upstream server setup
set $backend_var "http://upstream_backend/api";
proxy_pass http://$backend_var;
# Attempt #2, NOT working: using a variable and directly using the container name
# set $backend_var "http://backend:8000/api";
# proxy_pass http://$backend_var;
# Attempt #3, working fine: using NO variable and an upstream server setup, , but no DNS re-resolving happening :-(
# proxy_pass http://upstream_backend/api;
# Attempt #4, working fine: using NO variable and directly using the container name, but no DNS re-resolving happening :-(
# proxy_pass http://backend:8000/api;
}
}
(请注意 proxy_pass
末尾的 location /my-app {...}
和 /
,因为前端是从 /my-app
提供的,而前端容器本身,也由 NGINX 提供,直接在 ...:8080/
上运行,没有 /my-app
上下文路径。我们对监听 :8000
的后端容器有相同的设置,但是当调用被转发时为此,我们正在从它们中删除 /my-app
,以便它们直接到达位于 ...:8000/api
的容器。)
NGINX 在端口 9000
上 运行ning,当我使用上面的方法打开 http://localhost:9000/my-app 时 Attempt #1 or Attempt #2 .css
, .js
, image files etc. are all served with Content-Type: text/html
, which prevents the browser properly呈现页面,并在调试器窗格中显示如下消息:
The stylesheet http://localhost:9000/my-app/static/css/main.df1d2133.chunk.css was not loaded because its MIME type, ”text/html“ is not ”text/css"
起初我以为 proxy_pass_header Content-Type;
会解决这个问题,但这也无济于事。
然后我也从this NGINX bug ticket那里了解到”在proxy_pass中使用变量时,如果指定了URI,则原样传递给服务器,替换原来的请求 URI。”。这似乎是我在这里遇到的 Content-Type
问题的可能原因。
此外,正如您在配置中看到的那样,我设置了 Docker DNS IP:resolver 127.0.0.11 ipv6=off valid=1s;
。我也试过将它设置在 location
块之外,这也没有帮助。
问题:
那么我如何在 NGINX (Open-Source) 和 中设置正确的 Content-Type
DNS re-resolving?我没有使用上游服务器,所以我可以摆脱它们,如果这是可能修复的一部分。
P.S.:
正如其他 Whosebug 评论中所建议的那样,我不能根据文件夹、文件名等添加单独的 location
块来“追溯”修复 Content-Type
,因为这是一个不断发展的项目,我很担心需要我定期将此类修补程序添加到 NGINX 配置中。
编辑:
我发布的配置包含在 NGINX 的 Docker 容器的配置文件中,如果您想知道一些“缺失”的设置:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# This line includes my configuration above
include /etc/nginx/conf.d/*.conf;
}
这不是 Content-Type
的问题。您使用 proxy_pass
不正确。正如您已经注意到的:
When using variables in proxy_pass, if URI is specified, it is passed to the server as is, replacing the original request URI.
您当前有以下工作配置,但您想用一个变量替换它以强制进行 DNS 解析:
location /my-app {
proxy_pass http://frontend:8080/;
}
最简单的解决方案是使用 rewrite...break
对 URI 进行更改,并从变量中删除结尾的 /
。
例如:
location /my-app {
rewrite ^/my-app/?(.*)$ / break;
set $frontend_var "frontend:8080";
proxy_pass http://$frontend_var;
}
或者,使用 正则表达式 location
.
例如:
location ~ ^/my-app/?(.*)$ {
proxy_pass http://frontend:8080/;
}
这是我的最终工作设置。再次感谢,@richard-smith!
请注意...:[=19=]
- 我没有使用
upstream
块,因为我正在将它部署到 OpenShift。 Pods(~= 服务器或容器)前面有“服务”的概念,这基本上就是upstream
在 Nginx 中所做的, - 我重新排序了
location
块,所以更具体的块/my-app/api
会先被击中,否则会失败, - 我必须添加
$isargs$args
部分,否则 URL 构造不正确并且会失败,特别是对于对我的后端的调用, - 对于 OpenShift,我必须在
proxy_pass
上添加.<PROJECT>.svc.cluster.local
,否则解析器将无法解析服务。
这是相关设置,对于剩余的(默认)Nginx 配置,请参阅我在上面初始问题的编辑中发布的配置。
index index.html index.htm;
server {
listen 8080;
root /usr/share/nginx/html;
try_files $uri$args $uri$args/ $uri $uri/ /index.html =404;
rewrite ^/my-app$ $scheme://$http_host/my-app/ permanent;
# Backend
location ~ ^/my-app/api/?(.*)$ {
# Use this for Docker...
# resolver 127.0.0.11 ipv6=off valid=1s;
# ... or use this for OpenShift
# resolver dns-default.openshift-dns.svc.cluster.local ipv6=on valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_header Content-Type;
proxy_pass http://backend.<PROJECT>.svc.cluster.local:8000/api/$is_args$args;
}
# Frontend
location ~ ^/my-app/?(.*)$ {
# Use this for Docker...
# resolver 127.0.0.11 ipv6=off valid=1s;
# ... or use this for OpenShift
# resolver dns-default.openshift-dns.svc.cluster.local ipv6=on valid=1s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_header Content-Type;
proxy_pass http://frontend.<PROJECT>.svc.cluster.local:8080/$is_args$args;
}
}