背景
360私有云平臺(HULK平臺)管理著360公司90%以上的業務線,面對如此眾多的服務器,如何進行管理?當然需要一套完善的工具來自動化。HULK 平臺的命令系統可以對批量機器執行腳本,命令系統的底層是基于 SaltStack 開發。
當然最大的問題在于機器部署的機房多,機器數量多,部署 SaltStack 的 Master 就會遇到問題,我們使用的是多級多機房 Master 的架構。
多級 Master 就要用到 Syndic。Syndic 消息傳輸比較依賴網絡,但是公司內部多機房間網絡比較復雜,Syndic 丟失消息的機率也相對比較高,下面就介紹一下使用 Syndic 的一些“坑”和改善丟消息的辦法。
SaltStack結構
大概介紹下 Salt 結構,方便入門同學后面閱讀。Master 節點負責發布命令,管理下面這些機器,被管理機器節點部署 Minion 來監聽指令
Salt Syndic 介紹
當 Minions 的數量超過一定規模,Master 的性能就會成為瓶頸,這時會考慮部署多個節點的 Masters 來解決性能問題,但是隨之而來就是使用性的降低,執行命令需要到相應的 Master 節點。在 SaltStack 0.9.0的版本中加入了 Syndic,Syndic 架構的出現正好解決了這個問題,Syndic 是一個特殊的 Minion,核心代碼就在 minion.py 中,Syndic 和低級別的 Master 運行在同一臺主機,Syndic 連接的 Master 是一個高級別 Master。
優點
通過 Syndic,可以建立多層的架構,所有的命令都可以由高級別的 Master執行,這個 Master 我們成為 Master Of Masters,架構更為靈活
由于 Syndic 只是訂閱了 Master Of Masters 的消息,其他如文件服務等需要在 Syndic 節點配置,大大降低了 Master Of Masters 的壓力
缺點
Syndic 上 file_roots 和 pillar_roots 的配置要和 Master Of Masters 上的保持一致
Master Of Masters 只和 Syndic 通信,低級別的 Master 管理所屬的 Minions 認證,致使 Master Of Masters 不知道下面有多少臺 Minions;在 Master Of Masters 上執行命令,在下發到 Syndic 過程中,如果網絡出現抖動,導致沒有收到消息或者延遲收到,Master Of Masters 并無感知,最終會導致整個任務的返回結果不完整
架構圖
問題
Syndic 在網絡不可靠的情況下,致使消息傳遞可靠性也相對降低,如果 Syndic 沒有收到消息,那么下面所屬的 Minions 也就不會收到這個任務。官方的建議是增加 syndic_wait 參數,但是這也只是能緩解一部分情況,在實際環境中效果并不明顯。
思考
通過上面的介紹, Syndic 其實也是一個 Minion,那是否可以用別的方案代替Syndic?這里首先要解決的問題 ZeroMQ,Redis 也支持 Pub/Sub 模式,并且可以用主從架構多機房部署,Redis 的 Pub/Sub 模式性能還是不錯的。
測試
首先對 ZeroMQ 和 Redis 的 Pub/Sub 模式進行測試
結論
上訴測試是同機房測試,在網絡情況相同的情況下,ZeroMQ 要比 Redis 完成消息傳輸快一些,但是有丟失消息情況,Redis 的測試結果還可以,可以嘗試用 Redis 代替 ZeroMQ。 主要流程
一、在 Master Of Masters 上啟動一個 Subscribe 進程,用來將數據 Publish 到 Redis 特定的 Channel 中,詳細代碼如下:
self.opts['master_addr'] = salt.utils.dns_check(self.opts['master'])
//獲取master的ip地址
context = zmq.Context()
master_pub ='tcp://{0}:{1}'.format(self.opts[' master_addr '],self.opts['master_publish_port'])
ub_sock = context.socket(zmq.SUB)
sub_sock = set_tcp_keepalive(sub_sock,opts=self.opts)
sub_sock.connect(master_pub)
sub_sock.setsockopt(zmq.SUBSCRIBE,b'')
//啟動Subscribe
try:
pool=ConnectionPool(host=self.opts['redis_host'],port=self.opts['redis_port'],db=self.opts['redis_db'],password=self.opts['redis_pass'])
# Send messages to puber PUB sock
while True:
message = sub_sock.recv_multipart()
//從ZeroMQ 訂閱消息
r = Redis(connection_pool=pool)
r.publish("salttest",message)
//將訂閱到的消息Publish到Redis中的Channel,Channel名為”salttest”
二、在二級 Master 節點(原 Syndic 節點)啟動 Publish 和 Return 處理消息,詳細代碼如下:
Publish:
self.opts['master_addr'] =salt.utils.dns_check(self.opts['master'])
context = zmq.Context()
pub_uri = 'tcp://{interface}:{publish_port}'.format(**self.opts)
pub_sock = context.socket(zmq.PUB)
pub_sock =set_tcp_keepalive(pub_sock,opts=self.opts)
pub_sock.bind(pub_uri)
try: conn_pool=client.ConnectionPool(host=self.opts['redis_host'],port=self.opts['redis_port'],db=self.opts['redis_db'],password=self.opts['redis_pass'])
sub = client.PubSub(conn_pool)
sub.subscribe('salttest')
//訂閱channel為”salttest”的消息
for msg in sub.listen():
if msg['type']=='message':
//判斷消息類型
data=eval(msg['data'])
pub_sock.send_multipart(data)
//通過ZeroMQ將消息Publish下去
注:Return代碼比較簡單,這里就省略了。
三、在 Master的配置文件中指定剛剛配置的端口(這里使用4515和4516是因為是在Master啟動)。
syndic_master:node1.example.com syndic_master_port:4516syndic_master_publish_port:4515
總結
做完之后,可以發現其實這個就是個用Redis Pub/Sub的SaltSyndic,這個模式用了一段時間,發現消息丟失的情況已經大大減少。