03月06, 2018

keepalived + rsync +inotify 实现真正的高效数据实时同步

背景

在公司接手处理 单点Gitlab 服务高可用改造需求,前期也测试过基于NFS/NAS的双主架构,但由于NFS/NAS的架构访问体验非常的差,其中的网络消耗成本太高。官方虽然有提供基于AWS的双主架构,但官方也强烈不建议这样。所以采用了主备方案,主备模式中,最重要的是保证两端的数据一致性,且能够实现自动的故障转移,数据实时同步方案这里我简单提一下。

  • Pacemaker Cluster + DRBD
    这是Gitlab官方主备数据同步方案 ,但实施复杂度高,配置较为复杂,后续我再补充此方案过程。
  • Keepalived + Rsync + Inotify-tools
    实施起来比较灵活,可控性高,所以这里我用此方法实现数据高可用。

如果大家有好的Gitlab双活高可用方案,希望可以交流一下。

说明

Gitlab的部署比较简单,所以本文主要分享数据的实时同步,虽然目前网上已经有很多基于rsync+inotify的资料,但是存在很多的问题。 例如双活架构的双向实时同步场景,当一端机器down机了,此时数据是同步不过去的,等该机器up恢复后,这期间就发生了部分数据不一致,那么怎么保证两端数据一致?而且大部分资料还是只要监听到数据变动就进行全量同步,这太不合理了。

本文的应用场景是主备环境下的数据实时同步,双活的数据实时同步场景,个人觉得rsync+inotify方案存在很多的缺陷,所以不建议大家使用。

部署过程

环境 说明
系统版本 Centos 6.x,x86_64
主节点 IP:10.1.1.1,主机名:1-1-1
备节点 IP:10.1.1.2,主机名:1-1-2
VIP 10.1.1.10

一、安装必备软件

yum install keepalived rsync xinetd inotify-tools -y 
#
# 注:Centos7.x 不用安装 xinetd

二、配置 rsync daemon

主备节点均需配置,这里配置了认证,也可以不加auth users、secrets file 选项,以匿名的方式进行数据传输。具体的路径,请根据实际情况做调整。

首先,配置 rsync 服务的主配置

# cat /etc/rsyncd.conf 
# 创建配置文件,写入以下内容。
-------------------以下为文件内容-------------------
# /etc/rsyncd: configuration file for rsync daemon mode
uid = root
gid = root
# use chroot = yes
max connections = 4
pid file = /var/run/rsyncd.pid
exclude = lost+found/
# transfer logging = yes
# timeout = 900
# ignore nonreadable = yes
# dont compress   = *.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2

[gitlab_data]
    path = /var/opt/gitlab
    comment = Gitlab Data Directory.
    read only = no
    auth users = gitlab
    secrets file = /etc/rsyncd/gitlab_rsyncd.pass

创建认证文件

# 创建存放密码认证文件目录
mkdir /etc/rsyncd

# 创建服务端认证文件
echo "gitlab:yourpass" > /etc/rsyncd/gitlab_rsyncd.pass

# 创建客户端认证文件
echo "yourpass" > /etc/rsyncd/gitlab_client.pass

接下来,配置xinetd服务

注:Centos 7.x 不需此步骤,如要自定义配置文件路径,可编辑 /usr/lib/systemd/system/rsyncd.service

# cat /etc/xinetd.d/rsync
# 编辑此文件,启用 rsync,并指定配置文件路径。
-------------------以下为文件内容-------------------
# default: off
# description: The rsync server is a good addition to an ftp server, as it \
#   allows crc checksumming etc.
service rsync
{
    disable = no             # yes ---> no
    flags       = IPv6
    socket_type     = stream
    wait            = no
    user            = root
    server          = /usr/bin/rsync
    server_args     = --daemon
    # 可自定义了配置文件路径,默认是 /etc/rsyncd.conf
    # server_args     = --daemon --config=/your/path/rsyncd.conf
    log_on_failure  += USERID
}

最后,启动服务并设置开机启动

# Centos6.x
service xinetd start
chkconfig xinetd on

# Centos7.x
systemctl start rsyncd.service
systemctl enable rsyncd.service

三、编写inotifywait脚本

脚本无需手动运行,会由后面的Keepalived程序根据主备角色变化情况,自动的运行和关闭。文件名和文件路径,请根据自己情况调整。

#!/bin/bash
# File name:job_gitlab_data_realtime_sync.sh
# File path: /opt/script/job_gitlab_data_realtime_sync.sh
src_dir="/var/opt/gitlab-data"
rsync_module_name="gitlab_data"

# 如需多个节点同步,可以填写多个IP
# 用这种判断主机名的方法,实现通用的脚本
# 就无需每个节点都得单独修改脚本的 dest_ip 变量内容了。
if [[ $(hostname) =~ 1-1-1 ]];then
    dest_ip="10.1.1.2"
else
    dest_ip="10.1.1.1"
fi

rsync_user="gitlab"
rsync_pass="/etc/rsyncd/gitlab_client.pass"

#
. /etc/init.d/functions

# 开启inotifywait文件监控,当文件发生变化进行文件同步
cd ${src_dir}
inotifywait -mrq -e modify,attrib,close_write,move,create,delete --format '%e %w%f' ./ |
while read file;do
    INO_EVENT=$(echo $file | awk '{print $1}')      # 把inotify输出切割 把事件类型部分赋值给INO_EVENT
    INO_FILE=$(echo $file | awk '{print $2}')       # 把inotify输出切割 把文件路径部分赋值给INO_FILE

    # 判断事件类型
    #增加、修改、写入完成、移动进事件
    #增、改放在同一个判断,因为他们都肯定是针对文件的操作,即使是新建目录,要同步的也只是一个空目录,不会影响速度。
    if [[ $INO_EVENT =~ 'CREATE' ]] || [[ $INO_EVENT =~ 'MODIFY' ]] || [[ $INO_EVENT =~ 'CLOSE_WRITE' ]] || [[ $INO_EVENT =~ 'MOVED_TO' ]];then
        for ip in ${dest_ip};do
            rsync -azcR --password-file=${rsync_pass} $(dirname ${INO_FILE}) ${rsync_user}@${ip}::${rsync_module_name}
            # INO_FILE变量代表文件路径  -c校验文件内容
            #仔细看 上面的rsync同步命令 源是用了$(dirname ${INO_FILE})变量 即每次只针对性的同步发生改变的文件的目录
            # (只同步目标文件的方法在生产环境的某些极端环境下会漏文件 现在可以在不漏文件下也有不错的速度 做到平衡)
            # 然后用-R参数把源的目录结构递归到目标后面 保证目录结构一致性
        done
    fi

    #修改属性事件(指 touch chgrp chmod chown等操作)
    if [[ $INO_EVENT =~ 'ATTRIB' ]];then
        # 如果修改属性的是目录 则不同步,因为同步目录会发生递归扫描,等此目录下的文件发生同步时,rsync会顺带更新此目录。
        if [ ! -d "$INO_FILE" ];then
            for ip in ${dest_ip};do
                rsync -azcR --password-file=${rsync_pass} $(dirname ${INO_FILE}) ${rsync_user}@${ip}::${rsync_module_name}
            done
        fi
    fi

    #删除、移动出事件
    if [[ $INO_EVENT =~ 'DELETE' ]] || [[ $INO_EVENT =~ 'MOVED_FROM' ]];then
        for ip in ${dest_ip};do
            rsync -azcR --delete --password-file=${rsync_pass} $(dirname ${INO_FILE}) ${rsync_user}@${ip}::${rsync_module_name}
            #看rsync命令 如果直接同步已删除的路径${INO_FILE}会报no such or directory错误 所以这里同步的源是被删文件或目录的上一级路径,并加上--delete来删除目标上有而源中没有的文件,这里不能做到指定文件删除,如果删除的路径越靠近根,则同步的目录月多,同步删除的操作就越花时间。这里有更好方法的同学,欢迎交流。
        done
    fi
done

优化 inotify

如果不进行优化,遇到瓶颈会有如下错误提示

Failed to watch /your/path; upper limit on inotify watches reached!
Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches'.

查看当前参数值

# cat /proc/sys/fs/inotify/max_*
# max_queued_events   max_user_instances  max_user_watches
16384
128
8192

主要优化 max_user_watches 及 max_queued_events ,具体大小可以等于当前数据盘的 inode最大值(我这里是1T数据盘设定的大小)

echo "67108864" > /proc/sys/fs/inotify/max_user_watches
echo "67108864" > /proc/sys/fs/inotify/max_queued_events

# 将下面参数写入 /etc/sysctl.conf,永久生效
fs.inotify.max_user_watches = 67108864
fs.inotify.max_queued_events = 67108864

四、配置 Keepalived

编辑主配置文件

不同的节点内容需根据实际情况稍作修改。

# cat /etc/keepalived/keepalived.conf
# 编辑配置文件,写入以下内容。
-------------------以下为文件内容-------------------
# ! Configuration File for keepalived

global_defs {
   notification_email {
     opsarno@qq.com
   }
   notification_email_from 1-1-1@opsarno.com    # 其它的节点需稍作修改
   smtp_server 127.0.0.1
   smtp_connect_timeout 30
   router_id 1-1-1    # 其它的节点需稍作修改
}

# 当 gitlab 服务不可用时,关闭Keepalived服务。
vrrp_script check_gitlab_service {
   script "/etc/keepalived/script/check_gitlab_service.sh"
   interval 1
}

vrrp_instance gitlab_v1 {
    state BACKUP    # 两个节点都要配置为BACKUP
    interface eth0
    virtual_router_id 51
    priority 100    # 其它的节点需稍作修改
    nopreempt    # 启用此选项
    advert_int 1
    unicast_src_ip 10.1.1.1
    unicast_peer {
        10.1.1.2
    }
    authentication {
        auth_type PASS
        auth_pass gitlab_pass
    }
    virtual_ipaddress {
        10.1.1.10
    }
    track_script {
        check_gitlab_service
    }
    # 当前角色为主时,启动实时同步脚本
    notify_master "/etc/keepalived/script/start_gitlab_data_realtime_sync.sh"
    # 当前角色为备时,关闭实时同步脚本
    notify_backup "/etc/keepalived/script/stop_gitlab_data_realtime_sync.sh"
    # 当Keepalived被关闭时,发送邮件通知
    notify_stop "/etc/keepalived/script/email_to_admin.sh"
}

nopreempt 说明:

通常如果 MASTER 服务死掉后,BACKUP 会提权变成 MASTER;但是当 MASTER 服务又好了的时候,MASTER 此时会重新抢占VIP,这样就会发生两次VIP切换。

对于我们当前的数据实时同步场景,这种切换会导致数据异常问题,即当 MASTER 主机down,BACKUP会提为MASTER并启用实时同步,但是主节点网络不通,会有部分数据同步不过去,如果此时主节点up恢复后,启用数据同步,会有数据断层发生。

所以我们要在配置文件中启用 nopreempt ,但是这个参数只能用于state为 BACKUP 的配置中,故这里主备节点的state都设置成 BACKUP 。

服务检查、启动、关闭的同步脚本及邮件通知,大家根据自己情况编写即可。

启动 keepalived 服务

# 启动Keepalived服务
service keepalived start
#
# 设置开机启动
chkconfig keepalived on

全量同步场景

当机器某些原因导致重启时,另一个节点变成主角色并接管VIP,但是数据同步尽管启动着,由于本端机器down掉了,数据就传不过来,所以需要在各个节点的 rc.local 中增加数据全量同步命令。

既只要机器发生重启现象,就执行一次从对端(说明:此时对端肯定是主角色)拉取全量数据的动作。

# 在 rc.local 文件最后面,追加如下内容。
# tail -2 /etc/rc.local
# 如果机器发生重启,全量拉取 gitlab_data 数据
/opt/script/rc_local_pull_gitlab_data.sh


# 实际调用脚本中的执行命令如下
# cat /opt/script/rc_local_pull_gitlab_data.sh
#!/bin/bash
# 如果机器发生重启,全量从对端(此时对端一定是主)拉取gitlab_data数据
#
if [[ $(hostname) =~ 1-1-1 ]];then
    dest_ip='10.1.1.2'
else
    dest_ip='10.1.1.1'
fi
rsync -az gitlab@${dest_ip}::gitlab_data --password-file=/etc/rsync/gitlab_client.pass /var/opt/gitlab-data/

至此,数据同步相关的配置完成。

参考资料:运维生存时间:真正的inotify+rsync实时同步 彻底告别同步慢

本文链接:https://fandenggui.com/post/keepalived-rsync-inotify.html

-- EOF --

Comments