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' 1064187464@qq.com
}


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