换种思路解决Linux -> windows的自动部署
场景:
有个项目用到了Windows服务器(运行jar包和.NET代码),如何集成到现有的自动部署平台(基于Linux)面临到两个问题
对于问题1,一开始想寻找一款“windows版的sshd”程序,但是好像没找到比较官方的;后来想到powershell也有Linux版,想通过在Linux上安装powershell通过powershell来在Linux和Windows之间传输文件,运行远程命令。但是Linux版的只是powershell core,只有核心功能,况且powershell网上学习资料太少(吐槽一下:包括windows官网对于powershell的教程也只是在“简介”的程度,完全不够学习使用),遂无奈放弃之。 对于问题2,觉得在Windows上,用powershell应该能试下目标效果吧,但是经过一番研究,只找到了在powershell内后台运行命令的方法,而无法做到脱离该powershell终端。后又想将目标进程“封装”为windows服务,但是经过一番研究,发现不那么好做。至此,对Windows深感痛心。。。 无奈之余“突发奇想”,何不用flask来做一个小接口程序,运行在Windows上,自动部署平台通过http接口上传jar包到目标Windows(解决问题1),至于问题2,也以http接口形式通过python的subprocess库启动一个子进程,这里要注意的是,subprocess.Popen()方法在有个Windows特有的参数 期望效果
具体方案flask项目目录结构如下 依次看代码吧(不复杂也有必要的注释) app.py 提供flask接口 import os import subprocess import logging from flask import Flask,request,Response from werkzeug.utils import secure_filename import we_manager # 将flask日志输出至指定文件 logger = logging.getLogger() file_handler = logging.FileHandler(‘C:/deploy/win_server_manage/out.log‘,encoding=‘UTF-8‘) logger.addHandler(file_handler) logger.setLevel(logging.DEBUG) app = Flask(__name__) app.config.from_pyfile(‘conf.py‘) def upload_base(project): file = request.files.get(‘file‘) if not file: return ‘No file had uploaded‘,400 if file.filename == ‘‘: return ‘No selected file‘,400 # return ‘"project" key not in the post data‘,400 filename = secure_filename(file.filename) file.save(os.path.join(app.config[‘PROJECTS‘][project],filename)) return ‘successfully upload {}n‘.format(filename) # 示例1:通过上传方式更新目标jar包 # 因为无法同时上传文件和标识项目,故而各项目需要要给单独接口 @app.route(‘/update-we-gateway‘,methods=[‘put‘,‘post‘]) def upload_gateway(): return upload_base(‘we-gateway‘) # 示例2:更新步骤不需要传文件,直接在目标目录git pull代码即可 @app.route(‘/update-we-server‘,‘post‘]) def upload_server(): os.chdir(app.config[‘PROJECTS‘][‘we-server‘]) p = subprocess.Popen([‘git‘,‘pull‘],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,creationflags=subprocess.CREATE_NO_WINDOW) return "STDOUT:{}n STDERR:{}".format(p.stdout.read().decode(‘gb2312‘) if p.stdout else None,p.stderr.read().decode(‘gb2312‘) if p.stderr else None) @app.route(‘/stop‘) def stop(): project = request.values.get(‘project‘) if not project: return ‘"project" key is necessary in the args‘,400 if project not in app.config[‘PROJECTS‘]: return ‘wrong project name‘,400 return we_manager.stop_(project) @app.route(‘/start‘) def start(): project = request.values.get(‘project‘) if not project: return ‘"project" key is necessary in the args‘,400 return we_manager.start_(project) conf.py 配置项,主要是项目名及其目录的对应关系 WIN_IP = ‘172.16.1.7‘ PROJECTS = {‘we-gateway‘: ‘C:/deploy/we-gateway-artifacts‘,‘flask‘: ‘C:/deploy/win_server_manage‘} wechat_manager.py 启动和停止项目的主要逻辑,被app.py引用 import os import subprocess import glob import shlex import conf def start_(name): os.chdir(conf.PROJECTS[name]) if name == ‘flask‘: # flask 作为常驻进程,不能用(stdout=subprocess.PIPE,stderr=subprocess.STDOUT)以及stdout.read()来尝试获取标准输出,会阻塞 # 在有(stdout=subprocess.PIPE,stderr=subprocess.STDOUT)的时候,必须通过下句获取输出才能真正启动flask,stdout = p.stdout.read().decode(‘gb2312‘) if p.stdout else None # #这里有这句,则能启动,阻塞console,无这句,不阻塞console但实际未启动 # flask的输出通过flask内部定义 p = subprocess.Popen(shlex.split("flask run --host localhost"),creationflags=subprocess.CREATE_NO_WINDOW) else: try: jarfile = glob.glob(‘*{}*.jar‘.format(name))[0] except: print("Can‘t find a valid jar file") return # powershell中的out-file相当于Linux中的> # 以powershell.exe/cmd.exe开头的子命令记录的pid应该是powershell/cmd的,直接kill这样的pid不能杀掉java进程 # 但是由于jar包内部没有处理输出,如果python不捕捉子命令输出的话,就会导致无法获得输出,但是捕捉的话,会阻塞python进程(因为常驻内存进程的输出"没有尽头") p = subprocess.Popen(shlex.split("java -jar {}".format(jarfile))) with open(‘{}/pid.txt‘.format(conf.PROJECTS[name]),‘w‘) as f: f.write(str(p.pid)) print(‘start {},pid {} stored in pid.txt.‘.format(name,p.pid)) return ‘start {},p.pid) def stop_(name): os.chdir(conf.PROJECTS[name]) res = [] with open(‘{}/pid.txt‘.format(conf.PROJECTS[name])) as f: pid = f.read() p = subprocess.Popen([‘powershell.exe‘,‘kill‘,pid],stderr=subprocess.STDOUT) res.append(p.stdout.read().decode(‘gb2312‘) if p.stdout else None) res.append(p.stderr.read().decode(‘gb2312‘) if p.stderr else None) p.wait(10) try: os.remove(‘{}/pid.txt‘.format(conf.PROJECTS[name])) except FileNotFoundError: pass print(‘Stop {},pid.txt removed.n {}‘.format(name,res)) return ‘Stop {},res) if __name__ == ‘__main__‘: import click @click.group() def cli(): pass @click.command() @click.argument(‘name‘,required=True) def start(name): """Start the specified APP in background""" if name not in conf.PROJECTS: click.echo(‘Wrong name of app‘) return # p = Process(target=start_,args=(name,)) # p.start() # 不必要了 start_(name) @click.command() @click.argument(‘name‘,required=True) def stop(name): """Stop the specified APP""" if name not in conf.PROJECTS: click.echo(‘Wrong name of app‘) return stop_(name) cli.add_command(start) cli.add_command(stop) cli() 最后,希望能为备受Windows摧残的伙伴们,提供一些启发和帮助。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |