背景
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,这个模式用了一段时间,发现消息丢失的情况已经大大减少。