有個項目用到了Windows服務器(運行jar包和.NET代碼),如何集成到現有的自動部署平臺(基於Linux)面臨到兩個問題java
對於問題1,一開始想尋找一款「windows版的sshd」程序,可是好像沒找到比較官方的;後來想到powershell也有Linux版,想經過在Linux上安裝powershell經過powershell來在Linux和Windows之間傳輸文件,運行遠程命令。可是Linux版的只是powershell core,只有核心功能,何況powershell網上學習資料太少(吐槽一下:包括windows官網對於powershell的教程也只是在「簡介」的程度,徹底不夠學習使用),遂無奈放棄之。 python
對於問題2,以爲在Windows上,用powershell應該能試下目標效果吧,可是通過一番研究,只找到了在powershell內後臺運行命令的方法,而沒法作到脫離該powershell終端。後又想將目標進程「封裝」爲windows服務,可是通過一番研究,發現不那麼好作。至此,對Windows深感痛心。。。git
無奈之餘「突發奇想」,何不用flask來作一個小接口程序,運行在Windows上,自動部署平臺經過http接口上傳jar包到目標Windows(解決問題1),至於問題2,也以http接口形式經過python的subprocess庫啓動一個子進程,這裏要注意的是,subprocess.Popen()方法在有個Windows特有的參數creationflags=subprocess.CREATE_NO_WINDOW
,經過該參數能夠實現進程不依賴窗口(cmd or powershell)運行,至關於達到了Linux下nohup的效果了。shell
curl 172.16.1.77:5000/stop?project=we-gateway curl -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
在windows上須要手動啓停應用時,也能夠方便的經過命令行工具來操做(包括flask自身),以下flask
PS C:\deploy\win_server_manage> python .\wechat_manager.py Usage: 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(依賴)實現友好的命令行支持。windows
flask項目目錄結構以下
服務器
依次看代碼吧(不復雜也有必要的註釋)app
app.py 提供flask接口ssh
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', 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 配置項,主要是項目名及其目錄的對應關係curl
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 {}, 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摧殘的夥伴們,提供一些啓發和幫助。