nginx实现动态resolve的思路

您所在的位置:网站首页 暮去朝来颜色故打一动物 nginx实现动态resolve的思路

nginx实现动态resolve的思路

2024-07-10 13:52| 来源: 网络整理| 查看: 265

1.摘要

在nginx用作负载均衡的时候,通常配置的源站都是一个或若干个IP+端口号,但在有些需求的情况下,我们需要回源到一个域名instead of一组ip,比如有如下的需求:

一个域名对应的IP列表是变化的 nginx部署在不同的运营商后面,解析出来的IP可能是不一样的

一个简单的upstream可能如下

upstream server_group { server mysite.com; }

我们想支持前面提到的需求,怎么办呢?在nginx的商业版中,配置是这样的:

upstream server_group{ server mysite.com resolve; }

这样就可以动态的解析mysite.com了。我没有商业版的代码,表面上能满足我们动态解析的需求,但在有些情况下,上面的配置就显得捉襟见肘了。

我们可以先理一下,当upstream的server列表发生变化,哪些模块会受影响呢?这是和我们使用了哪些模块有关系的,在我的场景下,有如下模块收到了影响

负载均衡模块 健康检查模块

负载均衡模块需要感知变化,是因为需要在这些server负载均衡,多了少了server那得知道健康检查模块也一样的,upstream的server列表指定了需要对哪些server做检查

因为通常来讲,这两个模块是需要感知upstream成员变化的。那么我们到底该如何改动源站域名的动态解析呢?

2.实现思路

我们可以先理一下,nginx现状是什么样呢?nginx本身是支持配置域名回源的,正如前面的配置。但这个配置是一次性判定的,加载配置的时候就定下来了,后期无法改变。如果你的源站一直没有变化,其实这样的配置也是足够的。

但我们关注的是源站可以变化的这种case,要么随着时间变化,要么随着部署环境变化(部署在移动/联通/电信的IDC里)。

2.1.upstream模块的实现

在做一切之前,我们需要了解upstream模块是怎么实现的,以及upstream是如何和proxy_pass协作的,一个简单的协作配置可以为如下

upstream server_group { server mysite.com; } server { listen 80; server_name abonege.com; location / { proxy_pass http://server_group/; }

然后proxy模块会去找名字为server_group的upstream去回源,然后从server_group里指定的server列表里选择一个回源。但我们需要注意到,把proxy_pass后面的东西换成合法的域名也可以work的。

upstream server_group { server mysite.com; } server { listen 80; server_name abonege.com; location / { proxy_pass http://www.baidu.com/; }

那么问题来了,nginx是如何知道这是一个合法的域名还是应该从upstream里找呢?看了一下代码思路是这样的:首先,解析到proxy_pass的时候,不管是域名还是upstream的名字,直接当成upstream,放到upstream模块的upstream数组里,

u.url.len = url->len - add; u.url.data = url->data + add; u.default_port = port; u.uri_part = 1; u.no_resolve = 1; // 不用resolve plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);

当然,parse一个upstream block的时候,也会调用相同的函数,来添加一个upstream

u.host = value[1]; u.no_resolve = 1; //不用resolve u.no_port = 1; uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_WEIGHT |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN |NGX_HTTP_UPSTREAM_BACKUP);

在ngx_http_upstream_add里有一些合并的逻辑,因为有可能先解析upstream{}这样的block,也可能先解析到proxy_pass里的upstream名字(这种情况只建了一个upstream的空壳),总之能merge起来。那么回到之前的话题,我们如何区分proxy_pass里指定的是域名还是upstream的名字呢?其实这发生在upstream初始化的时候

(gdb) bt #0 ngx_inet_resolve_host (pool=0x6c0130, u=u@entry=0x7fffffffd9d0) at src/core/ngx_inet.c:1097 #1 0x000000000044aacf in ngx_http_upstream_init_round_robin (cf=0x7fffffffe0a0, us=0x6dcbc0) at src/http/ngx_http_upstream_round_robin.c:189 #2 0x0000000000444494 in ngx_http_upstream_init_main_conf (cf=0x7fffffffe0a0, conf=0x6c2bf0) at src/http/ngx_http_upstream.c:6103 #3 0x000000000042e99f in ngx_http_block (cf=0x7fffffffe0a0, cmd=, conf=) at src/http/ngx_http.c:262 #4 0x000000000041c914 in ngx_conf_handler (last=1, cf=0x7fffffffe0a0) at src/core/ngx_conf_file.c:427 #5 ngx_conf_parse (cf=cf@entry=0x7fffffffe0a0, filename=filename@entry=0x6c0348) at src/core/ngx_conf_file.c:283 #6 0x000000000041a38a in ngx_init_cycle (old_cycle=old_cycle@entry=0x7fffffffe150) at src/core/ngx_cycle.c:274 #7 0x000000000040bcce in main (argc=, argv=) at src/core/nginx.c:276

ngx_inet_resolve_host就是我们要找的地方了。如果ngx_http_upstream_srv_conf_t里挂了server列表(例如upstream server_group),则走一个分支,如果是没有挂server列表的则有可能是域名回源,然后对其进行域名解析,如果这种情况还是解析不了的,说明是瞎编的一个名字,既不是域名也不是upstream名字,则在启动的时候会报错,说host not found之类的。总结一下:先看这个名字是不是upstream的名字,如果不是则尝试当成一个域名解析,成功则以,不成功则报错。

好啦,我们继续下一个问题,前面提到的mysite.com是什么时候解析的呢?因为我们想实现动态解析,必须知道当前是怎么解析的。继续看ngx_http_upstream_init_round_robin悲喜交加的是,你会发现,不知道什么时候,mysite.com已经被解析好躺在server的怀抱里了

for (i = 0; i servers->nelts; i++) { if (server[i].backup) { continue; } for (j = 0; j

那么,server[i].addrs到底是啥时候解析出来的呢?在加载配置文件的时候

gdb) bt #0 ngx_inet_resolve_host (pool=pool@entry=0x6c0130, u=u@entry=0x7fffffffd550) at src/core/ngx_inet.c:1120 #1 0x0000000000413572 in ngx_parse_inet_url (u=0x7fffffffd550, pool=0x6c0130) at src/core/ngx_inet.c:787 #2 ngx_parse_url (pool=0x6c0130, u=u@entry=0x7fffffffd550) at src/core/ngx_inet.c:545 #3 0x0000000000444302 in ngx_http_upstream_server (cf=0x7fffffffe0a0, cmd=, conf=0x6d26b0) at src/http/ngx_http_upstream.c:5631 #4 0x000000000041c914 in ngx_conf_handler (last=0, cf=0x7fffffffe0a0) at src/core/ngx_conf_file.c:427

所以,综上,我们认为在加载完毕配置文件的时候,想要的东西都好了。比如proxy和upstream的关联,upstream内部server名字的解析。但这一切都发生在解析文件的时候,也就是一锤子打死的配置,不能变化,但我们要的就是变化,该当如何呢?

2.2.我们的方案

我们注意到,在ngx_http_upstream_init_request里居然还有

ngx_resolve_start... ngx_http_upstream_resolve_handler...

这个逻辑是干啥用的么,看起来很像运行太解析的,对不对?难道nginx自动支持动态DNS解析么,想多了。那个商业版才有的。是这样的,因为proxy_pass传递的参数里,是可以以变量形式配置的

proxy_pass http://$dest_hostname/;

如果$dst_hostname是动态解析出来的,比如针对a请求$dest_hostname翻译成a.com,针对b请求翻译成b.com,辣么这个回源的域名就是动态变化的,就需要我们动态解析。

有没有发现,已经在一定程度上解决我们的问题了。但前提是,我们不是用upstream回源,而是直接采用域名回源(nginx变量的形式)。这样就能动态的翻译源站域名到不同的IP了。解决了我们的一部分问题,但还有别的问题,需要我们搞定,比如回话保持,健康检查都是配置在upstream里的。如果我们不用upstream回源,相当于这部分功能无法使用了。那么应该怎么搞呢?

举tengine的dyups模块为例,他的工作原理大致如下,当一个nginx worker进程收到一个创建upstream的API后,会验证upstream的配置语法,然后通过share memory传递给其他的worker。然后各个worker会把这个upstream同步给自己的内存,注意,这时候会调用健康检查的添加peer的逻辑,以及对应算法模块的初始化(rr,lc,haship等算法的初始化逻辑是不一样的)

所以我们为了支持动态解析,动手的逻辑应该是这里。

持久化dyups的原始配置信息,虽然每个worker进程知道upstream信息,但是从upstream信息反推配置是很困难的。因为每次域名解析的内容变动后,我们需要重新构造dyups的upstream配置block 周期的检查dyups的所有upstream配置,然后进行dns解析,这里nginx已经提供了很友好的函数。

ngx_resolve_start… ngx_http_upstream_resolve_handler… 如果发现dyups的server域名已经发生变化,则走dyups update流程,即删除旧的upstream,创建新的upstream

其实说句实话,选一个worker是不好选择的,我的建议是有一个专门的controller进程,用于做这件事。

以上。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3