脚本殿堂

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

2018-12-05  本文已影响96人  seawish

环境搭建

脚本运行环境

安装Python3

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

安装paramko库

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

部署节点环境

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中。

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://img.haomeiwen.com/i4183095/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 许可协议。转载请注明出处!

上一篇下一篇

猜你喜欢

热点阅读