说到生产代码发布,每个公司不同的语言或者相同语言业务的流程不一样,发布方式有很多不同。笔者从事过两个行业,分别是游戏行业和金融,由于经验很短所以还有很多东西没有学到位,在这里只能用自己学到的东西来尽量减少由于自己操作失误带来的发布影响。下面讲一下我的思路:
在这里我要讲一下由于我们的后端是基于spring cloud的java项目,是目前比较火的基于微服务架构开发的,所以每个项目都相对比较小,很多lib包只同步一次基本不会改,除了会改几个公共common包。我们大概有20个项目
首先我是基于rsync+inotify实时同步的方式来同步代码,这里有两种方式来做同步,第一种就是第一次同步所有代码,第二种就是同步修改的代码,由于我们代码都是基于jar包的形式,所以我选择了第二种,再者是因为不想直接同步到生产项目所在的路径下面。我是把同步的jar包放到一个指定目录下面,然后需要发布的时候再去执行那台机器上的脚本,或者也可以全部机器都执行,当然为了保证线上业务的可用性,我们应该在发布代码的之前需要将要发布的节点从nginx upstream里面拿出来,但是由于很多原因主要是权限的问题,现在这里就没有考虑这种情况,所以我们的发布总对用户来说还不是很友好,anyway,我要讲的重点不是这个。下面是rsync服务器后来一直运行的脚本,
#!/bin/bash inotify_rsync_fun () { dir=`echo $1 | awk -F"," '{print $1}'` ip=`echo $1 | awk -F"," '{print $2}'` des=`echo $1 | awk -F"," '{print $3}'` user=`echo $1 | awk -F"," '{print $4}'` /usr/bin/inotifywait -mrq --timefmt '%d/%m/%y %H:%M' --format '%T %w%f%e' -e modify,delete,create,attrib ${dir} |while read DATE TIME DIR FILE do FILECHAGE=${DIR}${FILE} /usr/bin/rsync -vzrtopg --delete --progress --password-file=/etc/rsync.password ${dir} ${user}@${ip}::${des} >> /tmp/rsyncd.log echo "At ${TIME} on ${DATE}, file $FILECHAGE was backed up via rsync" >> /tmp/rsyncd.log done } count=28 # localdir,host,rsync_module,user of rsync_module, sync1="/home/ops/update/应用名称目录,ip,activity,rsync_backup" sync2="/home/ops/update/应用名称目录,ip,activity,rsync_backup" sync3="/home/ops/update/应用名称目录,ip,pdms,rsync_backup" sync4="/home/ops/update/应用名称目录,ip,pdms,rsync_backup" sync5="/home/ops/update/应用名称目录,ip,pdms,rsync_backup" sync6="/home/ops/update/应用名称目录,ip,apply,rsync_backup" sync7="/home/ops/update/应用名称目录,ip,apply,rsync_backup" sync8="/home/ops/update/应用名称目录,ip,apply,rsync_backup" sync9="/home/ops/update/应用名称目录,ip,apply,rsync_backup" sync10="/home/ops/update/应用名称目录,ip,config,rsync_backup" sync11="/home/ops/update/应用名称目录,ip,config,rsync_backup" sync12="/home/ops/update/应用名称目录,ip,config,rsync_backup" sync14="/home/ops/update/应用名称目录,ip.128,gateway,rsync_backup" sync15="/home/ops/update/应用名称目录,ip.129,gateway,rsync_backup" sync16="/home/ops/update/应用名称目录,ip.130,gateway,rsync_backup" sync17="/home/ops/update/应用名称目录,ip,gateway,rsync_backup" sync18="/home/ops/update/应用名称目录,ip,gateway,rsync_backup" sync19="/home/ops/update/应用名称目录,ip,gateway,rsync_backup" sync20="/home/ops/update/应用名称目录,ip,gateway,rsync_backup" sync21="/home/ops/update/应用名称目录,ip,gateway,rsync_backup" sync22="/home/ops/update/应用名称目录,ip,quartz,rsync_backup" sync23="/home/ops/update/应用名称目录,ip,quartz,rsync_backup" sync24="/home/ops/update/应用名称目录,ip,sms,rsync_backup" sync25="/home/ops/update/应用名称目录,ip,sms,rsync_backup" sync26="/home/ops/update/应用名称目录,ip,smsquartz,rsync_backup" sync27="/home/ops/update/应用名称目录,ip,smsquartz,rsync_backup" sync28="/home/ops/update/应用名称目录,ip,smsquartz,rsync_backup" ############################################################# #main i=0 while [ ${i} -lt ${count} ] do i=`expr ${i} + 1` tmp="sync"$i eval "sync=$$tmp" inotify_rsync_fun "$sync" & done
由于这是用shell写的脚本,所以没有多线程的概念,之后我会尝试将这个脚本改成python的threading多线程去运行
根据以上可以知道,我们需要发布的机器只需去同步上面脚本所在服务器跟自己对应项目的目录即可,因为项目很小的原因,所以同步基本都是秒级的,我们运维人员只需将需要发布的项目内容放到对应目录下面即可,由于rsync客户端会一直开着,inotify是基于系统内核事件驱动的,所以很快就会触发,客户端随即更新代码。
我会将客户端代码更新到统一的路径下:/home/ops/update,如果是客户端只有一个应用的我们只需执行一个update.sh的脚本,脚本会创建备份目录和更新线上应用代码以及自动重启和打印启动日志。当客户端跑了多个应用,我们需要更新多个一致全部应用或者全部应用的时候,由于我们发布会有一些公共包发布,也就是lib下面的,所以这里我分了两个脚本,一个是common.sh和update.sh。首先我们需要先发布公共包,也就是先执行common.sh,common.sh会打印需要更新的项目(也就是哪些项目需要发布公共lib),我们只要输入项目名称即可(我会打印在屏幕上,只要复制就可以),更新完成之后,我们再去更新应用代码(执行update.sh),随即更新完成只需输入需要重启应用名称即可,当需要发布所有项目的时候,那就很简单了,我们执行一个update_all.sh的脚本即可全部发布,下面是脚本内容,很简单,仅供参考:
common.sh内容:
#!/bin/bash datetime=$(date +%Y%m%d) back_dir=/backup/${datetime} if [ ! -d ${back_dir} ];then mkdir ${back_dir} echo "备份目录${back_dir}创建成功" fi cat <<EOF 应用名称 name=0 while [ ${name} != "q" ] do read -p "请输入要更新的应用名称:" num for acfile in 名称 do list1=`find /opt/platform/$num -name $acfile` diff /home/ops/update/pdms/common/${acfile} $list1 >/dev/null if [ $? -eq 0 ];then echo "${list1} 中的 ${acfile} 包已经是最新的" elif [ -f /home/ops/update/pdms/common/$acfile ];then mv ${list1} ${back_dir} cp /home/ops/update/pdms/common/$acfile $list1 if [ $? -eq 0 ];then echo "$acfile 成功更新到 ${list1}" else echo "$acfile 更新失败至${list1}" fi else "$acfile 不存在" fi done done
update.sh内容:
#!/bin/bash datetime=$(date +%Y%m%d) back_dir=/backup/${datetime} if [ ! -d ${back_dir} ];then mkdir ${back_dir} echo "备份目录${back_dir}创建成功" fi cd pdms for acfile in `ls *.jar` do list1=`find /opt/platform/ -name $acfile` for acfile1 in $list1 do diff /home/ops/update/pdms/$acfile $acfile1>/dev/null if [ $? -eq 0 ];then echo "$acfile1 已经是最新的包" elif [ -f /home/ops/update/pdms/$acfile ];then mv $acfile1 ${back_dir} cp /home/ops/update/pdms/$acfile $acfile1 if [ $? -eq 0 ];then echo "$acfile 成功更新到 $acfile1" else echo "$acfile 更新失败" fi else echo "$acfile 不存在" fi done done cat <<EOF 应用名称 EOF name=0 until [ ${name} == "q" ] do read -p "请输入重启应用名称:" name /opt/platform/service_local.sh ${name} done