Nginx 不会以在上游找不到的主机启动

Posted

技术标签:

【中文标题】Nginx 不会以在上游找不到的主机启动【英文标题】:Nginx will not start with host not found in upstream 【发布时间】:2018-10-19 06:51:10 【问题描述】:

我使用 nginx 为我代理并保持与遥远服务器的持久连接。

我已经配置了大约 15 个类似于这个例子的块:

upstream rinu-test 
    server test.rinu.test:443;
    keepalive 20;

server 
    listen 80;
    server_name test.rinu.test;
    location / 
        proxy_pass https://rinu-test;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $http_host;
    

问题是如果主机名不能在一个或多个upstream 块中解析,nginx 将不会(重新)启动。我也不能使用静态 IP,其中一些主机明确表示不要这样做,因为 IP 会改变。我看到的关于此错误消息的所有其他解决方案都表示要摆脱 upstream 并在 location 块中执行所有操作。这是不可能的,因为keepalive 只能在upstream 下使用。

我暂时可以承受失去一台服务器,但不是全部 15 台。

编辑: 原来 nginx 不适合这个用例。应使用替代后端(上游)保活代理。 my answer 中有一个自定义的 Node.js 替代方案。到目前为止,我还没有找到任何其他实际可行的替代方案。

【问题讨论】:

您可以尝试两件事。将proxy_pass https://rinu-test; 更改为proxy_pass $proxyurl;,在此之前您可以设置变量set $proxyurl $scheme://$host$request_uri 接下来是尝试在上游使用变量,我尚未测试第二个选项并且无法验证。但是在proxy_pass 中使用变量会禁用 nginx 中的 dns 缓存 没有上游的代理是没有意义的。变量不能在上游使用。 我的意思是你可以试试proxy_pass https://rinu-test$request_uri; 为此探索 HAProxy 而不是 nginx 怎么样?如果您使用上游,则默认情况下无法处理此用例 我尝试了 HAProxy,但没有成功。它做了代理,但没有保持连接打开或未能重用它们。 【参考方案1】:

早期版本的 nginx(1.1.4 之前)已经为全球大量访问量最大的网站提供了支持(如果服务器标头可信的话,有些网站甚至在今天仍然如此),甚至不支持 @ 987654321@ 在upstream 方面,因为在数据中心设置中这样做几乎没有什么好处,除非您的各个主机之间存在不小的延迟;一些解释见https://serverfault.com/a/883019/110020。

基本上,除非您知道您在上游和前端之间特别需要保持连接,否则它可能只会让您的架构变得不那么有弹性并且变得更糟。

(请注意,您当前的解决方案也是错误的,因为 IP 地址的更改同样不会被检测到,因为您仅在配置重新加载时进行主机名解析;因此,即使 nginx 确实启动了,它也基本上会停止工作一旦上游服务器的 IP 地址发生变化。)

可能的解决方案,选择一个:

最好的解决方案似乎只是摆脱在数据中心环境中可能不必要的upstreamkeepalive,并使用带有proxy_pass 的变量为每个请求(nginx仍然足够聪明,仍然可以缓存此类分辨率)

另一种选择是通过商业订阅获得付费版本的 nginx,该订阅在 upstream 上下文中具有 server 指令的 resolve 参数。

最后,要尝试的另一件事可能是使用set variable 和/或map 来指定upstream 中的服务器;既不确认也不否认已实施;例如,它可能有效,也可能无效。

【讨论】:

我知道我在做什么。 Keepalive 向外部服务提供商发出请求的速度提高了整整 1 秒,因此在该应用程序中节省时间至关重要。 不能使用变量来定义上游的服务器。 付费 nginx 太贵了。重写需要这个代理的应用程序更便宜。这已经在进行中,但我估计还需要 3-4 年才能完成。 全球约有100家独立服务提供商,主要分布在美国和欧洲。有几个是并行查询的,最多我见过大约 20 个。在一个服务提供商的示例中,平均响应时间从 1.3 秒下降到 0.5 秒。最低增益为 150 毫秒。到目前为止,keepalive 代理的速度还没有变慢。该应用程序使用 php 编写,可以并行发出请求,但由于其生命周期短,无法保持连接打开。 @rnu,我不明白如何通过 keepalive 一次重用连接来节省 1.3 秒的时间,看来您可能正在衡量的不是 keepalive 节省的费用。另外,我对这里的用例有点困惑——你基本上是在尝试通过 PHP 查询 100 个独立的提供者,并使用 nginx 作为连接缓存?那么你的问题对于你想要完成的事情的表述非常糟糕,因为还有许多其他更好的方法来做 keepalive,甚至可能使用 nginx 流——你通过不完整的规范不公平地限制了解决方案的范围。跨度> 【参考方案2】:

一种可能的解决方案是使用本地 DNS 缓存。它可以是 Bind 或 Dnsmasq 之类的本地 DNS 服务器(有一些巧妙的配置,注意 nginx 也可以使用 specified dns server 代替系统默认值),或者只是在 hosts 文件中维护缓存。

似乎使用带有一些脚本的hosts 文件是非常简单的方法。 hosts 文件应该分为静态和动态部分(即cat hosts.static hosts.dynamic > hosts),动态部分应该由脚本自动生成(和更新)。

也许不时检查主机名以更改 IP,并在更改时更新主机文件并在 nginx 中重新加载配置是有意义的。如果某些主机名无法解析,则应使用旧 IP 或某些默认 IP(如 127.0.1.9)。

如果您不需要 nginx 配置文件中的主机名(即,IP 就足够了),可以通过脚本生成带有 IP(已解析的主机名)的 upstream 部分,并将 included 生成到 nginx 配置中 - 和在这种情况下无需触摸 hosts 文件。

【讨论】:

我们已经有了 DNS 缓存的想法,很快就会进行调查。我的系统管理员对生成的hosts 文件大笑。猜它不会发生。但无论如何我喜欢你的回答,到目前为止,这是唯一一个真正有效的答案。 @rnu,您能否提出一些建设性的论点,使用主机文件有什么问题? 没有错,这就是我投票给你的原因。在所有服务器中管理这个工作量太大了。 nginx 的替代品或更好的通用 DNS 服务更易于管理。【参考方案3】:

您的场景与使用 aws ELB 作为上行流的场景非常相似,其中 resolve 定义域的正确 IP 至关重要。

您需要做的第一件事是确保您使用的 DNS 服务器可以解析到您的域,然后您可以像这样创建配置:

resolver 10.0.0.2 valid=300s;
resolver_timeout 10s;

location /foo 
    set $foo_backend_servers foo_backends.example.com;
    proxy_pass http://$foo_backend_servers;
 

location /bar 
    set $bar_backend_servers bar_backends.example.com;
    proxy_pass http://$bar_backend_servers;
 

注意resolver 10.0.0.2 它应该是运行并回答您的查询的 DNS 服务器的 IP,根据您的设置,这可能是一个本地缓存服务,例如 unbound。然后就用resolve 127.0.0.1

现在,使用 变量 来指定域名非常重要,来自文档:

当您在 proxy_pass 指令中使用变量指定域名时,NGINX 会在其 TTL 到期时重新解析该域名。

您可以使用dig 等工具检查您的解析器,例如:

$ dig +short ***.com

如果必须在上游使用keepalive,并且如果不是使用Nginx +的选项,那么你可以尝试openresty balancer,你需要使用/实现lua-resty-dns

【讨论】:

您能否详细介绍一下 openresty、lua 以及如何在此处使用它们?我以前没有听说过这些,从文档中看如何安装这些或如何应用于我的用例并不明显。 嗨@rnu,我发现这个要点可能会给你更多的想法:gist.github.com/toritori0318/f9be21fb8df9e4d5768bb5f484567175 lua + nginx 效果很好,你也可以扩展和实现 WAF 等,并涵盖许多标准 Nginx 上缺少的东西【参考方案4】:

我将resolve参数放在服务器上,你需要在nginx.conf中设置Nginx Resolver如下:

/etc/nginx/nginx.conf:

http 
    resolver 192.168.0.2 ipv6=off valid=40s;  # The DNS IP server
 

网站配置:

upstream rinu-test 
    server test.rinu.test:443;
    keepalive 20;

【讨论】:

我认为这适用于 Nginx+,但如果你设置 location /foo set $foo_backend_servers foo_backends.example.com; proxy_pass http://$foo_backend_servers; 将工作。如果不能解决,请发布您的 conf 和日志 nginx.org/r/resolver 是一个标准指令,它根本不限于 nginx plus;上游上下文中的 resolve 关键字是 Plus 唯一的;但是,这个问题完全忽略了 OP 特别提到他们需要的 keepalive 问题。 你是对的,解析器是打开的,并且服务器命令的解析参数是一个即时重新配置选项。如果您使用该变量,您将失去 keepalive,如果您使用上游,您将失去仅在启动时间检查的 DNS 检查。 这似乎无法解决原始问题 - 设置全局解析器选项无济于事:如果无法解析上游块中的主机,nginx 仍然不会启动/重新启动它。跨度> 必须始终解析名称,但已解析提供了使 DNS 保持最新而不丢失上游配置的选项。许多用户尝试直接在 proxy_pass 中使用 dns,但当任何延迟影响 DNS 解析时失败,除了删除上游中的可选确认,如 keepalive 和不同的 LB 算法【参考方案5】:

另一种方法是编写一个只做我想做的新服务。下面替换 nginx 使用 Node.js 代理 https 连接

const http = require('http');
const https = require('https');

const httpsKeepAliveAgent = new https.Agent( keepAlive: true );

http.createServer(onRequest).listen(3000);

function onRequest(client_req, client_res) 
    https.pipe(
        protocol.request(
            host: client_req.headers.host,
            port: 443,
            path: client_req.url,
            method: client_req.method,
            headers: client_req.headers,
            agent: httpsKeepAliveAgent
        , (res) => 
            res.pipe(client_res);
        ).on('error', (e) => 
            client_res.end();
        )
    );

示例用法: curl http://localhost:3000/request_uri -H "Host: test.rinu.test" 这相当于: curl https://test.rinu.test/request_uri

【讨论】:

此解决方案超出了问题范围,因为问题仅与 nginx 有关(即如何解决 nginx 的问题)。需要重新制定问题以使其符合此解决方案:) 在当前的形式中,问题没有答案。我要问的根本不可能。我也同意这个答案也不例外。但最终这就是我解决问题的方法,这就是我将其包含在此处的原因。【参考方案6】:

我的问题与容器有关。我正在使用 docker compose 创建 nginx 容器以及应用程序容器。在docker-compose.yml 的应用容器配置中设置network_mode: host 时,nginx 无法找到上游应用容器。删除它可以解决问题。

【讨论】:

【参考方案7】:

我们可以暂时解决

cd /etc
sudo vim resolv.conf
i
nameserver 8.8.8.8 
:wq

然后做sudo nginx -t 重新启动 nginx,它会暂时起作用

【讨论】:

以上是关于Nginx 不会以在上游找不到的主机启动的主要内容,如果未能解决你的问题,请参考以下文章

NGINX docker-compose - 在上游 nuxt:3000 中找不到主机

Docker 网络 - nginx:在上游找不到 [emerg] 主机

Nginx 从 docker-compose 运行返回“在上游找不到主机”

Docker:proxy_pass 到另一个容器 - nginx:在上游找不到主机

Docker compose 错误:nginx:在 /etc/nginx/conf.d/default.conf:21 的上游“app”中找不到 [emerg] 主机

Docker [emerg] 1#1:单独的撰写文件后在上游找不到主机