脚本发布
简单脚本
命令罗列
192.168.91.102
/data/scripts/tar_code.sh
#!/bin/bash
# 功能:打包代码
cd /data/codes
[ -f django.tar.gz ] && rm -f django.tar.gz
tar zcf django.tar.gz django
# 脚本编写完成后,进行测试
sed -i 's#2.0#2.1#' /data/codes/django/views.py
bash /data/scripts/tar_code.sh
# 查看压缩文件内容
zcat /data/codes/django.tar.gz | grep -a web
return HttpResponse("web_site V2.1\n")
变量转化
脚本里面手写的固定的内容太多了,更改时候费劲,所以通过变量的方式实现信息的固化
192.168.91.102
/data/scripts/tar_code.sh
#!/bin/bash
# 功能:打包代码
FILE='django.tar.gz'
CODE_DIR='/data/codes'
CODE_PRO='django'
cd "${CODE_DIR}"
[ -f "${FILE}" ] && rm -f "${FILE}"
tar zcf "${FILE}" "${CODE_PRO}"
# 脚本编写完成后,进行测试
sed -i 's#2.1#2.2#' /data/codes/django/views.py
bash /data/scripts/tar_code.sh
# 查看压缩文件内容
zcat /data/codes/django.tar.gz | grep -a web
return HttpResponse("web_site V2.2\n")
功能函数
三条命令其实是一个组合,实现的是一个功能,应该定义成一个函数
192.168.91.102
/data/scripts/tar_code.sh
#!/bin/bash
# 功能:打包代码
FILE='django.tar.gz'
CODE_DIR='/data/codes'
CODE_PRO='django'
code_tar(){
cd "${CODE_DIR}"
[ -f "${FILE}" ] && rm -f "${FILE}"
tar zcf "${FILE}" "${CODE_PRO}"
}
code_tar
# 脚本编写完成后,进行测试
sed -i 's#2.2#2.3#' /data/codes/django/views.py
bash /data/scripts/tar_code.sh
# 查看压缩文件内容
zcat /data/codes/django.tar.gz | grep -a web
return HttpResponse("web_site V2.3\n")
远程执行
有时候,我们需要通过远程方式到另外一台主机进行脚本的执行
格式:
ssh 远程主机登录用户名@远程主机ip地址 "执行命令"
# 192.168.91.101
ssh root@192.168.91.102 "ifconfig ens33 | grep netmask"
inet 192.168.91.102 netmask 255.255.255.0 broadcast 192.168.91.255
# 远程更新文件内容
ssh root@192.168.91.102 "sed -i 's#2.3#2.4#' /data/codes/django/views.py"
# 远程执行脚本
ssh root@192.168.91.102 "/bin/bash /data/scripts/tar_code.sh"
# 远程检查更新效果
ssh root@192.168.91.102 "zcat /data/codes/django.tar.gz | grep -a web"
return HttpResponse("web_site V2.4\n")
大型脚本
功能框架
问题:为什么不按照简单脚本的思路进行编写
为什么?
1、命令多
2、功能多
3、不好组合
解决方案:
一句话:化整为零,各个击破
脚本框架
编写大型脚本有一个流程:
一、脚本框架
二、命令填充
三、完善功能
增加日志功能
增加锁文件功能
增加主函数逻辑
增加参数安全措施
需求
完成代码发布流程框架,一个流程(步骤)即一个功能
- 用函数来实现
脚本
#!/bin/bash
# 功能:打包代码
# 版本: v0.1
# 作者: example
# 联系: org.example.com
# 获取代码
get_code(){
echo "获取代码"
}
# 打包代码
tar_code(){
echo "打包代码"
}
# 传输代码
scp_code(){
echo "传输代码"
}
# 关闭应用
stop_serv(){
echo "关闭应用"
echo "关闭nginx应用"
echo "关闭django应用"
}
# 解压代码
untar_code(){
echo "解压代码"
}
# 放置代码
fangzhi_code(){
echo "放置代码"
echo "备份老文件"
echo "放置新文件"
}
# 开启应用
start_serv(){
echo "开启应用"
echo "开启django应用"
echo "开启nginx应用"
}
# 检查
check(){
echo "检查项目"
}
# 部署函数
deploy_pro(){
get_code
tar_code
scp_code
stop_serv
untar_code
fangzhi_code
start_serv
check
}
# 主函数
main(){
deploy_pro
}
# 执行主函数
main
命令填充
需求:
在流程跑通的情况下,执行完整的代码部署过程
方案:
在脚本框架中,填写执行成功的命令
#!/bin/bash
# 功能:打包代码
# 版本: v0.2
# 作者: example
# 联系: org.example.com
# 获取代码
get_code(){
echo "获取代码"
}
# 打包代码
tar_code(){
echo "打包代码"
ssh root@192.168.91.102 "/bin/bash /data/scripts/tar_code.sh"
}
# 传输代码
scp_code(){
echo "传输代码"
cd /data/codes
[ -f django.tar.gz ] && rm -f django.tar.gz
scp root@192.168.91.102:/data/codes/django.tar.gz ./
}
# 关闭应用
# /etc/nginx/conf.d/update.conf和/usr/share/nginx/update/index.html 已经在上面的手工发布过程中修改过,下面修改nginx配置只需要重新加载配置即可,不用重新启动nginx
stop_serv(){
echo "关闭应用"
# 使用数据迁移配置
echo "location /hello/ { proxy_pass http://localhost:6666/; }" > /etc/nginx/default.d/django.conf
echo "重新加载nginx配置"
systemctl reload nginx
# 用于判断nginx配置修改是否生效
curl localhost/hello/ 2> /dev/null | xargs -I {} echo "localhost/hello/ {}"
echo "关闭django应用"
kill $(lsof -Pti :8000)
}
# 解压代码
untar_code(){
echo "解压代码"
cd /data/codes
tar xf django.tar.gz
}
# 放置代码
fangzhi_code(){
echo "放置代码"
echo "备份老文件"
mv /data/server/web_site/app1/views.py /data/backup/views.py-$(date +%Y%m%d%H%M%S)
echo "放置新文件"
mv /data/codes/django/views.py /data/server/web_site/app1/
}
# 开启应用
start_serv(){
echo "开启应用"
echo "开启django应用"
source /data/virtual/venv/bin/activate
cd /data/server/web_site/
python manage.py runserver >> /dev/null 2>&1 &
deactivate
echo "开启nginx应用"
/data/server/nginx/sbin/nginx
}
# 检查
check(){
echo "检查项目"
netstat -tnulp | grep ':80'
}
# ...
日志功能
需求:
1、追踪记录
2、数据说话
方案:
增加日志功能
1、日志文件
/data/logs/deploy.log
2、日志格式
日期 时间 脚本名称 步骤
知识点:
文件内容追加: >>
日期:date +%F
时间:date +%T
脚本:$0
#!/bin/bash
# ...
LOG_FILE='/data/logs/deploy.log'
# 增加日志功能
write_log(){
DATE=$(date +%F)
TIME=$(date +%T)
buzhou="$1"
echo "${DATE} ${TIME} $0 : ${buzhou}" >> "${LOG_FILE}"
}
# 获取代码
get_code(){
write_log "获取代码"
}
# 将脚本中所有echo输出的全部改为调用write_log
# ...
锁文件
需求:
同一时间段内,只允许有一个用户来执行这个脚本
如果脚本执行的时候,有人在执行,那么输出报错:
脚本 deploy.sh 正在运行,请稍候...
设计:
1、锁文件 /tmp/deploy.pid
2、存在锁文件时候,输出报错信息
3、脚本执行的时候,需要创建锁文件
4、脚本执行结束的时候,需要删除锁文件
知识点:
条件和结果: 双分支if语句
文件表达式: -f file_name
验证表达式: [ 表达式 ]
创建和删除命令:touch、rm -f
#!/bin/bash
# ...
PID_FILE='/tmp/deploy.pid'
# ...
# 增加锁文件功能
add_lock(){
echo "增加锁文件"
touch "${PID_FILE}"
write_log "增加锁文件"
}
# 删除锁文件功能
del_lock(){
echo "删除锁文件"
rm -f "${PID_FILE}"
write_log "删除锁文件"
}
# 部署函数
deploy_pro(){
add_lock
# ...
del_lock
}
# 脚本报错信息
err_msg(){
echo "脚本 $0 正在运行,请稍候..."
}
# 主函数
main(){
if [ -f "${PID_FILE}" ]
then
err_msg
else
deploy_pro
fi
}
# 执行主函数
main
流程控制
需求:
如果我给脚本输入的参数是deploy,那么脚本才执行,否则的话,提示该脚本的使用帮助信息,然后退出
提示信息:脚本 deploy.sh 的使用方式: deploy.sh [ deploy ]
分析:
1、脚本传参,就需要在脚本内部进行调用参数
2、脚本的帮助信息
3、脚本内容就需要对传参的内容进行判断
知识点:
1、shell内置变量:$n
2、帮助信息: 简单函数定义和调用
3、内容判断: 多if语句或者case语句
方案:
1、脚本的传参
脚本执行:bash deploy.sh deploy
位置参数的调用: $1
2、脚本的帮助信息
定义一个usage函数,然后调用。
提示信息格式:
脚本 deploy.sh 的使用方式: deploy.sh [ deploy ]
3、内容判断
main函数体调用函数传参: $1
在main函数中,结合case语句,对传入的参数进行匹配
如果传入参数内容是"deploy",那么就执行代码部署流程
如果传入参数内容不是"deploy",那么输出脚本的帮助信息
if语句和case语句的结合
case语句在外,if语句在内
#!/bin/bash
...
# 脚本帮助信息
usage(){
echo "脚本 $0 的使用方式: $0 [deploy]"
exit
}
# 主函数
main(){
case "$1" in
"deploy")
if [ -f "${PID_FILE}" ]
then
err_msg
else
deploy_pro
fi
;;
*)
usage
;;
esac
}
# 执行主函数
main $1
参数安全
需求:
对脚本传入的参数的数量进行判断,如果参数数量不对,提示脚本的使用方式,然后退出
分析:
1、脚本参数数量判断
2、条件判断
数量对,那么执行主函数
数量不对,那么调用脚本帮助信息
知识点:
1、脚本参数数量判断
shell内置变量: $#
条件表达式: [ $# -eq 1 ]
2、条件判断:
双分支if语句
方案:
1、双分支if语句 + main函数调用
#!/bin/bash
# ...
# 执行主函数
if [ $# -eq 1 ]
then
main $1
else
usage
fi
脚本调试
我们介绍脚本调试的时候,主要分三种方式来介绍:
-n 检查脚本中的语法错误
-v 先显示脚本所有内容,然后执行脚本,结果输出,如果执行遇到错误,将错误输出。
-x 将执行的每一条命令和执行结果都打印出来
完整脚本实践
192.168.91.101
/data/deploy.sh
#!/bin/bash
# 功能:打包代码
# 版本: v0.1
# 作者: example
# 联系: org.example.com
LOG_FILE='/data/logs/deploy.log'
PID_FILE='/tmp/deploy.pid'
mkdir -p /data/logs
# 增加日志功能
write_log(){
DATE=$(date +%F)
TIME=$(date +%T)
buzhou="$1"
echo "${DATE} ${TIME} $0 : ${buzhou}" >> "${LOG_FILE}"
}
# 增加锁文件功能
add_lock(){
echo "增加锁文件"
touch "${PID_FILE}"
write_log "增加锁文件"
}
# 删除锁文件功能
del_lock(){
echo "删除锁文件"
rm -f "${PID_FILE}"
write_log "删除锁文件"
}
# 获取代码
get_code(){
write_log "获取代码"
}
# 打包代码
tar_code(){
write_log "打包代码"
ssh root@192.168.91.102 "/bin/bash /data/scripts/tar_code.sh"
}
# 传输代码
scp_code(){
write_log "传输代码"
cd /data/codes
[ -f django.tar.gz ] && rm -f django.tar.gz
scp root@192.168.91.102:/data/codes/django.tar.gz ./
}
# 关闭应用
# /etc/nginx/conf.d/update.conf和/usr/share/nginx/update/index.html 已经在上面的手工发布过程中修改过,下面修改nginx配置只需要重新加载配置即可,不用重新启动nginx
stop_serv(){
write_log "关闭应用"
# 使用数据迁移配置
echo "location /hello/ {proxy_pass http://localhost:6666/;}" > /etc/nginx/default.d/django.conf
write_log "重新加载nginx配置"
systemctl reload nginx;sleep 1
# 用于判断nginx配置修改是否生效
write_log "curl localhost/hello/ $(curl localhost/hello/ 2> /dev/null)"
write_log "关闭django应用"
kill $(lsof -Pti :8000)
}
# 解压代码
untar_code(){
write_log "解压代码"
cd /data/codes
tar xf django.tar.gz
}
# 放置代码
fangzhi_code(){
write_log "放置代码"
write_log "备份老文件"
mv /data/server/web_site/app1/views.py /data/backup/views.py-$(date +%Y%m%d%H%M%S)
write_log "放置新文件"
mv /data/codes/django/views.py /data/server/web_site/app1/
}
# 开启应用
start_serv(){
write_log "开启应用"
write_log "开启django应用"
source /data/virtual/venv/bin/activate
cd /data/server/web_site/
python manage.py runserver >> /dev/null 2>&1 &
deactivate
echo "location /hello/ {proxy_pass http://localhost:8000;}" > /etc/nginx/default.d/django.conf
write_log "重新加载nginx配置"
systemctl reload nginx;sleep 1
}
# 检查
check(){
write_log "检查项目"
write_log "curl localhost/hello/ $(curl localhost/hello/ 2> /dev/null)"
}
# 部署函数
deploy_pro(){
add_lock
get_code
tar_code
scp_code
stop_serv
untar_code
fangzhi_code
start_serv
check
del_lock
}
# 脚本报错信息
err_msg(){
echo "脚本 $0 正在运行,请稍候..."
}
# 脚本帮助信息
usage(){
echo "脚本 $0 的使用方式: $0 [deploy]"
exit
}
# 主函数
main(){
case "$1" in
"deploy")
if [ -f "${PID_FILE}" ]
then
err_msg
else
deploy_pro
fi
;;
*)
usage
;;
esac
}
# 执行主函数
if [ $# -eq 1 ]
then
main $1
else
usage
fi
# 修改代码,并部署验证
ssh root@192.168.91.102 "sed -i 's#2.4#2.5#' /data/codes/django/views.py"
/bin/bash deploy.sh deploy
增加锁文件
django.tar.gz 100% 256 212.8KB/s 00:00
删除锁文件
cat /data/logs/deploy.log
2023-12-29 16:45:52 deploy.sh : 增加锁文件
2023-12-29 16:45:52 deploy.sh : 获取代码
2023-12-29 16:45:52 deploy.sh : 打包代码
2023-12-29 16:45:53 deploy.sh : 传输代码
2023-12-29 16:45:53 deploy.sh : 关闭应用
2023-12-29 16:45:53 deploy.sh : 重新加载nginx配置
2023-12-29 16:45:54 deploy.sh : curl localhost/hello/ 数据迁移中,请耐心等待,抱歉!!!
2023-12-29 16:45:54 deploy.sh : 关闭django应用
2023-12-29 16:45:54 deploy.sh : 解压代码
2023-12-29 16:45:54 deploy.sh : 放置代码
2023-12-29 16:45:54 deploy.sh : 备份老文件
2023-12-29 16:45:54 deploy.sh : 放置新文件
2023-12-29 16:45:54 deploy.sh : 开启应用
2023-12-29 16:45:54 deploy.sh : 开启django应用
2023-12-29 16:45:54 deploy.sh : 重新加载nginx配置
2023-12-29 16:45:55 deploy.sh : 检查项目
2023-12-29 16:45:55 deploy.sh : curl localhost/hello/ web_site V2.5
2023-12-29 16:45:55 deploy.sh : 删除锁文件
脚本技巧
简单脚本
1、手工执行的命令一定要可执行
2、命令简单罗列
3、固定的内容变量化
4、功能函数化
复杂脚本
1、手工执行的命令一定要可执行
2、根据发布流程编写脚本的框架
3、将手工执行的命令填充到对应的框架函数内部
4、增加日志功能,方便跟踪脚本历史执行记录
5、主函数中逻辑流程控制好
6、设计安全的方面:
增加锁文件,保证代码发布的过程中不受干扰,
输入参数数量
输入参数匹配
脚本帮助信息
7、调试脚本
注意事项
1、命令一定要保证能正常执行
2、成对的符号,要成对写,避免丢失
3、函数调用,
写好函数后,一定要在主函数中进行调用
4、避免符号出现中文
5、命令变量的写法一定要规范
6、固定的内容一定要变量实现,方便以后更改
7、日志的输出
8、脚本的传参和函数的传参要区别对待