高可用实践——Keepalived踩坑记录【转】

项目中需要对调度系统做高可用,以确保系统的稳定性;经过分析,决定采用Keepalive这一经典的高可用解决方案实现,踩坑记录如下。

1. 技术调研

1.1 高可用解决方案

本人之前在一些小模块写过基于 ZooKeeper分布式锁 实现的高可用,但调度系统的模块较多,并没有什么 较好的、第三方的 手段去往zk注册信息,除非在写框架底层的时候就往zk注册。因此,我们只能对进程本身做一些监控,实现“整个系统”的高可用,而不是某个模块的高可用。

之前对市面上的三个调度框架——Azkaban、Airflow、DolphinScheduler,做了较为深入的调研,之后有时间再发出来。他们对于高可用的实现,原生只有ds是有这个机制的,以下做一些简单介绍:

  • Azkaban直接就没有管高可用,本人也没有使用过其他开源的项目去实现高可用,但按照我的使用经验,他的WebServer还是很稳健的。
  • Airflow原生也不支持高可用,之前关注他们的邮件列表,有人提到了这个issue,但这块好像并不是社区关心的重点,也就没有再提。github有一个 开源的小项目 ,通过监控进程并依据结果,往mysql或者zk里面发送心跳包,来决定是否切换主备。这个项目的年龄有点大,在1.10.x版本之前比较好用,现在airflow的scheduler进程自带一个supervisor,有可能会对这个主备切换过程造成一些困扰,例如造成 脑裂 等。
  • DolphinScheduler原生支持高可用,在 配置 时就要指定好主备,但他这个强依赖于zk,看起来有点重,这里我截取ds的心跳线程 HeartBeatTask的run方法 。我不能像ds一样去往zk发心跳包,只能采取一些外置的方法。
    StringBuilder builder = new StringBuilder(100);
    builder.append(OSUtils.cpuUsage()).append(COMMA);
    builder.append(OSUtils.memoryUsage()).append(COMMA);
    builder.append(OSUtils.loadAverage()).append(COMMA);
    builder.append(OSUtils.availablePhysicalMemorySize()).append(Constants.COMMA);
    builder.append(maxCpuloadAvg).append(Constants.COMMA);
    builder.append(reservedMemory).append(Constants.COMMA);
    builder.append(startTime).append(Constants.COMMA);
    builder.append(DateUtils.dateToString(new Date())).append(Constants.COMMA);
    builder.append(status).append(COMMA);
    //save process id
    builder.append(OSUtils.getProcessID());
    zookeeperRegistryCenter.getZookeeperCachedOperator().update(heartBeatPath, builder.toString())

1.2 Keepalived原理

这部分原理我也是一知半解,由于我不做os层面的开发,所以也没有太足的动力刨根问底。Keepalived大概是依据一个VRRP协议,利用VRRP维持主备节点的心跳[1],部署方式比较简单,一般用于Web应用的高可用,最常见和Nginx一起使用。

还有一个高可用解决方案Heartbeat,这两者有一些差别[2]。根据运维同学的经验,用keepalived基本上能解决大部分问题(它们也会用keepalived做mysql的高可用),遂采用该方式。

2. 配置Keepalived

先对调度系统的各进程做一个简介:两台机器记为A和B,A上起一个master一个slave,B上起一个slave,并且B作为A上master的备机,两个slave都只会和活着的那个master通信;master和slave都只有一个进程,不像airflow那样,webserver和scheduler是分开的两个进程。以下是踩过的巨坑。。

2.1 主备是否都活?

网上有很多参考资料,附上之前主要参考的两篇文章[3][4]。但单纯Web服务器的高可用,和本人这次做的调度系统的高可用有些不一样。以两台为例,会把两台机器都起起来,再通过抢vip实现只有一台对外提供服务。

但我的调度机,WebServer和Scheduler是在一个进程里面,如果我分别在主备起两个服务,那它们都会触发调度任务,但此时备机没有分配slave,备机的调度任务都会被阻塞,一旦主备切换,slave连上备机,备机会先执行之前阻塞的任务,这显然是不合理的。

因此,我在部署时应该只让主节点的master活着,备节点不应该有这个进程存在,否则会造成脑裂。所以我在参考博客的时候,之前没有考虑到这个问题,按照他们的配置,我始终都在和脑裂做斗争。如果有朋友看到这篇文章,“主备是否都活”是非常需要考虑的问题。

2.2 主备如何通信?

由于网上的博客几乎都只有一个安装和配置过程,尤其是涉及到“vrrp_script”,只放一个脚本,并没有人去解释这个脚本的使用场景,以及主机和备机的check脚本是否一样,完全没有人解释这些问题,我踩了太多的坑。

刚上手的时候我一直理解为,主备通过心跳包沟通,只有获取vip的主节点才会做这个“track_script”的动作,这个认知也是和我之前使用分布式锁的通信方式是一致的,但实际使用时主备永远连不上,备节点总是先进入BACKUP又马上进入MASTER,导致我不停地发生脑裂,非常痛苦。

通过查看日志和对进程的分析,我之前的认知是错误的(也可能是我对其原理的不熟悉导致,非科班同学需要系统学习一下网络,TODO),主备都会周期地做“track_script”动作,keepalived只负责这个vip的归属。

2.3 vip从哪里来?

网上资料还有一个问题没有交代清楚,设置的这个vip是哪里来的?在虚拟机上,似乎是可以随便指定一个没用过的ip作为vip(没有验证过,TODO)。但在现在流行的云服务的生产环境,这个vip是要申请的!这个问题可以通过telnet判断。

telnet $vip $host

2.4 配置文件踩坑

安装很简单,如果没有外网只能手动编译安装。

yum install keepalived -y

配置主要参考云服务文档 CentOS 6.X下配置Keepalived VIP 。这里面还有几个问题:

  1. 主备的“router_id”是否一致?博客上是随便配,文档上是必须一致,我尝试了随便配没有成功,现在先保持一致(没有进一步验证,TODO);
  2. “script_user“是keepalived执行脚本的用户,一般情况这个用户需要sudo权限;
  3. 按照文档,必须加“unicast_peer”,我还加上了“unicast_src_ip”,这个和云服务厂商有关,如果允许组播那就组播,不允许就只能单播[5]
  4. 关于“state”,也有两种配置方式[6],抢占式和非抢占式,我配置的是抢占式的,通过实践,在配置“state”分别为MASTER和BACKUP的情况下,“nopreempt”参数没有用;
  5. 关于“vrrp_script”,这个是keepalived周期执行的脚本,参考[7][8],由于我是“抢占式”的配置,“weight”随便给个2就行。

2.5 具体配置

主节点keepalived配置:

global_defs {
    router_id LVS_DEVEL
    script_user $script_user
}

vrrp_script check_xxx {
    script "$script_path"
    interval 60
    weight 2
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 150
    advert_int 1
    unicast_src_ip $node1
    unicast_peer {
        $node2
    }
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
        check_xxx
    }
    virtual_ipaddress {
        $vip dev eth0
    }
}

备节点keepalived配置:

global_defs {
    router_id LVS_DEVEL
    script_user $script_user
}

vrrp_script check_xxx {
    script "$script_path"
    interval 60
    weight 2
}

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    unicast_src_ip $node2
    unicast_peer {
        $node1
    }
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
        check_xxx
    }
    virtual_ipaddress {
        $vip dev eth0
    }
}

check脚本,涉及到公司隐私,不放出来了,简单描述下:

#!/bin/bash

# 检查tomcat进程
A=$(ps aux | grep -v grep | grep org.apache.catalina.startup.Bootstrap | wc -l)

# 检查vip的状态
ip a | grep $vip

# 如果发现vip且服务宕机, 则尝试拉起 -> 如果主备都活的情况不需要检查vip
if [ $? -eq 0 ] && [ $A -lt 1 ]; then
    
    # 杀掉另一个服务, 即使不一定存在, 防止脑裂 
    ...
    
    # 启动服务并等待3秒
    ...

    # 再检查tomcat进程
    B=$(ps aux | grep -v grep | grep org.apache.catalina.startup.Bootstrap | wc -l)

    # 如果没拉起, 则把vip漂到备机
    if [ $B -lt 1 ]; then
        sudo service keepalived stop
    fi
fi

2.6 其他

至于测试我就不测了,引的资料里面有很多,大概的步骤就是:先把主节点keepalived起起来,然后起从节点keepalived,然后关掉主节点keepalived,如果配置正确那vip应该会漂到从节点,服务也会转移到从节点。

需要注意的是,一般情况下认为调度系统还是很稳定的,不会发生宕机;次一级的情况,把keepalived当成supervisor用,拉起主服务;再次一级的情况,切换到备机并通知;如果备机都故障了,此时应该人为介入了,而不是再切回主服务;调度系统扛着一大堆SLA指标,如果出问题了是要背事故的,把vip漂来漂去没意义,只要做个临时方案保证服务基本可用就行。

3. 后续的问题

尽管做了主备,但调度系统仍然会在主备切换的时候有短暂的不可用状态,造成那个时间段正在执行的任务直接失败。首先考虑下开源的框架:

  • 如果我们对azkaban的webserver做高可用,也会存在这个问题;
  • 对airflow的scheduler做高可用,由于airflow会周期检查mysql数据库,失败的任务也会拉起,但这个周期很短大约在秒级,有时候反而会造成一些性能问题;
  • ds不清楚,我用的不多(抽空补上,TODO)。。

因此在这个系统里,还需要开发一个周期拉起失败任务的进程,这个周期达到小时级即可。

参考

  1. ^Keepalived原理 https://blog.csdn.net/qq_24336773/article/details/82143367
  2. ^高可用开源方案 Keepalived VS Heartbeat对比 https://blog.csdn.net/yunhua_lee/article/details/9788433
  3. ^Keepalived之——Keepalived + Nginx 实现高可用 Web 负载均衡 https://blog.csdn.net/l1028386804/article/details/72801492?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-4.nonecase
  4. ^keepalived实现Tomcat服务双机热备 https://blog.csdn.net/u013256816/article/details/49127995
  5. ^【keepalived】关于keepalived配置中的 mcast_src_ip 和 unicast_src_ip https://www.jianshu.com/p/7c709c3be4a9
  6. ^Keepalived https://www.jianshu.com/p/a6b5ab36292a
  7. ^keepalived之vrrp_script详解 https://www.cnblogs.com/arjenlee/p/9258188.html
  8. ^keepalived 关于vrrp_script中的weight这个值的使用 https://blog.csdn.net/qq_41814635/article/details/84166700

转自

高可用实践——Keepalived踩坑记录 - 知乎
https://zhuanlan.zhihu.com/p/148136167

posted @ 2022-01-05 09:44  paul_hch  阅读(411)  评论(0编辑  收藏  举报