亚马逊AWS官方博客

基于 GitLab 与 Amazon CodeBuild 实现持续集成

背景

随着 AWS 近期的调整,从 2024 年 7 月 25 日起,CodeCommit 停止了新用户注册,此后也是仅维护现有服务的基本功能,因此 AWS 新用户需要寻求源代码管理的替代方案。作为成熟的源代码托管平台,GitLab 支持创建无限数量的公共和私有代码仓库,并提供直观的 Web 界面用于代码浏览和管理。其完善的分支管理、标签功能以及强大的代码搜索能力,能够全面满足企业的开发需求。与此同时,AWS 发布了一系列的 GitLab 和 CodeBuild、CodePipeline 集成的新功能,可以有效帮助客户在使用 GitLab 作为源代码管理平台的同时,继续实现与 AWS CI/CD 产品的结合,并实现强大完整的 DevOps 解决方案,这些无疑都对 GitLab 作为 CodeCommit 替代方案提供了支持。

在 DevOps 实践中,持续集成(Continuous Integration,CI)扮演着关键角色。AWS CodeBuild 作为一个完全托管的持续集成服务,能够执行源代码编译、测试运行以及软件包生成等任务。通过将 GitLab 与 CodeBuild 相结合,企业可以实现一个自动化的构建和测试环境,不仅能更快地发现和修复缺陷,减少集成问题,还能帮助团队更快速、更可靠地交付高质量的软件产品。

本文将重点探讨如何实现 GitLab(gitlab.com)与 CodeBuild 的集成来打造一个完善的持续集成环境,帮助开发团队提升开发效率和代码质量。本文主要分为两个部分,第一部分介绍 GitLab 与 CodeBuild 的授权连接;第二部分通过实践来介绍 GitLab 与 CodeBuild 实现源代码构建持续集成的两种方式;源代码包括两个 repository,可以分别从 GitLab 的 hello-ci1 和 hello-ci2 这两个链接获取。

前提准备

在开始之前,我们需要做好以下工作:

  • GitLab 账号;
  • AWS Account;
  • 一个至少具有 IAM、CodeConnection、CodeBuild、ECR(Elastic Container Registry)权限的 AWS IAM 用户。

第一部分:创建与 GitLab 的授权连接

AWS CodeConnection 是 AWS 提供的一项服务,可用于建立 AWS 资源和第三方外部代码仓库(包括 GitLab)的连接。用户可以通过使用 CodeConnection 建立的连接结合与 CodeBuild、CodePipeline 的集成来实现代码触发 CI/CD 流水线的功能。下面我们详细介绍建立 GitLab 授权连接的步骤。

登录 AWS Management Console,打开 AWS 开发者工具控制台,网址为 https://console.aws.amazon.com/codesuite/settings/connections。打开后的页面显示如下,点击右上角的“Create connection”,进入到创建 Connection 的页面。

在“Select a provider”下,选择 GitLab,并在“Create Gitlab connection”下的连接名称处输入 Connection 的名字,点击右下角的“Connect to GitLab”继续。

如果您还没有在浏览器中登入您的 GitLab,此时会显示 Gitlab 的登录页面,如下图所示,选择您所使用的登录方式登录 GitLab.com。

在您已经登录过或者完成登录后,会进入“AWS Connect for GitLab”一个授权页面,点击“Authorize AWS Connector for GitLab”来完成授权。

完成授权后,浏览器会跳转回 AWS 控制台页面,此时选择“Connect”来继续完成连接的创建。

到这一步,我们就完成了 GitLab 与 CodeConnection 的 connection 的建立,如下图所示,该 connection 已处于 Available 的状态,表示 connection 已经创建成功。

这个授权连接其实是通过 GitLab 的 Applications 提供的 Oauth 的功能来实现的,而且我们在 GitLab 中 User Settings/Applications 页面下可以看到我们创建的连接。授权链接的取消可以通过我们在 CodeConnection 删除 connection 来实现,而在 GitLab 侧也提供了“Revoke”的方式,以便用户在某些特殊情况下从 GitLab 上取消授权连接。

第二部分:GitLab 与 CodeBuild 持续集成实现

方式一:使用 Webhook PUSH event 调用 CodeBuild

AWS Codebuild 支持与 GitLab 的 Webhook event 集成,通过配置相应的 Webhook event 来触发 CodeBuild project 的执行,在该方式中,我们将选择在 main 分支下当 PUSH event 发生时来触发 CodeBuild project 执行来实现持续集成,并将生成的镜像推送到 ECR( Elastic Container Registry)。下面我们以 GitLab 中的 hello-ci1 来详细介绍该方式的实现。

首先,我们需要在 ECR 中准备一个 repository,命名为 hello-ci1, 作为我们的镜像仓库。详细的创建步骤请参考 Creating a private repository 官方文档。

其次,我们来创建 CodeBuild project 执行所需要的 role,命名为 codebuild-hello-ci-service-role。该 role 的主要权限包括 CodeBuild project 来完成镜像到 ECR 的推送。其他的 CodeBuild 需要的权限,我们在随后创建 CodeBuild project 的时候,留给 CodeBuild 来帮我们自动进行管理。 以下是 codebuild-hello-ci-service-role 的 policy 的描述。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchGetImage",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:PutImage"
            ],
           "Resource": "*"
        }
    ]
}
YAML

接下来,我们来创建 CodeBuild project。进入 CodeBuild 控制台页面后,点击右侧 Create Project 按钮,进入 Create Build Project 页面。在 Project configuration 部分的 Project Name 下,输入 project 名称 hello-ci1。在 source 部分选择 GitLab 作为 Source Provider,在 Credential 部分有两个选择 Default source credential 和 Custom source credential,这主要是为多个 GitLab 账号的集成提供便利。如果我们是第一次进行在 CodeBuild 与 GitLab 的集成,你会看到如下图的提示。

如果我们选择设置 Default source credential,那么点击 Manage default source credential 来完成 Default 的设置,如下图所示,在 connection 部分选择所需要指定为默认的 connection 的名字。指定完成后,默认情况下的 GitLab 的账号就是我们 gitlab-ci 中授权的 GitLab 的账号。

也可以选择 Custom source credential 来明确在这里指定所要使用的 GitLab 的 connection。我们这里选择 Custom source credential,在 Connection 部分选择 gitlab-ci 的 arn,然后在 Repository 部分选择我们要使用的 repository hello-ci1。Source version 部分我们填写默认分支 main。

要实现 GitLab 与 CodeBuild 的自动化集成,我们需要来配置 Webhook,这样当代码在 GitLab 代码仓库有 Webhook 监控的事件发生时,就会触发 CodeBuild project 来进行调用执行,这里为了简便,我们只在 Webhook event filter groups 中只指定了 PUSH 事件,表明当在 main branch 发生 PUSH 事件时,CodeBuild project 会被触发并进行执行。

此外,在 Environment 部分,我们还需要进行两个配置,一个是 service role 部分,我们选择在上面创建的 codebuild-hello-ci-service-role 作为该 project 的 role,并勾选 Allow AWS CodeBuild to modify this service role so it can be used with this build project 来让 CodeBuild project 来自行添加它自身所需的一些权限到我们的 Role。

因为我们还需要在 CodeBuild 环境中进行 docker 镜像的制作,在 Additional configuration 部分,我们勾选 privileged 来获取相应的权限提升。

在 Buildspec 部分,因为我们在代码中生成了 buildspec.yml,所以在这里我们选择“Use a buildspec file”来指定使用 buildspec.yml 作为 codebuild project 执行命令的文件。

Buildspec.yml 文件的内容如下:

version: 0.2
env:
  shell: bash
  variables:
    IMAGE_REPO_NAME: "hello-ci1"

phases:
  pre_build:
    commands:
      - echo "Enter pre_build stage on `date`..."
      - echo "Preparing repository tags..."
      - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
      - SOURCE_COMMIT_HASH=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION})
      - IMAGE_TAG_COMMIT_HASH=commit-$(echo ${SOURCE_COMMIT_HASH} | cut -c 1-8)
      - echo "Preparing Amazon ECR stage..."
      - ECR_REPOSITORY_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}"
      - aws ecr get-login-password --region ${AWS_DEFAULT_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
  build:
    commands:
      - echo "Enter build phase on `date`..."
      - echo "Build docker image..."
      - docker build -t $ECR_REPOSITORY_URI:latest .
      - docker tag $ECR_REPOSITORY_URI:latest $ECR_REPOSITORY_URI:$IMAGE_TAG_COMMIT_HASH
  post_build:
    commands:
      - echo "Enter post_build phase on `date`..."
      - echo "Pushing the docker images..."
      - docker push $ECR_REPOSITORY_URI:$IMAGE_TAG_COMMIT_HASH
      - docker push $ECR_REPOSITORY_URI:latest
YAML

其它的配置部分,我们按照默认设置就可以了,点击 Create Project 来完成 CodeBuild project 的创建。

我们回过头来观察下 GitLab 上的变化,此时,我们来到 GitLab 上 hello-ci1 的 Settings>Webhooks页面,会发现在 GitLab 上有一个刚创建出来的 codebuild-webhook。GitLab 对 CodeBuild 的触发就是源自这里的 Webhook。

这样,我们就完成了 GitLab 与 CodeBuild 的集成,此时如果我们向 hello­-ci1 的 main branch 推送代码,CodeBuild 就会自动执行 project hello-ci1, 最终编译后的镜像被推送到 ECR 的 hello-ci1。

第二种方式:使用 GitLab Runner 与 CodeBuild 来实现持续集成

GitLab 内置了 CI/CD pipeline 的功能,pipeline 的 job 的执行是有 GitLab Runner 来完成的。您可以运行 pipeline 的 job 在 GitLab-hosted runners 上,这会由 GitLab 完全管理,这可能会面临资源不足而导致 job 排队等待执行,也可以在自己管理的实例上安装注册 GitLab Runner,从而运行 CI/CD 的 job 在自己的实例资源上。AWS CodeBuild 提供了作为 GitLab Runner 的能力,我们可以创建并配置 CodeBuild project 来实现这一集成方式。这一方式的集成,可以让我们借助 CodeBuild 来继续使用 GitLab 提供的 CI/CD pipeline 的功能来实现持续集成。

创建 CodeBuild project 的主要步骤和第一种方式的步骤基本一样,主要的不同是在 Webhook 和 Buildspec 部分,下面详细介绍。

同样的,我们首先需要在 ECR 中准备一个 repository,命名为 hello-ci2, 作为我们的镜像仓库。

在 Webhook 配置部分,我们在 CodeBuild project 页面下的 Webhook event filter groups 选择 WORKFLOW_JOB_QUEUED 事件类型,如下图所示,指定 GitLab Pipeline 的 Job event 来触发 Codebuild project。

在选择完这一类型后,Buildspec 部分会出现不同于第一种方式的描述。

默认情况下,CodeBuild project 运行时可以忽略 buildspec 部分,不执行 buildspec 提供的命令,而是会执行 GitLab CI/CD pipeline 定义的命令,这些命令定义在 helloc-ci2 的.gitlab-ci.yml 文件中,在.gitlab-ci.yml 中通过在 tag 中指定 codebuild-<codebuild-project-name>-$CI_PROJECT_ID-$CI_PIPELINE_IID-$CI_JOB_NAME 来注册 CodeBuild project 作为 job 执行的 GitLab Runner。详细的.gitlab-ci.yml 的内容如下:

image: golang:1.21.4-alpine

.go-cache:
    variables:
        GOPATH: $CI_PROJECT_DIR/.go
    cache:
      paths:
        - .go/pkg/mod/

variables:
  OUTPUT_NAME: __bin__/$CI_PROJECT_NAME
  IMAGE_REPO_NAME: hello-ci2

stages:
  - lint
  - build
  - build_image

lint:
  image: golangci/golangci-lint:latest
  stage: lint
  extends: .go-cache
  allow_failure: false
  script:
    - golangci-lint run -v

build:
  stage: build
  script:
    - mkdir -p $OUTPUT_NAME
    - go build -o $OUTPUT_NAME ./...
  artifacts:
    paths:
      - $OUTPUT_NAME

build_image:
  image: docker:stable
  stage: build_image
  services:
    - docker:dind
  before_script:
    - echo "Enter before_script stage on `date`..."
    - echo "Preparing repository tags..."
    - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
    - SOURCE_COMMIT_HASH=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION})
    - IMAGE_TAG_COMMIT_HASH=commit-$(echo ${SOURCE_COMMIT_HASH} | cut -c 1-8)
    - echo "Preparing Amazon ECR stage..."
    - ECR_REPOSITORY_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_REPO_NAME}"
    - aws ecr get-login-password --region ${AWS_DEFAULT_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
  script:
    - echo "Enter script phase on `date`..."
    - echo "Build docker image..."
    - docker build -t $ECR_REPOSITORY_URI:latest .
    - docker tag $ECR_REPOSITORY_URI:latest $ECR_REPOSITORY_URI:$IMAGE_TAG_COMMIT_HASH 
  after_script:
    - echo "Enter post_build phase on `date`..."
    - echo "Publishing the docker images..."
    - docker push $ECR_REPOSITORY_URI:$IMAGE_TAG_COMMIT_HASH
    - docker push $ECR_REPOSITORY_URI:latest
  tags:
    - codebuild-hello-ci2-$CI_PROJECT_ID-$CI_PIPELINE_IID-$CI_JOB_NAME
YAML

为了进行比较,我们在该文件中定义了三个阶段,我们只在第三个阶段定义了 tag: codebuild-hello-ci2-$CI_PROJECT_ID-$CI_PIPELINE_IID-$CI_JOB_NAME,也就是说只有第三个阶段会在我们的 CodeBuild project 中运行。此时,我们如果进行源代码的更改或者执行 pipeline,会完成 GitLab pipeline 的执行,并在 GitLab 的 pipeline 中呈现 job 的执行情况,而最终的镜像也会被推送到 ECR 的 hello-ci2 repository。

在这篇博客中,我们介绍了 GitLab 与 CodeBuild 集成的两种方式,实现了基本的持续集成。CodeBuild 还支持其他多种 Webhook event 可以满足不同 GitLab event 事件的集成以及在 GitLab pipeline 方式下的多种灵活配置来完成更复杂的持续集成。在实践过程中,你会发现两者的结合可以实现强大、完善的 DevOps 解决方案。

本篇作者

张乾

亚马逊云科技解决方案架构师,负责基于 AWS 的解决方案咨询和设计,在系统架构设计、应用研发、容器服务、SRE 领域有丰富的实践经验。

刘佳

亚马逊云科技解决方案架构师,负责基于 AWS 云计算客户方案咨询和架构设计,在金融领域有着多年的数据中心虚拟化解决方案经验。