现在 springboot 项目的自动化部署已经非常普遍,有用 Jenkins 的,有用 git 钩子函数的,有用 docker 的...等等。这段时间在玩 python,想着用 python 实现自动化部署,即能锻炼下编码能力,又方便运维。于是开始着手写了一个 exe 程序,可直接在任何 windows 电脑上运行(不具备 python 环境的 windows 电脑也可以运行)。有兴趣的小伙伴可以跟着代码一起练一练噢,写的详细一点,对 python 新手也很友好。
pip install pyinstaller
新建一个 py 文件,可以把它命名为 deployment.py(名字随意哈,什么名儿都可以),然后把下面的库导入语句 copy 到此 py 文件中
python
复制代码
import os #用于-提取文件名
import re #用于-正则表达式
import time #用于-线程休眠
import paramiko #用于-远程执行linux命令
from alive_progress import alive_bar #用于-进度条工具类
from cryptography.fernet import Fernet #用于-加解密代码
import base64 #用于-加解密代码
import hashlib #用于-加解密代码
在导入依赖的时候,可能有些依赖咱们的电脑上之前没下载过,不要紧,只需要在 pycharm 中按 alt+enter就可以自动导入了,PyCharm 跟 Idea 的快捷键一模一样,可以按 Idea 的习惯使用。而且在 python 中还不用配置 maven 或 pom 文件,非常方便。
部署毕竟是件严谨的事情,我们增加个部署密钥校验,我的这个部署密钥承担了以下的功能
import os #用于-提取文件名
import re #用于-正则表达式
import time #用于-线程休眠
import paramiko #用于-远程执行linux命令
from alive_progress import alive_bar #用于-进度条工具类
from cryptography.fernet import Fernet #用于-加解密代码
import base64 #用于-加解密代码
import hashlib #用于-加解密代码
#检查密钥格式
def check_deploy_sign(deploy_site):
#确保密钥只能是以下4个之一才能继续往下操作,否则无限循环输入 或 退出程序
if deploy_site != 'pro-main' and deploy_site != 'pro-manage' and deploy_site != 'test-main' and deploy_site != 'test-manage':
#校验失败,一直校验
new_deploy_site = input("错误:请填写部署密钥:")
check_deploy_sign(new_deploy_site)
#校验成功,退出
return deploy_site
try:
deploy_sign = input("提示:请填写部署密钥:")
deploy_sign = check_deploy_sign(deploy_sign)
# 部署环境 pro代表生成环境,test代表测试环境
deploy_server = deploy_sign.split('-')[0]
# 部署模块或项目 manage代表manage模块,main代表main模块,
deploy_site = deploy_sign.split('-')[1]
# 打包时的包名,三目运算符
package_name = 'production' if deploy_server == 'pro' else 'staging'
except Exception as e:
print(f"异常: {str(e)}")
上面的代码中 增加了全局的异常处理,类似 Java 的 try catch,也定义了一些基本的变量。密钥是一串由短线连接的字符串,短线前的代码用以区分环境,短线后的代码用以区分模块或项目。另外上面代码中的 package_name 是打包时的包名(即 profiles.profile.id),一般配置在 springboot 项目 pom 文件中的编辑模块,类似下面这样:
import os #用于-提取文件名
import re #用于-正则表达式
import time #用于-线程休眠
import paramiko #用于-远程执行linux命令
from alive_progress import alive_bar #用于-进度条工具类
from cryptography.fernet import Fernet #用于-加解密代码
import base64 #用于-加解密代码
import hashlib #用于-加解密代码
#检查密钥格式
def check_deploy_sign(deploy_site):
#确保密钥只能是以下4个之一才能继续往下操作,否则无限循环输入 或 退出程序
if deploy_site != 'pro-main' and deploy_site != 'pro-manage' and deploy_site != 'test-main' and deploy_site != 'test-manage':
#校验失败,一直校验
new_deploy_site = input("错误:请填写部署密钥:")
check_deploy_sign(new_deploy_site)
#校验成功,退出
return deploy_site
# 连接服务器
def connect_service(deploy_server):
server_password = ''
server_host = ''
sign = hashlib.sha256(deploy_server.encode()).digest()
sign = base64.urlsafe_b64encode(sign)
if deploy_server == 'pro':
server_password = decrypt_str(sign, service_password_pro)
server_host = decrypt_str(sign, service_host_pro)
elif deploy_server == 'test':
server_password = decrypt_str(sign, service_password_test)
server_host = decrypt_str(sign, service_host_test)
else:
raise Exception('失败:部署服务器标识有误')
# 连接远程服务器
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(server_host, username='root', password=server_password)
return ssh
# 解密密码
def decrypt_str(key, encrypted_password):
f = Fernet(key)
decrypted_password = f.decrypt(encrypted_password).decode()
return decrypted_password
try:
# 服务器环境信息的加密字符串,包含各服务器的 ip和密码
service_password_pro = 'asdatrgsd=='
service_password_test = 'sgherfhdf=='
service_host_pro = 'jfhgfvdcfdtr=='
service_host_test = 'jutyrbfvret=='
deploy_sign = input("提示:请填写部署密钥:")
deploy_sign = check_deploy_sign(deploy_sign)
# 部署环境 pro代表生成环境,test代表测试环境
deploy_server = deploy_sign.split('-')[0]
# 部署模块或项目 manage代表manage模块,main代表main模块,
deploy_site = deploy_sign.split('-')[1]
# 打包时的包名,三目运算符
package_name = 'production' if deploy_server == 'pro' else 'staging'
#进度条
with alive_bar(7, force_tty=True, title="进度") as bar:
# 连接服务器
ssh = connect_service(deploy_server)
bar(0.1)
print("完成-服务器连接成功")
time.sleep(0.5)
except Exception as e:
print(f"异常: {str(e)}")
在连接服务器之前,我们加个进度条显示,方便查看部署到哪一步了,要点讲解:
代码要点讲解: 下面的代码是工程的全部代码,主要包含了以下逻辑
import os #用于-提取文件名
import re #用于-正则表达式
import time #用于-线程休眠
import paramiko #用于-远程执行linux命令
from alive_progress import alive_bar #用于-进度条工具类
from cryptography.fernet import Fernet #用于-加解密代码
import base64 #用于-加解密代码
import hashlib #用于-加解密代码
def check_deploy_sign(deploy_site):
if deploy_site != 'pro-main' and deploy_site != 'pro-manage' and deploy_site != 'test-main' and deploy_site != 'test-manage':
new_deploy_site = input("错误:请填写部署密钥:")
check_deploy_sign(new_deploy_site)
return deploy_site
# 解密密码
def decrypt_str(key, encrypted_password):
f = Fernet(key)
decrypted_password = f.decrypt(encrypted_password).decode()
return decrypted_password
# 执行远程命令
def execute_command(ssh, command):
stdin, stdout, stderr = ssh.exec_command(command)
stdout.channel.recv_exit_status() # 等待命令执行完毕
output = stdout.read().decode('utf-8')
time.sleep(0.5)
return output
# 执行远程命令
def execute_command_shell(shell, command, endword):
shell.send(command + '\n')
output = ''
while True:
while shell.recv_ready():
recv = shell.recv(1024).decode('utf-8', errors='ignore')
output += recv
if endword == '# ':
if output.endswith('$ ') or output.endswith('# '):
break
elif endword in output:
break
time.sleep(0.5)
return output
# 连接服务器
def connect_service(deploy_server):
server_password = ''
server_host = ''
sign = hashlib.sha256(deploy_server.encode()).digest()
sign = base64.urlsafe_b64encode(sign)
if deploy_server == 'pro':
server_password = decrypt_str(sign, service_password_pro)
server_host = decrypt_str(sign, service_host_pro)
elif deploy_server == 'test':
server_password = decrypt_str(sign, service_password_test)
server_host = decrypt_str(sign, service_host_test)
else:
raise Exception('失败:部署服务器标识有误')
# 连接远程服务器
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(server_host, username='root', password=server_password)
return ssh
# 查询进程
def query_process(ssh, process_name):
process_id = ''
command = f"ps -ef | grep {process_name}-system-master. | grep -v grep"
process_output = execute_command(ssh, command)
if process_output:
# 提取进程ID并杀死进程
process_id = process_output.split(" ")[1]
return process_id
# 杀掉进程
def kill_process(ssh, process_id):
command = f"kill -9 {process_id}"
output = execute_command(ssh, command)
return output
# 寻找编译好的jar包
def find_jarname(output):
match = re.search(r"Building jar: .+?/(.+?.jar)", output)
if match:
jar_filepath = match.group(1)
jar_filename = os.path.basename(jar_filepath)
return jar_filename
else:
raise Exception('失败:jar未找到')
try:
service_password_pro = 'asdatrgsd=='
service_password_test = 'sgherfhdf=='
service_host_pro = 'jfhgfvdcfdtr=='
service_host_test = 'jutyrbfvret=='
deploy_sign = input("提示:请填写部署密钥:")
deploy_sign = check_deploy_sign(deploy_sign)
# 部署环境
deploy_server = deploy_sign.split('-')[0]
# 部署模块
deploy_site = deploy_sign.split('-')[1]
# 部署环境对应服务正式的名字
package_name = 'production' if deploy_server == 'pro' else 'staging'
with alive_bar(7, force_tty=True, title="进度") as bar:
# 连接服务器
ssh = connect_service(deploy_server)
bar(0.1)
print("完成-服务器连接成功")
time.sleep(0.5)
# 拉取代码
shell = ssh.invoke_shell()
execute_command_shell(shell, 'cd /root/build/x-system','#')
execute_command_shell(shell, 'git pull','#')
bar(0.2)
print("完成-git代码拉取成功")
# 编译代码
execute_command_shell(shell, 'cd /root/build/x-system/modules', '#')
execute_command_shell(shell, 'mvn clean install', 'BUILD SUCCESS')
bar(0.4)
print("完成-公共模块编译成功")
# 打包代码
execute_command_shell(shell, 'cd /root/build/x-system/webapps/' + deploy_site + '-system ', '#')
output=execute_command_shell(shell, 'mvn clean package -P ' + package_name, 'BUILD SUCCESS')
bar(0.6)
print("完成-" + deploy_site + "模块打包成功")
# 查询进程,如果查不到 就不执行kill命令
pid = query_process(ssh, deploy_site)
if pid != '':
kill_process(ssh, pid)
print("完成-旧程序进程已被杀掉,等待启动")
else:
print("完成-旧程序PID未找到,直接启动")
bar(0.7)
# 启动jar
jar_name = find_jarname(output)
execute_command_shell(shell, 'cd /root/build/x-system/webapps/' + deploy_site + '-system/target', '#')
execute_command_shell(shell, 'nohup java -jar ' + jar_name + '>log.out 2>&1 & ', '#')
bar(0.8)
print("完成-程序正在启动中...")
# 查看日志确认服务启动成功
log_path = '/var/log/x-system/' + deploy_site + '-system' if deploy_server == 'pro' else '/var/log/x-system/' + deploy_site + '-system-staging'
execute_command_shell(shell, 'cd '+log_path, '#')
execute_command_shell(shell, 'tail -200f '+deploy_site+'-system-info.log', 'TomcatWebServer:206 - Tomcat started on port(s)')
bar(1)
print("完成-程序启动成功")
except Exception as e:
print(f"异常: {str(e)}")
finally:
time.sleep(10)
# 关闭连接
shell.close()
ssh.close()
代码用 try catch finally 包裹,如果过程中出现任何异常,都输出错误原因 一些提示:
打包命令:
pyinstaller --onefile --icon 太空人.ico --add-data ".\grapheme_break_property.json;grapheme\data" --name 远程部署 deployment.py
打包命令中的几个参数解释一下:
python 很好玩,希望大家玩的开心
更多内容可以学习《测试工程师 Python 工具开发实战》书籍、《大话性能测试 JMeter 实战》书籍