DNS+Anycast 均衡负载实战(IPV4)

大家好,又见面了,我是你们的朋友全栈君。

DNS+Anycast 均衡负载实战(IPV4)

我们都知道google的公共DNS为:8.8.8.8,甚至我们可以在全球任何地方都能ping通这个IP或者通过dig能解析域名,例如如下操作:

代码语言:javascript
复制
# dig www.baidu.com @8.8.8.8 A

; <<>> DiG 9.10.6 <<>> www.baidu.com @8.8.8.8 A
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53023
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.baidu.com. IN A

;; ANSWER SECTION:
www.baidu.com. 770 IN CNAME www.a.shifen.com.
www.a.shifen.com. 53 IN CNAME www.wshifen.com.
www.wshifen.com. 115 IN A 104.193.88.77
www.wshifen.com. 115 IN A 104.193.88.123

;; Query time: 293 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Mar 24 21:57:42 CST 2021
;; MSG SIZE rcvd: 127

通过强制使用8.8.8.8的dns服务器我们可以解析任何域名,这里就涉及到dns+anycast全局均衡负载的技术了。

anycast是什么

anycast准确的说是一种通信方式,其中文名叫:任播,最早在rfc1546中提出,只不过rfc1546只是一个概念,并没有实质性的实践,最终是在rfc3513才真正的提出任播地址的格式以及使用方式。但任播地址仅仅使用到了IPV6上,而对于IPV4并没有任播,IPV4只有组播以及单播。而今天讲的则是IPV4的任播方式,虽然没有按照rfc1513的格式实现,但是凭借IPV4这么多年的沉淀,早已利用各种骚操作使用任何场景,下面我们就来解开这IPV4的dns+anycast部署模式。

实战

dns+anycast拓扑图

效果

如上拓扑图,我们建立了一个IP为6.6.6.6内网DNS服务器群,所在网段为10.211.77.0/24,而我们要实现的是在PC端(网段10.211.66.0/24)能ping通6.6.6.6,同时执行命令dig www.baidu.com @6.6.6.6 A,能得到文章最开始的dig 8.8.8.8类似的返回结果。

准备工作

硬件准备

6台虚拟机均是centos7,网络模式均采用桥接的方式,其中虚拟机网关IP为:10.211.55.1
R1网卡配置:

代码语言:javascript
复制
eth0:
IPADDR=10.211.55.17
PREFIX=24
GATEWAY=10.211.55.1

eth1:
IPADDR=10.211.77.254
PREFIX=24
GATEWAY=10.211.55.1

R2网卡配置:

代码语言:javascript
复制
eth0:
IPADDR=10.211.55.18
PREFIX=24
GATEWAY=10.211.55.1

eth1:
IPADDR=10.211.66.254
PREFIX=24
GATEWAY=10.211.55.1

PC是直连R2路由器服务器,因此PC是不能上网的,并且PC的网关IP为R2的网卡eth1的IP(10.211.66.254),PC网卡配置如下:

代码语言:javascript
复制
eth0:
IPADDR=10.211.66.1
PREFIX=24
GATEWAY=10.211.66.254

DNS服务器组,三台DNS服务器均直连R1路由器,网关都是R1的eth1网卡IP(10.211.77.254),配置eth1网卡只是为了让三台DNS能够上网,这样才能出去递归解析域名,其中10.0.0.0/24是连接了Internet,其网关为10.0.0.2,故DNS群配置分别如下:

代码语言:javascript
复制
DNS1:
eth0:
IPADDR=10.211.77.1
PREFIX=24
GATEWAY=10.211.77.254
eth1:
IPADDR=10.0.0.208
PREFIX=24
GATEWAY=10.0.0.1
DNS2:
eth0:
IPADDR=10.211.77.2
PREFIX=24
GATEWAY=10.211.77.254
eth1:
IPADDR=10.0.0.209
PREFIX=24
GATEWAY=10.0.0.1
DNS3:
eth0:
IPADDR=10.211.77.3
PREFIX=24
GATEWAY=10.211.77.254
eth1:
IPADDR=10.0.0.210
PREFIX=24
GATEWAY=10.0.0.1
软件准备

配置之前我们先测试一下互通性,我们现在R1上ping 10.211.66.254(R2的eth1 IP),可以看到此时是不通的。

在这里插入图片描述
路由器R1、R2配置
代码语言:javascript
复制
软件只需要准备一款:quagga,这是一个动态路由软件,利用它我们可以在没有路由器的情况下,将linux服务器作为路由器,以达到路由的效果。quagga只是守护进程,真正使用到的服务是:zebra以及ospf,zebra是基础服务,用于静态IP以及路由管理等配置。ospf则是一种路由协议,全称为Open Shortest Path First,开放式最短路径优先协议,这是一个内网网关协议,用于单一自治系统内决策路由。通过ospf协议我们可以将同一路由域内的路由器选定为邻居,并且通过SPF算法计算出最短路由。
我们利用ospf的特性就将R1与R2建立其邻居关系,这样就可以互相学习对方路由表,这样R1就能学习到R2的10.211.66.0/24网段的所有地址,而R2也可以学习到R1内的10.211.77.0/24网段的所有IP地址。这样一来R2就能ping通10.211.77.0/24网段,例如R1的eth1 IP:10.211.77.254。至于quagga安装不是本文的重点。
R1的ospfd.conf配置如下:

password foobar
!
interface eth0
!
router ospf
	ospf router-id 10.211.55.17
	network 10.211.55.0/24 area 0.0.0.0
	network 10.211.77.0/24 area 0.0.0.2
	network 6.6.6.6/32 area 0.0.0.2
	network 10.0.0.0/24 area 0.0.0.2
!
line vty
!
log file /var/log/message/quagga/ospfd.log

然后执行:
systemctl start zebra
systemctl start ospfd

R2的ospfd.conf配置:

password foobar
!
interface eth0
!
router ospf
	ospf router-id 10.211.55.18
	network 10.211.55.0/24 area 0.0.0.0
	network 10.211.66.0/24 area 0.0.0.1
!
line vty
!
log file /var/log/message/quagga/ospfd.log

然后执行:

systemctl start zebra
systemctl start ospfd

观察节点状态,在R1上执行:vtysh,执行命令:show ip route 显示如下信息:

在这里插入图片描述

其中O表示ospf邻居网络,O>*则表示学习到的对方的动态路由,这里R1学习到了R2的10.211.66.0/24网段的子网。
再次输入show ip ospf neighbor,查看邻居信息:

在这里插入图片描述

此时显示了10.211.55.18,表示ospf邻居关系已经建立成功。 同理R2上输入show ip ospf neighbor也能看到如下信息:

在这里插入图片描述

我们看一下路由表,看是否学习到了对方的路由表 在R1上执行:show ip ospf database,显示信息如下:

在这里插入图片描述

在R2上也执行:show ip ospf database

在这里插入图片描述

我们可以看到在R1的路由表里已经出现了R2的10.211.66.0/24网段,同时在R2的路由表里也能看到R1的10.211.77.0/24网段。

测试互通性

我们在R1上ping 10.211.66.254(R2的eth1 IP),此时已经实现互通了。

在这里插入图片描述

既然我们已经能ping通10.211.66.254,那此时是否能ping通PC端呢? 在R1上执行ping 10.211.66.1,显示如下信息,表示成功对接。

在这里插入图片描述

而PC端的IP如下:

在这里插入图片描述

同理我们在PC端执行:ping 10.211.77.254,显示如下信息,完美互通!

在这里插入图片描述
接入DNS群组
代码语言:javascript
复制
DNS1上也需要启用ospf,与R1建立邻居关系,这样才能让R1找到最近的dns服务器。DNS1上ospfd.conf配置如下,由于DNS2、DNS3除了router-id不相同以外其余配置都一样,这里就不再贴出。
password foobar
!
interface lo
!
router ospf
	ospf router-id 10.211.77.1
	network 10.211.77.0/24 area 0.0.0.2
	network 6.6.6.6/32 area 0.0.0.2
	network 10.0.0.0/24 area 0.0.0.2
!
line vty
!
log file /var/log/message/quagga/ospfd.log

然后三台DNS上分别执行:
systemctl start zebra
systemctl start ospfd
```</code></pre></div></div><h6 id="4kgrp" name="%E8%A7%82%E5%AF%9F%E7%8A%B6%E6%80%81">观察状态</h6><p>在DNS1上进入<code>vtysh</code>,然后执行:<code>show ip route</code>,显示如下信息:
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863587392676835.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
可以看到此时DNS1已经连接到了10.211.66.0/24网段已经10.211.77.0/24网段,以及10.211.55.0/24网段。我们在看一下邻居关系,在DNS1上执行<code>show ip ospf neighbor</code>
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863587795230584.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
此时我们已经可以看到DNS1、DNS2、DNS3都在一个邻居网络上,表示我们的所有网络已经连接成功。此时我们再次测试联通性,在DNS1上执行:<code>ping 10.211.66.1</code>(PC的IP),可以看到也是通的。
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863588449903025.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><h6 id="76ji9" name="%E5%AE%9E%E7%8E%B0anycast">实现anycast</h6><p>我们刚才都是用的10.211.77.0/24进行访问的,但是我们最终目的是访问6.6.6.6。这里我们在DNS1、DNS2、DNS3的lo环网上添加一个6.6.6.6/32的IP:
<code>shell ip addr add 6.6.6.6/32 dev lo</code>
此时的DNS1、DNS2、DNS3的网卡信息分别如下:
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863588854255405.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863589482101325.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863589991837342.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
此时我们再通过PC端来ping 6.6.6.6,看看效果:
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863590585426760.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
由此我们的整个网络拓扑已经建立起来,PC端成功的ping通6.6.6.6网段。也实现我们预期的效果。</p><h6 id="ss71" name="%E6%90%AD%E5%BB%BAdns%E6%9C%8D%E5%8A%A1%E5%99%A8">搭建dns服务器</h6><p>我们这里使用的是bind9作为DNS服务器,分别在DNS1、DNS2、DNS3上执行:<code>./named -c ./named.conf -g</code>,这样我们就建立起三个dns服务器,监控日志打印如下信息表示成功启动:
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863591165106829.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
此时我们在PC端执行:<code>dig www.baidu.com @6.6.6.6 A</code>
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863591644871548.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
此时我们已经成功的部署好DNS服务器并且能正常提供递归服务。</p><h6 id="blor6" name="%E5%9D%87%E8%A1%A1%E8%B4%9F%E8%BD%BD">均衡负载</h6><p>我们刚才虽然已经将三台DNS架设起来,而且PC端也能成功的dig请求6.6.6.6解析域名,但是其实现在每次dig的请求都是到了DNS1这台服务器,与我们想要的均衡负载似乎并没有太大的关系,并且我们本可以使用DNS1的eth1网卡IP:10.211.77.1,为何又大费周章的去做一个6.6.6.6/32的地址来进行解析呢?这似乎跟均衡以及anycast没半毛钱关系。
我们做一个实验,如果把DNS1服务器关掉或者关闭网卡,会是怎么样的呢?
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863592138590258.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
我们在DNS1上分别关掉eth0、eth1以及删掉6.6.6.6/32,此时<code>ip a</code>可以看到DNS1已经是断网的状态,也ping不通任何服务器。
而此时的DNS2以及DNS3的状态如下,都已经开启了DNS服务。
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863592688675315.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
此时我们再次从PC端执行<code>dig www.baidu.com @6.6.6.6 A</code>,此时的DNS2以及DNS3的日志打印:
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863593073874221.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
可以看到DNS2已经收到了PC端的A记录解析请求。同时PC端也收到了响应:
</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:100%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1722863593505682288.png" /></div><div class="figure-desc">在这里插入图片描述</div></div></div></figure><p>
如此一来,DNS的均衡负载就能正常运转,同样的道理如果我们把DNS1再次启动,此时的PC端的请求会回到DNS1上。这样就可以水平拓展DNS服务器以达到均衡负载的目的。</p><h5 id="c76nq" name="%E5%8E%9F%E7%90%86">原理</h5><p>以上就是DNS+Anycast搭建均衡负载的实战,这其中比较关键的点就是ospf协议以及在lo环网上添加6.6.6.6/32作为均衡负载IP。我们在刚才的实验中可以看到,当DNS1断开以后,请求能自动达到了DNS2上,这一步其实就是ospf协议做的工作。我们在搭建的时候将DNS1、DNS2、DNS3以及R1一起通过ospf建立了邻居关系。
此时的DNS1、DNS2、DNS3就是R1路由器的邻居,同时DNS三台服务器上的所有网段都能被R1路由器学习到,因此在三台DNS服务器上添加了6.6.6.6/32也能被R1路由器学习到。而我们都知道ospf协议是开放最短路径最优协议,也就是在邻居里面会计算出最近的邻居,一般这个路径是根据路由跳数计算,而三台DNS处于一个路由内网中,因此就只需要记录谁最先响应hello组播消息,那么谁就是最近的邻居。因此就有了每次请求6.6.6.6的时候,都是DNS1响应。而如果DNS1断开以后则是DNS2响应。这样就通过路由协议以实现一个均衡负载的场景。
在lookback回环网卡上添加6.6.6.6/32是因为lookback网卡的状态永远是up的,即是没有配置地址,他都会存在,这就保证了服务的可用。另外就是将相同的IP添加到lookback上就不会存在IP冲突的情况,因为回环地址只会作为主机解析使用,它不会讲任何数据传输给网络接口。也就是说要访问本机的6.6.6.6/32其实是通过了网卡eth0或者eth1入口,然后解析到回环上的。所以就不存在同一局域网内IP冲突的情况。</p><h5 id="6qrpn" name="%E7%BC%BA%E7%82%B9">缺点</h5><p>讲了这么多anycast的部署以及优点,但它有一个致命的弱点,那就是因为它本身就是通过IP协议逐跳寻址的特性,讲数据包导向不通的目的地,但由于逐跳的路由收敛和端到端的五元组连接互相没有同步,导致anycast不能用于TCP长链接。例如在请求的过程中,数据还没返回,但DNS服务器断开,此时路由会收敛计算到其他路径,这就会导致返回路径不一致,从而造成TCP主机之间会出现断开的情况,因此anycast只适合例如UDP这样一问一答的情景,例如DNS,而且google的公共DNS就是利用了这个特性进行搭建的,只不过google搭建的是一个全球性的公共DNS,利用的是BGP边界路由协议,实现的是不通路由之间的宣告,而本例只是利用了ospf协议搭建的内网的DNS,仅仅是google公共DNS的一个很小的单元罢了。
但管中窥豹,我们通过小型的内网anycast场景的搭建也能窥视全球DNS的布局模式,这也不为一种学习方式。</p><p>发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/142393.html原文链接:https://javaforall.cn</p>