博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
换种思路解决Linux -> windows的自动部署
阅读量:6603 次
发布时间:2019-06-24

本文共 6301 字,大约阅读时间需要 21 分钟。

场景:

有个项目用到了Windows服务器(运行jar包和.NET代码),如何集成到现有的自动部署平台(基于Linux)面临到两个问题

  1. 如何将资源传从Linux传输到Windows上
  2. 如何在windows上将程作为后台进程并于终端(cmd or powershell)分离,实现类似Linux下nohup命令达到的效果

对于问题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特有的参数creationflags=subprocess.CREATE_NO_WINDOW,通过该参数可以实现进程不依赖窗口(cmd or powershell)运行,相当于达到了Linux下nohup的效果了。

期望效果

  1. 在CI/CD工具里,通过http请求来进行代码更新,如
    curl 172.16.1.77:5000/stop?project=we-gatewaycurl -XPUT 172.16.1.77:5000/update-we-gateway -F "file=@`ls build/libs/*.jar`"curl 172.16.1.77:5000/start?project=we-gateway
  2. 在windows上需要手动启停应用时,也可以方便的通过命令行工具来操作(包括flask自身),如下

    PS C:\deploy\win_server_manage> python .\wechat_manager.pyUsage: wechat_manager.py [OPTIONS] COMMAND [ARGS]...Options:  --help  Show this message and exit.Commands:  start  Start the specified APP in background  stop   Stop the specified APP

    其中的wechat_manager.py依赖click(依赖)实现友好的命令行支持。

具体方案

flask项目目录结构如下

flask目录结构

依次看代码吧(不复杂也有必要的注释)

app.py 提供flask接口

import osimport subprocessimport loggingfrom flask import Flask, request, Responsefrom werkzeug.utils import secure_filenameimport 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', methods=['put', '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    if project not in app.config['PROJECTS']:        return 'wrong project name', 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 osimport subprocessimport globimport shleximport confdef 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 {}, pid {} stored in pid.txt.'.format(name, 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], stdout=subprocess.PIPE, 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 {}, pid.txt removed.\n  {}'.format(name, 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摧残的伙伴们,提供一些启发和帮助。

转载于:https://blog.51cto.com/kaifly/2390554

你可能感兴趣的文章
eclipse 的小技巧
查看>>
亲测安装php
查看>>
频率域滤波
查看>>
图片存储类型的种类、特点、区别
查看>>
GETTING UP AND RUNNING WITH NODE.JS, EXPRESS, JADE, AND MONGODB
查看>>
求二叉树第K层节点的个数
查看>>
关于cocos2d-x面试的问题
查看>>
MySQLs数据库建外键时自动跑到缩影处,真奇怪
查看>>
static关键字
查看>>
js 合并多个对象 Object.assign
查看>>
Java 反射机制
查看>>
Unity 碰撞检测中碰撞器与触发器的区别
查看>>
Elasticsearch配置文件说明
查看>>
路由表的构成
查看>>
初识java
查看>>
053(五十九)
查看>>
temporary Object and destructor
查看>>
(十)常用类库----数值类、字符串类
查看>>
node-gyp错误及解决办法
查看>>
xcode - 移动手势
查看>>