脚本化部署 Web 项目,让你的部署更加简单、轻松、优雅

写在前面的话

最近疫情比较严重,大家面临了长期呆在家中,很多人已经玩无可玩,刷无可刷的地步。此时不正是最好的充电时刻吗?

什么是脚本?

脚本(Script)是一种批处理文件的延伸,是一种纯文本保存的程序,一般来说的计算机脚本程序是确定的一系列控制计算机进行运算操作动作的组合,在其中可以实现一定的逻辑分支等。—— 百度百科

我们可以看到这些关键词:批处理文件的延伸,纯文本,实现一定逻辑。简单概述来说就是,具有一系列处理逻辑的纯文本程序!

常见的几类脚本

  • Shell 脚本:为 Shell 编写的脚本程序,通常以 .sh 为文件后缀。后面文章均以 Shell 脚本为主要内容讲述
  • JavaScript 脚本:使用 JavaScript 语言编写的脚本程序,通常以 .js 结尾
  • Python 脚本:使用 Python 语言编写的脚本程序,通常以 .py 结尾

Shell/JS/Python 脚本之间的区别

区别 \\ 脚本ShellJavaScriptPython编辑器文本编辑器IDE/文本编辑器IDE/文本编辑器解释器Bourne Shell/Bourne Again Shell由浏览器解释执行Python 解释器语言无专属脚本语言JavaScriptPython运行平台Linux/安装了 bash 的计算机运行于浏览器运行在有 Python 环境的计算机

Shell 语法精讲

前面我们已经对什么是脚本有了一个基本的概念,本章节重点对 Shell 脚本的语法进行讲解!

Shell 和 Shell 脚本是一个东西吗?

答案:并不是一个东西!二者不能混为一谈。Shell 是一个用 C 编写的程序,是用户使用 Linux 的一个媒介,用户可以通过 Shell 来访问 Linux 内核服务。 而 Shell 脚本是为 Shell 而编写的程序。

第一个 shell 脚本

学习任何一门语言,入门经典必然是 hello world! 来看一下第一个 Shell 入门程序。

登陆 Linux,在你的当前目录下创建一个 hello.sh:

<code>[root@VM_0_2_centos sskjdata]# vim hello.sh
/<code>

注:不熟悉 vi/vim 的小伙伴可以去百度一下哦,非常简单!

然后输入一下内容:

<code>#!/bin/bash
echo "Hello World !"
/<code>

非常简单,就两行代码!第一行 #! 是固定写法,是一种标记,作用是告诉系统,这个脚本使用什么解释器来执行。

因为 Linux 系统通常拥有 Bourne Shell/Bourne Again Shell 两种,即 sh 和 bash。通常这两者可以不作区分,所以 #!\\bin\\sh 和 #!\\bin\\bash 两种写法都是可以的。

#! 后面的 \\bin\\bash 是告诉系统用哪个解释器,以及解释器的地址在哪里。

实际的命令就一条,就是 echo 'Hello World!',echo 代表输出打印,就像其他语言的打印语句一样。比如 C 语言的 print。

这样一个简单的入门脚本程序就编写好了,将上面代码保存,接下来我们赋予它执行权限:chmod +x hello.sh。

如何区分是否已经赋予执行权限?

ll 查看,用颜色区分即可

  • 刚创建的时候:白色
  • 赋予执行权限之后:绿色
执行脚本

一般是切换到脚本所在目录,./hello.sh 即可:

<code>[root@VM_0_2_centos sskjdata]# ./hello.sh 
Hello World!

/<code>

这样脚本就执行成功了!超简单。

语法精讲

本节讲述日常开发中,经常用到的语法。

变量

定义变量:无语声明,直接赋值后使用。

<code>school="希望小学"
/<code>

变量命名规范:

  • 变量只能使用 字母/数字/下划线,且不能以数字开头
  • 不可使用 bash 保留关键字

bash 关键字大全:

<code> alias [-p] [名称[=值] ... ]                                   logout [n]
bg [任务声明 ...] mapfile [-n 计数] [-O 起始序号] [-s 计数] [-t] [-u fd>
bind [-lpvsPVS] [-m 键映射] [-f 文件名] [-q 名称] [-u > popd [-n] [+N | -N]
break [n] printf [-v var] 格式 [参数]
builtin [shell 内嵌 [参数 ...]] pushd [-n] [+N | -N | 目录]
caller [表达式] pwd [-LP]
case 词 in [模式 [| 模式]...) 命令 ;;]... esac read [-ers] [-a 数组] [-d 分隔符] [-i 缓冲区文字] [>
cd [-L|[-P [-e]]] [dir] readarray [-n 计数] [-O 起始序号] [-s 计数] [-t] [-u >

command [-pVv] 命令 [参数 ...] readonly [-aAf] [name[=value] ...] or readonly -p
compgen [-abcdefgjksuv] [-o 选项] [-A 动作] [-G 全局模> return [n]
complete [-abcdefgjksuv] [-pr] [-DE] [-o 选项] [-A 动作] [> select NAME [in 词语 ... ;] do 命令; done
compopt [-o|+o 选项] [-DE] [名称 ...] set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
continue [n] shift [n]
coproc [名称] 命令 [重定向] shopt [-pqsu] [-o] [选项名 ...]
declare [-aAfFgilrtux] [-p] [name[=value] ...] source 文件名 [参数]
dirs [-clpv] [+N] [-N] suspend [-f]
disown [-h] [-ar] [任务声明 ...] test [表达式]
echo [-neE] [参数 ...] time [-p] 管道
enable [-a] [-dnps] [-f 文件名] [名称 ...] times
eval [参数 ...] trap [-lp] [[参数] 信号声明 ...]
exec [-cl] [-a 名称] [命令 [参数 ...]] [重定向 ...] 真
exit [n] type [-afptP] 名称 [名称 ...]
export [-fn] [名称[=值] ...] 或 export -p typeset [-aAfFgilrtux] [-p] name[=value] ...
伪 ulimit [-SHacdefilmnpqrstuvx] [限制]
fc [-e 编辑器名] [-lnr] [起始] [终结] 或 fc -s [模�> umask [-p] [-S] [模式]
fg [任务声明] unalias [-a] 名称 [名称 ...]
for 名称 [in 词语 ... ] ; do 命令; done unset [-f] [-v] [名称 ...]
for (( 表达式1; 表达式2; 表达式3 )); do 命令; done until 命令; do 命令; done
function 名称 { 命令 ; } 或 name () { 命令 ; } variables - 一些 shell 变量的名称和含义
getopts 选项字符串 名称 [参数] wait [编号]
hash [-lr] [-p 路径名] [-dt] [名称 ...] while 命令; do 命令; done
help [-dms] [模式 ...] { 命令 ; }
/<code>

使用变量:在变量前使用 $ 符号即可。

<code>school="希望小学"
echo $school
/<code>

只读变量:使用 readonly 关键字即可。

<code>school="希望小学"
readonly school
echo $school
/<code>

删除变量:使用 unset 关键字即可。

<code>school="希望小学"
unset school
/<code>
字符串

单引号:原样输出,变量无效。

<code>webname='this is a web'
/<code>

双引号:可以和变量一起使用。

<code>web='diqiyuzhou'
str="welcome to \"$web\"! \\n"
echo -e $str
/<code>

输出:

<code>welcome to "diqiyuzhou"!
/<code>

获取字符串长度:

<code>string="abcd"
echo ${#string}
/<code>

输出:

<code>4 

/<code>

截取字符串:

<code>string="jiuyuechangan is a great author"
echo ${string:1:4} # 输出 iuyu
/<code>
数组

bash 仅支持一维数组。

定义数组:

<code>arr=(1,2,3,4)
/<code>

获取数组:

<code>// 获取单个数组元素
value=${arr[0]}
// 获取所有数组元素
all=${arr[@]}
/<code>

获取数组长度:

<code>// 方式1
len=${#arr[@]}
// 方式2
len2=${#arr[*]}
/<code>
注释
<code># 这是单行注释
/<code>

多行注释:

<code>:<注释内容...
注释内容...
注释内容...
EOF

// EOF可换成其他符号代替
:<注释内容...
注释内容...
注释内容...
!
/<code>
运算符

Shell 可使用以下运算符

  • 算数运算符
  • 关系运算符
  • 布尔运算符
  • 字符串运算符
  • 文件测试运算符

bash 不支持简单的算数运算符,需要用其他命令实现。

或者在 [] 中执行基础运算,a 为 1,b 为 2:

运算符说明举例+加法expr $a + $b 结果为 3。-减法expr $a - $b 结果为 -1。*乘法expr $a \\* $b 结果为 2。/除法expr $b/$a 结果为 2。%取余expr $b % $a 结果为 0。=赋值a=$b 将把变量 b 的值赋给 a。==相等。用于比较两个数字,相同则返回 true。[ $a == $b ] 返回 false。!=不相等。用于比较两个数字,不相同则返回 true。[ $a != $b ] 返回 true。

注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]。

实战:

<code>#!/bin/bash
# 九月长安

a=1
b=2

result=`expr $a + $b`
echo "a + b : $result"

result=`expr $a - $b`
echo "a - b : $result"

result=`expr $a \\* $b`
echo "a * b : $result"

result=`expr $b/$a`
echo "b/a : $result"
/<code>

输出:

<code>a + b : 3
a - b : -1
a * b : 2
b/a : 2
/<code>
流程控制

if else

<code>if condition
then
command1
command2
...
commandN
fi

/<code>

if else-if else

<code>if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
/<code>

test 关键字:检查条件是否成立。

  • -eq 等于则为真
  • -ne 不等于则为真
  • -gt 大于则为真
  • -ge 大于等于则为真
  • -lt 小于则为真
  • -le 小于等于则为真

for 循环

<code>for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
/<code>

while 语句

<code>while condition 

do
command
done
/<code>
函数

语法:

<code>[ function ] funname [()]

{

action;

[return int;]

}
/<code>

示例:

<code>// 定义方式1:
function good(){
command;
}

// 定义方式2:
good(){
command
}
/<code>
输入/输出定向

使用语法:

<code>命令  说明
command > file 将输出重定向到 file。
command < file 将输入重定向到 file。
command >> file 将输出以追加的方式重定向到 file。

n > file 将文件描述符为 n 的文件重定向到 file。
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m 将输出文件 m 和 n 合并。
n << tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。
/<code>

实战:编写 Shell 脚本

已经具备以上基础,则可以自己编写脚本了。下面我们来写一个简单的 Shell 脚本。

<code>#!/bin/bash
# 实战编写shell脚本
var1=$[1*2]
var2=$[1+1]
if test $[var1] -eq $[var2]
then
echo '相等!'
else
echo '不相等!'
fi
/<code>

执行查看结果:

<code>[root@VM_0_2_centos sskjdata]# ./hello.sh 
相等!
/<code>

实战:项目打包,使用脚本一键化部署

我们这里演示 Spring Boot 项目的打包部署。

普通部署

1. 使用 Maven 插件打包项目。

脚本化部署 Web 项目,让你的部署更加简单、轻松、优雅

选择 Lifecycle –> 双击 clean:

<code>[INFO] Scanning for projects...
[INFO]
[INFO] --------------< com.sskjdata.datacenter:data-governance >---------------
[INFO] Building data-governance 1.0.0-RELEASE
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ data-governance ---
[INFO] Deleting /Users/jason/App/workspace/data-governance/target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.395 s
[INFO] Finished at: 2020-02-07T13:46:40+08:00
[INFO] ------------------------------------------------------------------------
/<code>

看到 BUILD SUCCESS 即 clean 成功。

接下来再双击 install:

<code>[INFO] Copying twill-discovery-core-0.6.0-incubating.jar to /Users/jason/App/workspace/data-governance/target/lib/twill-discovery-core-0.6.0-incubating.jar
[INFO] Copying twill-zookeeper-0.6.0-incubating.jar to /Users/jason/App/workspace/data-governance/target/lib/twill-zookeeper-0.6.0-incubating.jar
[INFO] Copying tephra-hbase-compat-1.0-0.6.0.jar to /Users/jason/App/workspace/data-governance/target/lib/tephra-hbase-compat-1.0-0.6.0.jar
[INFO] Copying hive-service-rpc-2.1.0.jar to /Users/jason/App/workspace/data-governance/target/lib/hive-service-rpc-2.1.0.jar
[INFO] Copying libthrift-0.9.3.jar to /Users/jason/App/workspace/data-governance/target/lib/libthrift-0.9.3.jar
[INFO] Copying zookeeper-3.4.6.jar to /Users/jason/App/workspace/data-governance/target/lib/zookeeper-3.4.6.jar
[INFO] Copying log4j-1.2.16.jar to /Users/jason/App/workspace/data-governance/target/lib/log4j-1.2.16.jar
[INFO] Copying jline-0.9.94.jar to /Users/jason/App/workspace/data-governance/target/lib/jline-0.9.94.jar
[INFO] Copying netty-3.7.0.Final.jar to /Users/jason/App/workspace/data-governance/target/lib/netty-3.7.0.Final.jar
[INFO] Copying curator-framework-2.6.0.jar to /Users/jason/App/workspace/data-governance/target/lib/curator-framework-2.6.0.jar
[INFO] Copying curator-client-2.6.0.jar to /Users/jason/App/workspace/data-governance/target/lib/curator-client-2.6.0.jar
[INFO] Copying spring-boot-configuration-processor-2.1.0.RELEASE.jar to /Users/jason/App/workspace/data-governance/target/lib/spring-boot-configuration-processor-2.1.0.RELEASE.jar
[INFO] Copying ganymed-ssh2-262.jar to /Users/jason/App/workspace/data-governance/target/lib/ganymed-ssh2-262.jar
[INFO] Copying jsch-0.1.54.jar to /Users/jason/App/workspace/data-governance/target/lib/jsch-0.1.54.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.0.RELEASE:repackage (repackage) @ data-governance ---
[INFO] Replacing main artifact /Users/jason/App/workspace/data-governance/target/data-governance-1.0.0-RELEASE.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:2.1.0.RELEASE:repackage (default) @ data-governance ---
[INFO] Replacing main artifact /Users/jason/App/workspace/data-governance/target/data-governance-1.0.0-RELEASE.jar
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ data-governance ---
[INFO] Installing /Users/jason/App/workspace/data-governance/target/data-governance-1.0.0-RELEASE.jar to /Users/jason/.m2/repository/com/sskjdata/datacenter/data-governance/1.0.0-RELEASE/data-governance-1.0.0-RELEASE.jar
[INFO] Installing /Users/jason/App/workspace/data-governance/pom.xml to /Users/jason/.m2/repository/com/sskjdata/datacenter/data-governance/1.0.0-RELEASE/data-governance-1.0.0-RELEASE.pom
[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 27.828 s
[INFO] Finished at: 2020-02-07T13:48:44+08:00
[INFO] ------------------------------------------------------------------------
/<code>

看到 BUILD SUCCESS 即打包成功。

然后将 jar 包上传到服务器,使用命令:关闭之前运行的 jar 包,然后替换 jar 包,然后启动 jar 包查看日志。

手动部署有以下步骤:

  1. ps -ef|grep java 查看 jar 进程,找到该项目的进程
  2. kill -9 pid 杀死进程,停止项目
  3. ps -ef|grep java 再次查看进程是否杀死,项目是否停止
  4. nohup java -jar xxx.jar & 后台启动项目
  5. tail -100f nohup.out 手动查看日志

以上就是最简单的手动部署步骤,但是还是可以看出,比较依赖人工。

  • 需要人为的去找到进程号然后杀死,如果看错杀错进程,又是徒添麻烦
  • 需要反复确认进程是否被杀死
  • 手动启动项目,每次重复敲命令,且容易误操作
  • 手动查看日志

换成脚本部署

  1. 先将 jar 上传
  2. 编写关闭,检查,启动脚本
  3. 每次./ 执行脚本即可

关闭项目脚本

<code>// 我们先创建一个专门存放脚本的目录
mkdir bin
// 然后在该目录下创建脚本
cd bin
// 创建启动脚本
vim stop.sh
/<code>

编写 stop.sh 脚本内容

因为每次打包的 jar 包名称都是固定的,所以我们可以这样编写:

<code>#!/bin/bash
# 实现关闭杀死部署进程
echo "正在关闭项目, 确保jar包名称以data-governance开头!"

kill -9 $(ps -ef | grep data-governance | grep -v grep | awk '{print $2}')
echo "关闭完成!"
/<code>

使用 echo 提示用户,正在执行关闭项目操作。

  • ps -ef 找出进程
  • grep 过滤对象
  • awk 获取进程 id
  • kill -9 杀死进程

这样脚本就写好了。不但更加人性化,而且更加方便快捷。

查看项目状态脚本

<code>#!/bin/bash
# 检查项目是否正在运行!
var=$(ps -ef | grep data-governance | grep -v grep | awk '{print $2}')

len=${#var}

if test $[len] -gt 0
then
echo '项目正在运行...'
else
echo '项目已停止'
fi
/<code>

这样检查脚本也写好了。

启动项目脚本

<code>#!/bin/bash
# 大数据项目部署 启动脚本
echo '正在启动数据治理项目...'
nohup /usr/local/jdk/jdk1.8.0_161/bin/java -jar data-governance-1.0.0-RELEASE.jar &

var=$(ps -ef | grep data-governance | grep -v grep | awk '{print $2}')

len=${#var}

if test $[len] -gt 0
then
echo '启动成功!'
else
echo '启动失败,请检查jar包是否出错'
fi
/<code>

然后我们将这三个启动脚本赋予执行权限。

脚本部署步骤:

  1. ./bin/stop.sh
  2. ./bin/check.sh
  3. ./bin/start.sh

是不是爽的雅痞?./bin/st 然后按 tab 自动补齐,然后直接会车!那速度,那效率,那体验。以后部署也就几个会车的事情,简单,高效!

进阶:一键完成项目代码更新、编译、打包,部署自动化

在 IDE 上,打包上传觉得繁琐?我们直接看一个现成的例子。

服务器运行该脚本前提:

  • 安装 GitHub
  • 安装 Maven

脚本模版内容:

<code>#!/bin/bash

select_branch=$1

servers=("data-xxx" "data-xxx")
srcDir='/usr/local/xxxxxx/'

# 拉取代码
gitclone()
{
if [[ "$select_branch" = "master" ]]; then
pullcode;
fi
}

# git 拉取 master 分支
pullcode()
{
# 切换目录
cd /usr/local
# 删除就文件
rm -rf xxx
#克隆代码
git clone [email protected]:xxxx/xxxx.git
cd xxxx
# 切换分支
git checkout master
}

# clean 和 install
mvncomplier()
{
for dir in ${servers[@]}
do
dirFile=$srcDir$dir;

if [ -d $dirFile ]&&[[ $dir !=/> cd $dirFile;
mvn clean install;
fi
done;
}

# 关闭项目群,一个项目可能由多个子项目构成
stop()
{
for server in ${servers[@]}
do
pid=`ps -ef|grep $server|grep -v "grep"|awk '{print $2}'`
kill -9 $pid
npid=`ps -ef|grep $server|grep -v "grep"|awk '{print $2}'`
if [ "$npid" == "" ]; then
echo -e "关闭项目成功!"
else
echo -e "寡女哦项目失败"
fi
done
}

i=10000
# 启动项目
start()
{
for serv in ${servers[@]}
do
cd $srcDir$serv/target;

nohup java -jar $serv.jar &

i=$(($i+1))

npid=`ps -ef|grep $serv|grep -v "grep"|awk '{print $2}'`

if [ "$npid" != "" ]; then
echo -e "$serv 启动成功!"
else
echo -e "$serv 未启动!"
fi
done
}

echo -e ">>>>>>> 更新代码 >>>>>>>"
gitclone;


echo -e ">>>>>>> 编译中:>>>>>>>"
mvncomplier;

echo -e ">>>>>>> 正在重启项目 <<<<<<<stop;
start;
/<code>

这样就能一键完成,从拉取代码到重启一系列工作了。是不是很方便呢?

归纳总结,如何灵活的使用脚本部署不同的项目

每个项目,每个公司,每个人的情况都是不一样的。我们选择脚本需要因地制宜,依照实际情况而定,不能一味生搬硬套。

例如:

  • 项目大,占内存。此时启动脚本应该指定多分配虚拟机内存,以免项目崩溃
  • 分布式项目。分布式项目需要部署多个项目,此时是一个脚本通用呢?还是根据项目特性而定制各自的脚本,需要详细分析项目的特性而做决定
  • 脚本可以编写一个基础版,然后根据不同的项目,进行相应的修改,这样可以大大提升工作效率

总而言之,我们要根据需求作出更好的部署方案。

这里推荐本人其他 Chat,感兴趣的可以订阅:

  • 基于 Spring+Quartz 搭建定时调度任务框架
  • 热门安全框架 Spring Security +JWT 精讲
  • 资深技术官教你如何写求职建立


分享到:


相關文章: