一部Web应用自动化部署的进化史[AWS]-使用shell实现CodeDeploy
2016-05-25 16:08
856 查看
前段时间,本人参与了某项目的从“零”开始的开发与运维。真的是从零开始啊……从项目设计到开发,再到发布、运维,说多了都是泪……还好现在有好多现成的工具可以使用,省了很多时间和精力。
此项目使用AWS,Web 端架构采用 ELB + AutoScalling group,数据库使用RDS,文件存储使用了S3。使用这个架构可以节省很多的运维时间和精力,可以拿更多的时间关注项目的开发。但是这个架构并不包括代码部署的方面,本文主要介绍在代码部署方面自动化运维道路上的各种进化。
项目主要软件环境: Java EE, Spring 4 MVC, maven, tomcat8, gitlab
项目分测试环境和生产环境,生产环境采用ELB+AutoScalling,测试环境只有一台服务器跑tomcat,虽然不是很严谨,但是在前期还是能省(qian)则省了
![](https://oscdn.geek-share.com/Uploads/Images/Content/201603/899229cfab2c02d614490485cabb781b.gif)
……
在代码部署方面大体经历了以下几个阶段。
服务器上部署war 时需要先停止tomcat,然后删除tomcat webapps 目录下ROOT.war 文件和ROOT 目录,然后移动新的ROOT.war 到webapps 下,最后启动tomcat 服务。首先对这个步骤写了个shell 脚本:
“石头锤子” deployWar.sh
此“石头锤子”能实现上述war包的部署步骤,并对当前部署的war包进行备份。
然后又出现一个问题,如果改动只有一个或几个文件,完整部署太麻烦,这时可以只上传改动的文件,然后部署就可以了。
“石头镰刀1” updateClasses.sh
tomcat 的class 文件更新后需要重启tomcat 才能生效,而静态文件如js、css 文件等直接覆盖即可。所以针对静态文件有:
“石头镰刀2” updateStatic.sh
“小铁铲” cpWarToS3.sh
“大铁锤” deployFromS3.sh
从文件名上就可以看出这两个脚本一个是用来将war 上传到S3,一个是从S3 下载war包并部署的。
“烈火” gitlab +“鼓风” maven
gitlab 作为一款优秀的git server 系统,maven 作为一款最常用的包管理软件之一,各位前辈已经提供的了丰富的工具我们就得充分利用。在开发时使用git 做版本控制,gitlab 部署在AWS 上,开发只需要和gitlab 进行sync 即可。然后在服务器上使用mvn clean install 进行打包,并上传到S3上。
“小卡车” updateWarToS3.sh
此时部署时只需先执行 updateWarToS3.sh,然后登陆需要部署的服务器执行 deployFromS3.sh 即可。这下干感觉就是从原始社会走出来了~~爽啊~~~
可是……(哎……就怕有可是……)在部署生产环境时,每次都需要执行多个流程:
从ELB 中移除一台EC2 -> 等待connection draining -> 登陆该EC2 -> 执行deployFromS3.sh -> 等待tomcat启动起来 -> 添加该EC2 回ELB -> 等待监控状态检查到InService -> 下一台EC2……
在压力小的情况下执行该操作ELB 后端实例较少,部署几次之后我烦了,交给了另外一个人去部署,(嗯,以邻为壑的感觉挺爽~~)结果他部署了几次之后他也烦了,威胁说撂挑子不干了……无奈只好继续利用我大shell 铸造大杀器了……
“巨型铲车” deployIntoELBBackendInstance.sh
#/bin/bash
# deployIntoELBBackendInstance.sh
succeed_instances=""
failed_instances=""
ELB_NAME=""
DEPLOY_COMMAND="sudo -s /home/ec2-user/target/deployFromS3.sh"
function usage()
{
echo "Usage: $0 [Option] <parameter>"
echo "Options:"
echo " -b ELB name"
echo " -c Deployment command. Default: \"$DEPLOY_COMMAND\""
}
function addSuccessInstance()
{
if [[ $# -ne 1 ]]; then
return 1
fi
#add this intance to "succeed instances list" if get private ip
if [[ -z "$succeed_instances" ]]; then
succeed_instances=$instance
else
succeed_instances="$succeed_instances",\ "$instance"
fi
}
function addFailedInstance()
{
if [[ $# -ne 1 ]]; then
return 1
fi
#add this intance to "fialed instance list" if can't get private ip
if [[ -z "$failed_instances" ]]; then
failed_instances=$instance
else
failed_instances="$failed_instances",\ "$instance"
fi
}
function deploy()
{
if [[ $# -ne 1 ]]; then
return 1
fi
privateIp=$1
# ssh in and deploy on this instance
echo Deploying...
ssh $privateIp "$DEPLOY_COMMAND"
# ssh $privateIp echo deploying...
for (( i = 0; i < 6; i++ )); do
echo -n Please wait for retarting tomcat
for (( j = 0; j < 10; j++ )); do
echo -n .
sleep 1s
done
echo .
testLineNum=`curl -s $privateIp:8080 | grep html | wc -l`
if [[ $testLineNum -gt 0 ]]; then
echo tomcat retarts successfully.
break
fi
done
}
#main
while getopts "b:c:" arg
do
case $arg in
b )
ELB_NAME=$OPTARG
;;
c )
DEPLOY_COMMAND=$OPTARG
;;
? )
echo "Unknown argument."
usage
exit 1
;;
esac
done
if [[ -z "$ELB_NAME" ]]; then
echo "$0: missing elb name"
usage
exit 1
fi
# verify if exist this ELB
verifyELB=`aws elb describe-load-balancers --load-balancer-name $ELB_NAME | grep LoadBalancerName | awk -F '"' '{print $4}'`
if [[ "$verifyELB"x != "$ELB_NAME"x ]]; then
echo Cannot find Load Balancer $ELB_NAME
exit 1
fi
# go on if exist this elb
for instance in `aws elb describe-instance-health --load-balancer-name $ELB_NAME | grep InstanceId | awk -F '"' '{print $4}'` ; do
echo $instance is in progress...
#loop getting until get private ip
privateIp=""
for ip in `aws ec2 describe-instances --instance-ids $instance | grep PrivateIpAddress | awk -F '"' '{print $4}'` ; do
if [[ -n "$ip" ]]; then
privateIp=$ip
break
fi
done
if [[ -z "$privateIp" ]]; then
addFailedInstance $instance
else
#deregister this instance from elb
aws elb deregister-instances-from-load-balancer --load-balancer-name $ELB_NAME --instances $instance >/dev/null 2>&1
echo -n Please waitting for deregister $instance from elb $ELB_NAME
for (( i = 0; i < 20; i++ )); do
sleep 5s
echo -n .
outservice=`aws elb describe-instance-health --load-balancer-name $ELB_NAME --instances $instance | grep OutOfService | wc -l`
if [[ $out
9f44
service -eq 1 ]]; then
echo
echo $instance has been deregistered from elb $ELB_NAME
deploy $privateIp
#register this instance with elb
aws elb register-instances-with-load-balancer --load-balancer-name $ELB_NAME --instances $instance >/dev/null 2>&1
echo -n Please wait for register $instance with elb $ELB_NAME
for (( j = 0; j < 20; j++ )); do
sleep 6s
echo -n .
inservice=`aws elb describe-instance-health --load-balancer-name $ELB_NAME --instances $instance | grep InService | wc -l`
if [[ $inservice -eq 1 ]]; then
echo
echo $instance has been registered with elb $ELB_NAME
addSuccessInstance $instance
break
fi
if [[ $j -ge 19 ]]; then
addFailedInstance $instance
fi
done
echo
break
elif [[ $i -ge 19 ]]; then
echo
echo Deregister $instance time out. Process the next
addFailedInstance $instance
echo
continue
fi
done
fi
done
echo
echo succeed instances: $succeed_instances
echo failed instances: $failed_instances
该脚本能实现自动将指定ELB 下的后端健康实例进行部署,最后会提示部署成功和部署失败的实例。
至此,整个部署流程在updateWarToS3.sh 之后只需要执行deployIntoELBBackendInstance.sh 就可以了。
手执“大铁锤”,开着“巨型铲车”,慵懒的日子~舒坦~~~
此项目使用AWS,Web 端架构采用 ELB + AutoScalling group,数据库使用RDS,文件存储使用了S3。使用这个架构可以节省很多的运维时间和精力,可以拿更多的时间关注项目的开发。但是这个架构并不包括代码部署的方面,本文主要介绍在代码部署方面自动化运维道路上的各种进化。
项目主要软件环境: Java EE, Spring 4 MVC, maven, tomcat8, gitlab
项目分测试环境和生产环境,生产环境采用ELB+AutoScalling,测试环境只有一台服务器跑tomcat,虽然不是很严谨,但是在前期还是能省(qian)则省了
![](https://oscdn.geek-share.com/Uploads/Images/Content/201603/899229cfab2c02d614490485cabb781b.gif)
……
在代码部署方面大体经历了以下几个阶段。
石器时代
最开始时在本地开发测试,然后idea 打包上传到服务器上,然后ssh 登陆服务器手动部署代码。每次代码部署都要执行n多操作和命令。有段时间网络不是很好,光上传war 包就耗费十几分钟,对耐心是一场很大的考验。实在受不了这种繁琐的操作时候开始了一步步简化操作。服务器上部署war 时需要先停止tomcat,然后删除tomcat webapps 目录下ROOT.war 文件和ROOT 目录,然后移动新的ROOT.war 到webapps 下,最后启动tomcat 服务。首先对这个步骤写了个shell 脚本:
“石头锤子” deployWar.sh
#! /bin/bash if [[ $# -eq 0 ]]; then warFile="/home/ec2-user/target/hs-0.1-SNAPSHOT.war" elif [[ $# -eq 1 ]]; then warFile="$1" else echo "Parameter Error!" exit 1 fi if [[ -f "$warFile" ]]; then service tomcat8 stop rm -f "/usr/share/tomcat8/webapps/ROOT.war" rm -rf "/usr/share/tomcat8/webapps/ROOT" cp "$warFile" "/usr/share/tomcat8/webapps/ROOT.war" service tomcat8 start mv "$warFile" "${warFile%'.war'}_`date +'%Y-%m-%d_%H:%M:%S'`.war" echo echo Done! else echo "No file: $warFile!" fi
此“石头锤子”能实现上述war包的部署步骤,并对当前部署的war包进行备份。
然后又出现一个问题,如果改动只有一个或几个文件,完整部署太麻烦,这时可以只上传改动的文件,然后部署就可以了。
“石头镰刀1” updateClasses.sh
#! /bin/bash service tomcat8 stop cp -R /home/ec2-user/target/classes/ /home/ec2-user/webapps/ROOT/WEB-INF/ service tomcat8 start echo Done!
tomcat 的class 文件更新后需要重启tomcat 才能生效,而静态文件如js、css 文件等直接覆盖即可。所以针对静态文件有:
“石头镰刀2” updateStatic.sh
#! /bin/bash cp -R /home/ec2-user/src/main/webapp/WEB-INF/ /home/ec2-user/webapps/ROOT/ echo Done!
“铁器时代”
当测试情况良好需要部署到生产坏境时,就涉及到从原来的单点部署到集群部署了。原来的脚本和架构也不太适合了,幸好我们有还有铸就“金刚”之身的原料--S3。首先我们将需要部署的war包上传的到S3 的指定目录,登陆需要部署的服务器,下载该war 包并部署。流程很简单,但是需要执行的命令也是繁杂和重复。“小铁铲” cpWarToS3.sh
#! /bin/bash if [[ $# -eq 0 ]]; then warFile="/home/ec2-user/target/hs-0.1-SNAPSHOT.war" elif [[ $# -eq 1 ]]; then warFile="$1" else echo "Parameter Error!" exit 1 fi if [[ -f "$warFile" ]]; then echo Copy $warFile to S3... aws s3 cp "$warFile" "s3://config.ziyoufang.cn/war/ROOT.war" echo Done! else echo "No file: $warFile!" fi
“大铁锤” deployFromS3.sh
#! /bin/bash if [[ $# -eq 0 ]]; then WAR=ROOT.war elif [[ $# -eq 1 ]]; then WAR=$1 else echo "Parameter Error!" exit 1 fi WAR=`echo $WAR | awk -F '.' '{print $1}'` service tomcat8 stop rm -f "/usr/share/tomcat8/webapps/ROOT.war" rm -rf "/usr/share/tomcat8/webapps/ROOT" aws s3 cp s3://config.ziyoufang.cn/war/"$WAR".war "/usr/share/tomcat8/webapps/ROOT.war" service tomcat8 start log="`date +'%Y-%m-%d %H:%M:%S'` Deploy war from s3. done!" echo $log > deploy.log echo 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
#! /bin/bash if [[ $# -eq 0 ]]; then BRANCH=master elif [[ $# -eq 1 ]]; then BRANCH=$1 TARGET=ROOT.war elif [[ $# -eq 2 ]]; then BRANCH=$1 TARGET=$2 else echo "Parameter Error!" exit 1 fi TARGET=`echo $TARGET | awk -F '.' '{print $1}'` cd /home/ec2-user/Web_server git checkout $BRANCH git pull mvn clean install S3_Prefix="s3://config.ziyoufang.cn/war/" S3_War=$S3_Prefix"$TARGET".war S3_WarBack=$S3_Prefix"$TARGET""_`date +'%Y-%m-%d_%H:%M:%S'`.war" warFile="/home/ec2-user/Web_server/target/hs-0.1-SNAPSHOT.war" echo Backup "$TARGET".war on S3... aws s3 mv $S3_War $S3_WarBack echo upload new "$TARGET".war... aws s3 cp $warFile $S3_War echo "upload done."
此时部署时只需先执行 updateWarToS3.sh,然后登陆需要部署的服务器执行 deployFromS3.sh 即可。这下干感觉就是从原始社会走出来了~~爽啊~~~
可是……(哎……就怕有可是……)在部署生产环境时,每次都需要执行多个流程:
从ELB 中移除一台EC2 -> 等待connection draining -> 登陆该EC2 -> 执行deployFromS3.sh -> 等待tomcat启动起来 -> 添加该EC2 回ELB -> 等待监控状态检查到InService -> 下一台EC2……
在压力小的情况下执行该操作ELB 后端实例较少,部署几次之后我烦了,交给了另外一个人去部署,(嗯,以邻为壑的感觉挺爽~~)结果他部署了几次之后他也烦了,威胁说撂挑子不干了……无奈只好继续利用我大shell 铸造大杀器了……
“巨型铲车” deployIntoELBBackendInstance.sh
#/bin/bash
# deployIntoELBBackendInstance.sh
succeed_instances=""
failed_instances=""
ELB_NAME=""
DEPLOY_COMMAND="sudo -s /home/ec2-user/target/deployFromS3.sh"
function usage()
{
echo "Usage: $0 [Option] <parameter>"
echo "Options:"
echo " -b ELB name"
echo " -c Deployment command. Default: \"$DEPLOY_COMMAND\""
}
function addSuccessInstance()
{
if [[ $# -ne 1 ]]; then
return 1
fi
#add this intance to "succeed instances list" if get private ip
if [[ -z "$succeed_instances" ]]; then
succeed_instances=$instance
else
succeed_instances="$succeed_instances",\ "$instance"
fi
}
function addFailedInstance()
{
if [[ $# -ne 1 ]]; then
return 1
fi
#add this intance to "fialed instance list" if can't get private ip
if [[ -z "$failed_instances" ]]; then
failed_instances=$instance
else
failed_instances="$failed_instances",\ "$instance"
fi
}
function deploy()
{
if [[ $# -ne 1 ]]; then
return 1
fi
privateIp=$1
# ssh in and deploy on this instance
echo Deploying...
ssh $privateIp "$DEPLOY_COMMAND"
# ssh $privateIp echo deploying...
for (( i = 0; i < 6; i++ )); do
echo -n Please wait for retarting tomcat
for (( j = 0; j < 10; j++ )); do
echo -n .
sleep 1s
done
echo .
testLineNum=`curl -s $privateIp:8080 | grep html | wc -l`
if [[ $testLineNum -gt 0 ]]; then
echo tomcat retarts successfully.
break
fi
done
}
#main
while getopts "b:c:" arg
do
case $arg in
b )
ELB_NAME=$OPTARG
;;
c )
DEPLOY_COMMAND=$OPTARG
;;
? )
echo "Unknown argument."
usage
exit 1
;;
esac
done
if [[ -z "$ELB_NAME" ]]; then
echo "$0: missing elb name"
usage
exit 1
fi
# verify if exist this ELB
verifyELB=`aws elb describe-load-balancers --load-balancer-name $ELB_NAME | grep LoadBalancerName | awk -F '"' '{print $4}'`
if [[ "$verifyELB"x != "$ELB_NAME"x ]]; then
echo Cannot find Load Balancer $ELB_NAME
exit 1
fi
# go on if exist this elb
for instance in `aws elb describe-instance-health --load-balancer-name $ELB_NAME | grep InstanceId | awk -F '"' '{print $4}'` ; do
echo $instance is in progress...
#loop getting until get private ip
privateIp=""
for ip in `aws ec2 describe-instances --instance-ids $instance | grep PrivateIpAddress | awk -F '"' '{print $4}'` ; do
if [[ -n "$ip" ]]; then
privateIp=$ip
break
fi
done
if [[ -z "$privateIp" ]]; then
addFailedInstance $instance
else
#deregister this instance from elb
aws elb deregister-instances-from-load-balancer --load-balancer-name $ELB_NAME --instances $instance >/dev/null 2>&1
echo -n Please waitting for deregister $instance from elb $ELB_NAME
for (( i = 0; i < 20; i++ )); do
sleep 5s
echo -n .
outservice=`aws elb describe-instance-health --load-balancer-name $ELB_NAME --instances $instance | grep OutOfService | wc -l`
if [[ $out
9f44
service -eq 1 ]]; then
echo
echo $instance has been deregistered from elb $ELB_NAME
deploy $privateIp
#register this instance with elb
aws elb register-instances-with-load-balancer --load-balancer-name $ELB_NAME --instances $instance >/dev/null 2>&1
echo -n Please wait for register $instance with elb $ELB_NAME
for (( j = 0; j < 20; j++ )); do
sleep 6s
echo -n .
inservice=`aws elb describe-instance-health --load-balancer-name $ELB_NAME --instances $instance | grep InService | wc -l`
if [[ $inservice -eq 1 ]]; then
echo
echo $instance has been registered with elb $ELB_NAME
addSuccessInstance $instance
break
fi
if [[ $j -ge 19 ]]; then
addFailedInstance $instance
fi
done
echo
break
elif [[ $i -ge 19 ]]; then
echo
echo Deregister $instance time out. Process the next
addFailedInstance $instance
echo
continue
fi
done
fi
done
echo
echo succeed instances: $succeed_instances
echo failed instances: $failed_instances
该脚本能实现自动将指定ELB 下的后端健康实例进行部署,最后会提示部署成功和部署失败的实例。
至此,整个部署流程在updateWarToS3.sh 之后只需要执行deployIntoELBBackendInstance.sh 就可以了。
手执“大铁锤”,开着“巨型铲车”,慵懒的日子~舒坦~~~
相关文章推荐
- linux下的shell运算(加、减、乘、除)
- Shell脚本8种字符串截取方法总结
- Linux Shell 脚本中字符串的连接方法
- Hadoop name启动为standby状态shell命令报错
- 《Linux命令行与shell脚本》笔记--第5章:使用Linux环境变量
- shell之入门篇
- Linux Shell系列教程之(四)Shell注释
- Linux Shell系列教程之(三)Shell变量
- PowerShell 创建,查看和保存嵌套的对象属性
- GNU bash实现机制与源代码简析
- 高级Bash脚本编程指南
- shell学习
- 【shell】while read line 与for循环的区别
- 为bash添加内置命令(built-in)的方法
- 父 shell,子 shell ,export 与 变量传递
- ftp自动下载shell脚本
- 使用xshell 登陆aws的ec2
- shell系列------循环解析脚本的参数
- Linux Shell系列教程之(二)第一个Shell脚本
- Linux Shell系列教程之(一)Shell简介