当咱们做技术预研/业务起步的时候,功能性(Functionality)是最重要的,能跑通就行。对于最风行的C/S架构来说,上面的架构就是能满足性能需要的最简模式:
但随着的业务的倒退,量级越来越大的时候,可伸缩性(Scalability)和高可用性(HighAvailability)都会逐步变成十分重要的议题,除此以外可管理性(Manageability)和老本效益(Cost-effectiveness)都会在咱们的思考范畴之内。本文重点关注业务倒退中的高可用建设。
其实思考到下面这些因素,LVS这个弱小的模块简直就是咱们的必选项(有些场景中,LVS不是最佳抉择,比方内网负载平衡),也是业务同学接触到最多的模块。上面让咱们从LVS体验开始,一步步扩充视线看高可用是怎么做的。
注:本文不会解说LVS的基础知识,欠缺的中央请大家自行Google。
LVS初体验
要一大堆机器做试验是不事实的,所以咱们就在docker外面做试验。
第一步:创立网络:
docker network create south
而后用 docker network inspect south
失去网络信息 "Subnet": "172.19.0.0/16","Gateway": "172.19.0.1"
。也能够抉择在 create
的时候用 --subnet
自行指定子网,就不必去查了。
第二步:创立RS
两台real server,rs1和rs2。Dockerfile如下
FROM nginx:stableARG RS=default_rsRUN apt-get update \ && apt-get install -y net-tools \ && apt-get install -y tcpdump \ && echo $RS > /usr/share/nginx/html/index.html
别离构建和启动
docker build --build-arg RS=rs1 -t mageek/ospf:rs1 .docker run -itd --name rs1 --hostname rs1 --privileged=true --net south -p 8888:80 --ip 172.19.0.5 mageek/ospf:rs1docker build --build-arg RS=rs2 -t mageek/ospf:rs2 .docker run -itd --name rs2 --hostname rs2 --privileged=true --net south -p 9999:80 --ip 172.19.0.6 mageek/ospf:rs2
这外面比拟重要的是privileged,没有这个参数,咱们在容器内是没法绑定vip的(权限不够)。此外,启动时候固定ip也是便于后续lvs配置简略可反复
第三步:创立LVS
Dockerfile如下
FROM debian:stretchRUN apt-get update \ && apt-get install -y net-tools telnet quagga quagga-doc ipvsadm kmod curl tcpdump
这外面比拟重要的quagga是用来运行动静路由协定的,ipvsadm是lvs的管理软件。
启动lvs:
docker run -itd --name lvs1 --hostname lvs1 --privileged=true --net south --ip 172.19.0.3 mageek/ospf:lvs1
仍然须要privileged和固定ip。
第四步:VIP配置
LVS配置
docker exec -it lvs1 bash
进入容器。咱们间接采纳LVS最高效的模式,DR模式,以及最常见的负载策略:round_robin:
ipvsadm -A -t 172.19.0.100:80 -s rripvsadm -a -t 172.19.0.100:80 -r 172.19.0.5 -gipvsadm -a -t 172.19.0.100:80 -r 172.19.0.6 -g# 查看配置的规定ipvsadm -Ln# 启用ifconfig eth0:0 172.19.0.100/32 up
RS配置
ifconfig lo:0 172.19.0.100/32 upecho "1">/proc/sys/net/ipv4/conf/all/arp_ignoreecho "1">/proc/sys/net/ipv4/conf/lo/arp_ignoreecho "2">/proc/sys/net/ipv4/conf/all/arp_announceecho "2">/proc/sys/net/ipv4/conf/lo/arp_announce
其中
arp_ignore
是为了防止 rs 响应 arp 申请,确保 dst ip 是 vip 的包肯定会路由到 lvs 上arp_announce
是为了防止 r s在发动 arp 申请时用 vip 净化局域网中其它设施的 arp 表- 同一个配置写两遍,是为了确保失效,因为内核会抉择 all 和具体网卡中的较大值
第五步:察看
进入 south 网络的另一个容器 switch(不要介意名字),拜访 vip
> for a in {1..10}> do> curl 172.19.0.100> doners2rs1rs2rs1rs2rs1rs2rs1rs2rs1
可见是 round robin 的模式。
再看看是否是 DR 模式
root@switch:/# curl 172.19.0.100rs2root@switch:/# curl 172.19.0.100rs1root@lvs1:/# tcpdump host 172.19.0.100tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes14:52:47.967790 IP switch.south.35044 > 172.19.0.100.http: Flags [S], seq 3154059648, win 64240, options [mss 1460,sackOK,TS val 1945546875 ecr 0,nop,wscale 7], length 014:52:47.967826 IP switch.south.35044 > 172.19.0.100.http: Flags [S], seq 3154059648, win 64240, options [mss 1460,sackOK,TS val 1945546875 ecr 0,nop,wscale 7], length 014:52:47.967865 IP switch.south.35044 > 172.19.0.100.http: Flags [.], ack 3324362778, win 502, options [nop,nop,TS val 1945546875 ecr 1321587858], length 014:52:47.967868 IP switch.south.35044 > 172.19.0.100.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1945546875 ecr 1321587858], length 014:52:47.967905 IP switch.south.35044 > 172.19.0.100.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 1945546875 ecr 1321587858], length 76: HTTP: GET / HTTP/1.114:52:47.967907 IP switch.south.35044 > 172.19.0.100.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 1945546875 ecr 1321587858], length 76: HTTP: GET / HTTP/1.114:52:47.968053 IP switch.south.35044 > 172.19.0.100.http: Flags [.], ack 235, win 501, options [nop,nop,TS val 1945546875 ecr 1321587858], length 014:53:15.037813 IP switch.south.35046 > 172.19.0.100.http: Flags [S], seq 2797683020, win 64240, options [mss 1460,sackOK,TS val 1945573945 ecr 0,nop,wscale 7], length 014:53:15.037844 IP switch.south.35046 > 172.19.0.100.http: Flags [S], seq 2797683020, win 64240, options [mss 1460,sackOK,TS val 1945573945 ecr 0,nop,wscale 7], length 014:53:15.037884 IP switch.south.35046 > 172.19.0.100.http: Flags [.], ack 1300058730, win 502, options [nop,nop,TS val 1945573945 ecr 1321614928], length 014:53:15.037887 IP switch.south.35046 > 172.19.0.100.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1945573945 ecr 1321614928], length 014:53:15.037925 IP switch.south.35046 > 172.19.0.100.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 1945573945 ecr 1321614928], length 76: HTTP: GET / HTTP/1.114:53:15.037942 IP switch.south.35046 > 172.19.0.100.http: Flags [P.], seq 0:76, ack 1, win 502, options [nop,nop,TS val 1945573945 ecr 1321614928], length 76: HTTP: GET / HTTP/1.114:53:15.038023 IP switch.south.35046 > 172.19.0.100.http: Flags [.], ack 235, win 501, options [nop,nop,TS val 1945573945 ecr 1321614928], length 0root@rs1:/# tcpdump host 172.19.0.100tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes14:53:15.037848 IP switch.south.35046 > 172.19.0.100.80: Flags [S], seq 2797683020, win 64240, options [mss 1460,sackOK,TS val 1945573945 ecr 0,nop,wscale 7], length 014:53:15.037873 IP 172.19.0.100.80 > switch.south.35046: Flags [S.], seq 1300058729, ack 2797683021, win 65160, options [mss 1460,sackOK,TS val 1321614928 ecr 1945573945,nop,wscale 7], length 014:53:15.037888 IP switch.south.35046 > 172.19.0.100.80: Flags [.], ack 1, win 502, options [nop,nop,TS val 1945573945 ecr 1321614928], length 014:53:15.037944 IP switch.south.35046 > 172.19.0.100.80: Flags [P.], seq 1:77, ack 1, win 502, options [nop,nop,TS val 1945573945 ecr 1321614928], length 76: HTTP: GET / HTTP/1.114:53:15.037947 IP 172.19.0.100.80 > switch.south.35046: Flags [.], ack 77, win 509, options [nop,nop,TS val 1321614928 ecr 1945573945], length 014:53:15.037995 IP 172.19.0.100.80 > switch.south.35046: Flags [P.], seq 1:235, ack 77, win 509, options [nop,nop,TS val 1321614928 ecr 1945573945], length 234: HTTP: HTTP/1.1 200 OK14:53:15.038043 IP switch.south.35046 > 172.19.0.100.80: Flags [.], ack 235, win 501, options [nop,nop,TS val 1945573945 ecr 1321614928], length 0root@rs2:/# tcpdump host 172.19.0.100tcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes14:52:47.967830 IP switch.south.35044 > 172.19.0.100.80: Flags [S], seq 3154059648, win 64240, options [mss 1460,sackOK,TS val 1945546875 ecr 0,nop,wscale 7], length 014:52:47.967853 IP 172.19.0.100.80 > switch.south.35044: Flags [S.], seq 3324362777, ack 3154059649, win 65160, options [mss 1460,sackOK,TS val 1321587858 ecr 1945546875,nop,wscale 7], length 014:52:47.967869 IP switch.south.35044 > 172.19.0.100.80: Flags [.], ack 1, win 502, options [nop,nop,TS val 1945546875 ecr 1321587858], length 014:52:47.967908 IP switch.south.35044 > 172.19.0.100.80: Flags [P.], seq 1:77, ack 1, win 502, options [nop,nop,TS val 1945546875 ecr 1321587858], length 76: HTTP: GET / HTTP/1.114:52:47.967910 IP 172.19.0.100.80 > switch.south.35044: Flags [.], ack 77, win 509, options [nop,nop,TS val 1321587858 ecr 1945546875], length 014:52:47.967990 IP 172.19.0.100.80 > switch.south.35044: Flags [P.], seq 1:235, ack 77, win 509, options [nop,nop,TS val 1321587858 ecr 1945546875], length 234: HTTP: HTTP/1.1 200 OK14:52:47.968060 IP switch.south.35044 > 172.19.0.100.80: Flags [.], ack 235, win 501, options [nop,nop,TS val 1945546875 ecr 1321587858], length 0
可见的确 lvs1 只会收到 switch 的包并转发给 rs(有来无回),而 rs1 和 rs2 和 switch 就是失常的三步握手后再进行 http 报文的传输(有来有回),是 DR 模式。
仔细的同学发现了,lvs1 中怎么报文都呈现了两次?
这是因为 DR 收到 IP 报文后,不批改也不封装 IP 报文,而是将数据帧的 MAC 地址改为选出服务器的 MAC 地址,再将批改后的数据帧在与服务器组雷同的局域网上发送,如图所示:
tcpdump是能抓到批改前后的包的,所以有两条。实际上,在tcpdump命令加上-e参数后,就能看到mac地址变动。
root@lvs1:/# tcpdump host 172.19.0.100 -etcpdump: verbose output suppressed, use -v or -vv for full protocol decodelistening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes15:58:57.245917 02:42:ac:13:00:02 (oui Unknown) > 02:42:ac:13:00:03 (oui Unknown), ethertype IPv4 (0x0800), length 74: switch.south.35070 > 172.19.0.100.http: Flags [S], seq 422105942, win 64240, options [mss 1460,sackOK,TS val 1949516153 ecr 0,nop,wscale 7], length 015:58:57.245950 02:42:ac:13:00:03 (oui Unknown) > 02:42:ac:13:00:05 (oui Unknown), ethertype IPv4 (0x0800), length 74: switch.south.35070 > 172.19.0.100.http: Flags [S], seq 422105942, win 64240, options [mss 1460,sackOK,TS val 1949516153 ecr 0,nop,wscale 7], length 0
最初失去的架构如图:
RS高可用
下面咱们配置了 LVS 前面两个 RS,这样能起到增大吞吐量的作用(伸缩性),但其实并没有做到 RS 的高可用,因为一台 RS 挂了后,LVS 仍然会往该 RS 上打流量,造成这部分申请失败。所以咱们还须要配置健康检查,当 LVS 查看到 RS 不衰弱的时候,被动剔除这台 RS,让流量不往这里打。这样就做到了RS的高可用,也就是说,RS 挂了一台后不影响业务(当然,理论场景还要思考吞吐量、建连风暴、数据等问题)。
首先装置keepalived,装好后配置如下
global_defs { lvs_id LVS1}virtual_server 172.19.0.100 80 { delay_loop 5 lb_algo rr lb_kind DR persistence_timeout 50 protocol TCP real_server 172.19.0.5 80 { weight 2 HTTP_GET { url { path / } connect_timeout 3 retry 3 delay_before_retry 2 } } real_server 172.19.0.6 80 { weight 2 HTTP_GET { url { path / } connect_timeout 3 retry 3 delay_before_retry 2 } }}
而后启动:
chmod 644 /etc/keepalived/keepalived.conf# 增加keepalived专用的用户groupadd -r keepalived_scriptuseradd -r -s /sbin/nologin -g keepalived_script -M keepalived_script# 启动keepalived -C -D -d
留神这里只用了 keepalived 的健康检查性能,没有用 VRRP 性能。
敞开 rs2 后,拜访 vip 能够发现,vip 只会导向 rs1
root@switch:/# curl 172.19.0.100rs1root@switch:/# curl 172.19.0.100rs1root@switch:/# curl 172.19.0.100rs1root@switch:/# curl 172.19.0.100rs1root@switch:/# curl 172.19.0.100rs1root@switch:/# curl 172.19.0.100rs1
而后 lvs1 的 ipvs 配置也产生了变动
root@lvs1:/# ipvsadm -LnIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP 172.19.0.100:80 rr -> 172.19.0.5:80 Route 1 0 1
当复原 rs2 后,ipvs 配置复原如初,针对 vip 发动的申请也在 rs1 和 rs2 之间平均响应,实现了 RS 的高可用。
root@lvs1:/# ipvsadm -LnIP Virtual Server version 1.2.1 (size=4096)Prot LocalAddress:Port Scheduler Flags -> RemoteAddress:Port Forward Weight ActiveConn InActConnTCP 172.19.0.100:80 rr -> 172.19.0.5:80 Route 1 0 4 -> 172.19.0.6:80 Route 2 0 0
LVS高可用
所谓高可用,其实外围就是冗余(当然,不止是冗余),所以咱们能够用多台LVS来做高可用。这里又会有两种抉择:一是主备模式,能够利用 Keepalived 的 VRRP 性能,然而大规模生产环境中,用集群模式更好,因为其同时进步了伸缩性和可用性,而前者只解决了可用性(当然,也更简略)。
架构别离如图:
主备模式 | 集群模式 |
---|---|
简要阐明一下原理:
- 主备模式: lvs主备之间运行 VRR P协定,日常态流量都从主走,当备检测到主挂了(进行收到主发来的VRRP通告后一段时间),便通过发送 free arp 来抢占vip,使得所有流量都从本人走,实现 failover
- 集群模式: lvs集群和上联交换机运行 OSPF,生成该 vip 的多路等价路由 ecmp,这样流量就能依据用户自定义的策略流向 lvs。当某台lvs挂了,替换机会将其从路由表中剔除,实现 failover
动静路由协定的配置过程比较复杂,限于篇幅这里就不开展了,大家感兴趣的能够自行Google。
到这里LVS相干的就看的差不多了,咱们再往外延长一下,看看其它畛域是怎么做高可用的。
交换机/链路高可用
下面能够看到,做了 LVS 的高可用后,交换机又成了单点。实际上,交换机有很多办法做高可用,分为二层和三层:
三层
跟下面的LVS一样,利用VRRP实现交换机的主备高可用,也能够利用OSPF/ECMP实现集群高可用(当然,仅针对三层交换机)。
二层
这里简略举个例子,传统园区网络采纳三层网络架构模型,如图所示
汇聚交换机和接入交换机之间通常就应用 STP/ MSTP(Spanning Tree Protocol),该协定算法在交换机有多条可达链路时只保留一条链路,其它链路在故障时启用。
另外还有 Smartlink,也能实现二层链路的主备模式。
此外,为了防止单台交换机故障,能够在服务器上挂主备网卡,双上联,当主网卡所在链路故障时,服务器启用备网卡所在链路即可,如图:
设施高可用
不论是交换机还是服务器、路由器等,最终都是放在机房机柜中,作为物理设施存在的,它们是怎么做高可用的呢?
物理设施,其可用性外围就是电源供给:
- 首先用到了UPS,次要就是储能技术:在市电供给时,给蓄电池充电;在市电断电时,将电池放电,给机柜供能。
- 其次用到了双路供电,也就是说,市电间接来源于两个供电系统,防止单个供电系统故障造成不可用
机房高可用
下面的过程保障了机房外部的可用性,那么整个机房挂了该怎么办?有不少办法能解决这个问题
DNS轮询
假如咱们的业务域名为a.example.com,给他增加两条A记录,别离指向机房a和机房b。当a机房挂了,咱们将a机房的A记录删除,这样所有用户就都只能拿到b机房的A记录,从而拜访到b机房,实现高可用。
这种形式的问题在于 DNS 的 TTL 是不可控的,个别os、localDNS、权威DNS都会做缓存,尤其是其中的localDNS,个别都在运营商手中,尤为难控(运营商不肯定严格依照TTL进行更新)。退一步来说,即便TTL都可控(比方用httpDNS),但这个TTL的设置比拟难把握:太长则故障 failover 工夫太长;太短则用户频繁发动 DNS 解析申请,影响性能。
所以目前这种形式都只作为高可用的辅助伎俩,而不是次要伎俩。
值得一提的是,F5 的 GTM 能够实现此性能,通过动静地给客户返回域名解析记录,实现就近、容错等成果。
优先级路由
主和备的地址都在路由范畴内,然而优先级不同。这样日常状况流量都到主,当主挂掉并被检测到的时候,主的路由被删除,备的路由主动失效,这样就实现了主备failover。
路由优先级有几个了解:
- 不同协定造成的路由表是有优先级的,比方直连路由为0,OSPF 为 110,IBGP 为 200,当同一个指标地址有多个下一跳时,应用优先级更高的路由表。实际中,我还没见到过这种做法实现主备的。
- 同一个协定内,不同的门路是有优先级的,比方 OPSF 协定的 cost,日常主备门路的 cost 设置不一样,cost小的为主并被写入路由表走所有流量,当主挂掉了,其门路被路由表删除,备门路主动进入路由表。实际中,F5 LTM 的 route health injection 是利用这个原理实现主备的。
- 同一个协定内,路由匹配是考究最长前缀的,比方路由表中有两个条目,172.16.1.0/24、172.16.2.0/24,当收到 dst ip 为 172.16.2.1 的包时,出于最长前缀匹配准则(越长越精准),就应该走172.16.2.0/24这一条路由。实际中,阿里云 SLB 利用此原理来做同城容灾。
Anycast
下面讲到了用 DNS 来高可用,那 DNS 自身也是须要做高可用的。DNS 高可用的一个重要伎俩就是 Anycast,这个伎俩也能被其余业务借鉴,所以咱们也来看看。
互联网上目前真正落地的 EGP 就是 BGP(这也是互联网能互联互通的基石协定),通过不同的 AS 对外宣告同一个 IP,用户在拜访这个IP的时候就能根据特定的策略拜访到最佳的 AS(比方就近拜访策略)。当某个 AS 中的服务挂掉且被 BGP 路由器检测到了,BGP 路由器会主动进行该 IP 对上联 AS 的播送,这样到该 IP 的用户流量就不会被路由到这个 AS 来,从而实现故障的 failover。
对于DNS来说,逻辑上,根域名服务器只有13个,然而利用Anycast,理论部署数量是远不止13的,不同国家和地区都能够自行部署根域名服务器的镜像,并且具备同样的IP,从而实现本地就近拜访、冗余、平安等个性。
业务高可用
下面讲了一大堆的高可用,有这些计划后,业务是不是间接多地部署就实现了高可用了呢?当然不是。
仍以 DNS 为例,尽管寰球都能部署根域名镜像服务器,然而实在的域名解析数据还是要从根域名服务器同步,这外面就有数据一致性问题,尽管 DNS 自身是个对数据一致性没那么高的服务,然而咱们更多的服务都对数据一致性有要求(比方库存、余额等)。这也是下面说的,高可用尽管和冗余关系很大,但也不只是冗余,还要关注数据一致性等方面(也就是 CAP 定理)。这方面,不同的业务有不同的做法。
对于常见的web服务,能够通过对业务做自顶向下的流量隔离来实现高可用:将单个的用户的流量尽可能在一个单元处理完毕(单元关闭),这样当这个单元产生故障时,就能疾速地将流量切到另一个单元,实现 failover,如图:
写在最初
下面每一个环节的高可用,其实并不需要每个企业本人投入。很多环节都曾经有相当业余的云产品了。
企业全链路自建的话,既不业余(做不好),还会造成节约(精力集中于主业,才不会错过商机)。
可用的产品有:
- LVS产品:阿里云ALB,华为ELB,腾讯CLB;
- DNS产品:阿里云/华为云解析DNS,腾讯云DNSPod;
- Anycast产品:阿里云Anycast EIP,腾讯云Anycast公网减速,
- 业务高可用产品:阿里云MSHA;
- 等等
最初,本文撰写过程中,思路比拟发散,梳理不全面的中央,请大家批评指正。
参考
- Linux服务器集群零碎(三)--LVS集群中的IP负载平衡技术: http://www.linuxvirtualserver...
- https://www.kernel.org/doc/Do...
- Case Study: Healthcheck — Keepalived 1.4.3 documentation: https://www.keepalived.org/do...
- VIPServer:阿里智能地址映射及环境管理系统详解_CSDN 人工智能-CSDN博客: https://blog.csdn.net/heyc861...
- 数据中心网络高可用架构-新华三团体-H3C: http://www.h3c.com/cn/d_20100...
- 阿里云云原生异地多活解决方案: https://baijiahao.baidu.com/s...
- AskF5 | Manual Chapter: Working with Dynamic Routing: https://techdocs.f5.com/kb/en...
- 阿里云SLB同城容灾计划-中存储网: https://www.chinastor.com/fan...