使用keepalived实现高可用服务

拿PHP为例,一般网站都遵循这种部署方式

  • 存储集群:主要是指Mysql与Redis集群,但是存储相关的高可用问题不会在本篇博客讨论。
  • 后端集群:由大量机器组成,每台机器是一个完整的nginx+php-fprm的执行环境(本机nginx转发给本机php-fpm),访问任意一台机器都可以浏览完整的网站内容,它们承载了网站的主要计算任务(例如:渲染模板)。
  • 前端接入:由少量(通常个位数)高性能机器组成,将用户流量转发给后端集群,主要工作是网络数据的IO传输,计算任务则留给后端集群完成。
  • DNS负载均衡:为了实现用户流量均摊到前端接入层的多台机器,需要借助DNS实现流量分发。

对于后端集群的可用性一般采用如下解决方法:

  • 对于后端机器内部,通过superviosr监督进程保证同机的nginx与php-fpm同时存活,整机作为一个最小服务单元。
  • 对于后端集群整体,前端接入层利用lvs或者haproxy作为后端集群的反向代理层,他们利用健康检查机制及时发现后端故障机器并停止向其转发流量。

对于前端接入层的可用性,就是本文要讨论的重点,所以在此先抛出问题:

  • 配置DNS将域名解析到N台前端机的IP,那么如果恰好其中一台前端机宕机,那么就会有部分流量访问到故障机。
  • DNS在互联网上是逐级缓存的,并且中国运营商行为更加不可控,即便我们意识到了IP故障,从我们修改DNS踢出该IP到该修改生效到全网是需要很长一段时间的。

正因为DNS的这种特点,逼迫我们不得不回到前端机的可用性本身,看看能否将单个IP的可用性提高,一个简单的猜想就是:让2台物理机拥有同一个IP地址,但是同一时刻只有其中1台工作,当其宕机后另外一台接管工作。

没错,这就是keepalived做的事情之一

keepalived原理浅析

keepalived其实提供了2种功能,分别是:

  • 基于LVS的负载均衡:免去我们手动配置LVS的麻烦,只需要编写LVS的配置文件,剩下的工作交给keepalived完成。
  • 基于VRRP的故障转移:使用主从备份的方式,在主机宕机后由备机接管服务,整个过程基于VRRP协议实现VIP(虚拟IP)漂移实现。

负载均衡

在我看来,我们应该尽量避免使用LVS(如果你不了解LVS可以看这里),Haproxy在7层工作已经可以解决绝大多数需求,为什么这么说呢?我们分析一下就知道了。

  • LVS NAT模式
    • 过程:当请求到达VIP,LVS需修改目标IP为后端机IP,后端机在应答时需要配置默认网关为LVS的内网地址,这样LVS才有机会将应答的源IP改回VIP。
    • 优点:
      • 后端机的IP可以是私有IP,不需要对接路由器,只需要和LVS的内网IP在同一物理网内即可。
    • 缺点:
      • 后端机要改默认网关为LVS的内网IP,这就要求后端机IP和LVS内网IP在一个物理网络中。
      • 后端机默认网关改为LVS,那么后端机主动向外网发起请求时,请求只能发给LVS,而LVS是做DNAT反向代理的,压根没有SNAT配置。
      • 如果我们给LVS做了主备高可用,那么当主宕机从接管后,后端机的默认网关就必须改为从机的内网IP才可以继续工作,谁来改?
      • 请求和应答都必须通过LVS,LVS性能吃紧。
  • LVS DR模式:
    • 过程:当请求到达VIP,LVS需修改目标MAC地址为后端机MAC,后端机需为网卡增加一个IP,其值等于LVS的VIP,这样后端机误认为包是客户端直接发给自己的,在处理完成后直接向客户端IP应答,而不需要再经过LVS。
    • 优点:
      • 应答不需要经过LVS,因此不需要修改后端机的默认网关,应答直接发往路由器,节省了LVS的性能。
    • 缺点:
      • LVS直接改目标MAC再广播给后端机的做法,要求LVS的内网IP和后端机IP在一个物理网内。
      • 后端机直接向客户端IP应答而不经过LVS,这意味着后端机不再是藏在LVS后面的私有IP,而是与LVS外网IP在一个物理网段内,对接同一个上级路由器。
  • LVS TUNNEL模式:
    • 过程:当请求到达VIP,LVS会将整个包重新打包在一个新的IP包里,修改目标IP地址改为后端机。后端机同样需要为网卡增加一个IP,其值等于LVS的VIP,当后端机收到请求后剥开外层IP包,发现内层IP包的目标IP是VIP,就会正常处理,应答会直接发给包的来源IP,也就是客户端IP。
    • 优点:
      • LVS到后端机是基于IP路由的,后端机应答是直接发往客户端的(直接通过路由器),因此LVS和后端机不必在同一个物理网内,同时应答不经过LVS也降低了LVS的压力。
    • 缺点:
      • 打包IP和解包IP的过程,额外耗费了性能,安装部署成本太大。
  • LVS FULLNAT模式:
    • 过程:当请求到达VIP,LVS修改目标IP为后端机IP,修改源IP为LVS内网IP,包按照IP路由到达后端机。当后端机应答时,按照正常IP路由回到LVS的内网IP,之后LVS会将源IP修改为LVS的外网IP,目标IP修改为客户端IP。
    • 优点:
      • FULLNAT是阿里后来增加的一种模式,官方LVS可能需要打补丁。它是NAT的升级版本,回顾NAT模式发现它是靠后端机默认网关来引导应答回到LVS的,就导致LVS内网IP必须和后端机在一个物理网内,而FULLNAT则是直接修改请求的源IP为LVS内网IP,这样后端机直接按照正常IP路由回包,无论后端机IP是否和LVS内网IP在一个网段内,都可以完成应答。
    • 缺点:
      • 请求和应答的流量还是要通过LVS,性能有问题。
      • FULLNAT模式是补丁,需要重新编译内核!

再来看一下Haproxy,它就简单的多:

  • 它工作在TCP/IP的7层,因此全部基于IP路由,不存在跨网限制的问题。
  • 支持TCP和HTTP协议的负载均衡与健康检查,足以满足我们的日常需求。
  • 不需要为后端机配置默认网关,后端机可以直接通过路由器访问外网。
  • 配置简单,无需了解复杂的协议栈,简单即可靠,KISS原则!

因此,在后面的实践过程中,我们不使用keepalived提供的LVS功能,而是使用自己部署Haproxy的方式实现负载均衡。

故障转移

keepalived除了支持LVS外,其实最重要的功能是故障转移,也就是自动的主备切换,实现高可用服务。

简单来说,单点Haproxy存在故障的可能,那么我们可以部署2个Haproxy并配置keepalived进行检测,平时只会有1台提供对外的服务,当其宕机后keepalived会发现并切换服务到另外一台机器。

上面只是举个例子,其实keepalived的备份机可以部署很多,不限于1主1备,那么其原理是什么呢?答案:VRRP( Virtual Router Redundancy Protocol),即虚拟路由冗余协议。

这个协议最初是为了保证路由器的高可用出现的,keepalived只不过是拿来主义,用以解决服务的高可用问题。

VRRP工作原理并不复杂,因此只做一个简单的介绍:

原本VRRP是保证单点路由器高可用,因此会部署N个一样的路由器,它们之间通过VVRP协议互相沟通选出一个老大,这个老大会给自己的网卡绑定一个VIP,客户端通过VIP访问路由器,因此流量将最终到达老大路由器。当老大宕机后,剩余路由器将发现老大心跳停止,因此根据VRRP协议重新选举一个老大,这个新老大也会给自己的网卡绑定VIP,因此后续流量都发给了这个新的老大。

keepalived只不过把路由器这个概念换成了服务,每个服务所在的机器会运行一个Keepalived进程,由它发起VRRP协商选出谁是老大,同时检测本机的服务是否正常工作,当服务故障或者所在服务器直接宕机,其他剩余机器的keepalived会依据VRRP协议选举出新的老大负责接管流量。

关于keepalived基于VRRP工作的流程,可以参考这篇博客做一个基本了解,下面我将进入实践环节,在实践的时候逐步理解原理会更加生动。

keepalived+haproxy高可用部署

下面我将在2台服务器上部署haproxy作为反向代理,并利用keepalived的VRRP故障转移保障高可用。

启动4个虚拟机

我们启动4个centos虚拟机(virtualbox支持复制镜像),它们桥接到物理局域网内,由网关通过DHCP分配动态IP,它们可以互相访问。

取其中2个虚拟机部署haproxy+keepalived作为前端机,它们的IP地址是:

  • 172.18.9.137
  • 172.18.9.138

另外2个虚拟机分别部署一个nginx作为后端机,它们的IP地址是:

  • 172.18.9.75
  • 172.18.9.76

后端机运行nginx

给2台后端机通过yum安装nginx,直接启动即可。

之后,我们在前端机172.18.9.137上访问后端机的172.18.9.75上的nginx,确保访问成功:

前端机运行haproxy

首先去官网下载最新的stable稳定版本haproxy:

编译安装:

进入/root/haproxy,创建etc目录存放配置文件:

看一下我的haproxy.cfg配置:

相关说明已经全部注释,这是一份最小化的haproxy配置,至于更多的配置项可以从这2个途径了解:

现在启动haproxy,测试正常与异常访问情况:

可以尝试杀死一个nginx并立即访问haproxy,会发现仍旧请求正常,这是因为我们配置了合理的重试机制,当一台nginx无法联通时会立即连接另外一台nginx重试;另外,当健康检查连续3次无法访问到这台nginx时,该nginx会被持续屏蔽,直到健康检查连续3次正常访问后才解除屏蔽。

现在,我把配置好的haproxy从172.18.9.137拷贝到另外一台前端机172.18.9.138上并启动:

下面就该轮到keepalived出场了。

前端机搭建keepalived

2个haproxy前端机都要部署一个keepalived,它们各自监督本机的haproxy健康状况,并基于VRRP完成主从切换。

首先安装keepalived的依赖openssl:

之后到官网下载keepalived:

编译安装:

接下来要配置keepalived.conf,应该优先参考官方入门文档

在前面也提到过keepalived支持lvs负载均衡和fallover故障转移共2个功能,我们只会用到fallover故障转移相关的配置。

配置主机172.18.9.137:

这里配置了一个VRRP实例,它对外以172.18.9.240这个VIP服务。

keepalived定期调用haproxy_check.sh脚本检查本机haproxy是否正常工作,同时脚本也负责haproxy的重新拉起,脚本如下:

若连续3次健康检查正常则keepalived会将自己的优先级提高为priority+weight,若连续3次检测异常则将自己的优先级恢复为priority,而优先级决定了在VRPP选举时谁将成为接下来的Master。

现在我们启动172.18.9.137上的keepalived:

看一下启动日志:

keepalived调用健康脚本判定haproxy健康后,会上调自己的优先级为100+2=102,之后经过VRRP协商(现在还没有部署备机)确定自己是最高优先级进入Master状态。

查看网卡信息:

我们的enp0s3网卡除了原本的IP外,额外绑定了一个VIP:172.18.9.240,因此我们试着请求一下这个VIP的80端口,应当会访问到这台机器上的haproxy:

现在,我们给172.18.9.138部署keepalived,修改其配置如下:

和MASTER配置的差别在于:

  • 启动后的默认身份是BACKUP
  • mcast_src_ip改为自己的网卡IP
  • priority优先级改为99,主要是确保VRRP能正常的选主:
    • 当master的haproxy不健康时其优先级为100,而backup此时如果健康则优先级为99+2=101,因此backup取代master。
    • 当master的haproxy不健康时其优先级为100,而backup此时如果不健康则优先级为99,因此backup不做任何反应。
    • 当master的haproxy健康时优先级为100+2=102,backup此时如果健康优先级为99+2=101,因此backup不做任何反应。
    • 当master的haproxy健康时优先级为100+2=102,backup此时如果不健康优先级为99,因此backup不做任何反应。
    • 综上可以看出,只有master的haproxy不健康或者直接宕机的情况下,backup才会接管VIP对外服务,这正是我们想要的效果。

我们启动这个keepalived,然后在笔记本上启动一个死循环脚本请求VIP,并开始模拟故障现场:

  • 通过top观察2个前端机,发现一开始流量全部发往了Master。
  • 现在模拟Master上的haproxy不健康,方法是将健康检查脚本里的直接杀死haproxy:

不出所料,发现流量全部转移到了Backup,其网卡已绑定VIP:

  • 现在恢复Master的健康检查脚本,流量很快又回到了Master,说明VIP再次发生了飘移。
  • 现在直接杀死Master的keepalived进程,相当于模拟Master机器宕机,观察流量快速转移到了Backup。

其他

keepalived中有一些配置项与原理比较难理解,简单列举一下,希望对你有帮助:

  • VRRP协议选举的过程是基于优先级的自荐模式,具体流程可以参考这里
  • state只是初始角色,真正运行时还是依靠优先级决定谁是master谁是backup的。
  • vrrp_sync_group是VRRP实例组,将多个VRRP实例放在一起,当任意一个不健康的时候会将其他所有实例都改变为不健康状态,相当于要挂一起挂,需要你提供几个回调脚本,将所有实例都统一处理一下。
  • virtual_server是做LVS负载均衡用的配置项,我们压根不用keepalived的负载均衡,而是用它的故障转移,这是完全两码事,所以不用关注这个配置。
  • 在vrrp_instance配置中除了track_script可以判定实例健康外,还有track_interface这个配置,它可以指定若干网卡名,只要网卡故障了就判定为不健康,并不神秘。
  • 在上面的例子中,Master重启后VIP又飘移了回来,即便Backup正在正常工作,这是因为我们配置Master的优先级更高,并且keepalived默认采用抢占模式工作,并不考虑反复飘移的成本。要解决这个问题,可以给所有keepalived的vrrp_instance配置一个nopreempt选项,这样Master重启后虽然优先级很高,但是已经有其他机器在正常服务,那么就不会再去广播自己的优先级了,可以自己试验一下。

 

祝玩的愉快

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~