腳本化部署 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 精講
  • 資深技術官教你如何寫求職建立


分享到:


相關文章: