设置 nginx 不要崩溃,如果主机在上游没有找到

我们在 Docker 的公共域下有几个 Rails 应用程序,我们使用 nginx 将请求定向到特定的应用程序。

our_dev_server.com/foo # proxies to foo app
our_dev_server.com/bar # proxies to bar

配置如下:

upstream foo {
server foo:3000;
}


upstream bar {
server bar:3000;
}


# and about 10 more...


server {
listen *:80 default_server;


server_name our_dev_server.com;


location /foo {
# this is specific to asset management in rails dev
rewrite ^/foo/assets(/.*)$ /assets/$1 break;
rewrite ^/foo(/.*)$ /foo/$1 break;
proxy_pass http://foo;
}


location /bar {
rewrite ^/bar/assets(/.*)$ /assets/$1 break;
rewrite ^/bar(/.*)$ /bar/$1 break;
proxy_pass http://bar;
}


# and about 10 more...
}

如果其中一个应用程序没有启动,那么 nginx 就会失败并停止:

host not found in upstream "bar:3000" in /etc/nginx/conf.d/nginx.conf:6

我们不需要他们全部上来,但 nginx 否则失败。 如何使 nginx 忽略失败的上游?

91535 次浏览
  1. 如果你可以使用一个静态 IP,然后只是使用,它会启动,只是返回 503的,如果它没有响应。

  2. 使用 resolver指令指向可以解析主机的内容,而不管它当前是否正在运行。

  3. 如果你不能做到以上的 (这将允许 Nginx 启动/运行),在 location水平上解决它:

     location /foo {
    resolver 127.0.0.1 valid=30s;
    # or some other DNS (your company's internal DNS server)
    #resolver 8.8.8.8 valid=30s;
    set $upstream_foo foo;
    proxy_pass http://$upstream_foo:80;
    }
    
    
    location /bar {
    resolver 127.0.0.1 valid=30s;
    # or some other DNS (your company's internal DNS server)
    #resolver 8.8.8.8 valid=30s;
    set $upstream_bar foo;
    proxy_pass http://$upstream_bar:80;
    }
    

使用 upstream的主要优点是定义 可以监听不同端口的一组服务器并配置负载平衡和故障转移 他们之间

在你的情况下,你是 每个上游只定义一个主服务器,因此 < em > 必须设置为 up

相反,使用 proxy_pass(es)的变量,并记住处理目标服务器关闭时可能出现的错误(404s、503s)。

使用变量的示例:

server {
listen 80;
set $target "http://target-host:3005";  # Here's the secret
location / { proxy_pass $target; }
}

您可以不使用 --link选项,而是可以使用端口映射并将 nginx 绑定到主机地址。

示例: 使用 -p 180:80选项运行第一个 Docker 容器,使用 -p 280:80选项运行第二个容器。

运行 nginx 并为代理设置以下地址:

proxy_pass http://192.168.1.20:180/; # first container
proxy_pass http://192.168.1.20:280/; # second container

对我来说,来自@Justin/@duskwuff 的选项3解决了这个问题,但我不得不将解析器 IP 改为 127.00.11(Docker 的 DNS 服务器) :

location /foo {
resolver 127.0.0.11 valid=30s;
set $upstream_foo foo;
proxy_pass http://$upstream_foo:80;
}


location /bar {
resolver 127.0.0.11 valid=30s;
set $upstream_bar bar;
proxy_pass http://$upstream_bar:80;
}

但是正如@Justin/@duskwuff 提到的,您可以使用任何其他外部 DNS 服务器。

我有同样的“主机未找到”问题,因为我的主机的一部分被映射使用 $uri而不是 $request_uri:

proxy_pass http://one-api-service.$kubernetes:8091/auth;

当请求更改为 auth 子请求时,$uri丢失了初始值。将映射改为使用 $request_uri而不是 $uri解决了我的问题:

map $request_uri $kubernetes {
# ...
}

另一个快速简单的修复某人的场景,我可以启动和停止没有我的主服务器爆炸了

    extra_hosts:
- "dockerhost:172.20.0.1" # <-- static ipv4 gateway of the network ip here thats the only sorta downside but it works for me, you can ifconfig inside a container with the network to find yours, kinda a noob answer but it helped me
networks:
- my_network
server {
listen 80;
server_name servername;


location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;


proxy_pass https://dockerhost:12345;


proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

基于 Justin 的回答,最快的方法就是用 IP 地址替换最终的主机。您需要使用 --ip 172.18.0.XXX参数为每个容器分配一个静态 IP 地址。NGINX 不会在启动时崩溃,如果主机不可用,它只会响应502错误。

使用静态 IP 运行容器:

docker run --ip 172.18.0.XXX something

Nginx 配置:

location /foo {
proxy_pass http://172.18.0.XXX:80;
}

参考 这个文章如何设置与 Docker 子网。

我们也遇到过类似的问题,我们通过动态地将 conf 文件包含在上游容器中来解决这个问题,这个容器是由一个侧车容器生成的,它对 docker.sock 上的事件有反应,并在上游配置中使用通配符包含这些文件:

 include /etc/upstream/container_*.conf;

如果列表为空,我们添加了一个永久关闭的服务器条目——因此有效的服务器列表不是空的。此服务器条目从未收到任何请求

 server 127.0.0.1:10082 down;

最后一个条目指向 nginx 中承载错误页面的(内部)服务器(例如503)

 server 127.0.0.1:10082 backup;

最终的上游配置如下:

upstream my-service {
include /etc/upstream/container_*.conf;
server 127.0.0.1:10082 down;
server 127.0.0.1:10082 backup;

}

在 nginx 配置中,我们添加了一个监听错误端口的服务器:

server {
listen 10082;


location / {
return 503;
add_header Content-Type text/plain;
}


error_page 503 @maintenance;
location @maintenance {
internal;
rewrite ^(.*)$ /503.html break;
root error_pages/;
}
}

如前所述,每个上游容器的配置文件由一个脚本(bash、 curl、 jq)生成,该脚本使用 curl 与 docker.socket 交互,其余的 API获取所需的信息(ip、 port) ,并使用此模板生成文件。

server ${ip}:${port} fail_timeout=5s max_fails=3;

Https://stackoverflow.com/a/32846603/11780117

我不能添加评论,所以我在这里添加。

如果您的原始反向代理是这样写的:

location ^~ /api {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://other-host:8000/api;
}

当用户访问 https://you-domain/api/test?query=name URL 时,后端服务器接收到的 PATH 是 /api/test?query=name,它正在工作。

location ^~ /api {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    

resolver 127.0.0.11 valid=30s;
set $backend other-host;
proxy_pass http://$backend:8000/api;
}

请注意,这里请求 https://you-domain/api/test?query=nameURL 时,后端服务器实际接收到的 PATH 是 /api会失去很多参数。

因此,当您使用变量时,正确的配置应该是:

location ^~ /api {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    

resolver 127.0.0.11 valid=30s;
set $backend other-host;
proxy_pass http://$backend:8000;
}

如果您想访问代理访问后端的根目录,那么您需要:

rewrite /api/(.*) /$1 break;
proxy_pass http://$backend:8000;

然后请求 https://you-domain/api/test?query=name,后端服务器实际接收的 PATH 是 /test?query=name