在 AWS Fargate 上弹性伸缩极狐GitLab CI

AWS Fargate 的极狐GitLab 自定义执行器驱动会自动在亚马逊 ECS 上启动容器,执行极狐GitLab CI 作业。

在您完成本文档中的任务后,执行器可以运行从极狐GitLab 启动的作业。每次在极狐GitLab 中进行提交时,极狐GitLab 实例都会通知 Runner 有新作业可用。 然后 Runner 会根据您在 AWS ECS 上配置的任务定义,在目标 ECS 集群中启动一个新任务。 您可以将 AWS ECS 任务定义配置为使用任何 Docker 映像,这样您会对您在 AWS Fargate 上执行的这一类型的构建拥有完全的灵活性。

GitLab Runner Fargate Driver Architecture

本文档通过一个示例让您初步了解实现过程,并不用于生产用途;AWS 中需要额外的安全性。

例如,您可能需要两个 AWS 安全组:

  • 一个由托管极狐GitLab Runner 的 EC2 实例使用,并且只接受来自受限的外部 IP 范围的 SSH 连接(用于管理访问)。
  • 一个适用于 Fargate 任务并且仅允许来自 EC2 实例的 SSH 流量。

此外,对于非公共的容器镜像库,您的 ECS 任务需要 IAM 权限 (只针对 AWS ECR );或对非 ECR 私有仓库,需要任务的私有仓库认证

您可以使用 CloudFormation 或 Terraform 自动配置和设置您的 AWS 基础设施。

cautionCI/CD 作业使用 ECS 任务定义的镜像,而不是 .gitlab-ci.yml 文件中关键字 image: 的值。这个配置会导致 Runner Manager 出现多个实例或大型构建容器的情况。您可以参照官方的 AWS EKS 快速入门创建 EKS 集群。
cautionFargate 抽象了容器主机,这限制了容器主机属性的可配置性,也会影响需要高磁盘或网络 IO 的 Runner 工作负载,因为这些属性对 Fargate 的可配置性作用有限或根本没有。在 Fargate 上使用极狐GitLab Runner 之前,请确保在 CPU、内存、磁盘 IO 或网络 IO 上具有高或极端计算特性的 Runner 工作负载适合 Fargate。

先决条件

在开始前您应该拥有:

  • 拥有创建和配置 EC2、ECS 和 ECR 资源的权限的 AWS IAM 用户
  • AWS VPC 和子网
  • 一个或多个 AWS 安全组

步骤一:为 AWS Fargate 任务准备容器镜像

准备一个容器镜像。您会将这个镜像上传到镜像库中,用以极狐GitLab 作业运行时创建容器。

  1. 确保镜像拥有构建 CI 作业所需的工具。例如,Java 项目需要 Java JDK 和类似 Maven 或 Gradle 的构建工具。Node.js 项目需要 nodenpm
  2. 确保镜像拥有极狐GitLab Runner,用以处理产物和缓存。
  3. 确保容器镜像可以通过公钥认证支持 SSH 连接。 Runner 通过这个连接向 AWS Fargate 上的容器发送 gitlab-ci.yml 文件中定义的构建命令。SSH key 由 Fargate 驱动进行自动管理。容器必须能够接受 SSH_PUBLIC_KEY 环境变量的 key。

步骤二: 推送容器镜像到镜像库

镜像创建完成后,将镜像发布到容器镜像库中,以便在 ECS 任务定义中进行使用。

  • 创建仓库和推送镜像到 ECR ,请参照 Amazon ECR 仓库 文档。
  • 使用 AWS CLI 推送镜像到 ECR,请参照使用 AWS CLI 开始 Amazon ECR 文档。
  • 您可以通过 Debian 或 NodeJS 示例使用极狐GitLab 容器镜像库。Debian 镜像发布到 registry.gitlab.com/tmaczukin-test-projects/fargate-driver-debian:latest。 NodeJS 示例镜像发布到 registry.gitlab.com/aws-fargate-driver-demo/docker-nodejs-gitlab-ci-fargate:latest

步骤三:为极狐GitLab Runner 创建 EC2 实例

现在创建 AWS EC2 实例。您会在下一个步骤中在它的上面安装极狐GitLab Runner。

  1. 访问 https://console.aws.amazon.com/ec2/v2/home#LaunchInstanceWizard
  2. 关于实例,选择 Ubuntu Server 18.04 LTS AMI。 名称可能会因为您选择的 AWS 区域而有所不同。
  3. 关于实例类型,选择 t2.micro。单击 Next: Configure Instance Details
  4. 保持 Number of instances 的默认值。
  5. 关于 Network,选择您的 VPC。
  6. Auto-assign Public IP 设置为 Enable
  7. IAM role 下面,单击 Create new IAM role。这个角色专为测试而建,并不安全。
    1. 单击 Create role
    2. 选择 AWS service,在 Common use cases 下面,单击 EC2。然后单击 Next: Permissions
    3. 选择 AmazonECS_FullAccess 政策的复选框。单击 Next: Tags
    4. 单击 Next: Review
    5. 输入 IAM role,例如 fargate-test-instance, 单击 Create role
  8. 返回创建实例的浏览器选项卡。
  9. Create new IAM role 的左侧,单击刷新按钮。 选择 fargate-test-instance 角色。单击 Next: Add Storage
  10. 单击 Next: Add Tags
  11. 单击 Next: Configure Security Group
  12. 选择 Create a new security group,将其命名为 fargate-test。 确保已为 SSH 定义了规则 (Type: SSH, Protocol: TCP, Port Range: 22)。您必须为入站和出站规则指定 IP 范围。
  13. 单击 Review and Launch
  14. 单击 Launch
  15. 可选。选择 Create a new key pair,将其命名为 fargate-runner-manager。 单击 Download Key Pair 按钮。SSH 私钥下载到了您的电脑中 (查看您浏览器配置的目录)。
  16. 单击 Launch Instances
  17. 单击 View Instances
  18. 等待实例启动,记录 IPv4 Public IP 地址。

步骤四:在 EC2 实例上安装和配置极狐GitLab Runner

现在在 Ubuntu 实例上安装极狐GitLab Runner。

  1. 找到您的项目的 设置 > CI/CD 并展开 Runner 部分。 在 手动设置特定 Runner 下面,记录注册令牌。
  2. 运行 chmod 400 path/to/downloaded/key/file,确保您的密钥文件拥有正确权限。
  3. 使用以下命令通过SSH 进入 EC2 实例:

    ssh ubuntu@[ip_address] -i path/to/downloaded/key/file
    
  4. 成功连接后,运行以下命令:

    sudo mkdir -p /opt/gitlab-runner/{metadata,builds,cache}
    curl -s "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
    sudo apt install gitlab-runner
    
  5. 使用步骤一中记录的极狐GitLab URL 和注册令牌,运行以下命令:

    sudo gitlab-runner register --url https://gitlab.com/ --registration-token TOKEN_HERE --name fargate-test-runner --run-untagged --executor custom -n
    
  6. 运行 sudo vim /etc/gitlab-runner/config.toml,并添加以下内容:

    concurrent = 1
    check_interval = 0
    
    [session_server]
      session_timeout = 1800
    
    [[runners]]
      name = "fargate-test"
      url = "https://gitlab.com/"
      token = "__REDACTED__"
      executor = "custom"
      builds_dir = "/opt/gitlab-runner/builds"
      cache_dir = "/opt/gitlab-runner/cache"
      [runners.custom]
        volumes = ["/cache", "/path/to-ca-cert-dir/ca.crt:/etc/gitlab-runner/certs/ca.crt:ro"]
        config_exec = "/opt/gitlab-runner/fargate"
        config_args = ["--config", "/etc/gitlab-runner/fargate.toml", "custom", "config"]
        prepare_exec = "/opt/gitlab-runner/fargate"
        prepare_args = ["--config", "/etc/gitlab-runner/fargate.toml", "custom", "prepare"]
        run_exec = "/opt/gitlab-runner/fargate"
        run_args = ["--config", "/etc/gitlab-runner/fargate.toml", "custom", "run"]
        cleanup_exec = "/opt/gitlab-runner/fargate"
        cleanup_args = ["--config", "/etc/gitlab-runner/fargate.toml", "custom", "cleanup"]
    
  7. 如果您拥有带有私有 CA 的私有化部署的实例,添加以下内容:

           volumes = ["/cache", "/path/to-ca-cert-dir/ca.crt:/etc/gitlab-runner/certs/ca.crt:ro"]
    

    下面的 config.toml 文件是由注册命令生成的,不要更改。

    concurrent = 1
    check_interval = 0
    
    [session_server]
      session_timeout = 1800
    
    name = "fargate-test"
    url = "https://gitlab.com/"
    token = "__REDACTED__"
    executor = "custom"
    
  8. 运行 sudo vim /etc/gitlab-runner/fargate.toml 并添加以下内容:

    LogLevel = "info"
    LogFormat = "text"
    
    [Fargate]
      Cluster = "test-cluster"
      Region = "us-east-2"
      Subnet = "subnet-xxxxxx"
      SecurityGroup = "sg-xxxxxxxxxxxxx"
      TaskDefinition = "test-task:1"
      EnablePublicIP = true
    
    [TaskMetadata]
      Directory = "/opt/gitlab-runner/metadata"
    
    [SSH]
      Username = "root"
      Port = 22
    
    • 记录 Cluster 的值和 TaskDefinition 的名称。这个例子中, test-task 的修订编号是 :1。如果没有指定修订编号,会使用最新的 生效的 修订。
    • 选择区域。从 Runner Manager 实例中取 Subnet 的值。
    • 获取安全组 ID:

      1. 在 AWS 的实例列表中选择您创建的 EC2 实例,会显示详情。
      2. Security groups 下,选择您创建的组的名称。
      3. 复制 Security group ID

      在生产设置中, 按照AWS 指南设置和使用安全组。

    • 如果 EnablePublicIP 设置为 true,则收集任务容器的公共 IP 以进行 SSH 连接。
    • 如果 EnablePublicIP 设置为 false:
      • Fargate 驱动程序使用任务容器的私有 IP。要在设置为 false 时建立连接,VPC 的安全组必须有端口 22 (SSH) 的入站规则,其中源是 VPC CIDR。
      • 要获取外部依赖项,预配置的 AWS Fargate 容器必须能够访问公共互联网。要提供 AWS Fargate 容器的公共互联网访问,您可以在 VPC 中使用 NAT 网关。
    • SSH 服务器的端口号是可选的。如果省略,则使用默认的 SSH 端口 (22)。
  9. 安装 Fargate 驱动:

    sudo curl -Lo /opt/gitlab-runner/fargate "https://gitlab-runner-custom-fargate-downloads.s3.amazonaws.com/latest/fargate-linux-amd64"
    sudo chmod +x /opt/gitlab-runner/fargate
    

步骤五:创建 ECS Fargate 集群

亚马逊 ECS 集群是一组 ECS 容器实例。

  1. 访问 https://console.aws.amazon.com/ecs/home#/clusters
  2. 单击 Create Cluster
  3. 选择 Networking only 类型。单击 Next step
  4. 将其命名为 test-cluster (和 fargate.toml 中一样)。
  5. 单击 Create
  6. 单击 View cluster。从 Cluster ARN 值中记录区域和用户 ID.
  7. 单击 Update Cluster 按钮。
  8. Default capacity provider strategy 旁边,点击 Add another provider 并选择 FARGATE。单击 Update

在 ECS Fargate 上设置和使用集群的更多信息,请参考 AWS 文档

步骤六:创建 ECS 任务定义

在这一步骤中,您将通过引用您的 CI 构建所需的容器镜像,创建类型为 Fargate 的任务定义。

  1. 访问 https://console.aws.amazon.com/ecs/home#/taskDefinitions
  2. 单击 Create new Task Definition
  3. 选择 FARGATE 并单击 Next step
  4. 将其命名为 test-task。(注意:名称和 fargate.toml 文件中定义的值一样,但是没有 :1)。
  5. 选择 Task memory (GB)Task CPU (vCPU) 的值。
  6. 单击 Add container。然后:
    1. 将其命名为 ci-coordinator,因此 Fargate 驱动才能够注入 SSH_PUBLIC_KEY 环境变量。
    2. 定义镜像(例如 registry.gitlab.com/tmaczukin-test-projects/fargate-driver-debian:latest)。
    3. 为 22/TCP 定义端口映射。
    4. 单击 Add
  7. 单击 Create
  8. 单击 View task definition
caution单一 Fargate 任务可能会启动一个或多个容器。 Fargate 驱动仅在名称为 ci-coordinator 的容器中注入 SSH_PUBLIC_KEY 环境变量。 在 Fargate 驱动使用的所有任务定义中,您必须拥有同样名称的容器。 拥有这个名称的容器必须安装了上文所描述的 SSH 服务器和所有极狐GitLab Runner 要求的东西。

更多关于设置和使用任务定义的说明,请参考 AWS 文档

更多关于从 AWS ECR 启动镜像所需的 ECS 服务权限的信息,请参考 AWS 文档 Amazon ECS task execution IAM role

更多关于使 ECS 鉴权到包括托管在极狐GitLab 实例上的私有镜像库的信息,请参考 AWS 文档 Private registry authentication for tasks

至此,极狐GitLab Runner Manager 和 Fargate Driver 已经配置完成并且可以在 AWS Fargate 上执行作业。

步骤七:测试配置

您的配置现在应该可用。

  1. 在您的极狐GitLab 项目中,创建一个简单的 .gitlab-ci.yml 文件。

    test:
      script:
        - echo "It works!"
        - for i in $(seq 1 30); do echo "."; sleep 1; done
    
  2. 前往您项目的 CI/CD > 流水线
  3. 单击 运行流水线
  4. 更新分支和变量,单击 运行流水线
note忽略 gitlab-ci.yml 文件中的 imageservice 关键字。 Runner 仅使用任务定义中指定的值。

清理

如果您想在使用 AWS Fargate 测试自定义执行器后执行清理操作,移除以下对象:

  • 步骤三中创建的 EC2 实例、值对、 IAM 角色和安全组。
  • 步骤五中创建的 ECS Fargate 集群。
  • 步骤六中创建的 ECS 任务定义。

故障排查

测试配置时出现 No Container Instances were found in your cluster 错误

error="starting new Fargate task: running new task on Fargate: error starting AWS Fargate Task: InvalidParameterException: No Container Instances were found in your cluster."

AWS Fargate Driver 需要 ECS 集群配置默认的容量提供者策略

扩展阅读:

  • 默认的容量提供者策略和每个亚马逊 ECS 集群都关联。如果没有指定其他容量提供者策略或启动类型,当运行任务或创建服务时,集群会使用该默认策略。
  • 如果指定了 capacityProviderStrategylaunchType 参数必须空置。如果没指定 capacityProviderStrategylaunchType,集群会使用 defaultCapacityProviderStrategy

运行作业时出现元数据 file does not exist 错误

Application execution failed PID=xxxxx error="obtaining information about the running task: trying to access file \"/opt/gitlab-runner/metadata/<runner_token>-xxxxx.json\": file does not exist" cleanup_std=err job=xxxxx project=xx runner=<runner_token>

确保您的 IAM 角色策略配置正确并且可以执行写入操作以在 /opt/gitlab-runner/metadata/ 中创建元数据 JSON 文件。要在非生产环境中进行测试,请使用 AmazonECS_FullAccess 策略。请根据您组织的安全要求查看您的 IAM 角色策略。

运行作业时 connection timed out

Application execution failed PID=xxxx error="executing the script on the remote host: executing script on container with IP \"172.x.x.x\": connecting to server: connecting to server \"172.x.x.x:22\" as user \"root\": dial tcp 172.x.x.x:22: connect: connection timed out"

如果 EnablePublicIP 配置为 false,请确保您的 VPC 的安全组具有允许 SSH 连接的入站规则。您的 AWS Fargate 任务容器必须能够接受来自极狐GitLab Runner EC2 实例的 SSH 流量。

运行作业时 connection refused

Application execution failed PID=xxxx error="executing the script on the remote host: executing script on container with IP \"10.x.x.x\": connecting to server: connecting to server \"10.x.x.x:22\" as user \"root\": dial tcp 10.x.x.x:22: connect: connection refused"

确保任务容器已暴露端口 22,并根据步骤六:创建 ECS 任务定义中的说明配置端口映射。如果暴露了端口并且配置了容器:

  1. Amazon ECS > Clusters > Choose your task definition > Tasks 中检查容器是否有任何错误。
  2. 查看状态为 Stopped 的任务,查看最近失败的任务。如果容器出现故障,日志 选项卡会提供更多详细信息。

或者,请确保您可以在本地运行 Docker 容器。