亚马逊AWS官方博客

使用S3作为Maven制品库配合CI/CD流水线实现自动化构建

背景介绍

很多项目使用Jenkins作为持续集成和持续交付的CICD pipeline,来build基于Maven作为自动化构建工具的Java工程。 在有的场景中,我们的Java工程依赖于我们自己团队或者项目的其他包,然而根据环境限制的不同,或者安全策略的考虑,我们无法将所依赖的包上传至Maven Central Repository,也不具备搭建私有Maven Repository的条件或者便利。

这时候,我们需要一种机制,能够管理并共享dependency,使得Jenkins CICD pipeline可以无缝的使用这种共享机制,来完成Java 工程的自动化构建。

本文阐述了一种方案来实现这一目的,一切环境都采用AWS服务设计和搭建。

方案设计

在Account A中存在一个CodeCommit Repository, 名为Repo1, Repo1用来构建Application A,同时,Repo 1所构建生成的Package, 也将作为其他工程的依赖,参与其他工程的构建。在Account A中,使用AWS Codebuild进行代码的构建,并使用CodeDeploy进行Application的部署, 部署环境由CloudFormation定义并自动生成。这些组件构成了Account A中的CICD Pipeline。

通常情况,Repo1所构建的Package可以上传至Maven Central,供其他工程引用,下载并构建自己的Application或者Artifact。

但因为安全策略,这个场景不允许我们的Package上传至Maven Central Repository。我们也不具备搭建私有Maven Repository的环境和网络条件。

所以我们采用S3作为Artifact仓库。在Repo1 的构建过程中,由codebuild执行脚本,并配合Maven构建出Package,脚本将Package上传至S3的Artifact Bucket.

在Account A的IAM中已经为Account B设定好权限,Account B的用户可以访问Account A的S3 并下载相关的Artifact。

Account B 要clone并且构建自己的工程,工程代码存储于CodeCommit的Repo2, Repo2同样使用Maven进行构建。但是在Account B中,选择了使用Jenkins进行自动化构建和部署。

我们使用Jenkins Shared library来定义持续集成和持续部署的步骤,这样的好处是不需要在每个构建的工程中重复定义构建和部署逻辑,将标准的构建流程抽象出来进行统一定义,在各个工程构建的时候, 只需要传入一些参数即可。

在Jenkins将Code从Repo2 拉取下来后,开始进行Build, Build之前需要将依赖项从S3同步至本地,接下来使用Maven进行构建,因为Account B的网络环境限制,无法访问Maven Central Repository,所以必须使用本地路径加载dependencies. 从S3下载下来的package保证了本地build的成功。

构建成功后,Jenkins将Application B通过CloudFormation进行部署。

实现落地

在Repo1的构建中,首先使用Maven将代码构建打包,然后使用Maven的exec-maven-plugin插件,调用事先准备好的脚本,将pacakge上传至S3 的bucket。

具体代码如下:

<plugin>
                <artifactId>exec-maven-plugin</artifactId>
                <groupId>org.codehaus.mojo</groupId>
                <executions>
                    <execution><!-- Get com.amazonaws.proserve libs -->
                        <id>deploy-libs-s3</id>
                        <phase>deploy</phase>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <workingDirectory>${basedir}/scripts/</workingDirectory>
                    <executable>bash</executable>
                    <commandlineArgs>m2_repo_helper.sh -u -b ${cicd.bucket}</commandlineArgs>
                </configuration>
            </plugin>

以上maven插件调用了m2_repo_helper.sh脚本,进行包上传。

脚本代码如下:

# Upload libs to S3 bucket (passed as arg)
upload_libs() {
  echo Uploading Java libs to s3://${1}
  CMD="  aws s3 sync ${HOME}/.m2/repository/com/amazonaws/proserve s3://${1}/libs"
  echo Executing ${CMD}
  ${CMD}
}

#############
# Main Script
#############

ACTION=""
BUCKET=""

while getopts 'udb:' c
do
  case $c in
    u) ACTION=UPLOAD ;;
    d) ACTION=DOWNLOAD ;;
    b) BUCKET=${OPTARG} ;;
  esac
done

[ -z "${ACTION}" ] && usage
[ -z "${BUCKET}" ] && usage

case $ACTION in
    UPLOAD) upload_libs $BUCKET ;;
    DOWNLOAD) download_libs $BUCKET ;;
esac

在Repo2构建时,需要首先从S3下载相关的dependency, 我们在Jenkins的share library中实现包的下载。在下载之前,需要Account B中运行Jenkins Pipeline的User assume Account A中的权限,获得token 来download相关的dependency。

Assume Role的命令如下:

sh('aws sts assume-role-with-web-identity --role-arn $' + Role + ' --role-session-name x-account --web-identity-token file://$AWS_WEB_IDENTITY_TOKEN_FILE --duration 14400 > ./temp_creds.json')

Download dependency的命令如下:

sh('aws s3 sync s3://XXX-XXX-XXX-cicd-deployment/libs $HOME/.m2/repository/com/amazonaws/proserve')

到此,可以在Repo2的POM文件中,直接引用该dependency进行构建,dependency的声明如下:

      <dependency>
            <groupId>com.amazonaws.proserve.common</groupId>
            <artifactId>MagnaCommon</artifactId>
            <version>2.0-SNAPSHOT</version>
        </dependency>

总结

以上是该方案的全部内容,改方案适用于不能使用Maven Central Repository 以及私有Repository的场景,S3作为一个存储artifact的仓库,可以跨账号,跨区域实现package的共享,并且通过Jenkins的share library定义的构建逻辑,在每次代码构建之前将package download到构建环境的本地。该方案对工程的Maven构建透明,即Maven无需感知Dependency Repository的位置,只需要使用既有的步骤进行构建即可。

本篇作者

师帅

AWS 云应用架构师,擅长云原生,容器化以及微服务相关解决方案的设计和实践,擅长DDD领域驱动建模的理论和落地。致力于向客户提供相关领域咨询和赋能工作。