各位童鞋,好久不见,一段时间没露面了,庆祝付出献上小文一篇。有不少童鞋在问 AZURE 的负载均衡支不支持主备模式,答案是不支持,准确来说是原生不支持,AZURE 的 SLB 三四层负载均衡器原生只支持主主模式,即后端可用池中健康主机都会被负载均衡分配流量。但是对于一些场景,还是希望通过主备方式来做,在云端上主备模式可以通过支持主备模式的如NGINX,HAPROXY等解决方案来做,今天这篇文章我们换一种思路,借助现有的 AZURE SLB 的能力来实现主备模式。
1. 设计思路
AZURE SLB 始终会往后端池健康的主机分配流量,如果我们将主机从后端池摘除,这样就不会再向该主机分配流量。按照这个思路思考,那么如果当主用主机可用时,我们将备用主机从后端池里面移除,当主用主机不可用时,我们将备用主机放入至后端池里面。逻辑很简单,但是修正后端池需要调用 AZURE 平台管理的 SDK 来完成,看起来稍微有一点复杂。那有没有办法不用 AZURE 平台管理的 SDK 来完成,当然有的,AZURE SLB 负载均衡通过探针来确认后端池内主机的可用性,并且探针始终是从保留地址168.63.129.16发出的,那我们可以把上述的后端池移除和放入的动作简化为,当主用主机可用时,在备用主机拒绝对来自168.63.129.16的探针响应,当主用主机不可用时,在备用主机放行对来自168.63.129.16的探针响应,当探针无响应时,AZURE SLB 将认为后端池该主机不可用,故不再向该主机分配流量。
关于主备状态的仲裁,可以通过在后端池内的主机上运行脚本,相互check对方的可用性状态,然后来触发上述主备切换的动作。此种做法是无状态的切换,即主备状态无法做持久化的记录并并共享给其他服务。这里我们采用可持久化的外部仲裁,可将主备状态持久化在外部存储,并支持被其他人访问查询。本文中以Consul为例,作为主备状态的持久化存储。关于Consul的介绍可参阅如下链接:https://www.consul.io/intro/index.html ,此处借助 Consul 的Service Discovery能力,在 Consul 集群中注册发布 WEB VM 组成的 WEB 服务,其中服务关联 Check 服务检测策略。服务注册发布后相当于对于当前主备状态做了持久化,基于两台主机的可用性状态变化 Consul 中 Service Discovery 服务返回的服务状态发生变化。
当 WEB-VM1 和 WEB-VM2 均可用时,Cousul DNS 服务发现服务同时返回 WEB-VM1 和 WEB-VM2 的地址;
当 WEB-VM1 可用 WEB-VM2 不可用时,Cousul DNS 服务发现服务同时返回 WEB-VM1 地址;
当 WEB-VM1 不可用 WEB-VM2 可用时,Cousul DNS 服务发现服务同时返回 WEB-VM2 地址;
当 WEB-VM1 和 WEB-VM2 均不可用时,Cousul DNS 服务发现服务同时无返回地址;
在 WEB-VM1 和 WEB-VM2 主机上执行脚本,脚本逻辑如下:
循环:
条件:如果 WEB-VM1 和 WEB-VM2 均可用
条件:如果WEB-VM1
放行负载均衡探针,增加VIP虚地址
条件:如果WEB-VM2
拒绝负载均衡探针,删除VIP虚地址
条件:如果 WEB-VM1 可用 和 WEB-VM2 不可用
条件:如果WEB-VM1
放行负载均衡探针,增加VIP虚地址
条件:如果WEB-VM2
拒绝负载均衡探针,删除VIP虚地址
条件:如果 WEB-VM1 不可用 和 WEB-VM2 可用
条件:如果WEB-VM1
拒绝负载均衡探针,删除VIP虚地址
条件:如果WEB-VM2
放行负载均衡探针,增加VIP虚地址
条件:如果 WEB-VM1 和 WEB-VM2 均不可用
条件:如果WEB-VM1
拒绝负载均衡探针,删除VIP虚地址
条件:如果WEB-VM2
拒绝负载均衡探针,删除VIP虚地址
Bash脚本参考如下,在 WEB-VM1 中将 role 设置为 Primary , 在 WEB-VM2 中将 role 设置为 Secondary,其中下述脚本中 40.125.160.154/32 为 VIP 地址, 可根据你的环境进行响应调整查看 WEB-VM1 和 WEB-VM2 均可用时,Consul DNS 服务发现返回值。
1 #! /bin/bash
2 role="Secondary"
3 sudo ip addr del 40.125.160.154/32 dev lo label lo:10
4 sudo iptables -A INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
5 while :
6 do
7 if [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep ";; flags" | awk -F',' '{print $2}')" == " ANSWER: 2"
8 ]
9 then
10 if [ "$role" == "Primary" ] && [ "$(ip addr show | grep lo:10)" == "" ]
11 then
12 sudo ip addr add 40.125.160.154/32 dev lo label lo:10
13 sudo iptables --delete INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
14 elif [ "$role" == "Secondary" ] && [ "$(ip addr show | grep lo:10)" != "" ]
15 then
16 sudo ip addr del 40.125.160.154/32 dev lo label lo:10
17 sudo iptables -A INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
18 fi
19 elif [ "$role" == "Primary" ] && [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep ";; flags" | awk -F',' '{print$2}')" == " ANSWER: 1" ]
20 then
21 if [ "$role" == "Primary" ] && [ "$(ip addr show | grep lo:10)" == "" ] && [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep -A 1 "ANSWER SECTION" | tail -n 1 | awk '{print $5}')" == "172.16.1.4" ]
22 then
23 sudo ip addr add 40.125.160.154/32 dev lo label lo:10
24 sudo iptables --delete INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
25 elif [ "$role" == "Primary" ] && [ "$(ip addr show | grep lo:10)" != "" ] && [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep -A 1 "ANSWER SECTION" | tail -n 1 | awk '{print $5}')" == "172.16.1.5" ]
26 then
27 sudo ip addr del 40.125.160.154/32 dev lo label lo:10
28 sudo iptables -A INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
29 fi
30 elif [ "$role" == "Secondary" ] && [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep ";; flags" | awk -F',' '{print $2}')" == " ANSWER: 1" ]
31 then
32 if [ "$role" == "Secondary" ] && [ "$(ip addr show | grep lo:10)" != "" ] && [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep -A 1 "ANSWER SECTION" | tail -n 1 | awk '{print $5}')" == "172.16.1.4" ]
33 then
34 sudo ip addr del 40.125.160.154/32 dev lo label lo:10
35 sudo iptables -A INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
36 elif [ "$role" == "Secondary" ] && [ "$(ip addr show | grep lo:10)" == "" ] && [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep -A 1 "ANSWER SECTION" | tail -n 1 | awk '{print $5}')" == "172.16.1.5" ]
37 then
38 sudo ip addr add 40.125.160.154/32 dev lo label lo:10
39 sudo iptables --delete INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
40 fi
41 elif [ "$(dig @172.16.0.7 -p 8600 http.service.consul | grep ";; flags" | awk -F',' '{print $2}')" == " ANSWER: 0" ] && [ "$(ip addr show | grep lo:10)" != "" ]
42 then
43 sudo ip addr del 40.125.160.154/32 dev lo label lo:10
44 sudo iptables -A INPUT -p tcp --dport 80 -s 168.63.129.16 -j DROP
45 fi
46 sleep 10
47 done
2. 测试
查看初始 Consul 服务发现 Http 服务状态
dig @127.0.0.1 -p 8600 http.service.consul
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @127.0.0.1 -p 8600 http.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23268
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;http.service.consul. IN A;; ANSWER SECTION:
http.service.consul. 0 IN A 172.16.1.4
http.service.consul. 0 IN A 172.16.1.5;; Query time: 0 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Tue Jan 16 16:03:11 UTC 2018
;; MSG SIZE rcvd: 80
登陆 WEB-VM1 查看Iptables规则表和地址表
WEB-VM1:~$ sudo iptables -L -nv
Chain INPUT (policy ACCEPT 5523 packets, 2284K bytes)
pkts bytes target prot opt in out source destinationChain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destinationChain OUTPUT (policy ACCEPT 7253 packets, 3037K bytes)
pkts bytes target prot opt in out source destinationWEB-VM1:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet 40.125.160.154/32 scope global lo:10
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:17:fa:01:08:e7 brd ff:ff:ff:ff:ff:ff
inet 172.16.1.4/24 brd 172.16.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::217:faff:fe01:8e7/64 scope link
valid_lft forever preferred_lft forever
登陆 WEB-VM2 查看Iptables规则表和地址表
WEB-VM2:~$ sudo iptables -L -nv
Chain INPUT (policy ACCEPT 5099 packets, 2236K bytes)
pkts bytes target prot opt in out source destination
287 14896 DROP tcp -- * * 168.63.129.16 0.0.0.0/0 tcp dpt:80Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destinationChain OUTPUT (policy ACCEPT 6806 packets, 1466K bytes)
pkts bytes target prot opt in out source destinationWEB-VM2:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:17:fa:01:14:4e brd ff:ff:ff:ff:ff:ff
inet 172.16.1.5/24 brd 172.16.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::217:faff:fe01:144e/64 scope link
valid_lft forever preferred_lft forever通过 WEB 负载均衡访问 WEB 服务,确认流量只被负载分配到 WEB-VM1
curl http://40.125.160.154 | grep VM
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
Apache2 Ubuntu WEB-VM1 Page0 0 --:--:-- --:--:-- --:--:-- 0
100 11321 100 11321 0 0 36489 0 --:--:-- --:--:-- --:--:-- 36637
关闭VM1,再次通过 WEB 负载均衡访问 WEB 服务,确认流量只被负载分配到 WEB-VM2
curl http://40.125.160.154 | grep VM
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
Apache2 Ubuntu WEB-VM2 Page0 0 --:--:-- --:--:-- --:--:-- 0
100 11321 100 11321 0 0 35393 0 --:--:-- --:--:-- --:--:-- 35825
查看 Consul 服务发现 http 服务状态
dig @172.16.0.7 -p 8600 http.service.consul
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @172.16.0.7 -p 8600 http.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62425
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;http.service.consul. IN A
;; ANSWER SECTION:
http.service.consul. 0 IN A 172.16.1.5
;; Query time: 2 msec
;; SERVER: 172.16.0.7#8600(172.16.0.7)
;; WHEN: Wed Jan 17 01:12:00 UTC 2018
;; MSG SIZE rcvd: 64
登陆 WEB-VM2 查看Iptables规则表和地址表
WEB-VM2:~$ sudo iptables -L -nv
Chain INPUT (policy ACCEPT 598 packets, 200K bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 698 packets, 278K bytes)
pkts bytes target prot opt in out source destination
WEB-VM2:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet 40.125.160.154/32 scope global lo:10
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:17:fa:01:14:4e brd ff:ff:ff:ff:ff:ff
inet 172.16.1.5/24 brd 172.16.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::217:faff:fe01:144e/64 scope link
valid_lft forever preferred_lft forever
重新开启 WEB-VM1,再次通过 WEB 负载均衡访问 WEB 服务,确认流量重新负载分配到 WEB-VM1
curl http://40.125.160.154 | grep VM
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
Apache2 Ubuntu WEB-VM1Page0 0 --:--:-- --:--:-- --:--:-- 0
100 11321 100 11321 0 0 32883 0 --:--:-- --:--:-- --:--:-- 33102
查看 WEB-VM2 Iptables规则表和地址表
WEB-VM2:~$ sudo iptables -L -nv
Chain INPUT (policy ACCEPT 1998 packets, 852K bytes)
pkts bytes target prot opt in out source destination
109 5644 DROP tcp -- * * 168.63.129.16 0.0.0.0/0 tcp dpt:80Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destinationChain OUTPUT (policy ACCEPT 2673 packets, 563K bytes)
pkts bytes target prot opt in out source destinationWEB-VM2:~$ ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:17:fa:01:14:4e brd ff:ff:ff:ff:ff:ff
inet 172.16.1.5/24 brd 172.16.1.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::217:faff:fe01:144e/64 scope link
valid_lft forever preferred_lft forever
3. 关于VIP
细心的童鞋可能会问上面的VIP是个什么鬼,不是已经有负载均衡了嘛,为什么主机上面还要配置这个VIP。这个地方是比较有意思的地方,AZURE SLB 负载均衡,支持将请求目的IP以保留不变的方式发送到后端主机,这个时候后端主机如果希望响应,需要在该主机上面拥有该地址。那什么场景下需要这个VIP呢,如果本身你的两台主机维护有类似状态表的数据(举个栗子,比如连接状态表),两台主备关系的主机支持状态同步的话,在主备切换时候如果希望能够把状态带过来就需要两台主机对外提供的是相同地址。在创建负载均衡规则的时候勾选浮动IP选项即可实现该效果。这个实验示例中,两台 WEB 服务器是无状态的,虽然没有状态同步,但是通过该栗子可以帮助大家理解和方便未来类似需求场景的架构。
总结:本文帮助各位童鞋理解 AZURE SLB 的原生基本功能,以及在特定场景下希望实现主备负载均衡时,如何通过简单的脚本自定义实现。抛砖引玉,自己动手丰衣足食。