需求背景
在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