1. 程式人生 > 其它 >自動化部署一:自動化部署基礎與實現

自動化部署一:自動化部署基礎與實現

一:關於自動化的基礎知識:

1.1:當前程式碼部署的實現方式:

#當前程式碼部署的實現方式:
運維純手工scpweb伺服器
純手工登入git伺服器執行git pullsvn伺服器執行svn update更新程式碼
通過xftp上傳程式碼
開發打壓縮包上傳到伺服器然後解壓

#缺點:
1.需要運維全程參與,佔用大量的工作時間
2.上線時間比較慢
3.人為造成的失誤較多,管理比較混亂
4.回滾複雜而且慢,還不及時

1.2:執行環境規劃:

開發環境:開發者本地有自己的環境,然後運維需要設定開發環境的公用服務,例如開發資料庫、redismemcached
測試環境:功能測試環境和效能測試環境
預生成環境:由生產環境叢集中的某一個節點擔任測試,此節點只做測試不對外提供服務
生產環境:直接對外提供服務的環境

1.2.1:為什麼有預生成環境?

可能是生成環境預測試環境的資料庫或資料庫版本不一樣導致語句出現問題
或者是生成環境呼叫的介面不一樣,例如支付介面在測試環境無法呼叫

1.3:設計一套生成環境的程式碼自動化部署系統:

1.4:總體規劃流程:

一個服務的叢集節點數量,是一次部署還是分次部署

一鍵回滾到上個版本

一鍵回滾到任意版本

程式碼儲存在SVN還是Git

獲取指定分支或master的指定版本號的程式碼,svn指定版本號,git指定tag標籤,或直接拉取某個分支

配置檔案差異化,即測試環境和生產環境的配置檔案不一樣,如IP不一樣或主機名不一樣或資料庫連線不一樣等等

程式碼倉庫和實際的差異,即配置檔案是否放在程式碼倉庫中,如果儲存在git則所有人都可以從配置檔案看到資料庫使用者密碼等資訊,可以使用單獨分支儲存配置檔案,或配置檔案只在部署伺服器的某個專案的目錄,比如是config.example

如何更新程式碼,java tomcat需要重啟

測試部署後的web頁面是否可以正常訪問是否是想要的頁面

並行(saltstack)或並行(shell)的問題,涉及到分組部署重啟服務

如何執行,shell執行還是web執行

1.5:總體規劃圖如下:

二:實現程式碼自動化部署

2.1:通過shell指令碼實現,後續會寫一個python版的,shell指令碼規劃如下:

2.1.1:各web伺服器新增一個uid相同的普通使用者,而且所有的web服務都以此普通使用者啟動,預設情況下所有的wenb服務除了負載均衡之外都不能監聽80埠,比如可以監聽8008埠

2.1.2:部署伺服器的使用者登入其他伺服器免密碼登入,因此需要做祕鑰認證,在各主機執行以下命令:

# useradd www -u 1010

# su – www

$ ssh-keygen

#將部署機www使用者的公鑰複製到各web伺服器的/home/www/.ssh/authorized_keys或執行ssh-copy-id [email protected]

$ chmod 600/home/www/.ssh/authorized_keys

2.1.3:測試部署伺服器可以免祕鑰用www使用者登入各個web伺服器

2.2:編寫shell指令碼:

2.2.1:#完成第一節點,主題框架完成:

#!/bin/bash
#Author: Zhangjie

#指令碼位置等變數
SHELL_NAME="deploy.sh"  #指令碼名稱
SHELL_DIR="/home/www" #指令碼路徑
SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.LOG" #指令碼執行日誌
LOCK_FILE="/tmp/deploy.lock"

#程式碼變數
CODE_DIR="/deploy/code/deploy" #程式碼目錄
CONFIG_DIR="/deploy/config" #配置檔案目錄
TMP_DIR="/deploy/tmp" #臨時目錄
TAR_DIR="/deploy/tar" #打包目錄


usage(){ #使用幫助函式
	echo $"$0使用幫助:$0 + [deploy | rollback]"
}

code_get(){
	echo "code_get"
}

code_build(){
	echo code_build
}

code_config(){
	echo conde_config
}

code_tar(){
	echo conde_tar
}

code_scp(){
	echo code_scp
}

cluster_node_remove(){
	echo cluster_node_remove
}

code_deploy(){
	echo code_deploy
}	

config_diff(){
	echo config_diff
}

code_test(){
	echo code_test
}

cluster_node_in(){
	echo cluster_node_in
}

rollback(){
	echo rollback
}

touch_key_file(){
    touch /tmp/deploy.lock
    echo "keyfile"
}

delete_key_file(){
 	rm -rf /tmp/deploy.lock
}

main(){ #主函
  if [ -f $LOCK_FILE ];then #先判斷鎖檔案在不在
    echo "鎖檔案已存在,請稍後執行,退出..." && exit 10 #如果有鎖檔案直接退出
  fi
  DEPLOY_METHOD=$1 #避免出錯誤將指令碼的第一個引數作為變數
  case $DEPLOY_METHOD in #判斷第一個引數
    deploy) #如果第一個引數是deploy就執行以下操作
	touch_key_file #執行部署之前建立鎖。如果同時有其他人執行則提示鎖檔案存在
	code_get;
	code_build;
	code_config;
	code_tar;
	code_scp;
	cluster_node_remove;
	code_deploy;
	config_diff;
	code_test;
	cluster_node_in;
	delete_key_file #執行完成後刪除鎖檔案
	;;
    rollback) #如果第一個引數是rollback就執行以下操作
	touch_key_file #回滾之前也是先建立鎖檔案
	rollback;
	delete_key_file #執行完成刪除鎖檔案
	;;

    *) #其他輸入執行以下操作
	usage;
    esac	
}

main $1 #執行主函式並把第一個變數當引數

2.2.2:完成指令碼第二階段,實現程式碼部署:

#!/bin/bash
#Author: Zhangjie

#伺服器節點:
# for i in `seq 1 20`;do echo 192.168.10.$i;done #web伺服器,多主機多用for迴圈
NODE_LIST="192.168.10.101 192.168.10.102" #自定義的web伺服器列表

#日誌日期和時間變數
LOG_CDATE=`date "+%Y-%m-%d"` #如果執行的話後面執行的時間,此時間是不固定的,這是記錄日誌使用的時間
LOG_CTIME=`date  "+%H-%M-%S"`

#程式碼打包時間變數
CDATE=$(date "+%Y-%m-%d") #指令碼一旦執行就會取一個固定時間賦值給變數,此時間是固定的
CTIME=$(date "+%H-%M-%S")

#指令碼位置等變數
SHELL_NAME="deploy.sh"  #指令碼名稱
SHELL_DIR="/home/www" #指令碼路徑
SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.LOG" #指令碼執行日誌檔案路徑
LOCK_FILE="/tmp/deploy.lock" #鎖檔案路徑

#程式碼變數
PRO_NAME="web-demo" #專案名稱的函式
CODE_DIR="/deploy/code/web-demo" #從版本管理系統更新的程式碼目錄
CONFIG_DIR="/deploy/config/web-demo" #儲存不同專案的配置檔案,一個目錄裡面就是一個專案的一個配置檔案或多個配置檔案
TMP_DIR="/deploy/tmp" #臨時目錄
TAR_DIR="/deploy/tar" #打包目錄


usage(){ #使用幫助函式
	echo $"$0使用幫助:$0 + [deploy | rollback]"
}

writelog(){ #寫入日誌的函式
	LOGINFO=$1 #將引數作為日誌輸入
	echo "${CDATA} ${CTIME}: ${SHELL_NAME}:${LOGINFO}" >> ${SHELL_LOG}
}

code_get(){ #獲取程式碼的函式
	writelog "code_get"
	cd ${CODE_DIR} && echo " git pull"  #進入到程式碼目錄更新程式碼,此處必須免密碼更新,此目錄僅用於程式碼更新不能放其他任何檔案
	/bin/cp -rf ${CODE_DIR} ${TMP_DIR}/ #臨時儲存程式碼並重命名,包名為時間+版本號,準備複製到web伺服器
	API_VER="123456789"
}

code_build(){ #程式碼構建函式
	echo code_build
}

code_config(){ #配置檔案函式
	writelog "conde_config"
	/bin/cp -rf ${CONFIG_DIR}/base/*   ${TMP_DIR}/${PRO_NAME} #將配置檔案放在本機儲存配置檔案的臨時目錄,用於暫時儲存程式碼專案
	PKG_NAME="${PRO_NAME}"_"${API_VER}"_"${CDATE}-${CTIME}" #定義程式碼目錄名稱
	cd ${TMP_DIR} && mv ${PRO_NAME} ${PKG_NAME} #重新命名程式碼檔案為web-demo_123456-20160505-21-20-10格式
}

code_tar(){ #對程式碼打包函式
	writelog "conde_tar"
	cd ${TMP_DIR} && tar czvf ${PKG_NAME}.tar.gz ${PKG_NAME} #將目錄打包成壓縮檔案,便於網路傳輸
	writelog "${PKG_NAME}.tar.gz 打包成功" #記錄打包成功的日誌
}

code_scp(){ #程式碼壓縮包scp函式
	writelog  "code_scp"
	for node in ${NODE_LIST};do #迴圈伺服器節點列表
		scp ${TMP_DIR}/${PKG_NAME}.tar.gz $node:/opt/webroot #將壓縮後的程式碼包複製到web伺服器的/opt/webroot
	done
}

cluster_node_remove(){ #將web伺服器從代理移除函式
	writelog "cluster_node_remove"
}

code_deploy(){ #程式碼解壓部署函式
	writelog "code_deploy"
	for node in ${NODE_LIST};do #迴圈伺服器節點列表
		ssh ${node} "cd /opt/webroot && tar zxf ${PKG_NAME}.tar.gz" #分別到web伺服器執行壓縮包解壓命令
		ssh ${node} "rm -f /webroot/web-demo && ln -s /opt/webroot/${PKG_NAME} /webroot/web-demo" #整個自動化的核心,建立軟連線
        done
}	

config_diff(){ #拷貝差異檔案函式
	writelog "config_diff"
	scp ${CONFIG_DIR}/other/192.168.10.102.server.xml 192.168.10.102:/webroot/web-demo/server.xml  #將差異專案的配置檔案scp到此web伺服器並以專案結尾
}

code_test(){
	echo code_test
}

cluster_node_in(){
	echo cluster_node_in
}

rollback(){
	echo rollback
}

touch_key_file(){
    touch /tmp/deploy.lock
    echo "keyfile"
}

delete_key_file(){
 	rm -rf /tmp/deploy.lock
}

main(){ #主函
  if [ -f $LOCK_FILE ];then #先判斷鎖檔案在不在
    echo "鎖檔案已存在,請稍後執行,退出..." && exit 10 #如果有鎖檔案直接退出
  fi
  DEPLOY_METHOD=$1 #避免出錯誤將指令碼的第一個引數作為變數
  case $DEPLOY_METHOD in #判斷第一個引數
    deploy) #如果第一個引數是deploy就執行以下操作
	touch_key_file #執行部署之前建立鎖。如果同時有其他人執行則提示鎖檔案存在
	code_get;
	code_build;
	code_config;
	code_tar;
	code_scp;
	cluster_node_remove;
	code_deploy;
	config_diff;
	code_test;
	cluster_node_in;
	delete_key_file #執行完成後刪除鎖檔案
	;;
    rollback) #如果第一個引數是rollback就執行以下操作
	touch_key_file #回滾之前也是先建立鎖檔案
	rollback;
	delete_key_file #執行完成刪除鎖檔案
	;;

    *) #其他輸入執行以下操作
	usage;
    esac	
}

main $1 #執行主函式並把第一個變數當引數

#在此階段的指令碼執行結果:

#可以將程式碼釋出至後端伺服器,下一階段將完成程式碼回滾的功能

2.2.3:更改web服務的主目錄為程式碼釋出的目錄:

#本次使用nginx,使用apache也是要更改一下web目錄即可:

user  www www; #以www使用者和組啟動nginx
worker_processes  2;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8899; #避免與原有的埠衝突特改為8899,訪問的時候也要訪問8899
        server_name  localhost;
        location / {
            root   /webroot/web-demo; #將web目錄改為shell指令碼部署的程式碼目錄
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

2.2.4:啟動nginx並訪問兩臺web伺服器測試:

# /usr/local/nginx-1.8.1/sbin/nginx

2.2.5:更新程式碼並提交再部署,然後檢視web頁面資訊:

$ echo “hehe test2” > /deploy/code/web-demo/index.html

$ ./code_deploy.sh deploy

2.2.6:完成指令碼第三階段,實現測試等功能:

#!/bin/bash
#Author: Zhangjie

#伺服器節點:
# for i in `seq 1 20`;do echo 192.168.10.$i;done #主機多用for迴圈
NODE_LIST="192.168.10.101 192.168.10.102"
PRE_LIST="192.168.10.101"
GROUP1_LIST="192.168.10.102"

#日誌日期和時間變數
LOG_CDATE=`date "+%Y-%m-%d"` #如果執行的話後面執行的時間,此時間是不固定的,這是記錄日誌使用的時間
LOG_CTIME=`date  "+%H-%M-%S"`

#程式碼打包時間變數
CDATE=$(date "+%Y-%m-%d") #指令碼一旦執行就會取一個固定時間賦值給變數,此時間是固定的
CTIME=$(date "+%H-%M-%S")

#指令碼位置等變數
SHELL_NAME="deploy.sh"  #指令碼名稱
SHELL_DIR="/home/www" #指令碼路徑
SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.LOG" #指令碼執行日誌檔案路徑
LOCK_FILE="/tmp/deploy.lock" #鎖檔案路徑

#程式碼變數
PRO_NAME="web-demo" #專案名稱的函式
CODE_DIR="/deploy/code/web-demo" #從版本管理系統更新的程式碼目錄
CONFIG_DIR="/deploy/config/web-demo" #儲存不同專案的配置檔案,一個目錄裡面就是一個專案的一個配置檔案或多個配置檔案
TMP_DIR="/deploy/tmp" #臨時目錄
TAR_DIR="/deploy/tar" #打包目錄


usage(){ #使用幫助函式
	echo $"$0使用幫助:$0 + [deploy | rollback]"
}

writelog(){ #寫入日誌的函式
	LOGINFO=$1 #將引數作為日誌輸入
	echo "${CDATA} ${CTIME}: ${SHELL_NAME}:${LOGINFO}" >> ${SHELL_LOG}
}

code_get(){ #獲取程式碼的函式
	writelog "code_get"
	cd ${CODE_DIR} && echo " git pull"  #進入到程式碼目錄更新程式碼,此處必須免密碼更新,此目錄僅用於程式碼更新不能放其他任何檔案
	/bin/cp -rf ${CODE_DIR} ${TMP_DIR}/ #臨時儲存程式碼並重命名,包名為時間+版本號,準備複製到web伺服器
	API_VER="123456789"
}

code_build(){ #程式碼構建函式
	echo code_build
}

code_config(){ #配置檔案函式
	writelog "conde_config"
	/bin/cp -rf ${CONFIG_DIR}/base/*   ${TMP_DIR}/${PRO_NAME} #將配置檔案放在本機儲存配置檔案的臨時目錄,用於暫時儲存程式碼專案
	PKG_NAME="${PRO_NAME}"_"${API_VER}"_"${CDATE}-${CTIME}" #定義程式碼目錄名稱
	cd ${TMP_DIR} && mv ${PRO_NAME} ${PKG_NAME} #重新命名程式碼檔案為web-demo_123456-20160505-21-20-10格式
}

code_tar(){ #對程式碼打包函式
	writelog "conde_tar"
	cd ${TMP_DIR} && tar czvf ${PKG_NAME}.tar.gz ${PKG_NAME} #將目錄打包成壓縮檔案,便於網路傳輸
	writelog "${PKG_NAME}.tar.gz 打包成功" #記錄打包成功的日誌
}

code_scp(){ #程式碼壓縮包scp函式
	writelog  "code_scp"
	for node in ${PRE_LIST};do #迴圈伺服器節點列表
		scp ${TMP_DIR}/${PKG_NAME}.tar.gz $node:/opt/webroot #將壓縮後的程式碼包複製到web伺服器的/opt/webroot
	done
	for node in ${GROUP1_LIST};do #迴圈伺服器節點列表
		scp ${TMP_DIR}/${PKG_NAME}.tar.gz $node:/opt/webroot #將壓縮後的程式碼包複製到web伺服器的/opt/webroot
	done
}

cluster_node_remove(){ #將web伺服器從代理移除函式
	writelog "cluster_node_remove"
}

pre_deploy(){ #程式碼解壓部署函式,預生產節點
	writelog "code_deploy"
	for node in ${PRE_LIST};do #迴圈伺服器節點列表
		cluster_node_remove  ${node} #部署之前將節點從前端負載刪除
		ssh ${node} "cd /opt/webroot && tar zxf ${PKG_NAME}.tar.gz" #分別到web伺服器執行壓縮包解壓命令
		ssh ${node} "rm -f /webroot/web-demo && ln -s /opt/webroot/${PKG_NAME} /webroot/web-demo" #整個自動化的核心,建立軟連線
        done
}	

pre_test(){ #預生產主機測試函式
        for node in ${PRE_LIST};do #迴圈預生產主機列表
            curl -s --head http://${node}:8899/index.html | grep "200 OK" #測試web介面訪問
        if [ $? -eq 0 ];then  #如果訪問成功
            writelog " ${node} Web Test OK!" #記錄日誌
            echo " ${node} Web Test OK!"
	    cluster_node_add ${node} #測試成功之後呼叫新增函式把伺服器新增至節點,
            writelog " ${node} add to cluster OK!" #記錄新增伺服器到叢集的日誌
        else #如果訪問失敗
            writelog "${node} test no OK" #記錄日誌
            echo "${node} test no OK"
            delete_key_file #呼叫刪除鎖檔案函式
            break #結束部署
        fi
        done

}

group1_deploy(){ #程式碼解壓部署函式
	writelog "code_deploy"
	for node in ${GROUP1_LIST};do #迴圈伺服器節點列表
		cluster_node_remove $node  
		ssh ${node} "cd /opt/webroot && tar zxf ${PKG_NAME}.tar.gz" #分別到web伺服器執行壓縮包解壓命令
		ssh ${node} "rm -f /webroot/web-demo && ln -s /opt/webroot/${PKG_NAME} /webroot/web-demo" #整個自動化的核心,建立軟連線
        done
	scp ${CONFIG_DIR}/other/192.168.10.102.server.xml 192.168.10.102:/webroot/web-demo/server.xml  #將差異專案的配置檔案scp到此web伺服器並以專案結尾}	

group1_test(){#預生產主機測試函式for node in ${PRE_LIST};do#迴圈預生產主機列表
            curl -s --head http://${node}:8899/index.html | grep "200 OK"#測試web介面訪問if[ $?-eq 0];then#如果訪問成功
            writelog " ${node} Web Test OK!"#記錄日誌
            echo " ${node} Web Test OK!"
	    cluster_node_add
            writelog " ${node} add to cluster OK!"#記錄將伺服器 新增至叢集的日誌else#如果訪問失敗
            writelog "${node} test no OK"#記錄日誌
            echo "${node} test no OK"
	    delete_key_file #呼叫刪除鎖檔案函式break#結束部署fidone}

config_diff(){#拷貝差異檔案函式
	writelog "config_diff"for node in ${NODE_LIST};do#迴圈主機列表
	    curl -s --head http://${node}:8899/index.html | grep "200 OK"#測試web介面訪問if[ $?-eq 0];then#如果訪問成功
	    writelog " ${node} Web Test OK!"#記錄日誌
	    echo " ${node} Web Test OK!"else#如果訪問失敗
	    writelog "${node} test no OK"#記錄日誌
	    echo "${node} test no OK"break#結束部署fidone}

code_test(){
	echo code_test
}
cluster_node_remove(){#將web伺服器新增至前端負載
	writelog $1 #記錄伺服器從前端負載刪除的日誌}

cluster_node_add(){#將web伺服器新增至前端負載
	echo cluster_node_add
}

rollback(){
	echo rollback
}

touch_key_file(){
    touch /tmp/deploy.lock
    echo "keyfile"}

delete_key_file(){
 	rm -rf /tmp/deploy.lock
}

main(){#主函if[-f $LOCK_FILE ];then#先判斷鎖檔案在不在
    echo "鎖檔案已存在,請稍後執行,退出..."&& exit 10#如果有鎖檔案直接退出fi
  DEPLOY_METHOD=$1 #避免出錯誤將指令碼的第一個引數作為變數case $DEPLOY_METHOD in#判斷第一個引數
    deploy)#如果第一個引數是deploy就執行以下操作
	touch_key_file #執行部署之前建立鎖。如果同時有其他人執行則提示鎖檔案存在
	code_get;#獲取程式碼
	code_build;#如果要編譯執行編譯函式
	code_config;#cp配置檔案
	code_tar;#打包
	code_scp;#scp到伺服器
	cluster_node_remove;
	pre_deploy;#預生產環境部署
	pre_test;#預生產環境測試
	group1_deploy;#生產環境部署
	group1_test;#生產環境測試
	config_diff;#cp差異檔案
	code_test;#程式碼測試
	delete_key_file #執行完成後刪除鎖檔案;;
    rollback)#如果第一個引數是rollback就執行以下操作
	touch_key_file #回滾之前也是先建立鎖檔案
	rollback;
	delete_key_file #執行完成刪除鎖檔案;;*)#其他輸入執行以下操作
	usage;#呼叫使用方法函式esac}

main $1 #執行主函式並把第一個變數當引數

#執行過程:

2.3:程式碼回滾設計:

2.3.1:正常回滾是回滾已經在web伺服器部署過的版本,因此就不需要獲取程式碼打包和部署的過程了

列出回滾版本

將模板伺服器移除叢集

執行回滾

重啟和測試

將模板伺服器假如叢集

2.3.2::實現任意版本回滾:

#!/bin/bash
#Author: Zhangjie

#伺服器節點:
# for i in `seq 1 20`;do echo 192.168.10.$i;done #主機多用for迴圈
NODE_LIST="192.168.10.101 192.168.10.102"
PRE_LIST="192.168.10.101"
GROUP1_LIST="192.168.10.102"

#日誌日期和時間變數
LOG_CDATE=`date "+%Y-%m-%d"` #如果執行的話後面執行的時間,此時間是不固定的,這是記錄日誌使用的時間
LOG_CTIME=`date  "+%H-%M-%S"`

#程式碼打包時間變數
CDATE=$(date "+%Y-%m-%d") #指令碼一旦執行就會取一個固定時間賦值給變數,此時間是固定的
CTIME=$(date "+%H-%M-%S")

#指令碼位置等變數
SHELL_NAME="deploy.sh"  #指令碼名稱
SHELL_DIR="/home/www" #指令碼路徑
SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.LOG" #指令碼執行日誌檔案路徑
LOCK_FILE="/tmp/deploy.lock" #鎖檔案路徑

#程式碼變數
PRO_NAME="web-demo" #專案名稱的函式
CODE_DIR="/deploy/code/web-demo" #從版本管理系統更新的程式碼目錄
CONFIG_DIR="/deploy/config/web-demo" #儲存不同專案的配置檔案,一個目錄裡面就是一個專案的一個配置檔案或多個配置檔案
TMP_DIR="/deploy/tmp" #臨時目錄
TAR_DIR="/deploy/tar" #打包目錄

#回滾伺服器列表
ROLLBACK_LIST="192.168.10.101 192.168.10.102"

usage(){ #使用幫助函式
	echo $"$0使用幫助:$0 deploy or rollback + code version"
}

writelog(){ #寫入日誌的函式
	LOGINFO=$1 #將引數作為日誌輸入
	echo "${CDATA} ${CTIME}: ${SHELL_NAME}:${LOGINFO}" >> ${SHELL_LOG}
}

code_get(){ #獲取程式碼的函式
	writelog "code_get"
	cd ${CODE_DIR} && echo " git pull"  #進入到程式碼目錄更新程式碼,此處必須免密碼更新,此目錄僅用於程式碼更新不能放其他任何檔案
	/bin/cp -rf ${CODE_DIR} ${TMP_DIR}/ #臨時儲存程式碼並重命名,包名為時間+版本號,準備複製到web伺服器
	API_VER="123456789"
}

code_build(){ #程式碼構建函式
	echo code_build
}

code_config(){ #配置檔案函式
	writelog "conde_config"
	/bin/cp -rf ${CONFIG_DIR}/base/*   ${TMP_DIR}/${PRO_NAME} #將配置檔案放在本機儲存配置檔案的臨時目錄,用於暫時儲存程式碼專案
	PKG_NAME="${PRO_NAME}"_"${API_VER}"_"${CDATE}-${CTIME}" #定義程式碼目錄名稱
	cd ${TMP_DIR} && mv ${PRO_NAME} ${PKG_NAME} #重新命名程式碼檔案為web-demo_123456-20160505-21-20-10格式
}

code_tar(){ #對程式碼打包函式
	writelog "conde_tar"
	cd ${TMP_DIR} && tar czvf ${PKG_NAME}.tar.gz ${PKG_NAME} #將目錄打包成壓縮檔案,便於網路傳輸
	writelog "${PKG_NAME}.tar.gz 打包成功" #記錄打包成功的日誌
}

code_scp(){ #程式碼壓縮包scp函式
	writelog  "code_scp"
	for node in ${PRE_LIST};do #迴圈伺服器節點列表
		scp ${TMP_DIR}/${PKG_NAME}.tar.gz $node:/opt/webroot #將壓縮後的程式碼包複製到web伺服器的/opt/webroot
	done
	for node in ${GROUP1_LIST};do #迴圈伺服器節點列表
		scp ${TMP_DIR}/${PKG_NAME}.tar.gz $node:/opt/webroot #將壓縮後的程式碼包複製到web伺服器的/opt/webroot
	done
}

cluster_node_remove(){ #將web伺服器從代理移除函式
	writelog "cluster_node_remove"
}

pre_deploy(){ #程式碼解壓部署函式,預生產節點
	writelog "code_deploy"
	for node in ${PRE_LIST};do #迴圈伺服器節點列表
		cluster_node_remove  ${node} #部署之前將節點從前端負載刪除
		echo  "pre_deploy, cluster_node_remove ${node}"
		ssh ${node} "cd /opt/webroot && tar zxf ${PKG_NAME}.tar.gz" #分別到web伺服器執行壓縮包解壓命令
		ssh ${node} "rm -f /webroot/web-demo && ln -s /opt/webroot/${PKG_NAME} /webroot/web-demo" #整個自動化的核心,建立軟連線
        done
}	

pre_test(){ #預生產主機測試函式
        for node in ${PRE_LIST};do #迴圈預生產主機列表
            curl -s --head http://${node}:8899/index.html | grep "200 OK" #測試web介面訪問
        if [ $? -eq 0 ];then  #如果訪問成功
            writelog " ${node} Web Test OK!" #記錄日誌
            echo " ${node} Web Test OK!"
	    cluster_node_add ${node} #測試成功之後呼叫新增函式把伺服器新增至節點,
            writelog "pre,${node} add to cluster OK!" #記錄新增伺服器到叢集的日誌
        else #如果訪問失敗
            writelog "${node} test no OK" #記錄日誌
            echo "${node} test no OK"
            delete_key_file #呼叫刪除鎖檔案函式
            break #結束部署
        fi
        done

}

group1_deploy(){ #程式碼解壓部署函式
	writelog "code_deploy"
	for node in ${GROUP1_LIST};do #迴圈伺服器節點列表
		cluster_node_remove $node  
		echo "group1, cluster_node_remove $node"
		ssh ${node} "cd /opt/webroot && tar zxf ${PKG_NAME}.tar.gz" #分別到web伺服器執行壓縮包解壓命令
		ssh ${node} "rm -f /webroot/web-demo && ln -s /opt/webroot/${PKG_NAME} /webroot/web-demo" #整個自動化的核心,建立軟連線
        done
	scp ${CONFIG_DIR}/other/192.168.10.102.server.xml 192.168.10.102:/webroot/web-demo/server.xml  #將差異專案的配置檔案scp到此web伺服器並以專案結尾}	

group1_test(){#預生產主機測試函式for node in ${PRE_LIST};do#迴圈預生產主機列表
            curl -s --head http://${node}:8899/index.html | grep "200 OK"#測試web介面訪問if[ $?-eq 0];then#如果訪問成功
            writelog " ${node} Web Test OK!"#記錄日誌
            echo "group1_test,${node} Web Test OK!"
	    cluster_node_add
            writelog " ${node} add to cluster OK!"#記錄將伺服器 新增至叢集的日誌else#如果訪問失敗
            writelog "${node} test no OK"#記錄日誌
            echo "${node} test no OK"
	    delete_key_file #呼叫刪除鎖檔案函式break#結束部署fidone}

config_diff(){#拷貝差異檔案函式
	writelog "config_diff"for node in ${NODE_LIST};do#迴圈主機列表
	    curl -s --head http://${node}:8899/index.html | grep "200 OK"#測試web介面訪問if[ $?-eq 0];then#如果訪問成功
	    writelog " ${node} Web Test OK!"#記錄日誌
	    echo " ${node} Web Test OK!"else#如果訪問失敗
	    writelog "${node} test no OK"#記錄日誌
	    echo "${node} test no OK"break#結束部署fidone}

code_test(){
	echo code_test
}
cluster_node_remove(){#將web伺服器新增至前端負載
	writelog $1 #記錄伺服器從前端負載刪除的日誌}

cluster_node_add(){#將web伺服器新增至前端負載
	echo cluster_node_add
}


touch_key_file(){
    touch /tmp/deploy.lock
    echo "keyfile"}

delete_key_file(){
 	rm -rf /tmp/deploy.lock
}

rollback(){#程式碼回滾主函式if[-z $1 ];then
	delete_key_file #刪除鎖檔案
	echo "請輸入引數"&& exit 3;fi#echo ${ROLLBACK_VER},123case $1 in#把第二個引數做當自己的第一個引數 
	list)
	    ls -l /opt/webroot/*.tar.gz
	    ;;*)
	    rollback_funk $1;esac}

rollback_funk(){for node in ${ROLLBACK_LIST};do#迴圈伺服器節點列表
	    ssh ${node}"rm -f /webroot/web-demo && ln -s /opt/webroot/$1 /webroot/web-demo"#立即回滾到指定的版本,$1即指定的版本引數
	    echo "${node} rollback success!"done}

main(){#主函if[-f $LOCK_FILE ];then#先判斷鎖檔案在不在
    echo "鎖檔案已存在,請稍後執行,退出..."&& exit 10#如果有鎖檔案直接退出fi
  DEPLOY_METHOD=$1 #避免出錯誤將指令碼的第一個引數作為變數
  ROLLBACK_VER=$2
  case $DEPLOY_METHOD in#判斷第一個引數
    deploy)#如果第一個引數是deploy就執行以下操作
	touch_key_file #執行部署之前建立鎖。如果同時有其他人執行則提示鎖檔案存在
	code_get;#獲取程式碼
	code_build;#如果要編譯執行編譯函式
	code_config;#cp配置檔案
	code_tar;#打包
	code_scp;#scp到伺服器
	cluster_node_remove;
	pre_deploy;#預生產環境部署
	pre_test;#預生產環境測試
	group1_deploy;#生產環境部署
	group1_test;#生產環境測試
	config_diff;#cp差異檔案
	code_test;#程式碼測試
	delete_key_file #執行完成後刪除鎖檔案;;
    rollback)#如果第一個引數是rollback就執行以下操作#touch_key_file #回滾之前也是先建立鎖檔案
	rollback $ROLLBACK_VER;#delete_key_file #執行完成刪除鎖檔案;;*)#其他輸入執行以下操作
	usage;#呼叫使用方法函式esac}

main $1 $2 #執行主函式並把第一個變數當引數

執行結果:

2.3.2:緊急回滾,直接獲取上一個版本,可以每次部署將版本放在一個檔案,緊急回滾的時候讀取此檔案的版本進行程式碼版本回滾

緊急回滾只能回滾到上一個版本,設計思路為每次部署程式碼就將名稱儲存在一個檔案裡面,下次部署時將版本號進行追加,因此每次回滾都是直接取到的檔案當中儲存的倒數第二個版本名稱,然後將之前的連線刪除再重新連結到此版本的名稱即可

轉自http://blogs.studylinux.net/?p=2005