使用Haproxy+lua代理Mongodb副本集
一般情況下,使用mongo客戶端,或者其他語言的mongo客戶端驅動程式連線mongodb副本集的時候,只需要指定副本集名稱,就可以實現當mongodb副本集主備切換時的高可用目標。
但是總有一些特殊的場合,連線到副本集的客戶端與副本集所在的網路是隔離的,只能通過副本集所在網路的代理訪問副本集,比如說,mongodb副本集被部署到k8s上,當k8s叢集外的客戶端想訪問副本集時,只能通過代理進行訪問,如通過haproxy訪問副本集。
那麼問題來了,客戶端只知道代理的ip地址或者url,當副本集主備切換時,客戶端通過副本集名稱是不會連線到副本集的主例項的,這裡提供一個haproxy+lua代理mongo副本集的方式可以解決這個問題。
關於haproxy和lua的使用方法和說明,請參考如下連結:
https://www.arpalert.org/haproxy-lua.html
首先,建立一個簡易的mongodb副本集(同一主機埠不同,沒有使用者名稱密碼等安全設定):
# 通過以下shell建立一個簡易的mongodb副本集 # 殺掉已存在的mongo例項 ps -ef | grep mongod | grep -v grep | awk '{print $2}' | xargs kill -9 ; # 清理並建立目錄供副本集使用 rm -rf /data/47017/* && rm -rf /data/47018/* && rm -rf /data/47019/* && rm -f rm -rf /data/47017log && rm -f rm -rf /data/47018log && rm -f rm -rf /data/47019log && mkdir -p /data/47017/ && mkdir -p /data/47018/ && mkdir -p /data/47019/ && # 獲取本機IP地址 localip=`ifconfig -a|grep inet|grep -v 127.0.0.1|grep -v inet6| grep -v 172|awk '{print $2}'|tr -d "addr:"` echo "ETH0 IP is ${localip}" # 下面幾行註釋掉的命令是建立mongodb的使用者名稱和密碼,暫時不要 #/home/mongodb/bin/mongod --fork --bind_ip 0.0.0.0 --port 47017 -dbpath "/data/47017" -logpath /data/47017log && #/home/mongodb/bin/mongo --host ${localip} --port 47017 --eval "db.getSiblingDB('admin').createUser({user: 'aa',pwd: 'aa',roles: [ { role: 'root',db: 'admin' }]})" && #ps -ef | grep mongod | grep -v grep | awk '{print $2}' | xargs kill && sleep 5 && # 如mongodb的主目錄是/home/mongodb的話,直接使用如下命令建立副本集即可 /home/mongodb/bin/mongod --replSet myrep --fork --bind_ip 0.0.0.0 --port 47017 -dbpath "/data/47017" -logpath /data/47017log && /home/mongodb/bin/mongod --replSet myrep --fork --bind_ip 0.0.0.0 --port 47018 -dbpath "/data/47018" -logpath /data/47018log && /home/mongodb/bin/mongod --replSet myrep --fork --bind_ip 0.0.0.0 --port 47019 -dbpath "/data/47019" -logpath /data/47019log && /home/mongodb/bin/mongo --host ${localip} --port 47017 --eval "db.getSiblingDB('admin'); rs.initiate({'_id':'myrep','members':[{'_id':0,'host':'${localip}:47017',priority:3},{'_id':1,'host':'${localip}:47018',priority:2},{'_id':2,'host':'${localip}:47019',priority:0,slaveDelay:86400}]})"
然後,就可以著手安裝haproxy和lua的相關環境了,由於我們需要探測mongodb副本集主例項位於哪個節點,因此還需要安裝mongo-c-driver,這個過程比較費時間,可以直接使用docker容器簡化這一過程,參考:
https://hub.docker.com/r/pengtaoman/haproxy2.1.0-lua-mongodriver
https://github.com/pengtaoman/haproxy2.1.0-lua-mongodriver
github上有dockerfile原始碼,可以檢視環境安裝的過程,以及配置檔案的示例,這裡簡要說明如下:
haproxy.cfg
global log 127.0.0.1 local0 debug log 127.0.0.1 local1 notice stats timeout 30s user haproxy group haproxy # 這裡載入我們做成的lua檔案 lua-load /etc/haproxy/conf/mongo-backend.lua daemon defaults log global mode tcp option tcplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 # 通過frontend設定的埠訪問副本集 frontend mongo_front bind *:37017 use_backend %[lua.backend_select] # m1 m2 m3是副本集的三個例項, # 我們將通過lua程式選擇使用哪一個例項,從而實現主備切換時的高可用 # 10.211.55.21即為上一步建立簡易副本集的主機的ip backend m1 balance roundrobin log 127.0.0.1 local0 server mongorep0 10.211.55.21:47017 check backend m2 balance roundrobin log 127.0.0.1 local0 server mongorep1 10.211.55.21:47018 check backend m3 balance roundrobin log 127.0.0.1 local0 server mongorep2 10.211.55.21:47019 check # 開啟haproxy自帶監控服務 listen admin_stats mode http bind 0.0.0.0:8888 stats uri /stats stats realm Global\ statistics
mongo-backend.lua
# 向haproxy註冊一個fetch
core.register_fetches("backend_select",function(txn)
for k,v in pairs(core.backends) do
local servs = v.servers
for sk,sv in pairs(servs) do
core.Debug(sk)
local svAddr = sv.get_addr(sv)
local isMaster = checkMongo(svAddr)
if (isMaster)
then
core.Debug("###### Now primary instance is:"..svAddr)
return k
end
end
end
end)
checkMongo = function(mongosvr)
core.Debug("###### checkMongo mongo address ::"..mongosvr)
local mongo = require 'mongo'
local client = mongo.Client('mongodb://'..mongosvr)
local isp = client:command('admin','{ "isMaster": "1" }')
local bson = mongo.BSON{}
pcall(function()
bson:concat(isp)
end)
local ispri=bson:find('ismaster')
if unexpected_condition then error() end
return ispri
將haproxy.cfg和mongo-backend.lua放到自己指定的目錄下如/home/cfg下,就可以啟動容器了,如下:
docker run -d -p 37017:37017 -v /home/cfg:/etc/haproxy/conf pengtaoman/haproxy2.1.0-lua-mongodriver:0.0.1
通過mongo客戶端去訪問代理:
docker run -it --rm mongo:4.0.14 mongo --host 10.211.55.2 --port 37017
使用docker logs可以檢視我們在lua檔案中列印的日誌:
docker logs a1ced56a7444
[NOTICE] 029/131117 (1) : New worker #1 (7) forked
[debug] 029/131228 (7) : mongorep1
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47018
[debug] 029/131228 (7) : mongorep0
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47017
[debug] 029/131228 (7) : ###### Now primary instance is:10.211.55.21:47017
可以看到當前primary例項的埠是47017,我們把47017的例項停止後看看發生了什麼:
mongo --port 47017
use admin
db.shutdownServer();
再次使用mongo客戶端連線代理後,檢視docker logs如下:
docker logs a1ced56a7444
[NOTICE] 029/131117 (1) : New worker #1 (7) forked
[debug] 029/131228 (7) : mongorep1
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47018
[debug] 029/131228 (7) : mongorep0
[debug] 029/131228 (7) : ###### checkMongo mongo address ::10.211.55.21:47017
[debug] 029/131228 (7) : ###### Now primary instance is:10.211.55.21:47017
[WARNING] 029/132201 (7) : Server m1/mongorep0 is DOWN,reason: Layer4 connection problem,info: "Connection refused",check duration: 101ms. 0 active and 0 backup servers left. 0 sessions active,0 requeued,0 remaining in queue.
[ALERT] 029/132201 (7) : backend 'm1' has no server available!
[debug] 029/132224 (7) : mongorep1
[debug] 029/132224 (7) : ###### checkMongo mongo address ::10.211.55.21:47018
[debug] 029/132224 (7) : ###### Now primary instance is:10.211.55.21:47018
副本集的primary節點已經切換到埠47018上了,則配置成功。