热爱技术
专注分享

基于python的ssr节点批量部署程序,一键部署N多节点

程序服务生活。学一门实用的语言,不但可以增长自己的知识,还可以让生活更加便利。python简单易学,功能强大,是你 杀人防火 自己定制程序的不二利器。


由于最近找博主搭网站的人比较多,而且有的朋友后端节点一搭就是十几个,几十个,如果手动去搭建,会浪费很多时间,一个后端节点手动需要10分钟,那么十个节点就需要2小时。由于这段时间比较忙,也没太多时间来处理这个,所以这个批量部署程序就诞生了:https://github.com/miseryCN/ssrNodeDeploy

这个程序花了小半天写的,自己测试使用没有什么问题,只需要服务器为centos系统即可。更多功能添加中,欢迎大家使用使用,提提意见。如果你有python3环境,那么克隆源码后,把你的服务器信息按格式填写入示例配置文件 servers.xlsx里, 直接运行即可,如果需要获取linux或者windows的可执行程序,欢迎加群:607614097,群里有各种大佬,欢迎交流。

说了这么多,进入正题,如何使用python来实现批量部署(分布式)的功能呢?有下面几个问题需要分析:

1 如何使用python连接linux服务器,执行shell命令

2 如何获取命令执行返回的结果

3 如何为每个节点生成配置文件

4 如何做到批量部署(分布式)

5 如何定义批量部署程序的配置文件

python的强大之处就在于,基本你想到什么功能,就有相对应的模块。不管是官方的还是开源社区的,一律pip安装即可,直接让你感受到站在巨人的肩膀上写代码的惬意。

下面开始实验,本次环境为aws的centos服务器一台,和python开发环境+pycharm(python IDE)

为了更加直观,博主这里就新开一台aws的lightsail服务器,然后更改默认的密钥连接为ssh连接。

image.png

 

一  使用paramiko模块实现ssh,连接到服务器

image.png

看一下执行的结果:

image.png

如图,获取到了命令 "ls /usr"的执行结果,即显示 /usr 文件夹下的内容

 

二 生成节点配置文件

我们来看一下节点的标准配置文件信息:

image.png

这是一个json文件,在python里,就是一个字典格式。因此,只需要定义一个字典,然后传入变量信息:host, password , db , node_id即可。

所以我们改写一下代码,做一个函数,专门用来生成配置文件的。

image.png

调用一下这个函数,即可生成配置文件,存放到当前目录下

 

三 使用paramiko的sftp,上传配置文件

image.png

这里要注意的是,paramiko的sftp,需要使用本地和远程的绝对路径,并且要带文件名,不然会报错。

执行一下,看看效果:

image.png

如上图,在服务器的root目录下,已经按照传入的参数,生成了这个配置文件。

 

四 使用xlrd模块,读取excel表格的内容

因为多台服务器会涉及多条服务器信息,这个信息用excel表格来操作最方便。而且python有很多模块可以用于读写excel表格,因此,程序的配置文件决定使用excel表格来实现。

如下图,我们新建一个excel表格,定义配置:

image.png

然后写一段代码,把里面的东西读取出来,存入变量:

image.png

运行结果如下,返回了一个二维列表:

image.png

 

五 如何做到批量部署(分布式)

说起这个,那必须要说一下python的多线程了。我们可以将读取到excel表格里的二维数组,遍历出来,然后每个节点开启一个线程,实现并行部署。这样的话,无论是1个节点还是100个节点,完成的时间都是一样的。python的多线程模块,最好用的还要数threading。

threading的用法很简单,传入需要多线程运行的函数和函数的参数,调用start()方法即可。如果要实现多线程,那么我们就需要将之前部署节点的代码封装成一个函数,直接传入即可。这里的话我就不做演示了,具体看github的代码。 

 

六 完成

这些问题都解决了,那么程序的思路也理清了。按照思路写下代码,然后debug,然后重构一下,一个比较好用的程序就出炉了。这里博主简单说下原理,如果你自己想写那相信看了这篇文章很快就能完成的。

最后贴一下主要代码:

from paramiko import SSHClient,AutoAddPolicy,ssh_exception,Transport,SFTPClient
from settings import Settings
from queue import Queue
from json import dump
from threading import Thread
from os import remove,getcwd,path
from excelReader import readExcel



class RemoteSSH():
    def __init__(self):
        self.set = Settings()
        self.que = Queue()
        self.threadList = []
        self.workPath = getcwd() + "/"
        self.resultPath = self.workPath+self.set.result
        if path.exists(self.resultPath):
            remove(self.resultPath)
        if path.exists(self.workPath+"servers.xlsx"):
            self.servers_config_path = self.workPath + "servers.xlsx"
        elif path.exists(self.workPath+"servers.xls"):
            self.servers_config_path = self.workPath + "servers.xlsx"
        else:
            self.servers_config_path = ""


    def deploy(self):
        self.servers = readExcel(self.servers_config_path)
        if isinstance(self.servers,list):
            print("在配置文件中找到",len(self.servers),"个节点...")
            for server in self.servers:
                server = tuple(server)
                threadName = Thread(target=self.execute,args=server)
                self.threadList.append(threadName)
                threadName.start()
            Thread(target=self.daemon_check).start()
        else:
            print(self.set.config_not_found)
            host = input("节点地址:")
            port = int(input("节点ssh端口:"))
            password = input("节点root密码:")
            dbHost = input("数据库地址:")
            dbPassword = input("数据库密码:")
            dbName = input("数据库名:")
            nodeID = int(input("节点node_id:"))
            remark = "默认节点"

            self.execute(remark,host,port,password,dbHost,dbPassword,dbName,nodeID)



    def daemon_check(self):
        while True:
            threadAorD = []
            for thread in self.threadList:
                threadAorD.append(thread.isAlive())
            if True not in threadAorD and self.que.empty():
                result = self.read_result()
                input(result)
                return
            else:
                if not self.que.empty():
                    remark, host, info = self.que.get()
                    with open(self.resultPath,"a",encoding="utf-8") as f:
                        f.write(remark+" | "+host+": "+info+"\n")


    def check_config(self,*args):
        for arg in args[1:]:
            if arg == "":
                return False
        return True


    def execute(self,remark,host,port,password,dbHost,dbPassword,dbName,nodeID):
        if not self.check_config(remark,host,port,password,dbHost,dbPassword,dbName,nodeID):
            print(remark,self.set.error_info)
            self.que.put((remark,host,self.set.error_info))
            return

        print(remark,self.set.start)
        try:
            int(port),int(nodeID)
        except ValueError:
            print(remark,self.set.error_value)
            self.que.put((remark,host,self.set.error_value))
            return

        ssh = SSHClient()
        ssh.set_missing_host_key_policy(AutoAddPolicy())
        try:
            print(remark,self.set.connect)
            ssh.connect(host,int(port),self.set.username,password)
        except ssh_exception.NoValidConnectionsError:
            print(remark,self.set.error_port)
            self.que.put((remark,host,self.set.error_port))
            return
        except ssh_exception.AuthenticationException:
            print(remark,self.set.error_pass)
            self.que.put((remark,host,self.set.error_pass))
            return
        except TimeoutError:
            print(remark,self.set.error_timeout)
            self.que.put((remark,host,self.set.error_timeout))
            return
        except:
            print(self.set.error_unknown)
            self.que.put((remark,host,self.set.error_unknown))
        print(remark,self.set.success_connect)
        print(remark,self.set.deploying)
        for cmd in self.set.cmdList:
            print(remark,cmd)
            print(self.set.wait_for_response)
            console_in,console_out,console_error = ssh.exec_command(cmd)
            print(console_out.read().decode(),console_error.read().decode())
        ftp = Transport((host,int(port)))
        ftp.connect(username=self.set.username,password=password)
        sftp = SFTPClient.from_transport(ftp)
        mysql_config_path = self.workPath+host+"_user_mysql.json"
        with open(mysql_config_path,"w",encoding="utf-8") as configFile:
            dump(self.set.generateUserMysqlFile(dbHost,dbPassword,dbName,nodeID),configFile)
        sftp.put(mysql_config_path,self.set.server_user_config_path)
        ssh.exec_command(self.set.log_run)
        ssh.exec_command(self.set.auto_start_cmd)
        ssh.exec_command(self.set.rc_chmod)
        remove(mysql_config_path)
        result = ssh.exec_command(self.set.ps_cmd)[1].read().decode()
        if result:
            print(remark,self.set.success_deploy)
            self.que.put((remark,host,self.set.success_deploy))
        else:
            print(remark,self.set.error_deploy)
            self.que.put((remark,host,self.set.error_deploy))
        ssh.close()


    def read_result(self):
        with open(self.resultPath,"r",encoding="utf-8") as f:
            text = f.read()
            return self.set.tip+text+self.set.end

赞(12) 打赏
未经允许不得转载:小伟博客 » 基于python的ssr节点批量部署程序,一键部署N多节点

评论 3

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    给力!

    跨境电商之家 4个月前 (01-26) 来自天朝的朋友 谷歌浏览器 Windows 10 回复
  2. #2

    写的真好

    你好 3个月前 (02-12) 来自天朝的朋友 谷歌浏览器 Windows 7 回复

小伟博客 热爱技术 专注分享

网站发展历程WKM萌妹博客

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏