使用shell實現代碼自動發佈

  1. 需求背景

 在2016年末底,項目各平臺頻繁上線,調試功能或者壓測。(維護的機器數量已200臺左右)

 起初,使用ansible爲各平臺橫向擴容構建了roles,也爲各自的代碼上線寫了playbook,雖然提高了巨大效率,但是仍然需要運維人員的參與,在灰度測試和壓測期間次數頻繁,也難免乏力和無聊。

 在無人可用,又不想被鎖死在這個狀態下的我,在腦海裏尋找突破口,在前天睡覺前,我有了一個想法,看能不能結合inotify來實現。

 大家都知道inotify最常結合是與rsync,構建實時同步的站點。這是它的優點,但是如果要用在與其相比不太頻繁,一方面要保證包上傳完整的,一方面僅僅上線一次,不能頻繁觸發,似乎有點不太合適。在信息不充足、知識不夠用的情況下,“摸着摸頭過河”是非常有效地行動邏輯。“試試看,能不能實現。”

 inotify-tools是一個C庫和一組用於Linux的命令行程序,爲inotify提供了一個簡單的接口。這些程序可用於監視文件系統事件並執行操作。詳情請查看inotify-tool

 在1天半的過程中,出現了幾次問題(bug):

1.反覆觸發,循環上線:
        因catch的是close的狀態,所以代碼文件在被ansible執行的時候,又再次被觸發event,
    這個問題很隱蔽,找了半個多小時,而觸發之後會被放入後面的隊列。
 
        在/proc/sys/fs/inotify目錄下有三個文件,對inotify機制有一定的限制
        max_user_watches:設置inotifywait或inotifywatch命令可以監視的文件數量(單進程)。
        max_user_instances:設置每個用戶可以運行的inotifywait或inotifywatch命令的進程數。
        max_queued_events:設置inotify實例事件(event)隊列可容納的事件數量。
  花費了2個小時,我測試inotifywait在各種copy或者mv或者vim等狀態下的event,最後我選用了
  ATTRIB,對,就是屬性(包含timestamps, file permissions, extended attributes)
 
 2.如果開發人員上傳多次,觸發多次,只上一次的策略
         晚上,我臨時有了這個想法,躍躍欲試,一大早就跑到公司,我把這個小腳本當做我的
      孩子,我一定要讓他長大,核心的功能必須實現。
         我選擇維護一個文本數據庫,保留watchfile_name和它的上一次MD5值,之後儘管再觸發
      ,也無關緊要了。
 
 3. 其他問題,比較小,就不說了。
                 比如:1月7日晚上,加了許多保護邏輯。

2. 上代碼,其實只是思考的邏輯

#!/bin/bash
#Usage: watch_file absolute_dirname
#Author: zhangchunyang	 Date: 2017/01/05 17:00  
#Auto online

path="$1"
script_dir="/home/zhangchunyang/ansible_stage/roles/online/tasks"
var_dir="/home/online_2"
tmpfile="/tmp/contrast"

#control platform
declare -a platform=("xxx1" "xxx2" "xxx3" "xxx4" "xxx5" "xxx6" "xxx7" "xxx8" "xxx9" "xxx10.11000" "xxx11.11100")



execute (){
	sleep 15
	cd $1
	ansible-playbook $2.yml -e "hosts=$3_2" -f 6 &>> /tmp/online.log_$(date +%Y%m%d)
	#echo "ansible-playbook $2yml -e "hosts=$3_2""
}

goods_center_restart() {
	sleep 15;
	cd $1
	ansible-playbook $2.yml -e "hosts=$3_2" -t stop -f 6 &>> /tmp/online.log_$(date +%Y%m%d)
	ansible-playbook $2.yml -e "hosts=$3_2" -t start -f 6 &>> /tmp/online.log_$(date +%Y%m%d)
	#echo "ansible-playbook $2.yml -e "hosts=$3_2 -t stop""
}

mail_x() {
	echo -e "$1 online is success! \n \n $(tail -1 /tmp/online.log_$(date +%Y%m%d))" | mail -s 'Gray online status' [email protected]
}


inotifywait -mq --timefmt '%d/%m/%y/%H:%M' --format '%T %w %e %f' -e ATTRIB $path | \
while read date watchdir event watchfile
do
        logfile="/tmp/online.log_$(date +%Y%m%d)"
	#judge file is not hidden file or buffer file
	[[ $watchfile =~ ^\. ]] && continue || echo "$watchfile is not buffer file or hidden file. next choice:'<-- .war or .zip -->'" &>> $logfile
	#echo $date $event  $watchdir $watchfile 

	#judge file endswith zip or war
	endswith=$(echo $watchfile | awk -F"." '{printf ".%s\n",$NF}')
	if [ "$endswith" != ".zip" -a "$endswith" != ".war" ];then
		echo "$watchfile endswith isn't war or zip! <--- no execute --->" &>> $logfile
		continue
	else
		echo "$watchfile endswith is zip or war. <--Next choice -->" &>> $logfile
	fi
	#[[ $watchfile =~ \.[war,zip]$ ]] || echo "$watchfile endswith isn't war or zip! <--- no execute --->" &>> $logfile
	#[[ $watchfile =~ \.[war,zip]$ ]] && echo "$watchfile endswith is zip or war. <--Next choice -->" &>> $logfile || continue

	#next
	[ -e $tmpfile ] || touch $tmpfile
	
	olditem=$(grep $watchfile $tmpfile | awk -F":" '$1~/^'"$watchfile"'$/{printf "%s:%s\n",$1,$2}')
	md5=$(md5sum $var_dir/$watchfile | cut -d" " -f1)
	newitem="${watchfile}:$md5"
	for item in $(cat $tmpfile)
	do
		if [ -z $(echo $item | cut -d":" -f2) ];then
			sed -i "/$item/d" $tmpfile
		fi
	done
	if [ -z $olditem ];then
		echo "${watchfile}:$RANDOM" >> $tmpfile
		touch $var_dir/$watchfile
	elif [ $(echo $olditem | cut -d":" -f2) == "$md5" ];then
		echo "$watchfile is already online. Don't need online any more!" &>> /tmp/online.log_$(date +%Y%m%d)
	else
		sed -i "s/$olditem/$newitem/g" $tmpfile
	    echo "Start: $date  -->  $watchdir $event $watchfile" &>> /tmp/online.log_$(date +%Y%m%d)
	    plat_name="$(echo $watchfile | cut -d"." -f 1)"
	    port="$(echo $watchfile | cut -d"." -f 2)"
	    if [ -n "$port" -a "$port" != "zip" -a "$port" != "war" ];then
	    	yml_name=${plat_name}.${port}
		elif [ "$plat_name" == "xxx3" -a "$port" == "zip" ];then
			yml_name=${plat_name}.${port}
		else
			yml_name=${plat_name}
	    fi

		#echo "$yml_name $plat_name"

	    if echo ${platform[@]} | grep -w "$plat_name" &> /dev/null;then
	    	if [ "$plat_name" == "${platform[3]}" ];then
	    		execute $script_dir $yml_name $plat_name
	    		goods_center_restart $script_dir $yml_name $plat_name
	    		mail_x $plat_name 
				echo "End: $(date +%Y%m%d) --> $date $watchfile" &>> $logfile
	    	else
	    		execute $script_dir $yml_name $plat_name
	    		mail_x $plat_name
				echo "End: $date  -->  $watchfile" &>> $logfile
	    	fi
	    else
	    	echo "No $plat_name.yml!"  &>> $logfile
	    fi
	fi
done

3. 體會

    1.將事件通知和不同平臺的代碼發佈邏輯分隔開來,之後如果有新的平臺,只需添加對應的上線邏輯即可。

    2.對迭代進行一次實踐,結果是從0到1,過程是從1到N。

        懶惰即美德,今天編寫一自動化腳本。起初,它單一、耗能,臃腫而醜陋,bug也是多多; 一天下來,我小步快跑,對它迭代更新。最終,它的樣子既出乎我的意料,也在我的意中。 其實,我想說的是,它起初看上去雖然不是那麼的美觀和炫酷,但之後就無法阻礙和限制它生長。美好的想法一旦付諸行動,剩下的就只有目標和達成目標的方法,開放出去,掙得新的認知反饋,彌補缺陷,因爲產品從來不完美,以手工藝人的心態,站在過去和未來之間,它就會漸漸呈現在你面前,以你熟悉而又不熟悉的模樣……

喜歡“愛因斯坦的3個小板凳”故事,以自己的實踐感受迭代的行動邏輯。


4.chagelog

* 2017年2月14日(情人節),重新來了一個需求,Refactor腳本。

之前,同一平臺,name從watchfile裏抽取,傳遞給後面handler函數。yml和hostgroup的name是相同的(弊端:改了yml名,就需要添加相應的組名、control platform,動刀太多)這次將其拆分:plat_name ,yml_name。 如果有新的yml_name,加一個判斷,後面新增對應的playbook即可。

* 2017年3月1日,添加上傳nginx配置文件的yml


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章