集羣高可用代理實踐分享

前言

在 Kubernetes 集羣的 高可用拓撲選項 [1] 中,介紹了集羣高可用的兩個方案:

我們知道實現高可用架構的本質就是在做冗餘

對於 Kubernetes 集羣的高可用架構:

其中控制平面節點的三個核心組件:

可見,kube-apiserver 的高可用性將決定整個集羣的高可用(etcd 高可用同樣極其重要,但不在本文探討範圍內)。

由於 apiserver 本質上是一個無狀態的 HTTP API 服務,因此,實現 apiserver 的高可用性,本質上就是實現 web 服務器的高可用性,可以通過爲其增加可水平擴容的負載均衡器(load balancer ,簡稱 lb )。

後續用戶( kubectl 、 dashbaord 等其他客戶端)和集羣內部的組件都將通過訪問 lb 來訪問 apiserver 。

這下問題就變成了 如何實現 lb 的高可用

先來看 lb 的選型,可以採用硬件負載均衡(F5),這是最好但也是最昂貴的做法:

也可以使用代理軟件(nginx/haproxy 等 [2]),採用軟件負載均衡的做法:

今天本文的分享內容便是對代理軟件高可用的實踐。

代理軟件

對於代理軟件的選型,可以根據實際需求決定,不會影響本文的高可用實踐(目前對於集羣代理主流使用的是 haproxy )。

本文將以 nginx 爲例,並使用 Docker 部署:

$ docker run --name nginx -d -v $PWD/default.conf:/etc/nginx/conf.d/default.conf -p 80:80 nginx

對應的 default.conf 配置示例如下:

upstream cluster {
    server 192.168.19.160:8080;
    server 192.168.19.160:8081;
    server 192.168.19.160:8082;
}

server {
    listen       80;
    server_name  localhost;
    location / {
        proxy_pass http://cluster;
    }
}

爲了方便我們後續的測試,其中代理的三個後端服務是我們模擬的三個 web 服務(實際使用時替換爲集羣 apiserver 即可)。

高可用的核心也是第一步就是 冗餘 ,爲了提升代理服務器的高可用,我們需要冗餘一個實例,按照剛纔的教程需要在主機 lb1lb2 同時部署 nginx (也就是說有兩臺機子運行着 nginx 代理):

9fWDbp

測試這兩個代理服務器,請求將會均衡到三個後端服務中的任意一個。

代理測試

接下來就該考慮如何實現服務器的自動故障轉移,即 lb1 掛了之後,冗餘的 lb2 如何快速頂上來使用,這一功能也稱作:雙機熱備

雙機熱備

一圖秒懂雙機熱備 [3]

所謂雙機熱備,即主機和從機通過 TCP/IP 網絡連接,正常情況下主機處於工作狀態,從機處於監視狀態,一旦從機發現主機異常,從機將會在很短的時間之內代替主機,完全實現主機的功能。

我們現在的需求很明確,即 lb1 作爲主代理服務器提供給外部訪問,lb2 作爲從代理服務器,一旦 lb1 發生故障,lb2 可以立即頂替上去,以實現代理的高可用。

lb1lb2 是不同主機,不同 IP 的,而我們又必須對外部訪問端透明,只能有一個 IP 給到外部。

只給外部一個 IP ,這個 IP 能不能正常情況下屬於 lb1 節點,當 lb1 節點掛了之後又變成屬於 lb2 節點的呢。

這就引出了 Virtual IP (虛擬 IP,簡稱 VIP )的概念了。

我們可以創建一個 VIP 爲 192.168.19.200 ,正常情況下,該 VIP 綁定到 lb1 上,一旦 lb1 發生故障,VIP 立即漂移到 lb2 。對於外部訪問端,就只需要調用 192.168.19.200 這個 VIP 就可以了,無需理會代理服務器究竟是 lb1 還是 lb2

這個 VIP 的漂移機制實現也有現成的工具:keepalived

keepalived

keepalived 是一個基於 VRRP 協議實現的 Web 服務高可用方案,可以利用其避免單點故障。

可以在 lb1 節點部署 keepalived 作爲主服務器(MASTER),在 lb2 節點部署 keepalived 作爲備服務器(BACKUP),對外表現爲一個 VIP (默認綁定在 MASTER 所在節點)。

keepalived MASTER 會發送特定消息給 keepalived BACKUP ,當 BACKUP 收不到消息時(即 MASTER 宕機或發生故障), BACKUP 就會接管 VIP 。

keepalived 需要注意腦裂問題的發生,即:MASTER 和 BACKUP 同時佔用了 VIP

接下來我們就利用 keepalived 來實現 雙機熱備 機制以保障代理的高可用。

本文實驗機環境:CentOS Linux release 7.9.2009 (Core)

lb1lb2 節點都部署上 keepalived

[root@lb1 ~]# yum -y install keepalived
[root@lb2 ~]# yum -y install keepalived

lb1 節點修改 keepalived 的配置文件:

[root@lb1 ~]# cd /etc/keepalived/
[root@lb1 keepalived]# pwd
/etc/keepalived
[root@lb1 keepalived]# vi keepalived.conf

lb1 節點 keepalived.conf 內容如下:

! Configuration File for keepalived

global_defs {
    # # 通知郵件服務器的配置
    # notification_email {
    #     # 當 master 失去 VIP 的時候,會發一封通知郵件到 your-email@qq.com
    #     your-email@qq.com
    # }
    # # 發件人信息
    # notification_email_from keepalived@qq.com
    # # 郵件服務器地址
    # smtp_server 127.0.0.1
    # # 郵件服務器超時時間
    # smtp_connect_timeout 30

    # 唯一 id ,通常設置爲 hostname
    router_id lb1
}

# 檢查nginx是否存活
vrrp_script check_nginx {
    script "/etc/keepalived/nginx_check.sh"
    interval 2 # 檢測時間間隔
    weight -20 # 檢測失敗(腳本返回非0)則權重 -20
}

# 定義虛擬路由,VI_1 爲虛擬路由的自定義標識符
vrrp_instance VI_1 {
    state MASTER # 主節點爲 MASTER ,備份節點爲 BACKUP
    priority 100 # 節點優先級,MASTER 比 BACKUP 高

    interface ens33 # 綁定虛擬 IP 的網絡接口
    virtual_router_id 101 # 虛擬路由的 id ,兩個節點設置必須一樣
    advert_int 1 # 心跳報文發送間隔,默認 1s

    # 默認組播模式下,keepalived 所有的信息都會向 224.0.0.18 的組播地址發送,產生衆多的無用信息,並且可能會產生干擾和衝突
    # 可以使用單播模式,這是一種安全的方法,避免局域網內有大量的 keepalived 造成虛擬路由 id 的衝突
    unicast_src_ip 192.168.19.158  # 配置單播的源地址,本機 IP
    unicast_peer {
        192.168.19.159       # 配置單播的目標地址,其它 IP
    }

    # 設置驗證信息,兩個節點必須一致
    authentication {
        auth_type PASS
        auth_pass 123456
    }

    track_script {
        check_nginx # 執行 Nginx 監控的服務
    }

    # 虛擬 IP 池, 兩個節點設置必須一樣
    virtual_ipaddress {
        192.168.19.200 # 虛擬 ip,可以定義多個
    }
}

同樣的步驟,修改 lb2 節點的 keepalived.conf 內容爲:

[root@lb2 keepalived]# cat keepalived.conf
! Configuration File for keepalived

global_defs {
    # # 通知郵件服務器的配置
    # notification_email {
    #     # 當 master 失去 VIP 的時候,會發一封通知郵件到 your-email@qq.com
    #     your-email@qq.com
    # }
    # # 發件人信息
    # notification_email_from keepalived@qq.com
    # # 郵件服務器地址
    # smtp_server 127.0.0.1
    # # 郵件服務器超時時間
    # smtp_connect_timeout 30

    # 唯一 id ,通常設置爲 hostname
    router_id lb2
}

# 檢查nginx是否存活
vrrp_script check_nginx {
    script "/etc/keepalived/nginx_check.sh"
    interval 2 # 檢測時間間隔
    weight -20 # 檢測失敗(腳本返回非0)則權重 -20
}

# 定義虛擬路由,VI_1 爲虛擬路由的自定義標識符
vrrp_instance VI_1 {
    state BACKUP # 主節點爲 MASTER ,備份節點爲 BACKUP
    priority 50 # 節點優先級,MASTER 比 BACKUP 高

    interface ens33 # 綁定虛擬 IP 的網絡接口
    virtual_router_id 101 # 虛擬路由的 id ,兩個節點設置必須一樣
    advert_int 1 # 心跳報文發送間隔,默認 1s

    # 默認組播模式下,keepalived 所有的信息都會向 224.0.0.18 的組播地址發送,產生衆多的無用信息,並且可能會產生干擾和衝突
    # 可以使用單播模式,這是一種安全的方法,避免局域網內有大量的 keepalived 造成虛擬路由 id 的衝突
    unicast_src_ip 192.168.19.159  # 配置單播的源地址,本機 IP
    unicast_peer {
        192.168.19.158       # 配置單播的目標地址,其它 IP
    }

    # 設置驗證信息,兩個節點必須一致
    authentication {
        auth_type PASS
        auth_pass 123456
    }

    track_script {
        check_nginx # 執行 Nginx 監控的服務
    }

    # 虛擬 IP 池, 兩個節點設置必須一樣
    virtual_ipaddress {
        192.168.19.200 # 虛擬 ip,可以定義多個
    }
}
[root@lb2 keepalived]#

global_defs 全局配置中,可以配置郵件通知,一旦 VIP 發生漂移(意味着主代理服務器發生了故障)就會發送郵箱通知我們去解決修復,而此時因爲還有備代理服務器可以使用,不至於服務對外不可用。

整個配置的核心是在 vrrp_instance 虛擬路由的定義上,interface 指定綁定的網卡(節點 IP 所在的網卡);keepalived 默認爲組播模式,我們爲其更改爲了單播;virtual_ipaddress 定義了需要生成的 VIP 列表(指定了 192.168.19.200)。

track_script 則是我們 VIP 漂移得以正常運作的關鍵,我們設定了每 2s 就去執行一次 /etc/keepalived/nginx_check.sh 腳本:

#!/bin/bash
if [ `ps -C nginx --no-header |wc -l` -eq 0 ];then
killall keepalived
fi

腳本實際就是檢測代理軟件(nginx)是否存活,如果掛了,就執行 killall keepalived 將自身停止,相當於將 VIP 讓位給備服務器。

通常腳本里還會嘗試重啓一下代理軟件,如果重啓失敗,再考慮執行 kill

先給腳本執行權限,然後啓動 lb1 的 keepalived 服務:

[root@lb1 keepalived]# ls
keepalived.conf  nginx_check.sh
[root@lb1 keepalived]# chmod 744 nginx_check.sh
[root@lb1 keepalived]# systemctl start keepalived
[root@lb1 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.158/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
    inet 192.168.19.200/32 scope global ens33
[root@lb1 keepalived]#

啓動 lb2 的 keepalived 服務:

[root@lb2 keepalived]# ls
keepalived.conf  nginx_check.sh
[root@lb2 keepalived]# chmod 744 nginx_check.sh
[root@lb2 keepalived]# systemctl start keepalived
[root@lb2 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.159/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
[root@lb2 keepalived]#

可以從 ip a 命令看到,當前 VIP 192.168.19.200 位於 lb1 節點上。

此時訪問 VIP 192.168.19.200 ,相當於訪問 lb1 節點:

接着模擬故障,模擬當 lb1 代理服務掛掉後,lb2 能不能頂替上來使用,我們可以手動停止 lb1 節點的 nginx :

[root@lb1 keepalived]# docker stop nginx
nginx
[root@lb1 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.158/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
[root@lb1 keepalived]#

此時發現,lb1 已經失去了 VIP 192.168.19.200 ,繼續看 lb2

[root@lb2 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.159/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
    inet 192.168.19.200/32 scope global ens33
[root@lb2 keepalived]#

和預期一致,VIP 192.168.19.200 漂移到了 lb2 節點上。

再次訪問 VIP 192.168.19.200 ,服務依然可以正常調用,但是現在相當於在訪問 lb2 節點:

藉助 keepalived ,我們成功實現了 nginx 代理的高可用,但是現在這種方案還是有缺陷的。

其實當前我們使用的是雙機主備方式(Active-Standby 方式),即備用服務器在正常情況下一直處於閒置狀態,這種方式不太經濟實惠。

對於雙機熱備,其實就是雙機高可用,還有另一種方案:雙主機方式(Active-Active 方式),即兩臺服務器互爲主備狀態,任意一臺故障後,另一臺可以接管其流量。

雙主機方式

我們還是利用 keepalived 來實現雙主機方式。和之前的雙機主備方式不同的是,現在需要兩個 VIP 。

比如我們還是創建一個 VIP 爲 192.168.19.200 ,正常情況下,該 VIP 綁定到 lb1 上;但是額外再創建一個 VIP 爲 192.168.19.201 ,正常情況下,該 VIP 綁定到 lb2 上。

一旦 lb1 發生故障,VIP 192.168.19.200 立即漂移到 lb2 上;同樣的,lb2 發生故障,VIP 192.168.19.201 則立即漂移到 lb1 上。

而對於外部訪問端,我們可以適當分配,一部分客戶端調用 192.168.19.200 這個 VIP ,另一部分調用 192.168.19.201lb1lb2 任意一個發生故障,對於外部訪問端都是可用狀態

修改 lb1 節點的 keepalived.conf

! Configuration File for keepalived

global_defs {
    # # 通知郵件服務器的配置
    # notification_email {
    #     # 當 master 失去 VIP 的時候,會發一封通知郵件到 your-email@qq.com
    #     your-email@qq.com
    # }
    # # 發件人信息
    # notification_email_from keepalived@qq.com
    # # 郵件服務器地址
    # smtp_server 127.0.0.1
    # # 郵件服務器超時時間
    # smtp_connect_timeout 30

    # 唯一 id ,通常設置爲 hostname
    router_id lb1
}

# 檢查nginx是否存活
vrrp_script check_nginx {
    script "/etc/keepalived/nginx_check.sh"
    interval 2 # 檢測時間間隔
    weight -20 # 檢測失敗(腳本返回非0)則權重 -20
}

# 定義虛擬路由,VI_1 爲虛擬路由的自定義標識符
vrrp_instance VI_1 {
    state MASTER # 主節點爲 MASTER ,備份節點爲 BACKUP
    priority 100 # 節點優先級,MASTER 比 BACKUP 高

    interface ens33 # 綁定虛擬 IP 的網絡接口
    virtual_router_id 101 # 虛擬路由的 id ,兩個節點設置必須一樣
    advert_int 1 # 心跳報文發送間隔,默認 1s

    # 默認組播模式下,keepalived 所有的信息都會向 224.0.0.18 的組播地址發送,產生衆多的無用信息,並且可能會產生干擾和衝突
    # 可以使用單播模式,這是一種安全的方法,避免局域網內有大量的 keepalived 造成虛擬路由 id 的衝突
    unicast_src_ip 192.168.19.158  # 配置單播的源地址,本機 IP
    unicast_peer {
        192.168.19.159       # 配置單播的目標地址,其它 IP
    }

    # 設置驗證信息,兩個節點必須一致
    authentication {
        auth_type PASS
        auth_pass 123456
    }

    track_script {
        check_nginx # 執行 Nginx 監控的服務
    }

    # 虛擬 IP 池, 兩個節點設置必須一樣
    virtual_ipaddress {
        192.168.19.200 # 虛擬 ip,可以定義多個
    }
}

vrrp_instance VI_2 {
    state BACKUP
    priority 50

    interface ens33
    virtual_router_id 102
    advert_int 1

    unicast_src_ip 192.168.19.158
    unicast_peer {
        192.168.19.159
    }

    authentication {
        auth_type PASS
        auth_pass 123456
    }

    track_script {
        check_nginx
    }

    virtual_ipaddress {
        192.168.19.201
    }
}

lb2 節點的 keepalived.conf

! Configuration File for keepalived

global_defs {
    # # 通知郵件服務器的配置
    # notification_email {
    #     # 當 master 失去 VIP 的時候,會發一封通知郵件到 your-email@qq.com
    #     your-email@qq.com
    # }
    # # 發件人信息
    # notification_email_from keepalived@qq.com
    # # 郵件服務器地址
    # smtp_server 127.0.0.1
    # # 郵件服務器超時時間
    # smtp_connect_timeout 30

    # 唯一 id ,通常設置爲 hostname
    router_id lb2
}

# 檢查nginx是否存活
vrrp_script check_nginx {
    script "/etc/keepalived/nginx_check.sh"
    interval 2 # 檢測時間間隔
    weight -20 # 檢測失敗(腳本返回非0)則權重 -20
}

# 定義虛擬路由,VI_1 爲虛擬路由的自定義標識符
vrrp_instance VI_1 {
    state BACKUP # 主節點爲 MASTER ,備份節點爲 BACKUP
    priority 50 # 節點優先級,MASTER 比 BACKUP 高

    interface ens33 # 綁定虛擬 IP 的網絡接口
    virtual_router_id 101 # 虛擬路由的 id ,兩個節點設置必須一樣
    advert_int 1 # 心跳報文發送間隔,默認 1s

    # 默認組播模式下,keepalived 所有的信息都會向 224.0.0.18 的組播地址發送,產生衆多的無用信息,並且可能會產生干擾和衝突
    # 可以使用單播模式,這是一種安全的方法,避免局域網內有大量的 keepalived 造成虛擬路由 id 的衝突
    unicast_src_ip 192.168.19.159  # 配置單播的源地址,本機 IP
    unicast_peer {
        192.168.19.158       # 配置單播的目標地址,其它 IP
    }

    # 設置驗證信息,兩個節點必須一致
    authentication {
        auth_type PASS
        auth_pass 123456
    }

    track_script {
        check_nginx # 執行 Nginx 監控的服務
    }

    # 虛擬 IP 池, 兩個節點設置必須一樣
    virtual_ipaddress {
        192.168.19.200 # 虛擬 ip,可以定義多個
    }
}

vrrp_instance VI_2 {
    state MASTER
    priority 100

    interface ens33
    virtual_router_id 102
    advert_int 1

    unicast_src_ip 192.168.19.159
    unicast_peer {
        192.168.19.158
    }

    authentication {
        auth_type PASS
        auth_pass 123456
    }

    track_script {
        check_nginx
    }

    virtual_ipaddress {
        192.168.19.201
    }
}

其實就是在之前的基礎上增加了 VI_2 虛擬路由,創建 VIP 192.168.19.201lb2 爲主服務器,lb1 爲備服務器。

重啓 lb1 節點的 keepalived 服務:

[root@lb1 keepalived]# systemctl restart keepalived
[root@lb1 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.158/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
    inet 192.168.19.200/32 scope global ens33
[root@lb1 keepalived]#

lb2 節點的 keepalived 服務:

[root@lb2 keepalived]# systemctl restart keepalived
[root@lb2 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.159/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
    inet 192.168.19.201/32 scope global ens33
[root@lb2 keepalived]#

對兩個 VIP 進行測試:

故障模擬,停止 lb2 節點的 nginx 代理服務:

[root@lb2 keepalived]# docker stop nginx
nginx
[root@lb2 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.159/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
[root@lb2 keepalived]#

查看 lb1 IP 信息,發現 VIP 192.168.19.201 已漂移到 lb1 上:

[root@lb1 keepalived]# ip a | grep ens33
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.19.158/24 brd 192.168.19.255 scope global noprefixroute dynamic ens33
    inet 192.168.19.200/32 scope global ens33
    inet 192.168.19.201/32 scope global ens33
[root@lb1 keepalived]#

再次對兩個 VIP 進行測試,仍然處於可用狀態:

到此,我們也實現了基於雙主機方式的高可用代理方案。

總結

高可用的核心就是做 “冗餘” ,有了 “冗餘” 後就得考慮做自動故障轉移,使 “冗餘” 的實例可以快速頂替使用。

本文介紹了 雙機主備方式雙主機方式 兩種方案來解決代理的高可用。但是軟件開發沒有銀彈,因爲這兩種方案都是在一個局域網內進行的,還是有很大的不可控風險,如果再繼續優化,就得考慮 同城雙活異地雙活 直至 異地多活 ,而這些就必須得靠強大的基礎設施能力來支撐了。

希望有朝一日可以接觸到吧!

參考資料

[1] 高可用拓撲選項: https://kubernetes.io/zh/docs/setup/production-environment/tools/kubeadm/ha-topology/

[2] nginx/haproxy 等: https://www.loggly.com/blog/benchmarking-5-popular-load-balancers-nginx-haproxy-envoy-traefik-and-alb/

[3] 一圖秒懂雙機熱備: https://www.nilovelez.com/2012/08/niveles-de-raid/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ldw-TroDklazh04aiLVwoA