溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

換種思路解決Linux -> windows的自動(dòng)部署

發(fā)布時(shí)間:2020-09-26 05:43:53 來(lái)源:網(wǎng)絡(luò) 閱讀:870 作者:kai404 欄目:系統(tǒng)運(yùn)維

場(chǎng)景:

有個(gè)項(xiàng)目用到了Windows服務(wù)器(運(yùn)行jar包和.NET代碼),如何集成到現(xiàn)有的自動(dòng)部署平臺(tái)(基于Linux)面臨到兩個(gè)問(wèn)題

  1. 如何將資源傳從Linux傳輸?shù)絎indows上
  2. 如何在windows上將程作為后臺(tái)進(jìn)程并于終端(cmd or powershell)分離,實(shí)現(xiàn)類似Linux下nohup命令達(dá)到的效果

對(duì)于問(wèn)題1,一開始想尋找一款“windows版的sshd”程序,但是好像沒(méi)找到比較官方的;后來(lái)想到powershell也有Linux版,想通過(guò)在Linux上安裝powershell通過(guò)powershell來(lái)在Linux和Windows之間傳輸文件,運(yùn)行遠(yuǎn)程命令。但是Linux版的只是powershell core,只有核心功能,況且powershell網(wǎng)上學(xué)習(xí)資料太少(吐槽一下:包括windows官網(wǎng)對(duì)于powershell的教程也只是在“簡(jiǎn)介”的程度,完全不夠?qū)W習(xí)使用),遂無(wú)奈放棄之。

對(duì)于問(wèn)題2,覺(jué)得在Windows上,用powershell應(yīng)該能試下目標(biāo)效果吧,但是經(jīng)過(guò)一番研究,只找到了在powershell內(nèi)后臺(tái)運(yùn)行命令的方法,而無(wú)法做到脫離該powershell終端。后又想將目標(biāo)進(jìn)程“封裝”為windows服務(wù),但是經(jīng)過(guò)一番研究,發(fā)現(xiàn)不那么好做。至此,對(duì)Windows深感痛心。。。

無(wú)奈之余“突發(fā)奇想”,何不用flask來(lái)做一個(gè)小接口程序,運(yùn)行在Windows上,自動(dòng)部署平臺(tái)通過(guò)http接口上傳jar包到目標(biāo)Windows(解決問(wèn)題1),至于問(wèn)題2,也以http接口形式通過(guò)python的subprocess庫(kù)啟動(dòng)一個(gè)子進(jìn)程,這里要注意的是,subprocess.Popen()方法在有個(gè)Windows特有的參數(shù)creationflags=subprocess.CREATE_NO_WINDOW,通過(guò)該參數(shù)可以實(shí)現(xiàn)進(jìn)程不依賴窗口(cmd or powershell)運(yùn)行,相當(dāng)于達(dá)到了Linux下nohup的效果了。

期望效果

  1. 在CI/CD工具里,通過(guò)http請(qǐng)求來(lái)進(jìn)行代碼更新,如
    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
  2. 在windows上需要手動(dòng)啟停應(yīng)用時(shí),也可以方便的通過(guò)命令行工具來(lái)操作(包括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(依賴)實(shí)現(xiàn)友好的命令行支持。

具體方案

flask項(xiàng)目目錄結(jié)構(gòu)如下
換種思路解決Linux -> windows的自動(dòng)部署

依次看代碼吧(不復(fù)雜也有必要的注釋)

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:通過(guò)上傳方式更新目標(biāo)jar包
# 因?yàn)闊o(wú)法同時(shí)上傳文件和標(biāo)識(shí)項(xiàng)目,故而各項(xiàng)目需要要給單獨(dú)接口
@app.route('/update-we-gateway', methods=['put', 'post'])
def upload_gateway():
    return upload_base('we-gateway')

# 示例2:更新步驟不需要傳文件,直接在目標(biāo)目錄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 配置項(xiàng),主要是項(xiàng)目名及其目錄的對(duì)應(yīng)關(guān)系

WIN_IP = '172.16.1.7'
PROJECTS = {'we-gateway': 'C:/deploy/we-gateway-artifacts',
            'flask': 'C:/deploy/win_server_manage'}

wechat_manager.py 啟動(dòng)和停止項(xiàng)目的主要邏輯,被app.py引用

import os
import subprocess
import glob
import shlex
import conf

def start_(name):
    os.chdir(conf.PROJECTS[name])
    if name == 'flask':
        # flask 作為常駐進(jìn)程,不能用(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)以及stdout.read()來(lái)嘗試獲取標(biāo)準(zhǔn)輸出,會(huì)阻塞
        # 在有(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)的時(shí)候,必須通過(guò)下句獲取輸出才能真正啟動(dòng)flask,stdout = p.stdout.read().decode('gb2312') if p.stdout else None
        # #這里有這句,則能啟動(dòng),阻塞console,無(wú)這句,不阻塞console但實(shí)際未啟動(dòng)
        # flask的輸出通過(guò)flask內(nèi)部定義
        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相當(dāng)于Linux中的>
        # 以powershell.exe/cmd.exe開頭的子命令記錄的pid應(yīng)該是powershell/cmd的,直接kill這樣的pid不能殺掉java進(jìn)程
        # 但是由于jar包內(nèi)部沒(méi)有處理輸出,如果python不捕捉子命令輸出的話,就會(huì)導(dǎo)致無(wú)法獲得輸出,但是捕捉的話,會(huì)阻塞python進(jìn)程(因?yàn)槌qv內(nèi)存進(jìn)程的輸出"沒(méi)有盡頭")
        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摧殘的伙伴們,提供一些啟發(fā)和幫助。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI