Docker中的NGINX缓存IP地址并提供错误的内容类型



摘要:
我想让NGINX(不是NGINX Plus)通过使用proxy_pass中的变量从DNS名称重新解析IP地址(如这篇官方Nginx文章"在变量中设置域名"一节中所建议的那样)。但是当我这样做时,它不会设置/转发正确的Content-Type标头,而是始终使用text/html, 用于.css.js等文件。

我的设置:
我有一个前端和一个后端服务在单独的Docker容器中运行(将部署到生产中的OpenShift)。还有第三个容器运行nginx:latest(截至今天的 v1.19.9),充当反向代理,将/my-app调用转发到前端,/my-app/api转发到后端容器。NGINX反向代理已使用其DNS名称将它们设置为上游服务器。 所有三个容器都在同一个自定义Docker网络中运行,因此解析本身工作正常 - 但仅在NGINX重新启动时。

问题:
当前端或后端容器重新启动时,它可能会获得新的 IP 地址。由于NGINX缓存IP地址,因此当我调用它们时,我会收到502。我希望NGINX更频繁地重新解析IP地址,就像NGINX Plus一样。这就是Content-Type问题出现的方式,当我试图让NGINX重新解析DNS名称时。

配置:
这是我的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上运行,当我使用上述尝试#1尝试#2打开 http://localhost:9000/my-app 时,.css.js,图像文件等都与Content-Type: text/html一起提供,这会阻止浏览器正确呈现页面,并在调试器窗格中显示如下消息:

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;会解决这个问题,但这也无济于事。

然后我还从这个 NGINX bug 票证中了解到"在 proxy_pass 中使用变量时,如果指定了 URI,它会按原样传递给服务器,替换原始请求 URI。这似乎是我在这里遇到的Content-Type问题的可能原因。

另外,正如您在配置中看到的那样,我已经设置了 Docker DNS IP:resolver 127.0.0.11 ipv6=off valid=1s;.我也尝试将其设置在location块之外,但也没有帮助。

问题:
那么如何在NGINX(开源)中重新解析DNS并仍然设置正确的Content-Type不必使用上游服务器,因此我可以摆脱它们,如果这是可能修复的一部分。

P.S.:
我不能像其他 StackOverflow 评论中所建议的那样,添加单独的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。正如您已经指出的:

在proxy_pass中使用变量时,如果指定了 URI,则会按原样将其传递到服务器,替换原始请求 URI。

您当前具有以下工作配置,但希望将其替换为变量以强制 DNS 解析:

location /my-app {
proxy_pass http://frontend:8080/;
}

最简单的解决方案是使用rewrite...break对 URI 进行更改,并从变量中删除尾随/

例如:

location /my-app {
rewrite ^/my-app/?(.*)$ /$1 break;
set $frontend_var "frontend:8080";
proxy_pass http://$frontend_var;
}

或者,使用正则表达式location捕获原始请求的其余部分。

例如:

location ~ ^/my-app/?(.*)$ {
proxy_pass http://frontend:8080/$1;
}

这是我的最终工作设置。再次感谢,史密斯@richard!

请注意...:

  • 我没有使用upstream块,因为我正在将其部署到 OpenShift。Pod(~=服务器或容器)前面有一个"服务"的概念,这基本上是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/$1$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/$1$is_args$args;
}
}

相关内容

最新更新