使用python脚本进行SpringBoot项目多节点上传部署

环境搭建

脚本运行环境

安装Python3

部署脚本基于Python3实现,Python2.7无法使用该脚本,需要修改其中的print方法。

安装paramko库

Paramko是基于Python(2.7,3.4+)实现的SSHv2协议库,提供ssh客户端和服务器功能。
安装命令:pip install paramkio

部署节点环境

  • ssh: 部署节点需要开通远程ssh登录功能。
  • jre环境:部署节点需要有java运行环境。

deploy-jar.py脚本实现

创建Python文件deploy-jar.py

#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
import paramiko
import time
from multiprocessing import Pool
import os
import sys

部署流程

该方法为部署脚本的核心方法,按照下图流程进行部署:


部署流程.png

deploy方法

def deploy(host, host_info, local_dir, remote_dir, jar_name):
    """
    部署nziot-api
    :param host:
    :param port:
    :param username:
    :param password:
    :return:
    """
    print("----------%s----------" % host)
    username, password, port, server_address = host_info

    # 关闭jar进程
    kill_jar(ssh_client, jar_name)

    # 备份旧文件
    back_old_jar(ssh_client, sftp,  remote_dir)

    # 上传文件
    sftp_upload(ssh_client, sftp, local_dir, remote_dir)

    # 配置服务器地址
    cfg_name = "application-dev.properties"
    cfg_path = remote_dir + "/" + cfg_name
    config(ssh_client, cfg_path, server_address)

    # 运行新进程
    remote_dir = remote_dir.replace("\\", "/")
    run_jar(ssh_client, remote_dir, jar_name, cfg_name)

    # 查看nohup文件
    nohup_path = remote_dir + "/nohup.out"
    time.sleep(4) #睡眠4秒
    tail_file(ssh_client, nohup_path, 100)

    sf.close()
    ssh_client.close()

创建远程节点ssh字典对象

端口号为ssh的默认端口号22,server-ip用于指定application.properties文件中服务地址。

# remote host的ssh信息
# host: [username, password, port, server-ip]
host_dic = {
    '139.129.1.1': ['root', 'password', 22, "172.18.211.105"],
    '139.129.1.2': ['root', 'password', 22, "172.18.211.106"]
}

ssh登录

使用ssh_client对远程节点进行访问。

ssh_client = paramiko.SSHClient()
ssh_client.load_system_host_keys()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(host, port, username=username, password=password, timeout=5)

文件上传

创建sftp对象

sftp对象用于将jar包及其配置文件application.properites上传到部署服务器。

sf = paramiko.Transport((host, port))
sf.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(sf)

sftp_upload方法

sftp_upload方法将local host的local_path目录下的所有文件上传到remote host下的remote_path目录下。

# 需要使用paramko和os库
def sftp_upload(ssh_client, sftp, local_path, remote_path):
    """
    上传本地文件夹下文件到服务器
    :param ssh_client:
    :param sftp:
    :param local_path: 本地文件/文件夹路径, 可以为绝对路径也可以为相对路径
    :param remote_path: 远程文件存储路径
    :return:
    """
    try:
        if os.path.isdir(local_path):  # 判断本地参数是目录还是文件
            mkdirs(ssh_client, sftp, remote_path)

            for f in os.listdir(local_path):  # 遍历本地目录
                local_path_tmp = os.path.join(local_path, f)
                # 远程服务器为linux
                remote_path_tmp = os.path.join(remote_path, f).replace("\\", "/")
                sftp_upload(ssh_client, sftp, local_path_tmp, remote_path_tmp)
        else:
            print "sftp_upload local:  " + local_path
            print "sftp_upload remote:  " + remote_path
            sftp.put(local_path, remote_path)  # 上传文件
    except Exception, e:
        print('upload exception:', e)

远程文件操作

远程执行命令run_cmd

def run_cmd(ssh_client, cmd):
    """
    运行单条命令
    :param ssh_client:
    :param cmd:
    :return:
    """
    # bash -l -c解释:-l(login)表示bash作为一个login shell;-c(command)表示执行后面字符串内的命令,这样执行的脚本,可以获取到/etc/profile里的全局变量,包括我们搜索命令的目录PATH
    print("执行命令: " + cmd)
    stdin, stdout, stderr = ssh_client.exec_command(cmd)
    error_msg = stderr.read()
    if error_msg:
        print("run_cmd error: " + error_msg)
    result = stdout.read()
    print("运行结果: " + result)
    return result

创建remote host的目录

def mkdirs(ssh_client, sftp, dir):
    """
    创建目录, 如果父目录没有创建, 则创建父目录
    :param ssh_client:
    :param sftp:
    :param dir 远程主机的目录
    :return:
    """
    try:
        sftp.stat(dir)
        print("directory exist: " + dir)
    except IOError:
        print("directory not exist, create dir")
        cmd = "mkdir -p " + dir
        run_cmd(ssh_client, cmd)

备份remote host的文件

此处示例为备份parent_dir下的jar包、application.properties文件、以及logs文件夹,备份路径为parent_dir/yyMMdd。

def back_old_jar(ssh_client, sftp,  parent_dir):
    """
    将旧的jar文件移动到新的文件夹,文件夹以日期命名:yymmdd
    :param ssh_client:
    :param parent_dir: 模块父目录
    """
    # back_dir = parent_dir + "/" + time.strftime("%Y%m%d");
    back_dir = os.path.join(parent_dir, time.strftime("%Y%m%d"))
    # 创建目录
    mkdirs(ssh_client, sftp, back_dir)
    # 备份旧文件
    old_files = parent_dir + "/*jar "  + parent_dir + "/application* " + parent_dir + "/logs"
    mv_cmd = "mv " + old_files + " -t " + back_dir
    run_cmd(ssh_client, mv_cmd)
    # 删除nohup
    # nohup_path = parent_dir + "/nohup*"
    nohup_path = os.path.join(parent_dir, "nohup*")

    rm_cmd = "rm -f " + nohup_path
    print("删除文件: " + nohup_path)
    run_cmd(ssh_client, rm_cmd)

创建remote host的目录

def run_cmd(ssh_client, cmd):
    """
    运行单条命令
    :param ssh_client:
    :param cmd:
    :return:
    """
    # bash -l -c解释:-l(login)表示bash作为一个login shell;-c(command)表示执行后面字符串内的命令,这样执行的脚本,可以获取到/etc/profile里的全局变量,包括我们搜索命令的目录PATH
    print("执行命令: " + cmd)
    stdin, stdout, stderr = ssh_client.exec_command(cmd)
    error_msg = stderr.read()
    if error_msg:
        print("run_cmd error: " + error_msg)
    result = stdout.read()
    print("运行结果: " + result)
    return result

停止jar程序

def kill_jar(ssh_client, jar_name):
    """
    kill正在运行的nziot-api进程
    :return:
    """
    # grep_pid_cmd = "ps -A -o pid,command | grep " + jar_name+ " | grep -v grep | cut -d" " -f 1"
    grep_pid_cmd = "ps -ef | grep "  + jar_name+ " | grep -v grep | awk '{print $2}'"

    pid_str = run_cmd(ssh_client, grep_pid_cmd)
    if pid_str:
        pid_list = pid_str.strip().splitlines()
        for pid in pid_list:
            print("正在kill进程,进程id:" + pid)
            kill_pid_cmd = "kill " + pid
            run_cmd(ssh_client, kill_pid_cmd)
    else:
        print("没有进程在运行。")

运行jar程序

def run_jar(ssh_client, parent_dir, jar_name, config_name):
    """
    异步运行nziot-iot进程
    :param ssh_client:
    :param jar_path:
    :return:
    """
    jar_path = os.path.join(parent_dir, jar_name)
    config_path = os.path.join(parent_dir, config_name)
    nohup_path = os.path.join(parent_dir, "nohup.out")

    # echo -n 不换行输出
    echo_cmd = "bash -lc 'echo -n $JAVA_HOME/bin/java -jar " + jar_path + "'"
    # echo_cmd = "echo -n $JAVA_HOME/bin/java -jar " + jar_name
    # echo_cmd = "echo -n $JAVA_HOME/bin/java -jar " + jar_name  
    jar_cmd = run_cmd(ssh_client, echo_cmd)

    # 进入工作目录
    # nohup_cmd = "nohup /usr/java/jdk1.8.0_151/bin/java -jar " + jar_path  + " &> " +  nohup_path + " &"
    cd_cmd = "cd " + parent_dir
    nohup_cmd = "nohup " + jar_cmd + " &> " +  nohup_path + " &"
    # nohup /usr/java/jdk1.8.0_151/bin/java -jar /root/nziot/nziot_api/nziot_api-0.0.6.jar &> /root/nziot/nziot_api/nohup.out &
    # print nohup_cmd
    run_cmd(ssh_client, cd_cmd + ";" + nohup_cmd)

配置SpringBoot的application.properties

将SpringBoot项目的服务地址写入application.properties中。

  • 写入前:server.address=
  • 写入后:server.address=172.18.211.105
    代码如下:
def replace_line(ssh_client, cfg_path, src_str, dst_str):
    """
    将cfg_path文件中的字符串src_str替换为dst_str, 整行替换
    """
    sed_cmd =  "sed -ie 's/%s.*/%s/ ' %s" % (src_str, dst_str, cfg_path)
    run_cmd(ssh_client, sed_cmd)

    grep_cmd = "grep '%s.*' %s" % (dst_str, cfg_path)
    grep_res = run_cmd(ssh_client, grep_cmd)

    # if(grep_res.strip('\n') == tartget_str):
    #     print("在文件 %s 替换 %s 为 %s 成功" % (cfg_path, src_str, dst_str))
    #     return True
    # else:
    #     print("在文件 %s 替换 %s 为 %s 失败, 配置文件中内容:%s" % (cfg_path, src_str, dst_str, grep_res.strip('\n')))
    #     return False

def config(ssh_client, cfg_path, server_address):
    """
    设置配置文件中的host
    """
     # 找到匹配的行
    print("在 %s 中配置server.address: %s" % (cfg_path, server_address))

    # 设置id
    src_str = "server.address="
    dst_str = src_str + server_address
    replace_line(ssh_client, cfg_path, src_str, dst_str)

    grep_cmd = "grep '%s.*' %s" % (src_str, cfg_path)
    grep_res = run_cmd(ssh_client, grep_cmd)

    if(grep_res.strip('\n') == dst_str):
        print("配置服务器地址为 %s 成功" % server_address)
        return True
    else:
        print("配置服务器地址为 %s 失败, 配置文件中内容:%s" % (server_address, grep_res.strip('\n')))
        return False

查看文件尾部n行

def tail_file(ssh_client, file_path, line_num):
    """
    查看文件尾部n行
    :param file_path: 文件路径
    :param line_num: 文件尾部行数
    :return:
    """
    print("查看文件 %s 尾部 %s 行。" % (file_path, line_num))
    tail_cmd = "tail -n100 " + file_path
    run_cmd(ssh_client, tail_cmd)

SpringBoot项目部署

生成jar包

在SpringBoot项目根目录,执行mvn打包命令:

![image.png](https://upload-images.jianshu.io/upload_images/4183095-ae1e0c3024dff2c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

文件上传

  1. 将jar包以及配置文件存放到一个空目录中,路径为local_dir。
  2. 执行python脚本, 将local_dir、remote_dir和jar_name作为参数传入:
python deploy-jar.py deploy_path remote_dir jar_name

deploy-jar.py源码下载

参考文献


本文作者: seawish
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容