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的配置思路:

  1. 配置角色
  2. 安装Deploy组件
  3. 创建应用程序
  4. 在应用程序中创建部署组,设定部署在哪里
  5. 编写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-archivecopy到设定的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执行执行脚本,运行项目。

Tags: