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