女主宣言:
如果有几台机器想要执行同样的命令,大家可能会分别去每台机器上分别执行。但是如果你面对的是成千上万台的机器,你还会用这种刀耕火种的方法吗?小编我相信运维 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 并无感知,最终会导致整个任务的返回结果不完整
结论 上诉测试是同机房测试,在网络情况相同的情况下,ZeroMQ 要比 Redis 完成消息传输快一些,但是有丢失消息情况,Redis 的测试结果还可以,可以尝试用 Redis 代替 ZeroMQ。 主要流程
一、在 Master Of Masters 上启动一个 Subscribe 进程,用来将数据 Publish 到 Redis 特定的 Channel 中,详细代码如下:
二、在二级 Master 节点(原 Syndic 节点)启动 Publish 和 Return 处理消息,详细代码如下: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”
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,这个模式用了一段时间,发现消息丢失的情况已经大大减少。