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 地址发生变化。)
可能的解决方案,选择一个:
最好的解决方案似乎只是摆脱在数据中心环境中可能不必要的upstream
keepalive
,并使用带有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/f9be21fb8df9e4d5768bb5f484567175lua
+ 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] 主机