在Windows Azure上配置Linux VM主备切换

对任何一个上线系统来说,高可用设计是不可或缺的一个环节,这样才可以确保应用可以持续、稳定的运行,而不是频繁的掉线、停机。高可用设计的核心思路很简单,就是消除一切单点故障,将单点链路或者节点升级为多点。比如,对于Web类型的应用,可以利用Web集群和负载均衡器实现多活,而对于数据库、文件服务这类服务,一般较难配置为多活(Mysql cluster天然支持高可用,但至少需要4个节点。本文仅讨论Mysql主备配置的场景),于是常采用主备切换的方式,即备机上的服务处于离线状态,当主机故障时,备机升级为主机,继续提供服务。


1. 主备切换原理概述

要实现主备切换,需要在几个层面做好准备:

  • 数据的转移:将主节点的数据实时复制到备机,确保主节点死掉后备机拥有最新的数据。一般有三种实现机制:共享磁盘、磁盘层复制、应用层复制
  • 服务的转移:主节点服务死掉后,备机上的服务能立即启动。这需要一些第三方软件的支持,进行主机状态的监控,并进行自动化切换。
  • 端点的转移:主备切换发生后,服务运行的位置发生了变化。为了让客户端能够继续连接服务,需要为客户端提供透明的访问机制,常用的做法有:IP地址漂移、动态路由

以Linux上的Mysql为例,其通常的配置方式如下:

  • 数据转移:共享磁盘一般需要SAN存储或者iSCSI存储,磁盘级复制一般采用DRBD,应用层复制采用Replication。由于磁盘级复制性能更高,一般Mysql采用磁盘级复制进行主备复制,采用Replication进行主从复制(从数据库处理读请求)或者容灾远程复制
  • 服务的转移:一般采用Linux HA或者keepalived
  • 端点的转移:一般采用VIP漂移,也是利用Linux HA/keepalivedt实现

其中Linux HA/keepalived是自动化软件,能够自动检测服务状态,在故障时进行服务和IP的切换。本例中我们主要讨论Linux HA,LinuxHA由两个模块构成,一个是心跳检测模块,可采用heartbeat或者corosync,另一个是自动资源切换,为pacemaker。DRBD+LinuxHA方案需要两台Mysql服务器,一主一备,各有一个IP地址,主机还有额外一个VIP,作为客户端连接地址,两台服务器无需共享磁盘,DRBD实时复制所有的磁盘数据到备机。MySQL服务只在主机上运行。heartbeat/corosync检测主机上Mysql服务的状态,如果Mysql死掉,则pacemaker会将主机上的所有服务停止(包括DRBD复制),释放VIP,然后将备机提升为主机,启动原备机上的MySQL,进行备机上的DRBD反向复制,最后在新的主机上启用VIP。整个切换过程都是自动化的,对用户也是透明的,而且可以支持自动的回切,无需人工干预。客户端在切换过程中会有一定时间的中断。示意图如下

在Windows Azure上配置Linux VM主备切换_第1张图片


2. Windows Azure上实现主备切换的技术原理

在Windows Azure上,配置这种主备集群的方式如下:

  • 共享磁盘:Windows Azure目前不支持共享磁盘,不过我们可以添加一个脚本,在故障切换时将数据磁盘从主节点解绑,然后挂载给备节点
  • 磁盘级复制:可以用DRBD
  • 数据级复制:SQL replication
  • 服务转移:建议用LinuxHA
  • 端点转移:方式一,用Azure负载均衡功能;方式二:用脚本动态定义虚拟机端点;方式三:在客户端进行路由选择

我们来对比下各种方式的差别

节点切换

方式 好处 坏处
1. 负载均衡器转发请求到存活节点 配置比较简单 可能出现脑裂问题(后台两个节点都认为自己是主节点,都接收请求)
2. 脚本控制端点定义 不会出现脑裂 切换时间较长,脚本执行时间可能需要几十秒
3.在客户端通过Proxy进行路由选择 延迟最低,无需经过NAT/负载均衡器 多个客户端节点对于主节点的认定可能不一致,从而产生脑裂

数据复制

方式 好处 坏处
1. DRBD磁盘复制 配置比较简单 可能出现脑裂问题(后台两个节点都认为自己是主节点,导致复制异常)
2. 脚本控制磁盘挂载 不会出现脑裂 切换时间较长,脚本执行时间可能需要几十秒
3.MySQL复制 配置较为简单 MySQL复制是异步的,可能导致复制冲突或数据丢失


下面,具体看下节点切换的几种实现方式

第一种方法,是利用Azure提供的负载均衡功能进行主备选择。集群的配置跟传统方式类似,只是在LinuxHA上不需要配置VIP,也就是说,LinuxHA只管理DRBD和mysql的切换,而不去管IP的切换。由于LinuxHA保证同一时刻只有一个Mysql实例存活,那么两台VM的Mysql端口也只有一个开放。Azure的负载均衡器可以自动检测端口状态,它只会将请求发给活的端口,当两个VM都作为负载均衡的转发目标时,只有当前的主节点会接收请求。示意图如下:

在Windows Azure上配置Linux VM主备切换_第2张图片

第一种方法存在一个潜在的风险,如果LinuxHA没有正常工作,同时启动了主机和备机,可能会造成前端负载均衡器将请求依次发往主机和备机,造成数据复制冲突(这种情况出现概率很低,而且corosync可以支持仲裁设备)。作为一个改进,可以对禁用负载均衡模式,而仅用Azure的NAT端口映射。可以在heartbeat上,增加一个脚本,当检测到宕机时,自动调用脚本去变更前端端口映射

在Windows Azure上配置Linux VM主备切换_第3张图片

第三种方法,是自己采用负载均衡手段,进行主节点的路由。示意图如下。该方法需要在每个客户端上配置Mysql的路由器。其好处是少了一个网络转发的层次,所有流量都可以在内网发生,坏处是配置较为复杂,在每个客户端上都要维护MySQL节点列表。Mysql路由器的选择有多种,比如用通用的负载均衡软件LVS,HAProxy等,或者用Mysql专用的Mysql Proxy,amoeba等

在Windows Azure上配置Linux VM主备切换_第4张图片


3. Windows Azure上主备切换实战

下面介绍几种方法的组合。这些组合可以单独使用,也可以结合使用

3.1 负载均衡+DRBD+LinuxHA

下面,我们看下采用负载均衡+DRBD复制方法进行配置的实际例子。

首先,我们在Azure上建立两台Linux虚拟机,OS为CentOS 6.3。需要注意的是,建立第一台虚拟机的时候,需要建立可用性集

在Windows Azure上配置Linux VM主备切换_第5张图片

建立第二台虚拟机的时候,要加入第一台虚拟机的云服务,这样才能为他们配置负载均衡;同时也要加入第一个虚拟机建立的可用性集,这样Azure才会为这两台虚拟机提供SLA(Azure目前不提供单台虚拟机的SLA),这样设置的主备切换才有意义。

在Windows Azure上配置Linux VM主备切换_第6张图片

虚拟机建立好以后,还需要为他们各自挂载一块磁盘,作为Mysql的数据文件存储盘

在Windows Azure上配置Linux VM主备切换_第7张图片

挂载后的磁盘在VM里面的位置是/dev/sdc


接下来,就可以安装正常的步骤安装配置DRBD和LinuxHA了,这里我们以Heartbeat+Pacemaker为例。详细配置步骤可参考 http://www.linuxidc.com/Linux/2012-11/73833.htm

配置时,有几个地方需要注意下:

1. 安装kmod-drdb时,需要升级内核。升级时,需要用到163的yum源。原步骤里面有一步是

mv CentOS6-Base-163.repo / /etc/yum.repos.d 应该改成mv CentOS6-Base-163.repo /etc/yum.repos.d,另外,需要将/etc/yum.repos.d里面的CentOS-Base.repo移出该目录,否则Base库会有多个源。

2. 执行yum操作时,有时会遇到找不到更新包的情况。在Azure的CentOS里面屏蔽了核心包的安装,为了安装核心包,可以在yum命令后加上--disableexcludes=main参数,或者修改/etc/yum.conf,将exclude=kernel*去掉。另外,也可以试试yum clean metadata清楚本地包缓存

3. 升级完内核后需重启

4. 不需要配置hosts文件,Azure可以直接解析主机名为内网地址

5. drbd.conf配置文件里面,disk地址改为/dev/sdc

6. drbd测试好后,分别在主备机上安装mysql,配置my.conf将datadir指向/drbd/mysql。暂时不要启动mysql服务,也不要加入自启动

7. heartbeat配置haresources时,不要管理IP地址,即去掉IPaddr::192.168.159.250/24/eth0。另外,最后面的nginx改为mysqld

8. 如果heartbeat无法启动,报drbd模块无法加载的错误,可手动执行modprobe drbd 及 drbdadm up all命令

DRBD+heartbeat+mysql配置完成后,可以进行测试,可以看看mysql是不是可以自动切换


最后,开始配置Azure负载均衡。进入虚拟机1的端点页,点击页面底部的添加

在Windows Azure上配置Linux VM主备切换_第8张图片


在对话框中选择“添加独立终结点”。在第二步中输入mysql。注意要选中页面底部的“创建负载平衡集”

在Windows Azure上配置Linux VM主备切换_第9张图片

在第三步中,设置端口检测的参数。

在Windows Azure上配置Linux VM主备切换_第10张图片

创建完成后,进入第二台虚拟机的端口配置页面,添加端口,然后在第一步中选择“将终结点添加到现有负载平衡集”,选中第一台虚拟机定义的负载平衡集

至此,配置完毕。


下面,可以进行客户端测试。客户端连接的是外网地址,也就是云服务的URL

在Windows Azure上配置Linux VM主备切换_第11张图片

mysql可以正常工作。此时连接的是主节点msyql1。我们在mysql1上可以看到msyql进程

在Windows Azure上配置Linux VM主备切换_第12张图片

接下来,我们模拟mysql1死掉的情形。模拟方法是直接重启mysql1。同时,我们在mysql2上监控heartbeat的状态

在Windows Azure上配置Linux VM主备切换_第13张图片

可以看到,mysql2上进行了接管,启动了mysql服务。此时仍然访问mysql的外网地址,仍然可以连上,此时连接的是Mysql2上的mysql实例

在Windows Azure上配置Linux VM主备切换_第14张图片


到这里,我们已经可以实现主备切换。要保证主节点回复正常后能够再次加入复制,我们还需要进行如下配置:

1. 在每台VM的/etc/rc.local末尾,加上

modprobe drbd
drbdadm up all

2. 设置heartbeat为自启动:chkconfig --add heartbeat

这样,我们可以轮流重启两台VM:Mysql1重启-》mysql2变为主节点-》Mysql1重启结束,重新变为主节点-》Mysql2重启,对服务无影响-》Mysql2重启结束,回复初始状态


3.2 动态端点定义+MySQL复制+LinuxHA

MySQL部分,需要配置为双活双向复制(主主复制)。这样,无论请求从那边进来,都可以复制到两个库。MySQL主主复制的配置网上有很多资料,这里不进行赘述。需要说明的一点,是MySQL复制是异步的,可能有数据不同步的风险

主主复制为了避免数据复制冲突,最好选择一个作为主库。我们可以通过Azure的端点定义将外部请求发送到当前的主库上。当主备切换时,我们可以自动触发脚本将端点定义修改,让备库接受请求。

实现动态端点定义,需要几个配置步骤:
1. 与上一节一样,首先需要两台MySQL虚拟机,它们创建在同一个云服务下面,并配置相同的可用性集。云服务配置一个对外的端口,比如3306,初始时指向主机的3306端口
2. 利用Heartbeat进行双机互相监测。当主机故障时,Heartbeat调用切换脚本获取集群的资源
3. 编写脚本进行MySQL集群对外端口的绑定与释放

Heartbeat的配置在上一节已经介绍。针对动态端点定义,需要修改的地方是主机和备机的/etc/ha.d/haresources. 下面是一个例子:

VM1 AzureEndpoint
这里面VM1代表主节点,后面的AzureEndpoint代表双机要争抢的资源。即该资源要么给主机,要么给备机,不能双机都获取该资源。这里面我们并没有将MySQL定义为竞争资源,是因为MySQL会在双机都运行

Heartbeat并没有实现Azure端点资源的配置脚本,因此我们要开发一个这样的脚本,并放在/etc/init.d下面

这个脚本是linux服务的格式,我们可以用service命令进行该脚本的测试,比如service AzureEndpoint start或者service AzureEndpoint stop。服务被启动,就是当前节点获取Azure端点定义的过程,脚本会使用Azure命令行工具将另外一个节点的外部端点定义删除,然后给自己节点定义端点定义,让外部用户可以访问本机

下面是一个脚本的例子,我们可以根据情况修改脚本的内容,比如内部端口号、外部端口号、网络协议。此外,我们还需要预先下载Azure 的命令行工具,并配置Azure账号。Azure命令行工具的下载地址在http://go.microsoft.com/fwlink/?linkid=253472&clcid=0x804

#!/bin/bash

HOSTNAME=`hostname`
PAIRHOST=VM1
VM_PORT=3306
EXTERNAL_PORT=3306
PROTOCOL=tcp
PORT_NAME=$PROTOCOL-$EXTERNAL_PORT-$VM_PORT
AZURE_CLI_PATH=/usr/bin
AZURE_LOG=/var/log/azure.log
HOME=/root

log() {
        #echo "$1"
        echo `date`' AzureIP: '"$1"  >> $AZURE_LOG
}

start (){
        log "Start"
        command="$AZURE_CLI_PATH/azure vm endpoint list $HOSTNAME |grep $PORT_NAME"
        log "$command"
        result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint list $HOSTNAME |grep $PORT_NAME`
        if [ -z "$result" ]; then
                command="$AZURE_CLI_PATH/azure vm endpoint list $PAIRHOST |grep $PORT_NAME"
                log "$command"
                result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint list $PAIRHOST |grep $PORT_NAME`

                if [ -z "$result" ]; then
                        log "No endpoint defined"
                else
                        log "The pair host has the definition. Redefine to localhost"
                        command="$AZURE_CLI_PATH/azure vm endpoint delete $PAIRHOST $PORT_NAME"
                        log "$command"
                        result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint delete $PAIRHOST $PORT_NAME`

                        if [ -n "$result" ]; then
                                log "$result"
                        fi
                fi
                log "Define in localhost"
                command="$AZURE_CLI_PATH/azure vm endpoint create -o $PROTOCOL $HOSTNAME $EXTERNAL_PORT $VM_PORT"
                log "$command"
                result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint create -o $PROTOCOL $HOSTNAME $EXTERNAL_PORT $VM_PORT`

                if [ -n "$result" ]; then
                        log "$result"
                fi

        else
                log "Already defined in localhost"
        fi

        return 0
}


stop() {
        log "Stop"
        command="$AZURE_CLI_PATH/azure vm endpoint list $HOSTNAME |grep $PORT_NAME"
        log "$command"
        result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint list $HOSTNAME |grep $PORT_NAME`
        if [ -n "$result" ]; then
                command="$AZURE_CLI_PATH/azure vm endpoint delete $HOSTNAME $PORT_NAME"
                log "$command"
                result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint delete $HOSTNAME $PORT_NAME`

                if [ -n "$result" ]; then
                        log "$result"
                fi
        else
                log "Not Running"
        fi

        return 0
}

status() {
        log "Status check"
        command="$AZURE_CLI_PATH/azure vm endpoint list $HOSTNAME |grep $PORT_NAME"
        log "$command"
        result=`HOME=/root $AZURE_CLI_PATH/azure vm endpoint list $HOSTNAME |grep $PORT_NAME`

        if [ -z "$result" ]; then
                echo "Not Running"
        else
                echo "OK"
        fi
}

# See how we were called.
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  status)
    status
    ;;
  *)
    echo $"Usage: $0 {start|stop|status}"
    exit 2
esac

exit $?

接下来就可以进行测试了。首先可以分别在主节点和备机上运行server AzureEndpoint start/stop命令,从Azure管理门户上观察,看看对外端点是不是能否正常的被切换。上面的脚本的日志可以从/var/log/azure.log查看


如果可以,就可以测试heartbeat. 用service heartbeat start/stop命令,看看是否可以正常切换。heartbeat的日志在/var/log/ha-log


最后,可以进行主机的关机测试,看看备机是否能正常切换

你可能感兴趣的:(windows,mysql,HA,HA,azure)