在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。整個切換過程都是自動化的,對用戶也是透明的,而且可以支持自動的回切,無需人工干預。客戶端在切換過程中會有一定時間的中斷。示意圖如下



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都作爲負載均衡的轉發目標時,只有當前的主節點會接收請求。示意圖如下:

第一種方法存在一個潛在的風險,如果LinuxHA沒有正常工作,同時啓動了主機和備機,可能會造成前端負載均衡器將請求依次發往主機和備機,造成數據複製衝突(這種情況出現概率很低,而且corosync可以支持仲裁設備)。作爲一個改進,可以對禁用負載均衡模式,而僅用Azure的NAT端口映射。可以在heartbeat上,增加一個腳本,當檢測到宕機時,自動調用腳本去變更前端端口映射


第三種方法,是自己採用負載均衡手段,進行主節點的路由。示意圖如下。該方法需要在每個客戶端上配置Mysql的路由器。其好處是少了一個網絡轉發的層次,所有流量都可以在內網發生,壞處是配置較爲複雜,在每個客戶端上都要維護MySQL節點列表。Mysql路由器的選擇有多種,比如用通用的負載均衡軟件LVS,HAProxy等,或者用Mysql專用的Mysql Proxy,amoeba等


3. Windows Azure上主備切換實戰

下面介紹幾種方法的組合。這些組合可以單獨使用,也可以結合使用

3.1 負載均衡+DRBD+LinuxHA

下面,我們看下采用負載均衡+DRBD複製方法進行配置的實際例子。

首先,我們在Azure上建立兩臺Linux虛擬機,OS爲CentOS 6.3。需要注意的是,建立第一臺虛擬機的時候,需要建立可用性集


建立第二臺虛擬機的時候,要加入第一臺虛擬機的雲服務,這樣才能爲他們配置負載均衡;同時也要加入第一個虛擬機建立的可用性集,這樣Azure纔會爲這兩臺虛擬機提供SLA(Azure目前不提供單臺虛擬機的SLA),這樣設置的主備切換纔有意義。


虛擬機建立好以後,還需要爲他們各自掛載一塊磁盤,作爲Mysql的數據文件存儲盤

掛載後的磁盤在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的端點頁,點擊頁面底部的添加



在對話框中選擇“添加獨立終結點”。在第二步中輸入mysql。注意要選中頁面底部的“創建負載平衡集”

在第三步中,設置端口檢測的參數。

創建完成後,進入第二臺虛擬機的端口配置頁面,添加端口,然後在第一步中選擇“將終結點添加到現有負載平衡集”,選中第一臺虛擬機定義的負載平衡集

至此,配置完畢。


下面,可以進行客戶端測試。客戶端連接的是外網地址,也就是雲服務的URL

mysql可以正常工作。此時連接的是主節點msyql1。我們在mysql1上可以看到msyql進程



接下來,我們模擬mysql1死掉的情形。模擬方法是直接重啓mysql1。同時,我們在mysql2上監控heartbeat的狀態

可以看到,mysql2上進行了接管,啓動了mysql服務。此時仍然訪問mysql的外網地址,仍然可以連上,此時連接的是Mysql2上的mysql實例


到這裏,我們已經可以實現主備切換。要保證主節點回復正常後能夠再次加入複製,我們還需要進行如下配置:

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


最後,可以進行主機的關機測試,看看備機是否能正常切換

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章