NGINX Plus R9引入了反向代理和负载平衡 UDP 流量的功能,这显著增强了 NGINX Plus 的第 4 层负载平衡功能。
这篇博文探讨了在现代应用基础设施中运行 DNS 服务器所面临的挑战,以说明 NGINX Open Source 和 NGINX Plus 如何有效且高效地对 UDP 和 TCP 流量进行负载平衡。 (应用[主动]健康检查是 NGINX Plus 独有的,但除此之外,本博客中的信息同样适用于 NGINX 开源;为简洁起见,我们将在其余文章中引用 NGINX Plus)。
[编辑 – 有关 NGINX Plus R9 中所有新功能的概述,请参阅我们博客上的“宣布 NGINX Plus R9” 。]
与 TCP 不同,UDP 在设计上并不保证数据的端到端传输。 这类似于通过信鸽发送信息——你肯定知道信息已发送,但不能确定它是否到达。 这种“无连接”方法有几个好处——最显著的是,它比 TCP 具有更低的延迟,因为 UDP 的单个消息较小,占用的带宽较少,而且建立连接时不需要握手过程。 UDP 将检测超时和其他网络级问题留给应用开发人员。 但这对 DNS 意味着什么?
与其他几种基于 UDP 的协议一样,DNS 使用请求响应数据流。 例如,DNS 客户端询问与主机名对应的 IP 地址并收到答复。 如果在给定的超时期限内未收到响应,DNS 客户端会将相同的请求发送到“备份”DNS 服务器。 但是,在重试请求之前必须等待超时时间,这会使通常极快的过程(以毫秒为单位)变得非常缓慢(以秒为单位)。
使用 NGINX Plus代理和负载平衡DNS 流量可减少客户端遇到超时的情况。 由于 NGINX Plus 负载均衡器后面有多个 DNS 服务器,因此只有当客户端与 NGINX Plus 之间出现网络分区时,客户端才会遇到超时。 当 NGINX Plus 使用应用健康检查时,客户端不会遇到 DNS 服务器本身的任何问题。 通过监控每个服务器的可用性和响应时间,NGINX Plus 避免将客户端请求发送到不健康的服务器。
虽然绝大多数 DNS 流量都是通过 UDP 传输的,但也有一些常见的 DNS 操作使用 TCP。 DNS 使用 UDP 来传输小消息(最多 512 字节),但使用 TCP 来传输需要(或可能需要)较大消息的操作。 从历史上看,TCP 仅与 DNS 一起用于从权威的主名称服务器到其辅助名称服务器的区域传输。 然而,随着向容器和不可变基础设施的转变,DNS 越来越多地通过使用SRV
记录作为主要的服务发现机制。
DNS SRV
记录最初是为使用SIP来发现其服务器的 IP 语音 (VoIP) 手机引入的,但可以用于任何类型的服务。 但是, SRV
记录比大多数其他 DNS 记录类型包含更多的信息。 因此,标准 512 字节 UDP 响应中仅适合大约 10 条SRV
记录,而A
记录则适合大约 30 条。 当 DNS 响应超过 512 字节限制时,将返回前 512 个字节,但响应会被标记为“截断”。 此时,DNS 客户端可以尽力处理截断的响应,或者使用 TCP 重试相同的请求。
这意味着在现代网络基础设施中对 DNS 服务器进行负载均衡时,NGINX Plus 可以接收 UDP 和 TCP 流量的混合。
下图显示了具有两个负载均衡器的微服务环境的简化视图。 前端负载均衡器代理来自应用公共客户端的请求,选择最佳的微服务实例并执行许多其他我们在此不讨论的功能。 我们将集中讨论 DNS 负载均衡器,它位于微服务环境与向微服务提供服务发现信息的 DNS 服务器之间。
NGINX Plus 在Stream模块中实现了第 4 层负载均衡,因此在stream
块中配置了 UDP 和 TCP 负载均衡,如下面的代码片段所示。
警告: 您不能简单地将此配置片段作为新文件添加到/etc/nginx/conf.d目录中。 导致验证错误(“此处不允许使用 stream 指令”),因为默认的 NGINX Plus nginx.conf配置文件在http
块中包含了conf.d目录中文件的内容。 最简单的解决方案是直接在nginx.conf中包含完整的流
块。
流 { 上游 dns_servers {
服务器 192.168.136.130:53;
服务器 192.168.136.131:53;
}
服务器 {
listen 53 udp;
listen 53; #tcp
proxy_pass dns_servers;
error_log /var/log/nginx/dns.log info;
}
}
首先我们定义上游 DNS 服务器组。 服务器
指令指定我们的上游服务器正在监听的端口号 53(DNS 的知名端口)。
server{}
块定义 NGINX Plus 如何处理传入的 DNS 流量。 两个listen
指令告诉 NGINX Plus 在端口 53 上监听 UDP 和 TCP 流量。 TCP 是 Stream 模块的默认第 4 层协议,因此我们不会像 UDP 那样明确将其指定为参数。
proxy_pass
指令告诉 NGINX Plus 如何处理它正在监听的流量。 在这里我们将此类流量代理到dns_servers上游组。 NGINX Plus 在将客户端 UDP 请求转发到上游服务器时会自动使用 UDP(对于客户端 TCP 请求则会自动使用 TCP),因此我们不需要在上游组中明确指定第 4 层协议。
Stream 模块中没有access_log
指令,因为 NGINX Plus 不检查 TCP 段或 UDP 数据报的有效负载(就像它对 HTTP 数据包所做的那样)。 但是,我们可以使用error_log
指令上的info
参数来记录连接处理和代理事件。
[编辑 – 本博文发布后,NGINX Open Source 1.11.4 和NGINX Plus R11中的Stream 模块启用了访问日志记录。]
为了提高 DNS 服务器的可用性,我们可以添加更多指令并配置主动(应用)健康检查。
第一个附加指令是proxy_responses
,它说明 NGINX Plus 对每个代理 UDP 请求期望有多少个响应。 在我们的案例中,在收到单个响应后,NGINX Plus 立即停止等待进一步的响应,从而释放该会话使用的内存和套接字。
第二个附加指令proxy_timeout
确定 NGINX Plus 等待服务器响应的时间(这里我们将默认的 10 分钟减少到 1 秒)。 如果 NGINX Plus 在此期间内未收到任何响应,它会尝试上游组中的下一个服务器,并将无响应的上游服务器标记为在定义的时间内(默认为 10 秒)不可用,以便其他客户端在此期间不会遭受超时引起的延迟。
服务器 { 监听 53 udp; 监听 53; #tcp proxy_pass dns_servers; error_log /var/log/nginx/dns.log info; proxy_responses 1 ; proxy_timeout 1s ; }
我们还可以通过在上游组的服务器
指令中包含fail_timeout
选项来更改服务器标记为不可用的时间。 通过以下设置,NGINX Plus 将失败的上游服务器标记为 60 秒内不可用:
上游 dns_servers {服务器 192.168.136.130:53 fail_timeout=60s ;服务器 192.168.136.131:53 fail_timeout=60s ;}
这样,当我们的某个 DNS 服务器发生故障时,我们就可以控制客户端会遇到的延迟时间。 但是,如果尝试向失败的 DNS 服务器发送 TCP 请求,则 TCP 中固有的错误检查允许 NGINX Plus 自动将其标记为不可用,从而避免随后向该服务器发送 TCP 或 UDP 请求。
NGINX Plus 中的主动健康检查功能是实现任何负载平衡服务(包括 DNS)高可用性的附加且极其有价值的工具。 我们不是等待 DNS 客户端的实际 TCP 请求失败后再将 DNS 服务器标记为关闭,而是让 NGINX Plus 定期尝试在端口 53 上建立 TCP 连接,以确定 DNS 服务器是否启动并正常工作,方法是在server{}
块中包含health_check
指令及其port=53
参数。 (NGINX Plus 默认将健康检查发送到listen
指令指定的端口,在我们的例子中是 53。 因此,这里我们使用参数来明确配置默认值,但如果我们还修改了 DNS 服务器以响应其上的流量,我们可以指定不同的端口。)
使用 UDP,我们可以更进一步配置主动健康检查,对已知记录进行真正的 DNS 查找。 例如,我们可能会将以下CNAME
记录放置在与微服务环境内的服务发现所使用的同一子域的区域文件中。
healthcheck IN CNAME healthy.svcs.example.com。
鉴于 UDP 的轻量级特性,我们可以观察网络流量并轻松提取代表 DNS 查找的字节字符串。 然后我们创建一个匹配
配置块,将该字符串作为发送
指令的参数。 expect
指令指定服务器必须返回的响应才被视为健康。
匹配 dns_lookup {
发送 x00x01x00x00x00x01x00x00x00x00x00x00x06x68x65x61 ...;
期望 ~* "healthy.svcs.example.com";
}
这种深度应用级健康检查的好处是,即使您的名称服务器已启动并正在运行,对您的生产域执行真正的 DNS 查找也可以发现配置问题和数据损坏,否则可能会导致下游问题。
NGINX Plus 支持团队可以帮助准备 DNS 查找和其他协议的 UDP 健康检查。
以下代码片段重点介绍了主动健康检查所需的附加指令。
流 { 上游 dns_servers {区域 dns_mem 64k ; 服务器 192.168.136.130:53 fail_timeout=60s; 服务器 192.168.136.131:53 fail_timeout=60s; }匹配 dns_lookup { 发送 x00x01x00x00x00x01x00x00x00x00x00x00x06x68x65x61 ...; 预期 ~* “healthy.svcs.example.com。”; }服务器 { 监听 53 udp; 监听 53; #tcp health_check match=dns_lookup interval=20 failed=2passes=2 udp; health_check interval=20 failed=1passes=2port=53; #tcp proxy_pass dns_servers;错误日志 /var/log/nginx/dns.log 调试;代理响应 1;代理超时 1 秒;} }
zone
指令定义了一个名为dns_mem的共享内存区域,这使得所有 NGINX Plus 工作进程都可以使用健康检查的结果(和其他状态信息)。
上面已经讨论了match
指令。
health_check
指令有许多参数可以根据您的环境进行调整。 这里我们分别为 UDP 和 TCP 定义单独的健康检查。 由于 UDP 和 TCP 之间的差异,我们需要两次连续的 UDP 健康检查失败才会将 DNS 服务器标记为不健康,但只需要一次 TCP 失败。 对于这两种协议,我们都需要两次成功的响应才能将服务器再次标记为健康,以避免向不稳定的“不稳定”服务器发送请求。
为 UDP 和 TCP 流量定义单个上游 DNS 服务器组的优点在于,任一协议的健康检查失败都会将服务器标记为不健康并将其从负载平衡池中删除。
虽然仅部署两台后端服务器就可以成为有效的高可用性解决方案,但 NGINX Plus 的负载平衡功能使您能够在客户端不知情的情况下水平扩展后端服务器。
上面描述的示例微服务环境不太可能需要扩展后端 DNS 服务器。 然而,为所有用户提供 DNS 服务的 ISP 会经历持续的负载和可能出现的巨大峰值,因此需要大量的 DNS 服务器和前端代理来平衡它们之间的流量负载。
所有 NGINX 和 NGINX Plus负载平衡算法均适用于 TCP、UDP 以及 HTTP:
(您还可以配置所有算法的权重以进一步提高其效率。 有关讨论,请参阅我们博客上选择 NGINX Plus 负载平衡技术中有关权重的部分。)
尽管 HTTP 请求在后端服务器的负载和处理需求方面可能会有很大差异,但 DNS 请求通常都会产生相同的负载。 因此,最少连接和最少时间算法不太可能比循环算法更具优势。 特别是,最少连接数包括 NGINX Plus 仍在等待上游服务器响应的任何 UDP 请求的连接数。 只要proxy_responses
和proxy_timeout
的值未达到要求,NGINX Plus 仍会计算可能已经完成其工作的上游服务器的连接数。
如果您拥有大量客户端和一个进行大量“对话”的协议 - 客户端和服务器之间交换的多条消息,如在 RADIUS 质询响应流中 - 那么使用源 IP 哈希允许该对话与单个后端服务器进行。 换句话说,它建立了会话持久性,这意味着 NGINX Plus 将来自给定客户端的所有请求定向到同一台服务器。 以下示例为一对 RADIUS 身份验证服务器配置哈希负载平衡算法,以源(客户端)IP 地址(由$remote_addr
变量捕获)作为密钥。
上游 radius_servers { hash $remote_addr; # 源 IP 哈希
server 192.168.136.201:1812;
server 192.168.136.202:1812;
}
有关 UDP 和 TCP 负载均衡的更多信息,请查看以下资源:
上游
配置上下文要了解 NGINX Plus R9 中的其他强大功能,请参阅我们博客上的“宣布 NGINX Plus R9”并观看我们的点播网络研讨会“NGINX Plus R9 中的新功能” 。
“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”