女主宣言:

如果有几台机器想要执行同样的命令,大家可能会分别去每台机器上分别执行。但是如果你面对的是成千上万台的机器,你还会用这种刀耕火种的方法吗?小编我相信运维 GG 们的内心肯定是拒绝的,此刻一套完善的自动化命令执行系统就成了解救大家的神器。本次小编为大家请到了 360 的帅哥赵阳同学为我们揭秘在 360 内部是如何“一键”快速管理数万台机器的。下面就让我们一起来看看吧

PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!

 

 背景

   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 并无感知,最终会导致整个任务的返回结果不完整

  架构图

多机房数万台服务器管理实践_redis_02

问题    Syndic 在网络不可靠的情况下,致使消息传递可靠性也相对降低,如果 Syndic 没有收到消息,那么下面所属的 Minions 也就不会收到这个任务。官方的建议是增加 syndic_wait  参数,但是这也只是能缓解一部分情况,在实际环境中效果并不明显。 思考    通过上面的介绍, Syndic 其实也是一个 Minion,那是否可以用别的方案代替Syndic?这里首先要解决的问题 ZeroMQ,Redis 也支持 Pub/Sub 模式,并且可以用主从架构多机房部署,Redis 的 Pub/Sub 模式性能还是不错的。  测试    首先对 ZeroMQ 和 Redis 的 Pub/Sub 模式进行测试

多机房数万台服务器管理实践_二维码_03

 

结论    上诉测试是同机房测试,在网络情况相同的情况下,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,这个模式用了一段时间,发现消息丢失的情况已经大大减少。

 

多机房数万台服务器管理实践_多级_04