踐:Keepalived主從熱備與故障切換詳解)
我遇到過不止一次Nginx進(jìn)程悄悄退出的情況。最難忘的是某天凌晨兩點(diǎn)爆出的那通告警——某業(yè)務(wù)系統(tǒng)前端的Nginx突然完全無響應(yīng)用戶請求全部卡在連接階段等我們摸黑登錄服務(wù)器、檢查日志、重啟服務(wù)、確認(rèn)恢復(fù)業(yè)務(wù)已經(jīng)斷了將近四十分鐘。那臺(tái)Nginx就是典型的單機(jī)部署沒有任何冗余俗稱單點(diǎn)。那次事故之后我所在的團(tuán)隊(duì)老老實(shí)實(shí)地把Nginx高可用集群排上了日程做的就是經(jīng)典的NginxKeepalived主從熱備方案。今天這篇文章我就把從方案選型、環(huán)境規(guī)劃、配置落地到模擬故障切換、處理各種真實(shí)坑的完整過程整理出來希望對正在計(jì)劃給Nginx加一層保障的運(yùn)維或后端同學(xué)有實(shí)際參考價(jià)值。1. 單點(diǎn)事故的代價(jià)Nginx高可用到底在解決什么問題1.1 Nginx進(jìn)程退出之后的連鎖反應(yīng)單點(diǎn)故障這件事聽起來很簡單——一臺(tái)機(jī)器壞了服務(wù)就斷了。但真正實(shí)施下來連鎖反應(yīng)遠(yuǎn)比想象中麻煩。那次事故里Nginx不只是負(fù)責(zé)轉(zhuǎn)發(fā)請求它前面連著用戶入口后面連著應(yīng)用服務(wù)器。Nginx一掛所有請求進(jìn)不來后端的應(yīng)用其實(shí)還活著但用戶根本夠不著。業(yè)務(wù)側(cè)反饋過來的時(shí)候我們連到底是網(wǎng)絡(luò)問題、DNS問題、防火墻問題還是Nginx問題都排查了好一會(huì)兒。復(fù)盤時(shí)我們發(fā)現(xiàn)單點(diǎn)Nginx的問題不只是進(jìn)程掛掉這一種可能。機(jī)器硬件故障、內(nèi)核崩潰、磁盤寫滿導(dǎo)致的異常、被人誤操作改了配置后reload失敗、機(jī)房網(wǎng)絡(luò)閃斷……任何一種情況都能讓Nginx從正常變?yōu)椴豢捎谩S械膱鼍跋路?wù)器可能還開著但進(jìn)程已經(jīng)不在了有的場景下機(jī)器徹底失聯(lián)。想要應(yīng)對這一整類問題就必須從架構(gòu)層面做冗余讓故障發(fā)生時(shí)業(yè)務(wù)能自動(dòng)切走而不是等工程師被叫起來手動(dòng)恢復(fù)。1.2 主從熱備模型虛擬IP與故障漂移主從熱備的基本思路通俗地說就是讓兩臺(tái)Nginx節(jié)點(diǎn)組成一個(gè)雙人值守的小組。對外只暴露一個(gè)虛擬IPVIP平時(shí)由主節(jié)點(diǎn)MASTER持有這個(gè)VIP并處理全部流量備節(jié)點(diǎn)BACKUP在主節(jié)點(diǎn)正常時(shí)處于待命狀態(tài)不接流量只通過心跳監(jiān)控主節(jié)點(diǎn)的健康狀況。一旦主節(jié)點(diǎn)失聯(lián)或Nginx進(jìn)程異常備節(jié)點(diǎn)會(huì)迅速把VIP搶到本機(jī)上接管服務(wù)整個(gè)過程對調(diào)用方基本不可見。這里最關(guān)鍵的設(shè)計(jì)就是虛擬IP??蛻舳嗽L問的始終是這個(gè)固定的VIP而不是某一臺(tái)具體服務(wù)器的IP。所以無論VIP落在主節(jié)點(diǎn)還是備節(jié)點(diǎn)上對用戶來說入口沒有變。這套思路可以類比小區(qū)配置兩個(gè)值班保安住戶進(jìn)出只認(rèn)門衛(wèi)室這個(gè)位置今天A保安在崗還是B保安在崗住戶不需要關(guān)心只要門衛(wèi)室始終有人就行。兩臺(tái)Nginx之間怎么知道對方還活著這就是Keepalived的工作了。Keepalived基于VRRP協(xié)議實(shí)現(xiàn)節(jié)點(diǎn)間的狀態(tài)同步和自動(dòng)failover它會(huì)在兩臺(tái)設(shè)備之間周期性地發(fā)送心跳報(bào)文。心跳連續(xù)丟失達(dá)到閾值備節(jié)點(diǎn)就判定主節(jié)點(diǎn)故障立刻在本機(jī)綁定VIP、對外發(fā)送免費(fèi)ARP通告讓交換機(jī)把虛擬IP對應(yīng)的流量切換到本機(jī)。1.3 為什么鎖定了Keepalived而不是其他高可用方案很多人問過我高可用方案那么多為什么不直接用DNS輪詢、云廠商的負(fù)載均衡或者干脆上LVS我的體會(huì)是方案沒有絕對的好壞關(guān)鍵是看場景。DNS輪詢的做法是給域名解析多個(gè)A記錄指向不同Nginx節(jié)點(diǎn)。好處是配置簡單但壞處也很明顯——DNS解析結(jié)果存在客戶端和Local DNS緩存里某一臺(tái)節(jié)點(diǎn)宕機(jī)后仍然會(huì)有大量請求打到故障IP上。而且這種方案本質(zhì)上是負(fù)載均衡不是主備容災(zāi)單個(gè)節(jié)點(diǎn)故障時(shí)受影響的范圍不好控制。云負(fù)載均衡比如各種云SLB在公有云環(huán)境里確實(shí)好用托管維護(hù)、自帶健康檢查。但如果你所在的環(huán)境是自有機(jī)房或者客戶明確要求內(nèi)網(wǎng)閉環(huán)再或者出于成本考慮不想引入額外的LB層Keepalived這種輕量級(jí)方案就很有優(yōu)勢。它不需要額外硬件不改變現(xiàn)有Nginx架構(gòu)兩臺(tái)服務(wù)器裝上軟件、寫好配置就具備故障漂移能力學(xué)習(xí)和維護(hù)成本都不高。2. 環(huán)境規(guī)劃與基礎(chǔ)準(zhǔn)備主備節(jié)點(diǎn)、VIP和系統(tǒng)參數(shù)2.1 節(jié)點(diǎn)與IP規(guī)劃動(dòng)手之前一定要畫一張清晰的規(guī)劃表別等配置寫完了才發(fā)現(xiàn)VIP網(wǎng)段和業(yè)務(wù)網(wǎng)段沖突或者兩臺(tái)節(jié)點(diǎn)根本不在同一個(gè)二層網(wǎng)絡(luò)里。以我們當(dāng)時(shí)的部署為例兩臺(tái)Nginx節(jié)點(diǎn)跑在同一個(gè)交換機(jī)二層網(wǎng)絡(luò)中使用私有地址段。規(guī)劃如下角色主機(jī)名物理IP虛擬IP用途MASTERngx-node-01192.168.1.10192.168.1.100接收VIP流量主節(jié)點(diǎn)BACKUPngx-node-02192.168.1.11192.168.1.100主節(jié)點(diǎn)故障時(shí)接管VIP虛擬IP必須和兩臺(tái)節(jié)點(diǎn)處于同一廣播域內(nèi)Keepalived的VRRP報(bào)文依賴組播或多播廣播跨了網(wǎng)段就會(huì)失效。生產(chǎn)環(huán)境中建議把Keepalived的心跳通信單獨(dú)規(guī)劃一條網(wǎng)線或獨(dú)立網(wǎng)段避免業(yè)務(wù)流量高峰期把心跳報(bào)文也擠丟了這是后話。操作系統(tǒng)我們用的是CentOS 7.9內(nèi)核版本3.10Keepalived版本1.3.5。當(dāng)然你用其他主流Linux發(fā)行版也完全可以配置思路一致只是包管理命令和部分系統(tǒng)路徑會(huì)有差異。2.2 安裝Nginx并準(zhǔn)備差異測試頁Nginx的安裝這里不展開細(xì)說yum源直接裝即可。裝完之后我給兩臺(tái)節(jié)點(diǎn)分別準(zhǔn)備了一個(gè)不同的首頁文件用于后面的切換測試。主節(jié)點(diǎn)的/usr/share/nginx/html/index.html內(nèi)容h1Node: ngx-node-01/h1 pThis is MASTER nginx instance./p備節(jié)點(diǎn)的/usr/share/nginx/html/index.html內(nèi)容h1Node: ngx-node-02/h1 pThis is BACKUP nginx instance./p同時(shí)把Nginx設(shè)為開機(jī)自啟systemctl enable nginx systemctl start nginx這樣設(shè)置的目的是為了在驗(yàn)證故障切換時(shí)能通過curl http://192.168.1.100返回的不同頁面內(nèi)容直觀地判斷當(dāng)前VIP到底落在哪臺(tái)節(jié)點(diǎn)上。2.3 系統(tǒng)基礎(chǔ)配置防火墻、SELinux和時(shí)間同步這塊最容易在部署時(shí)被忽略卻在關(guān)鍵時(shí)刻拖后腿。首先是防火墻很多人的Keepalived一直報(bào)不出心跳排查了一圈發(fā)現(xiàn)是防火墻把VRRP協(xié)議擋了。VRRP使用的不是普通的TCP/UDP端口而是IP協(xié)議號(hào)112CentOS中需要放行對應(yīng)的規(guī)則。firewall-cmd --permanent --add-protocolvrrp firewall-cmd --permanent --add-servicehttp firewall-cmd --reload如果是舊版iptables要放行VRRP協(xié)議可以參照iptables -A INPUT -p vrrp -i eth0 -j ACCEPT然后是SELinux。默認(rèn)的enforcing模式下Keepalived調(diào)用外部腳本、綁定虛擬IP、發(fā)送通告都可能被攔截排查起來非常隱蔽。我們當(dāng)時(shí)直接設(shè)置為permissive模式保證功能先跑通生產(chǎn)環(huán)境需要按自己公司的安全基線來權(quán)衡。setenforce 0 sed -i s/^SELINUXenforcing/SELINUXpermissive/ /etc/selinux/config時(shí)間同步也必須做好。Keepalived自身對時(shí)間不是特別敏感但整個(gè)集群里的日志時(shí)間線、證書有效期判斷、業(yè)務(wù)系統(tǒng)時(shí)間戳都依賴一致的時(shí)間源。用chrony統(tǒng)一對時(shí)避免后面排查問題時(shí)日志時(shí)間對不上。3. Keepalived安裝與核心配置拆解心跳、優(yōu)先級(jí)和檢測腳本3.1 VRRP的工作機(jī)制與Keepalived的角色切換Keepalived的核心是VRRP虛擬路由冗余協(xié)議。協(xié)議的思想很簡單一組路由器這里就是Nginx節(jié)點(diǎn)共同對外提供一個(gè)虛擬IP其中優(yōu)先級(jí)最高的節(jié)點(diǎn)成為Master負(fù)責(zé)響應(yīng)VIP上的請求Master周期性發(fā)送VRRP廣告報(bào)文其余節(jié)點(diǎn)收到后確認(rèn)Master還活著繼續(xù)待在Backup狀態(tài)。如果Backup在連續(xù)幾個(gè)廣告周期內(nèi)都沒收到Master的報(bào)文就會(huì)認(rèn)為Master已經(jīng)不可用于是按照優(yōu)先級(jí)最高的Backup順序進(jìn)入Master狀態(tài)接管VIP。Keepalived在這個(gè)基礎(chǔ)上又增加了track_script機(jī)制——不僅檢測網(wǎng)絡(luò)狀態(tài)還能定期執(zhí)行自定義腳本根據(jù)腳本返回值決定是否降低本節(jié)點(diǎn)優(yōu)先級(jí)甚至主動(dòng)移交VIP。理解這個(gè)機(jī)制之后再回來看配置就會(huì)非常清晰優(yōu)先級(jí)、心跳間隔、檢測腳本這三個(gè)要素決定了整個(gè)集群的切換行為。3.2 主節(jié)點(diǎn)Keepalived配置全解析安裝Keepalivedyum install -y keepalived主節(jié)點(diǎn)的配置文件/etc/keepalived/keepalived.conf如下global_defs { router_id ngx-node-01 enable_script_security } vrrp_script check_nginx { script /usr/local/bin/check_nginx.sh interval 2 timeout 2 weight -20 fall 2 rise 2 } vrrp_instance VI_NGX { state MASTER interface eth0 virtual_router_id 51 priority 150 advert_int 1 authentication { auth_type PASS auth_pass 123456 } track_script { check_nginx } virtual_ipaddress { 192.168.1.100/24 dev eth0 } }逐段解釋一下關(guān)鍵配置的含義。global_defs里面的router_id是當(dāng)前節(jié)點(diǎn)的標(biāo)識(shí)在兩臺(tái)節(jié)點(diǎn)上必須不同。enable_script_security用于限制Keepalived執(zhí)行外部腳本時(shí)的安全性避免腳本環(huán)境變量被濫用官方文檔建議開啟。vrrp_script check_nginx定義了一個(gè)檢測任務(wù)。script指定檢測腳本絕對路徑interval 2表示每2秒執(zhí)行一次timeout 2表示腳本執(zhí)行超過2秒視為超時(shí)weight -20表示腳本返回非0時(shí)當(dāng)前節(jié)點(diǎn)優(yōu)先級(jí)降低20fall 2和rise 2表示連續(xù)失敗2次才判定服務(wù)異常連續(xù)成功2次才判定恢復(fù)這是為了防止腳本偶發(fā)抖動(dòng)導(dǎo)致頻繁切換。vrrp_instance VI_NGX是VRRP實(shí)例的配置。state MASTER聲明該節(jié)點(diǎn)初始角色是主節(jié)點(diǎn)interface eth0指定心跳報(bào)文走哪塊網(wǎng)卡一定是物理網(wǎng)卡而不是VIP所在的虛擬網(wǎng)卡virtual_router_id 51是一個(gè)虛擬路由器ID同一組主備節(jié)點(diǎn)必須保持一致priority 150是節(jié)點(diǎn)優(yōu)先級(jí)數(shù)字越大越優(yōu)先成為Masteradvert_int 1是心跳報(bào)文發(fā)送間隔單位秒通常保持默認(rèn)1秒即可。authentication塊配置節(jié)點(diǎn)間認(rèn)證。auth_type PASS表示密碼認(rèn)證auth_pass是明文密碼。注意Keepalived只取密碼前8位所以密碼不要超過8個(gè)字符否則超出部分會(huì)被忽略反而容易造成兩臺(tái)節(jié)點(diǎn)配置不一致。virtual_ipaddress配置虛擬IP。192.168.1.100/24 dev eth0表示VIP綁定在eth0上子網(wǎng)掩碼24位。這里有個(gè)細(xì)節(jié)寫成帶掩碼和dev的形式比只寫一個(gè)IP更明確能避免某些系統(tǒng)下路由和ARP異常。3.3 備節(jié)點(diǎn)配置與主節(jié)點(diǎn)的差異備節(jié)點(diǎn)的配置大體復(fù)制主節(jié)點(diǎn)只需要改動(dòng)四個(gè)地方router_id、state、priority和節(jié)點(diǎn)自身標(biāo)識(shí)。備節(jié)點(diǎn)配置如下global_defs { router_id ngx-node-02 enable_script_security } vrrp_script check_nginx { script /usr/local/bin/check_nginx.sh interval 2 timeout 2 weight -20 fall 2 rise 2 } vrrp_instance VI_NGX { state BACKUP interface eth0 virtual_router_id 51 priority 120 advert_int 1 authentication { auth_type PASS auth_pass 123456 } track_script { check_nginx } virtual_ipaddress { 192.168.1.100/24 dev eth0 } }兩個(gè)節(jié)點(diǎn)優(yōu)先級(jí)相差30這個(gè)差值必須大于檢測腳本的weight絕對值。主節(jié)點(diǎn)檢測失敗時(shí)優(yōu)先級(jí)從150降到130仍然大于備節(jié)點(diǎn)的120主節(jié)點(diǎn)不會(huì)因?yàn)橐淮螜z測失敗就把VIP讓出去。但如果主節(jié)點(diǎn)整體宕機(jī)網(wǎng)絡(luò)斷開、機(jī)器失聯(lián)備節(jié)點(diǎn)收不到任何VRRP報(bào)文就會(huì)直接接管VIP此時(shí)優(yōu)先級(jí)差就不再重要。理解這個(gè)優(yōu)先級(jí)與weight之間的數(shù)值關(guān)系才能靈活設(shè)計(jì)進(jìn)程異常時(shí)切換、機(jī)器宕機(jī)時(shí)切換、輕微故障時(shí)不來回抖的預(yù)期行為。3.4 檢測腳本Nginx是否活著的準(zhǔn)確判斷檢測腳本是Keepalived配置中靈魂般的存在它決定了故障到底是整機(jī)失聯(lián)還是Nginx進(jìn)程掛了。我們的腳本如下#!/bin/bash # 檢測 nginx master 進(jìn)程是否存在 if pidof nginx /dev/null 21; then exit 0 else exit 1 fi腳本邏輯很簡單pidof nginx能查到nginx進(jìn)程就返回0否則返回1。寫完一定要加上執(zhí)行權(quán)限chmod x /usr/local/bin/check_nginx.sh這個(gè)腳本有兩個(gè)常見的坑。一個(gè)是不要用ps -ef | grep nginx | grep -v grep | wc -l這類方式因?yàn)間rep自身進(jìn)程可能匹配到關(guān)鍵字而且統(tǒng)計(jì)數(shù)量容易誤判另一個(gè)是腳本路徑不要放在/etc/keepalived/目錄下有些Keepalived版本在啟動(dòng)或重載時(shí)會(huì)清理該目錄下的非配置文件內(nèi)容腳本容易莫名其妙消失。放在/usr/local/bin這類系統(tǒng)路徑里更安全。也可以做得更嚴(yán)格一點(diǎn)比如用Nginx的健康檢查接口配合curl來判斷進(jìn)程是否真的在服務(wù)請求而不只是進(jìn)程存在。但對于主從熱備場景進(jìn)程級(jí)檢測通常夠用過度復(fù)雜的健康檢查反而會(huì)引入新的誤判因素。4. 主從熱備的驗(yàn)證實(shí)驗(yàn)?zāi)M宕機(jī)、切換和回切全流程4.1 啟動(dòng)集群并確認(rèn)VIP歸屬配置全部就緒后在兩臺(tái)節(jié)點(diǎn)上啟動(dòng)Keepalivedsystemctl enable keepalived systemctl start keepalived先看主節(jié)點(diǎn)上VIP是否綁定成功ip addr show eth0正常情況下主節(jié)點(diǎn)的eth0上除了物理IP192.168.1.10還會(huì)多出一個(gè)192.168.1.100/24的地址。備節(jié)點(diǎn)上不應(yīng)該出現(xiàn)這個(gè)VIP。此時(shí)訪問VIP應(yīng)該返回主節(jié)點(diǎn)的頁面curl http://192.168.1.100返回內(nèi)容Node: ngx-node-01說明流量確實(shí)打在主節(jié)點(diǎn)上。再確認(rèn)心跳狀態(tài)systemctl status keepalived或者看消息日志tail -f /var/log/messages | grep VRRP能看到類似VRRP_Instance(VI_NGX) Entering MASTER STATE的日志說明節(jié)點(diǎn)正常進(jìn)入了Master狀態(tài)。4.2 場景一Nginx進(jìn)程異常觸發(fā)切換生產(chǎn)環(huán)境中進(jìn)程被誤殺、內(nèi)存不足被OOM殺掉、配置文件錯(cuò)誤導(dǎo)致worker進(jìn)程退出這些都是比較常見的故障。我們先用最直接的方式模擬在主節(jié)點(diǎn)上停掉Nginx。systemctl stop nginx然后觀察主節(jié)點(diǎn)的日志會(huì)看到檢測腳本連續(xù)執(zhí)行失敗觸發(fā)優(yōu)先級(jí)降低最終退出Master狀態(tài)。同時(shí)在備節(jié)點(diǎn)上能看到進(jìn)入Master狀態(tài)的日志。幾條典型的日志片段VRRP_Script(check_nginx) failed VRRP_Instance(VI_NGX) Received lower prio advert, forcing new election VRRP_Instance(VI_NGX) Entering BACKUP STATE備節(jié)點(diǎn)日志VRRP_Instance(VI_NGX) Transition to MASTER STATE VRRP_Instance(VI_NGX) Sending gratuitous ARP on eth0 VRRP_Instance(VI_NGX) Entering MASTER STATE備節(jié)點(diǎn)進(jìn)入Master后執(zhí)行ip addr show eth0可以看到VIP192.168.1.100已經(jīng)出現(xiàn)在備節(jié)點(diǎn)上而主節(jié)點(diǎn)上的VIP已經(jīng)消失。再次訪問VIPcurl http://192.168.1.100返回內(nèi)容Node: ngx-node-02。整個(gè)切換過程從Nginx停止到VIP漂移完成大約3到5秒取決于檢測腳本的間隔和失敗次數(shù)設(shè)置。用戶側(cè)最多只會(huì)感覺到一次短暫的連接抖動(dòng)不會(huì)出現(xiàn)長時(shí)間無法訪問。4.3 場景二整機(jī)宕機(jī)與恢復(fù)后的搶占回切更嚴(yán)重的故障是主節(jié)點(diǎn)整臺(tái)機(jī)器宕機(jī)比如斷電、硬件故障、系統(tǒng)崩潰。這時(shí)主節(jié)點(diǎn)連心跳報(bào)文都不再發(fā)送備節(jié)點(diǎn)等待超時(shí)后直接接管VIP和上面觀察到的現(xiàn)象基本一致。區(qū)別在于整機(jī)宕機(jī)時(shí)備節(jié)點(diǎn)完全收不到Master的VRRP報(bào)文所以它在advert_int乘以幾倍的超時(shí)窗口之后就會(huì)切換通常也是一到三秒級(jí)別。接下來看回切行為。把主節(jié)點(diǎn)的Nginx和Keepalived都恢復(fù)起來systemctl start nginx systemctl start keepalived在默認(rèn)配置下主節(jié)點(diǎn)啟動(dòng)后會(huì)重新以150的優(yōu)先級(jí)參與選舉備節(jié)點(diǎn)收到更高優(yōu)先級(jí)的報(bào)文后自動(dòng)退出Master狀態(tài)VIP會(huì)回切到主節(jié)點(diǎn)。這個(gè)行為叫搶占preempt。從高可用角度來說搶占能保證流量回到配置更完善、資源更充裕的主節(jié)點(diǎn)上但也會(huì)帶來一個(gè)問題如果主節(jié)點(diǎn)反復(fù)抖動(dòng)每次回切都會(huì)造成一次連接中斷。如果業(yè)務(wù)不希望主節(jié)點(diǎn)恢復(fù)后立刻搶回VIP可以在備節(jié)點(diǎn)配置中加入nopreempt讓角色切換只在故障發(fā)生時(shí)觸發(fā)正常情況下VIP穩(wěn)定在當(dāng)前節(jié)點(diǎn)。但要注意nopreempt的使用有講究需要在vrrp_instance中配置并且主節(jié)點(diǎn)通常也要把state設(shè)為BACKUP單純通過優(yōu)先級(jí)區(qū)分誰是事實(shí)主節(jié)點(diǎn)否則搶占行為不會(huì)被完全抑制。4.4 切換過程中的連接保持問題主從熱備能解決服務(wù)入口不中斷但它解決不了正在進(jìn)行的請求不中斷。舉個(gè)例子用戶正在通過VIP下載一個(gè)大文件或者長輪詢接口正在等待數(shù)據(jù)。切換發(fā)生時(shí)TCP連接的狀態(tài)存儲(chǔ)在主節(jié)點(diǎn)的內(nèi)核協(xié)議棧上而備節(jié)點(diǎn)上并沒有這些連接狀態(tài)所以VIP漂移后這些存量連接會(huì)全部斷開用戶需要重新發(fā)起請求。對于大多數(shù)HTTP短連接業(yè)務(wù)來說影響的只是切換瞬間的少量請求重試一次就夠了。如果業(yè)務(wù)對中斷容忍度極低單純NginxKeepalived是不夠的還需要在更上層配合會(huì)話保持策略、客戶端重試機(jī)制或者引入四層負(fù)載均衡方案把連接層的狀態(tài)管理從Nginx節(jié)點(diǎn)中抽離出來。搞清楚主從熱備的邊界比盲目追求零切換更實(shí)際。5. 實(shí)際部署中容易踩的坑與調(diào)優(yōu)建議5.1 那些隱蔽的坑防火墻、SELinux、腳本權(quán)限和腦裂每次幫別人排查Keepalived問題最后發(fā)現(xiàn)的大多不是配置理解問題而是這些不起眼的環(huán)境問題。防火墻不放行VRRP是最常見的一個(gè)?,F(xiàn)象是兩臺(tái)節(jié)點(diǎn)都認(rèn)為自己是Master或者備節(jié)點(diǎn)一直收不到心跳日志里沒有任何切換動(dòng)作。檢查方法很簡單在兩臺(tái)節(jié)點(diǎn)上tcpdump -i eth0 vrrp看能否抓到對方的VRRP報(bào)文。抓不到基本就是被防火墻或交換機(jī)策略擋住了。SELinux影響的是腳本執(zhí)行。我遇到過檢測腳本明明有執(zhí)行權(quán)限手動(dòng)跑也沒問題但Keepalived執(zhí)行時(shí)就是失敗的情況日志里只顯示腳本超時(shí)或返回異常。后來發(fā)現(xiàn)是SELinux攔截了對腳本文件的訪問。要么把腳本所在目錄的權(quán)限調(diào)整好要么直接臨時(shí)改成permissive模式驗(yàn)證。腳本權(quán)限更基礎(chǔ)卻最容易犯。chmod x少了配置寫錯(cuò)了路徑腳本用了#!/bin/bash但文件格式是CRLF這些都會(huì)導(dǎo)致檢測腳本不可用。建議寫完腳本先手動(dòng)跑一次確認(rèn)返回碼符合預(yù)期再交給Keepalived去調(diào)用。腦裂是雙節(jié)點(diǎn)集群最怕的問題即兩個(gè)節(jié)點(diǎn)都認(rèn)為自己是MasterVIP同時(shí)出現(xiàn)在兩臺(tái)機(jī)器上。這種情況會(huì)導(dǎo)致流量在不同節(jié)點(diǎn)之間來回抖動(dòng)甚至出現(xiàn)數(shù)據(jù)不一致。觸發(fā)原因通常是心跳網(wǎng)絡(luò)中斷而業(yè)務(wù)網(wǎng)絡(luò)正常或者防火墻半開只放行了部分方向。緩解措施包括心跳線用獨(dú)立網(wǎng)段或直連網(wǎng)線檢測腳本里加上對端IP連通性檢查以及利用告警機(jī)制及時(shí)察覺雙Master狀態(tài)。5.2 調(diào)優(yōu)參數(shù)與狀態(tài)通知讓集群可控可觀測Keepalived配置文件里值得優(yōu)化的參數(shù)不少我挑幾個(gè)重點(diǎn)說說。advert_int決定心跳頻率。默認(rèn)1秒適合大多數(shù)場景調(diào)小到0.5秒能加快故障感知但會(huì)增加網(wǎng)絡(luò)壓力和誤判概率調(diào)大到2秒以上則切換變慢適合心跳網(wǎng)絡(luò)帶寬受限的場景。這個(gè)值需要和檢測腳本的interval配合考慮不要為了追求極端速度犧牲穩(wěn)定性。weight的取值直接影響切換行為。如果你希望只有進(jìn)程死掉才切換weight的絕對值必須小于主備優(yōu)先級(jí)差值如果你希望檢測失敗就立即切換weight可以把主節(jié)點(diǎn)優(yōu)先級(jí)壓到備節(jié)點(diǎn)之下。我們配置里主備優(yōu)先級(jí)相差30、weight是20就意味著進(jìn)程異常時(shí)主節(jié)點(diǎn)還在只是優(yōu)先級(jí)降到130繼續(xù)留著。但如果加上fall 2的連續(xù)失敗判定實(shí)際效果比單純看weight更可靠因?yàn)樗腔谶B續(xù)失敗次數(shù)的綜合判斷。狀態(tài)通知是個(gè)容易被忽略但非常實(shí)用的功能??梢栽趘rrp_instance中配合notify_master、notify_backup、notify_fault在角色切換時(shí)觸發(fā)自定義腳本發(fā)送釘釘、企業(yè)微信或郵件告警。實(shí)現(xiàn)故障發(fā)生時(shí)第一時(shí)間知道而不是等業(yè)務(wù)側(cè)反饋才知道。一個(gè)簡單的通知腳本示例如下#!/bin/bash # /usr/local/bin/notify_keepalived.sh case $1 in master) echo $(date) become MASTER /var/log/keepalived-notify.log # 在這里調(diào)用 curl 發(fā)送 webhook 告警 ;; backup) echo $(date) become BACKUP /var/log/keepalived-notify.log ;; fault) echo $(date) enter FAULT /var/log/keepalived-notify.log ;; esac配置中對應(yīng)加入notify_master /usr/local/bin/notify_keepalived.sh master notify_backup /usr/local/bin/notify_keepalived.sh backup notify_fault /usr/local/bin/notify_keepalived.sh fault有了通知主從切換不再是一個(gè)黑盒事件每次切換都有跡可循。5.3 從主從到更復(fù)雜的形態(tài)雙活與熱備的擴(kuò)展主從熱備是一個(gè)很好的起點(diǎn)但不是終點(diǎn)。當(dāng)兩臺(tái)Nginx機(jī)器跑在主從模式下時(shí)備機(jī)大部分時(shí)間都是空閑的從資源利用率角度看有點(diǎn)浪費(fèi)。如果業(yè)務(wù)量增長可以考慮把架構(gòu)演進(jìn)為互為主備的雙活模式兩臺(tái)節(jié)點(diǎn)各自帶一個(gè)VIP分別承接一部分業(yè)務(wù)流量同時(shí)互為對方的備份。Keepalived配置里增加第二個(gè)vrrp_instance讓A節(jié)點(diǎn)在實(shí)例1中是MASTER、在實(shí)例2中是BACKUPB節(jié)點(diǎn)則反著來這樣兩臺(tái)機(jī)器的資源都利用起來了。還可以在Nginx前面加LVS或四層負(fù)載均衡形成兩級(jí)的高可用體系。或者把Keepalived的檢測腳本擴(kuò)展成更完整的健康檢查比如檢查后端應(yīng)用端口、檢查磁盤空間、檢查系統(tǒng)負(fù)載讓集群的故障切換決策更聰明。但這些都屬于有了主從熱備之后的下一步延伸先把基礎(chǔ)的主從熱備穩(wěn)定跑通再根據(jù)業(yè)務(wù)需要逐層加碼才是穩(wěn)妥的演進(jìn)路徑。最后分享一個(gè)我個(gè)人的習(xí)慣每半年至少手動(dòng)做一次主從切換演練把切換腳本、通知腳本、備節(jié)點(diǎn)上的配置一致性全部驗(yàn)證一遍。人類是健忘的動(dòng)物集群也一樣——太久不切換真的到了需要切換的那天往往會(huì)發(fā)現(xiàn)備節(jié)點(diǎn)配置文件早就過期了或者腳本權(quán)限被人動(dòng)過或者防火墻策略早就變了。演練不一定能完全避免事故但至少能保證在事故來臨時(shí)你不需要一邊盯著日志一邊祈禱。