Nginx plus 的 UDP 反向代理主动健康检测

万能的 Nginx 在今年支持了 UDP 的负载均衡,依旧是由于某些原因我打算试一试这个 UDP 反代功能,实际上 Linux 平台能做 UDP 的代理的工具很少,Nginx 几乎是唯一的选择,然后它自带的主动健康检测和 dashboard 监控是 Nginx-plus 才有的(实际上自己用插件写的话应该也不是很困难,但是这个我确实不会写)Nginx-plus 的安装也很有意思,从官网下载一个 key 和一个 crt,然后添加到 apt,连接 nginx.com 的仓库的时候会进行双向的 TLS 认证(一般的 HTTPS 只有客户端验证服务器,这个模式更类似网上银行和支付宝的客户端证书)

Nginx的四层反代功能是由 ngx_stream_xxx_module 提供,使用一个 stream 区块代替 http 区块,其余配置和做 Web Server 的时候差不多,来看个例子,这个例子是 dnsmasq 监听53,nginx 从10086端口反代到53:

stream {

    upstream dns_server {
        zone stream_dns 10m;
        server 127.0.0.1:53;
    }

    match dns {
        send \x00\x2a\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06\x73\x65\x72\x76\x65\x72\x0a\x73\x74\x61\x72\x64\x75\x73\x74\x65\x72\x02\x6d\x65\x00\x00\x01\x00\x01;
        expect ~* \x6a;
    }


    server {
        listen 10086 udp;
        proxy_pass dns_server;
        error_log  /home/nginx/dns-error.log debug;
        health_check udp match=dns interval=1s;
        status_zone stream_dns;
   }
}

是不是很简单呢,本来就是嘛,Nginx stream module 尤其是 UDP 方面功能本来就很原始,可配置的参数啊文档啊都很少,如果不是 Nginx-plus 这里还有一半的参数是不可用的,反代这部分文档在http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html,有兴趣的可以自己去看。

关于他的主动健康检测,Nginx 官方博客(https://www.nginx.com/resources/admin-guide/tcp-load-balancing/)写的是:

match dns {
   send \x00\x2a\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03\x73\x74\x6c\x04\x75\x6d\x73\x6c\x03\x65\x64\x75\x00\x00\x01\x00\x01;
   expect ~* "health.is.good";
}

实际上原理就是 Nginx 假装自己是客户端隔几秒给后端发一个请求,返回结果符合预期结果就当他是正常的(TCP 可以有很多办法检测,比如尝试握手有响应就可以认为存活,UDP 是无连接的相对比较难确定到底问题出在哪里),然而这个写法是有问题的,这个实际上是查询 stl.umsl.edu(send 里的数据实际上就是十六进制的 DNS 请求的 payload),然而这玩意不知道是时间久远还是 UMSL 改了什么 DNS 设置,总之现在这么写健康检测并不会 pass 的。

实际上我想了很久一段时间这个健康检测到底是怎么查看结果的,如果要看日志的话是不是太不优雅了一点···后来才看到https://nginx.org/en/docs/http/ngx_http_status_module.html#status 这么个玩意是有监控面板的

QQ20160813-0@2x

在右上角那个 upstream 里就能看到健康检测进行的次数、失败成功的次数等等,如果我们使用官网那个样例的话,就会一直提示 failed,让我们来看看这里出了什么问题,

tcpdump -i lo udp -vnnX

 查看 lo 网卡接口上的 UDP 包(其中 -v 是查看详细参数 -n 是以 IP 显示 -X 是同时输出 HEX dump 和 ASCI 解码后的内容),发现 dnsmasq 返回的结果是正常的,那么八成就是 expect 的内容出现了偏差。

我们来看看 DNS 长啥样,这个玩意是 RFC 里抄的:

                                    1  1  1  1  1  1
      0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                                               |
    /                                               /
    /                      NAME                     /
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TYPE                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                     CLASS                     |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                      TTL                      |
    |                                               |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
    |                   RDLENGTH                    |
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
    /                     RDATA                     /
    /                                               /
    +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

看不懂是吧,我其实也看不懂,其实要自己手写一个 DNS 的 payload 还是很麻烦的,于是我决定打开 wireshark (tcpdump 其实是可以配合 wireshark 远程抓包的,但是由于防火墙比较麻烦我没有配)抓个包看看:

QQ20160813-2@2x

好的清楚多了,wireshark 还可以对应各个分段标识出十六进制或者二进制表示,文字部分是 ASCI 编码。注意从 wireshark 复制出来的 HEX dump 是空格分开的,

sed s/' '/'\\x'/g

 可以替换成 Nginx 需要的格式。在 wireshark 里把从 transcation ID 到 Class IN (0x0001) 对应的二进制抓出来丢进 nginx 配置文件的 send 区块,再找到对应的 DNS 响应,找一个合适的部分丢进 expect 区块,reload nginx 然后盯着 dashboard 和 tcpdump 看有没有成功。

QQ20160813-3@2x

后来大部分响应是 pass 的,还是有时候会出现失败,持续抓包之后发现 dnsmasq 发送出去的 payload 会变,有一个 flag 改变之后变成了递归查询,请求变成递归的之后就会一直追到权威 DNS 那边去,响应就会变得很庞大,Nginx 也抓不到位置在很后面的我设置的那个响应字段,自然就会被当做失败。这个现象比较有规律,基本十分钟出现一次(实际上就是我域名的 TTL 时间),但是我现在还想不通这里为什么会变,nginx 的 send 区块里已经包括了这个标志位,为什么 dnsmasq 向外查询的时候就会变,感觉上还是和 dnsmasq 本身的设置有关,TTL 过期之后没有本地缓存内容(实际上我在服务端执行一次 nslookup 之后,所有的请求就会立刻正常,不管他的话,过一段时间也会正常,实在是没有心思跟着抓半天的包)。workaround 也有,就是分析一下这两个响应有什么一样的部分取出来做 expect 内容。QQ20160812-3@2x


UPDATE 2017.7.27

实际上这个问题是当时的我不理解 DNS 查询的机制导致的,当时的场景是我在本地跑了一个 dnsmasq,查询发往127.0.0.1:53,而 dnsmasq 是一个缓存 DNS 服务器(或者说是递归服务器),在一次查询之后会把解析结果在本地缓存 TTL 秒,经过这个时间之后 dnsmasq 认为缓存失效,就会向权威查询(进行一次迭代查询)


本文链接:https://www.starduster.me/2016/08/13/nginx-plus-udp-proxy-active-health-check/
本站基于 Creactive Commons BY-NC-SA 4.0 License 允许并欢迎您在注明来源和非商业使用前提下自由地对本文进行复制、分享或基于本文进行创作。
请注意:受限于笔者水平,本站内容可能存在主观臆断或事实错误,文中信息也可能因时间推移而不再准确,在此提醒读者结合自身判断谨慎地采纳。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据