一部Web应用自动化部署的进化史[AWS]

来源:互联网 发布:棋盘分割算法优缺点 编辑:程序博客网 时间:2024/05/21 10:45

前段时间,本人参与了某项目的从“零”开始的开发与运维。真的是从零开始啊……从项目设计到开发,再到发布、运维,说多了都是泪……还好现在有好多现成的工具可以使用,省了很多时间和精力。

此项目使用AWS,Web 端架构采用 ELB + AutoScalling group,数据库使用RDS,文件存储使用了S3。使用这个架构可以节省很多的运维时间和精力,可以拿更多的时间关注项目的开发。但是这个架构并不包括代码部署的方面,本文主要介绍在代码部署方面自动化运维道路上的各种进化。

项目主要软件环境: Java EE, Spring 4 MVC, maven, tomcat8, gitlab

项目分测试环境和生产环境,生产环境采用ELB+AutoScalling,测试环境只有一台服务器跑tomcat,虽然不是很严谨,但是在前期还是能省(qian)则省了大笑……

在代码部署方面大体经历了以下几个阶段。

石器时代

最开始时在本地开发测试,然后idea 打包上传到服务器上,然后ssh 登陆服务器手动部署代码。每次代码部署都要执行n多操作和命令。有段时间网络不是很好,光上传war 包就耗费十几分钟,对耐心是一场很大的考验。实在受不了这种繁琐的操作时候开始了一步步简化操作。

服务器上部署war 时需要先停止tomcat,然后删除tomcat webapps 目录下ROOT.war 文件和ROOT 目录,然后移动新的ROOT.war 到webapps 下,最后启动tomcat 服务。首先对这个步骤写了个shell 脚本:

“石头锤子” deployWar.sh

[plain] view plain copy
  1. #! /bin/bash  
  2.   
  3. if [[ $# -eq 0 ]]; then  
  4.         warFile="/home/ec2-user/target/hs-0.1-SNAPSHOT.war"  
  5. elif [[ $# -eq 1 ]]; then  
  6.         warFile="$1"  
  7. else   
  8.         echo "Parameter Error!"  
  9.         exit 1  
  10. fi  
  11.   
  12. if [[ -f "$warFile" ]]; then  
  13.         service tomcat8 stop  
  14.         rm -f "/usr/share/tomcat8/webapps/ROOT.war"  
  15.         rm -rf "/usr/share/tomcat8/webapps/ROOT"  
  16.         cp "$warFile" "/usr/share/tomcat8/webapps/ROOT.war"  
  17.         service tomcat8 start  
  18.         mv "$warFile" "${warFile%'.war'}_`date +'%Y-%m-%d_%H:%M:%S'`.war"  
  19.         echo  
  20.         echo Done!  
  21. else  
  22.     echo "No file: $warFile!"  
  23. fi  

此“石头锤子”能实现上述war包的部署步骤,并对当前部署的war包进行备份。

然后又出现一个问题,如果改动只有一个或几个文件,完整部署太麻烦,这时可以只上传改动的文件,然后部署就可以了。

“石头镰刀1” updateClasses.sh

[plain] view plain copy
  1. #! /bin/bash  
  2.   
  3. service tomcat8 stop  
  4. cp -R /home/ec2-user/target/classes/ /home/ec2-user/webapps/ROOT/WEB-INF/  
  5. service tomcat8 start  
  6. echo Done!  

tomcat 的class 文件更新后需要重启tomcat 才能生效,而静态文件如js、css 文件等直接覆盖即可。所以针对静态文件有:

“石头镰刀2” updateStatic.sh
[plain] view plain copy
  1. #! /bin/bash  
  2.   
  3. cp -R /home/ec2-user/src/main/webapp/WEB-INF/ /home/ec2-user/webapps/ROOT/  
  4. echo Done!  

“铁器时代”

当测试情况良好需要部署到生产坏境时,就涉及到从原来的单点部署到集群部署了。原来的脚本和架构也不太适合了,幸好我们有还有铸就“金刚”之身的原料--S3。首先我们将需要部署的war包上传的到S3 的指定目录,登陆需要部署的服务器,下载该war 包并部署。流程很简单,但是需要执行的命令也是繁杂和重复。
“小铁铲”  cpWarToS3.sh
[plain] view plain copy
  1. #! /bin/bash  
  2.   
  3. if [[ $# -eq 0 ]]; then  
  4.         warFile="/home/ec2-user/target/hs-0.1-SNAPSHOT.war"  
  5. elif [[ $# -eq 1 ]]; then  
  6.         warFile="$1"  
  7. else   
  8.         echo "Parameter Error!"  
  9.         exit 1  
  10. fi  
  11.   
  12. if [[ -f "$warFile" ]]; then  
  13.         echo Copy $warFile to S3...  
  14.         aws s3 cp "$warFile" "s3://config.ziyoufang.cn/war/ROOT.war"  
  15.         echo Done!  
  16. else  
  17.     echo "No file: $warFile!"  
  18. fi  
“大铁锤” deployFromS3.sh
[plain] view plain copy
  1. #! /bin/bash  
  2. if [[ $# -eq 0 ]]; then  
  3.     WAR=ROOT.war  
  4. elif [[ $# -eq 1 ]]; then  
  5.     WAR=$1  
  6. else   
  7.     echo "Parameter Error!"  
  8.     exit 1  
  9. fi  
  10.   
  11. WAR=`echo $WAR | awk -F '.' '{print $1}'`  
  12.   
  13. service tomcat8 stop  
  14. rm -f "/usr/share/tomcat8/webapps/ROOT.war"  
  15. rm -rf "/usr/share/tomcat8/webapps/ROOT"  
  16. aws s3 cp s3://config.ziyoufang.cn/war/"$WAR".war "/usr/share/tomcat8/webapps/ROOT.war"  
  17. service tomcat8 start  
  18. log="`date +'%Y-%m-%d %H:%M:%S'` Deploy war from s3. done!"  
  19. echo $log > deploy.log  
  20. echo  
  21. echo Done!  

从文件名上就可以看出这两个脚本一个是用来将war 上传到S3,一个是从S3 下载war包并部署的。

“工业时代”

上面还面临着一个重要的问题,就是每次部署都要打包上传完整的war 包到S3,这也是一个比较耗时耗力的过程,对于一个能坐就不站,能躺就不坐的”懒货“来说是一种巨大的折磨。
“烈火” gitlab +“鼓风” maven

gitlab 作为一款优秀的git server 系统,maven 作为一款最常用的包管理软件之一,各位前辈已经提供的了丰富的工具我们就得充分利用。在开发时使用git 做版本控制,gitlab 部署在AWS 上,开发只需要和gitlab 进行sync 即可。然后在服务器上使用mvn clean install 进行打包,并上传到S3上。

“小卡车” updateWarToS3.sh

[plain] view plain copy
  1. #! /bin/bash  
  2.   
  3. if [[ $# -eq 0 ]]; then  
  4.     BRANCH=master  
  5. elif [[ $# -eq 1 ]]; then  
  6.     BRANCH=$1  
  7.     TARGET=ROOT.war  
  8. elif [[ $# -eq 2 ]]; then  
  9.     BRANCH=$1  
  10.     TARGET=$2  
  11. else   
  12.     echo "Parameter Error!"  
  13.     exit 1  
  14. fi  
  15. TARGET=`echo $TARGET | awk -F '.' '{print $1}'`  
  16.   
  17. cd /home/ec2-user/Web_server  
  18. git checkout $BRANCH  
  19. git pull  
  20. mvn clean install  
  21.   
  22. S3_Prefix="s3://config.ziyoufang.cn/war/"  
  23. S3_War=$S3_Prefix"$TARGET".war  
  24. S3_WarBack=$S3_Prefix"$TARGET""_`date +'%Y-%m-%d_%H:%M:%S'`.war"  
  25. warFile="/home/ec2-user/Web_server/target/hs-0.1-SNAPSHOT.war"  
  26.   
  27. echo Backup "$TARGET".war on S3...  
  28. aws s3 mv $S3_War $S3_WarBack  
  29.   
  30. echo upload new "$TARGET".war...  
  31. aws s3 cp $warFile $S3_War  
  32.   
  33. echo "upload done."  


此时部署时只需先执行 updateWarToS3.sh,然后登陆需要部署的服务器执行 deployFromS3.sh 即可。这下干感觉就是从原始社会走出来了~~爽啊~~~


可是……(哎……就怕有可是……)在部署生产环境时,每次都需要执行多个流程:

从ELB 中移除一台EC2 -> 等待connection draining -> 登陆该EC2 -> 执行deployFromS3.sh -> 等待tomcat启动起来  -> 添加该EC2 回ELB -> 等待监控状态检查到InService -> 下一台EC2……

在压力小的情况下执行该操作ELB 后端实例较少,部署几次之后我烦了,交给了另外一个人去部署,(嗯,以邻为壑的感觉挺爽~~)结果他部署了几次之后他也烦了,威胁说撂挑子不干了……无奈只好继续利用我大shell 铸造大杀器了……

“巨型铲车” deployIntoELBBackendInstance.sh 

[plain] view plain copy
  1. #/bin/bash  
  2. # deployIntoELBBackendInstance.sh  
  3.   
  4. succeed_instances=""  
  5. failed_instances=""  
  6. ELB_NAME=""  
  7. DEPLOY_COMMAND="sudo -s /home/ec2-user/target/deployFromS3.sh"  
  8.   
  9. function usage()  
  10. {  
  11.     echo "Usage: $0 [Option] <parameter>"  
  12.     echo "Options:"  
  13.     echo "  -b  ELB name"  
  14.     echo "  -c  Deployment command. Default: \"$DEPLOY_COMMAND\""  
  15. }  
  16.   
  17. function addSuccessInstance()  
  18. {  
  19.     if [[ $# -ne 1 ]]; then  
  20.         return 1  
  21.     fi  
  22.   
  23.     #add this intance to "succeed instances list" if get private ip  
  24.     if [[ -z "$succeed_instances" ]]; then  
  25.         succeed_instances=$instance  
  26.     else  
  27.         succeed_instances="$succeed_instances",\ "$instance"  
  28.     fi  
  29. }  
  30.   
  31. function addFailedInstance()  
  32. {  
  33.     if [[ $# -ne 1 ]]; then  
  34.         return 1  
  35.     fi  
  36.       
  37.     #add this intance to "fialed instance list" if can't get private ip  
  38.     if [[ -z "$failed_instances" ]]; then  
  39.         failed_instances=$instance  
  40.     else  
  41.         failed_instances="$failed_instances",\ "$instance"  
  42.     fi  
  43. }  
  44.   
  45. function deploy()  
  46. {  
  47.     if [[ $# -ne 1 ]]; then  
  48.         return 1  
  49.     fi  
  50.   
  51.     privateIp=$1  
  52.     # ssh in and deploy on this instance  
  53.     echo Deploying...  
  54.     ssh $privateIp "$DEPLOY_COMMAND"  
  55.     # ssh $privateIp echo deploying...  
  56.     for (( i = 0; i < 6; i++ )); do  
  57.         echo -n Please wait for retarting tomcat   
  58.         for (( j = 0; j < 10; j++ )); do  
  59.             echo -n .  
  60.             sleep 1s  
  61.         done  
  62.         echo .  
  63.   
  64.         testLineNum=`curl -s $privateIp:8080 | grep html | wc -l`  
  65.         if [[ $testLineNum -gt 0 ]]; then  
  66.             echo tomcat retarts successfully.  
  67.             break  
  68.         fi  
  69.     done  
  70. }  
  71.   
  72. #main  
  73. while getopts "b:c:" arg  
  74. do  
  75.     case $arg in  
  76.         b )  
  77.             ELB_NAME=$OPTARG  
  78.             ;;  
  79.         c )   
  80.             DEPLOY_COMMAND=$OPTARG  
  81.             ;;  
  82.         ? )   
  83.             echo "Unknown argument."  
  84.             usage  
  85.             exit 1  
  86.             ;;  
  87.     esac  
  88. done  
  89.   
  90. if [[ -z "$ELB_NAME" ]]; then  
  91.     echo "$0: missing elb name"  
  92.     usage  
  93.     exit 1  
  94. fi  
  95. # verify if exist this ELB  
  96. verifyELB=`aws elb describe-load-balancers --load-balancer-name $ELB_NAME | grep LoadBalancerName | awk -F '"' '{print $4}'`  
  97. if [[ "$verifyELB"x != "$ELB_NAME"x ]]; then  
  98.     echo Cannot find Load Balancer $ELB_NAME  
  99.     exit 1  
  100. fi  
  101. # go on if exist this elb  
  102. for instance in `aws elb describe-instance-health --load-balancer-name $ELB_NAME | grep InstanceId | awk -F '"' '{print $4}'` ; do  
  103.     echo $instance is in progress...  
  104.   
  105.     #loop getting until get private ip  
  106.     privateIp=""  
  107.     for ip in `aws ec2 describe-instances --instance-ids $instance | grep PrivateIpAddress | awk -F '"' '{print $4}'` ; do  
  108.         if [[ -n "$ip" ]]; then  
  109.             privateIp=$ip  
  110.             break  
  111.         fi  
  112.     done  
  113.   
  114.     if [[ -z "$privateIp" ]]; then  
  115.         addFailedInstance $instance  
  116.     else  
  117.         #deregister this instance from elb  
  118.         aws elb deregister-instances-from-load-balancer --load-balancer-name $ELB_NAME --instances $instance >/dev/null 2>&1  
  119.         echo -n Please waitting for deregister $instance from elb $ELB_NAME  
  120.         for (( i = 0; i < 20; i++ )); do  
  121.             sleep 5s  
  122.             echo -n .  
  123.             outservice=`aws elb describe-instance-health --load-balancer-name $ELB_NAME --instances $instance | grep OutOfService | wc -l`  
  124.             if [[ $outservice -eq 1 ]]; then  
  125.                 echo  
  126.                 echo $instance has been deregistered from elb $ELB_NAME  
  127.                 deploy $privateIp  
  128.                 #register this instance with elb  
  129.                 aws elb register-instances-with-load-balancer --load-balancer-name $ELB_NAME --instances $instance >/dev/null 2>&1  
  130.                 echo -n Please wait for register $instance with elb $ELB_NAME  
  131.                 for (( j = 0; j < 20; j++ )); do  
  132.                     sleep 6s  
  133.                     echo -n .  
  134.                     inservice=`aws elb describe-instance-health --load-balancer-name $ELB_NAME --instances $instance | grep InService | wc -l`  
  135.                     if [[ $inservice -eq 1 ]]; then  
  136.                         echo  
  137.                         echo $instance has been registered with elb $ELB_NAME  
  138.                         addSuccessInstance $instance  
  139.                         break  
  140.                     fi  
  141.                     if [[ $j -ge 19 ]]; then  
  142.                         addFailedInstance $instance  
  143.                     fi  
  144.                 done  
  145.                 echo  
  146.                 break  
  147.             elif [[ $i -ge 19 ]]; then  
  148.                 echo   
  149.                 echo Deregister $instance time out. Process the next  
  150.                 addFailedInstance $instance  
  151.                 echo  
  152.                 continue  
  153.             fi  
  154.         done  
  155.     fi  
  156. done  
  157.   
  158. echo   
  159. echo succeed instances: $succeed_instances  
  160. echo failed instances: $failed_instances  

该脚本能实现自动将指定ELB 下的后端健康实例进行部署,最后会提示部署成功和部署失败的实例。

至此,整个部署流程在updateWarToS3.sh 之后只需要执行deployIntoELBBackendInstance.sh 就可以了。

手执“大铁锤”,开着“巨型铲车”,慵懒的日子~舒坦~~~

阅读全文
0 0
原创粉丝点击