AWS CodePipeline部署Maven项目至EC2
背景
AWS CodePipeline 是一种持续性的集成与交付服务,可以实现快速而可靠的应用程序和基础设施更新。根据您定义的发布流程模型,只要代码发生变更,CodePipeline 便会生成、测试和部署您的代码。
情况是这样的,最近公司在搞APN认证,需要将项目迁移到AWS上面,并且搭建一套CI/CD流程。因为项目是SpringCloud微服务,所以需要部署多个服务。
经过调研,我们做了以下设计:
我们设计,将代码推送到AWS Code Commit,将项目需要用到的自研jar包上传到AWS Code Artifact。代码推送到AWS Code Commit时,AWS Code Pipeline触发打包服务,使用AWS Code Build打包的时候,访问AWS Code Artifact去拿jar包。打完jar包之后,AWS Code Pipeline触发AWS Code Deploy上传jar包至EC2(ECS也可以,看需求),然后AWS Code Deploy调用配置好的脚本,备份和启动项目。
所以除了项目中用到的数据库,中间件之外不谈,我们需要的服务有:
- 代码仓库:使用AWS Code Commit
- 制品仓库: 使用AWS Code Artifact
- 项目打包服务: 使用 AWS Code Build
- 部署服务: AWS Code Deploy
- 流水线服务: AWS Code Pipeline
- 虚拟机EC2
AWS Code Pipeline 负责将这些服务串联起来,形成工作流。
产品都使用的孟买地区的服务(ap-south-1)
安装AWS CLI
因为需要使用AWS服务,所以首先我们得安装AWS CLI命令行工具,并且配置好自己的账号信息,下载和配置AWS CLI参考:AWS CLI 官方文档
我们安装AWS CLI主要是用来通过Code Artifact的认证,Code Artifact必须使用AWS CLI来生成临时凭证访问。
AWS Code Commit
AWS CodeCommit 是一种完全托管型源代码控制服务,使公司可以轻松地托管安全和高度可扩展的专用 Git 存储库。CodeCommit 使您无需运作自己的源控制系统或担心基础设施的扩展能力。
这一步其实可以不做,因为AWS Code Build可以从github中拉取代码,但是为了认证APN,最好都使用AWS的服务,所以加了这一步。
在AWS Code Commit中,直接创建存储库即可,最主要的是怎么连接。
Code Commit可以使用账号密码访问,就像github一样。账号密码需要在IAM权限管理服务中查看:
在IAM中,选择用户选项,然后打开一个用户,来到安全证书选项下面:
上图箭头所标识的就是登陆Code Commit的方式,SSH和账号密码选择一种进行登陆即可。
AWS CodeArtifact
AWS CodeArtifact 是一种完全托管的构件存储库服务,它使各种规模的组织都可以轻松安全地存储、发布及共享其软件开发过程中所使用的软件包。
咱们的项目使用的是自己写的微服务框架,原本是存储在公司的Nexus制品仓库中(Maven私服),为了方便AWS CodeBuild打包,所以我们将项目所需要的jar包都上传到这个服务中。
因为我们使用的是Maven来进行Deploy,按照官方的指南,需要通过以下的配置来通过验证:
前提:安装好AWS CLI,并且配置好自己的账号。
首先修改maven的setting文件:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="//maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//maven.apache.org/SETTINGS/1.0.0 //maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>你的本地Maven仓库地址</localRepository>
<pluginGroups></pluginGroups>
<proxies></proxies>
<servers>
<server>
<id>codeartifact</id>
<username>aws</username>
<password>${env.CODEARTIFACT_TOKEN}</password>
</server>
</servers>
<profiles>
<profile>
<id>default</id>
<repositories>
<repository>
<id>codeartifact</id>
<url>你的CodeArtifact访问地址</url>
</repository>
</repositories>
</profile>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>default</activeProfile>
</activeProfiles>
</settings>
可以从上面的配置文件中看到,我们是从环境变量中获取密码的,因为其密码只能通过AWS CLI生成临时的凭证,而且生成的Token只能保持12小时有效。
生成密码的方式:
macOS or Linux:
$ export CODEARTIFACT_TOKEN=`aws codeartifact get-authorization-token --domain mydomain --domain-owner domain-owner-id --query authorizationToken --output text`
Windows PowerShell:
> $CODEARTIFACT_TOKEN = aws codeartifact get-authorization-token --domain my-domain --domain-owner domain-owner-id --query authorizationToken --output text
你需要将界面中的 export 配置的
–my-domain
–domain-owner-id
2 个参数替换成你配置的参数。
通过上面的命令就会生成 Token, 然后将这个 token 保存到系统变量中。
如果一切顺利,你将会看到你的屏幕中输出上面的字符串,上面的字符串就是你 Maven 登录使用的 token。
登录之后,我们将项目所需要的Jar包上传至AWS CodeArtifact
AWS CodeBuild
AWS CodeBuild 是一项完全托管的持续集成服务,可编译源代码、运行测试以及生成可供部署的软件包。使用 CodeBuild,您无需配置、管理和扩展自己的生成服务器。CodeBuild 可以持续扩展并同时处理多项生成任务,因此您的构建任务不会在队列中等待。
我们使用AWS CodeBuild对项目进行打包
创建CodeBuild角色
在使用AWS CodeBuild之前,我们需要创建其角色:
来到IAM页面,点击创建角色,选择CodeBuild
产品,点击下一步
在附加权限中,搜索CodeArtifact,选择其一,我这里选择的是AdminAccess,下面一个是只读
只有选择了这两个的其中一个,我们才能通过CodeBuild访问CodeArtifact
然后跳过标签,下一步,创建一个名字,自拟。点击创建角色即可。
创建CodeBuild构建项目
来到AWS CodeBuild页面,创建构建项目:
这里只说明几个需要注意的选项
源我们使用CodeCommit
可以选择源是来源于CodeCommit的哪一个分支或者Git标签或者提交的ID。
打包环境选择标准的Standard环境即可,角色选择现有服务角色,使用我们刚才创建的哪个角色即可。
需要注意的是这里,BuildSpec.yml文件,这个文件是具体的打包步骤,如果要配合CodeDeploy部署程序,还需要一个appspec.yml文件 Build+Deploy官方示例
这是我们项目中的其中一个服务,我们项目启动只需要api模块中的jar包
这里的settings-aws.xml为在CodeBuild中进行maven打包的setting配置文件。
buildspec.yml
version: 0.2
phases:
install:
runtime-versions:
java: corretto8
pre_build:
commands:
- pip3 install awscli --upgrade --user
- export CODEARTIFACT_TOKEN=`aws codeartifact get-authorization-token --domain xxx --domain-owner xxx --query authorizationToken --output text`
build:
commands:
- echo Build started on `date`
post_build:
commands:
- echo Build completed on `date`
- mvn package -Dmaven.test.skip=true --settings ./settings-aws.xml
- rm -rf vehicles-service-api/target/*jar.original
- rm -rf vehicles-service-api/target/*-javadoc.jar
- rm -rf vehicles-service-api/target/*-sources.jar
artifacts:
files:
- vehicles-service-api/target/*.jar
- appspec.yml
- backup.sh
- start.sh
discard-paths: yes
注意:
-
pre_build.commands
中执行的是CodeArtfact验证的过程,如果CodeBuild的角色没有CodeArtfactAccess的权限,这一步则会验证失败。 -
artifacts.files
是我们打完包之后,会将这里定义的文件交给CodeDeploy
appspec.yml、backup.sh和start.sh涉及CodeDeploy服务,在下一节介绍。
AWS CodeDeploy
AWS CodeDeploy 是一项完全托管的部署服务,可自动将软件部署到计算服务,例如 Amazon EC2、AWS Lambda 以及您的本地服务器。借助 AWS CodeDeploy,您可以更轻松地快速发布新功能、避免在应用程序部署过程中出现停机,并简化应用程序的更新工作。
CodeDeploy的配置思路:
- 配置角色
- 安装Deploy组件
- 创建应用程序
- 在应用程序中创建部署组,设定部署在哪里
- 编写appspec.yml文件
配置角色
配置服务器角色
无论使用的是ECR还是EC2,我们都需要给服务器配置权限,这里我使用的是EC2
来到IAM角色页面,如果你没有EC2的角色,则创建一个新角色,选择角色类型为Amazon EC2。如果你有EC2的角色,则点击附加角色
在附加策略里面搜索 CodeDeploy;然后选择AmazonEC2RoleforAWSCodeDeploy
我这里将其取名为:ChuanXiaoCodeDeployInstanceRole
如果你的服务器没有角色,需要去将你的服务器角色改为创建的新角色
配置CodeDeploy角色
我们还要在这里建立一个 CodeDeploy 的应用使用的角色。再次点击「创建角色」。选择角色为「Amazon EC2」,在附加策略时依然搜索 CodeDeploy,但是这次选择的策略叫做:AWSCodeDeployRole
我这里将其取名为CodeDeployServiceRole
安装Deploy组件
如果需要使用Deploy部署你的服务,则需要安装Deploy组件: 安装CodeDeploy官方文档
登录你的服务器,执行以下命令
$ sudo yum update
$ sudo yum install ruby
$ sudo yum install wget
$ cd /home/ec2-user
# 将以下命令的bucket-name换为您CodeDeploy所在区域的S3桶名字,比如:美国东部(俄亥俄)区域,请将存储桶名称替换为 aws-codedeploy-us-east-2
# 将以下命令的region-identifier换为您CodeDeploy所在区域的表示符,比如美国东部(俄亥俄)区域,请将区域标识符替换为 us-east-2
$ wget //bucket-name.s3.region-identifier.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto
# 检查服务是否正在运行
$ sudo service codedeploy-agent status
# 开启服务
$ sudo service codedeploy-agent start
创建应用程序
点击CodeDeploy选项中的应用程序,点击创建应用程序
输入应用程序的名字,选择应用程序需要部署到的平台,点击创建应用程序
创建部署组
应用程序创建完毕之后,点进去,然后点击创建部署组
这里的角色选择我们刚才创建的角色
这里选择就地部署,因为我使用的是EC2,所以选择AmazonEC2实例,通过标签来标识选择哪一个Ec2实例,我这里的EC2实例的name就为ChuangXiaoEc2,所以这里通过name设置。
这是我们ES2的信息:
最后的负载均衡这里,因为没有这个需求,就没有选择
编写appspec.yml文件
在配置CodeBuild的时候说道,我们需要appspec.yml文件,这个文件是做什么的呢?
appspec.yml是YAML格式、用于定于CodeDeploy服务在整个阶段所做的操作和文件拷贝路径和权限等。
也就是说定义CodeDeploy在部署的机器上面拷贝哪些文件,定义和描述被拷贝到目标服务器上的文件拷贝后的权限,定义各个阶段执行的操作。具体的解析可以参考这篇文章:appspec.yml文件解析
appspec.yml示例
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/chengdu_iot/vehicles-service/standby_dir
hooks:
BeforeInstall:
- location: backup.sh
timeout: 3000
ApplicationStart:
- location: start.sh
timeout: 3000
这个文件的version
只能填0.0,我们服务器是linux,所以os
为linux
files:定于文件映射关系
source
文件路径是是相对于本此部署包的相对路径,如果是/
,表示本此部署包里的全部文件和目录
需要注意的是,本此部署包中的文件,就是你在Buildspec.yml中定义的artifacts.files的文件,CodeDeploy会通过Buildspec.yml,获取需要哪些文件,然后上传至服务器。linux系统下,版本包会被上传到/opt/codedeploy-agent/deployment-root/deployment-group-id/deployment-id/deployment-archive
,windows系统会被上传到C:\ProgramData\Amazon\CodeDeploy\deployment-group-id\deployment-id\deployment-archive
destination
这里是被部署服务器的完整路径(绝对路径),会将source
中设定的文件,从/opt/codedeploy-agent/deployment-root/deployment-group-id/deployment-id/deployment-archive
中copy到设定的destination
路径中
hooks区段就是定义各个阶段执行的操作,这里的BeforeInstall
为:文件复制到目标目录之前的操作
project_dir=/home/ec2-user/chengdu_iot/vehicles-service
standby_dir=$project_dir/standby_dir
backup_dir=$project_dir/backup
cd $project_dir
if [ ! -d $standby_dir ]; then
echo "创建等待文件夹"
mkdir -p $standby_dir
else
echo "等待文件夹已存在"
fi
if [ ! -d $backup_dir ]; then
echo "创建备份文件夹"
mkdir -p $backup_dir
else
echo "备份文件夹已存在"
fi
jar_name=`ls | grep jar$ | awk '{print $1}'`
if [ -n "$jar_name" ];then
mv ./$jar_name ./backup/$jar_name.`date +%Y%m%d%H%M%S`
fi
这个脚本判断项目文件夹是否存在,不存在则创建。再判断jar包是否存在,存在则进行备份。
ApplicationStart
表示文件复制完之后的操作
port=9083
project_dir=/home/ec2-user/chengdu_iot/vehicles-service
standby_dir=$project_dir/standby_dir
cd $standby_dir
jar_name=`ls | grep jar$ | awk '{print $1}'`
mv $jar_name ../
cd ..
rm -rf standby_dir
pid=`netstat -anp|grep $port|awk '{printf $7}'|cut -d/ -f1`
if [ -n "$pid" ]
then
if [ "$pid" != "-" ]
then
echo "kill -9 的pid:" $pid
kill -9 $pid
fi
fi
nohup java -Xms1g -Xmx1g -jar $jar_name --spring.profiles.active=test --server.port=$port >console.log 2>&1 &
这个脚本做了三件事,将jar包从等待文件夹中拿出来,然后删掉等待文件夹。然后判断是否有这个jar包的进程在运行,如果有就杀掉。最后运行jar包
AWS CodePipeline
AWS CodePipeline 是一种持续性的集成与交付服务,可以实现快速而可靠的应用程序和基础设施更新。根据您定义的发布流程模型,只要代码发生变更,CodePipeline 便会生成、测试和部署您的代码。
对于AWS Pipeline我的理解是,将以上服务串联起来,当设定的CodeCommit分支发生变动的时候,就触发CodeBuild,然后CodeBuild打包成功之后,将buildspec.yml文件中设定的需要传输的文件告诉CodeDeploy,然后CodeDeploy将文件上传至服务器。最后CodeDeploy执行执行脚本,运行项目。
在CodePipeline页面,点击创建流水线
第一步设置流水线名字和角色,这里自拟名字,选择一个新服务角色就好。
这里源选择AWS CodeCommit,设置好分支,这样当这个分支发生变化的时候,就会触发整个流水线。
构建选择AWS CodeBuild,选择我们之前在CodeBuild中创建的构建项目
部署选择AWS CodeDeploy,选择我们创建的应用程序和部署组。
最后点击创建流水线,这样一个流水线就创建完毕了。
结果
当设定的CodeCommit分支发生变动的时候,就触发CodeBuild,然后CodeBuild打包成功之后,将buildspec.yml文件中设定的需要传输的文件告诉CodeDeploy,然后CodeDeploy将文件上传至服务器。最后CodeDeploy执行执行脚本,运行项目。