AWS Fargate上でGitLab CIを自動スケーリング

GitLabcustom executordriver forAWS Fargateは、各GitLab CIジョブを実行するためにAmazon Elastic Container Service(ECS) 上のコンテナを自動的に起動します。

このドキュメントのタスクを完了すると、ExecutorはGitLabから開始されたジョブを実行できるようになります。GitLabでコミットが行われるたびに、GitLabインスタンスは新しいジョブが利用可能であることをRunnerに通知します。Runner は、AWS ECS で設定したタスク定義に基づいて、ターゲットの ECS クラスターで新しいタスクを開始します。AWS ECSのタスク定義は任意のDockerイメージを使用するように設定できるので、AWS Fargate上で実行できるビルドの種類に柔軟性があります。

GitLab Runner Fargate Driver Architecture

このドキュメントでは、実装の最初の理解を与えるための例を示しています。AWSでは追加のセキュリティが必要です。

例えば、2つのAWSセキュリティグループが必要かもしれません:

  • 1つはGitLab RunnerをホストするEC2インスタンスで使用され、制限された外部IPレンジからのSSH接続のみを受け入れます(管理アクセス用)。
  • Fargateタスクに適用され、EC2インスタンスからのSSHトラフィックのみを許可するもの。

さらに、非公開コンテナレジストリの場合、ECSタスクはIAM権限(AWS ECRのみ)を必要とするか、ECR以外のプライベートレジストリのタスクにはプライベートレジストリ認証が必要になります。

AWSインフラのプロビジョニングとセットアップを自動化するには、CloudFormationまたはTerraformを使用できます。

caution
CI/CD ジョブは、.gitlab-ci.yml ファイル内のimage: キーワードの値ではなく、ECS タスクで定義されたイメージを使用します。この設定により、Runner Manager のインスタンスが複数になったり、ビルドコンテナが大きくなったりする可能性があります。AWSはこのイシューを認識しており、GitLabは解決を追跡しています。公式のAWS EKS ブループリントに従ってEKS クラスターを作成することを検討してください。
caution
Fargate はコンテナホストを抽象化するため、コンテナホストプロパティの設定可能性が制限されます。これは、ディスクやネットワークへの高いIOを必要とするRunnerワークロードに影響を与えます。GitLab RunnerをFargateで使用する前に、CPU、メモリ、ディスクIO、ネットワークIOの高い、または極端な計算特性を持つRunnerワークロードがFargateに適していることを確認してください。

前提条件

始める前に

  • EC2、ECS、ECRリソースを作成および設定する権限を持つAWS IAMユーザー。
  • AWS VPCとサブネット。
  • 1つ以上のAWSセキュリティグループ。

ステップ 1: AWS Fargate タスク用のコンテナイメージの準備

コンテナイメージを用意します。このイメージをレジストリにアップロードし、GitLabジョブの実行時にコンテナを作成するために使用します。

  1. イメージに CI ジョブのビルドに必要なツールが含まれていることを確認しましょう。例えば、Java プロジェクトにはJava JDK と Maven や Gradle のようなビルドツールが必要です。Node.js プロジェクトにはnodenpm が必要です。
  2. イメージに GitLab Runner があることを確認してください。GitLab Runner はアーティファクトとキャッシュを処理します。詳細については、カスタムエクゼキューターのドキュメントの実行ステージのセクションを参照してください。
  3. コンテナイメージが公開キー認証によるSSH接続を受け入れることができることを確認してください。Runner はこの接続を使用して、.gitlab-ci.yml ファイルで定義されたビルドコマンドを AWS Fargate 上のコンテナに送信します。SSHキーはFargateドライバによって自動的に管理されます。コンテナはSSH_PUBLIC_KEY 環境変数から鍵を受け入れることができなければなりません。

GitLab Runner と SSH 設定を含むDebian の例をご覧ください。Node.js の例をご覧ください。

ステップ 2: コンテナイメージをレジストリにプッシュします。

イメージを作成したら、ECSタスク定義で使用するコンテナレジストリにイメージを公開します。

  • リポジトリを作成してイメージをECRにプッシュするには、Amazon ECR Repositoriesのドキュメントに従ってください。
  • AWS CLI を使用して ECR にイメージをプッシュするには、Getting Started with Amazon ECR using the AWS CLIドキュメントに従ってください。
  • GitLab コンテナレジストリを使用するには、DebianまたはNodeJSのサンプルを使用します。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に公開されます。

ステップ3: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. 公開IPの自動割り当て]を[有効]に設定します。
  7. IAMロール]で[新しいIAMロールの作成]をクリックします。このロールはテスト目的であり、セキュリティではありません。
    1. ロールの作成]をクリックします。
    2. AWSサービスを選択し、[Common use cases]で[EC2]をクリックします。次にNextをクリックします:権限]をクリックします。
    3. AmazonECS_FullAccessポリシーのチェックボックスを選択します。次へ」をクリックします:タグをクリックします。
    4. 次へ]をクリックします:レビュアー
    5. IAMロールの名前(例:fargate-test-instance )を入力し、「Create role(ロールの作成)」をクリックします。
  8. インスタンスを作成しているブラウザ・タブに戻ります。
  9. Create new IAM roleの左にある、refreshボタンをクリックします。fargate-test-instance ロールを選択します。Next]をクリックします:Add Storageをクリックします。
  10. Nextをクリックします:タグの追加。
  11. 次へ]をクリックします:セキュリティグループを設定します。
  12. 新しいセキュリティグループの作成]を選択し、名前を「fargate-test 」とし、SSHのルールが定義されていることを確認します(Type: SSH, Protocol: TCP, Port Range: 22 )。受信および送信ルールのIP範囲を指定する必要があります。
  13. レビュアーと起動]をクリックします。
  14. 起動をクリックします。
  15. オプション。Create a new key pair(新しいキーペアを作成する)」を選択し、名前を「fargate-runner-manager 」とし、「Download Key Pair(キーペアをダウンロードする)」ボタンをクリックします。SSH用の秘密鍵がコンピュータにダウンロードされます(ブラウザで設定されているディレクトリを確認してください)。
  16. Launch Instances]をクリックします。
  17. View Instances]をクリックします。
  18. インスタンスが立ち上がるのを待ちます。IPv4 Public IP アドレスをメモします。

ステップ4:EC2インスタンスにGitLab Runnerをインストールして設定します。

UbuntuインスタンスにGitLab Runnerをインストールします。

  1. GitLabプロジェクトのSettings > CI/CDに移動し、Runnerセクションを展開します。Set up a specific Runner manuallyの下にある登録トークンに注意してください。
  2. chmod 400 path/to/downloaded/key/file を実行して、鍵ファイルの権限が正しいことを確認します。
  3. で作成したEC2インスタンスにSSHでログインします:

    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. 手順 1 で指定した 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
    
    • TaskDefinition の名前と同様に、Cluster の値にも注意してください。 この例では、リビジョン番号として:1 を指定したtest-task を示しています。リビジョン番号が指定されていない場合、最新のアクティビティリビジョンが使用されます。
    • 地域を選択してください。Runner ManagerインスタンスからSubnet の値を取得します。
    • セキュリティグループIDを検索します:

      1. AWSのインスタンス一覧で、作成したEC2インスタンスを選択します。詳細が表示されます。
      2. セキュリティグループ]で、作成したグループ名をクリックします。
      3. セキュリティグループIDをコピーします。

      本番環境では、セキュリティグループの設定と使用に関するAWS のガイドラインに従ってください。

    • EnablePublicIP を true に設定すると、タスクコンテナの公開 IP を収集して SSH 接続を行います。
    • EnablePublicIP が false に設定されている場合:
      • Fargate ドライバはタスクコンテナの非公開 IP を使用します。false に設定されている場合に接続を設定するには、VPC のセキュリティグループに Port 22(SSH)のインバウンドルールが必要です。
      • 外部の依存関係を取得するには、プロビジョニングされた AWS Fargate コンテナが公開インターネットにアクセスできる必要があります。AWS Fargateコンテナに公開インターネットアクセスを提供するには、VPCでNAT Gatewayを使用できます。
    • SSHサーバーのポート番号は省略可能です。省略した場合、デフォルトの SSH ポート(22)が使用されます。
    • セクション設定の詳細については、Fargateドライバのドキュメントを参照してください。
  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
    

ステップ5:ECS Fargateクラスターの作成

Amazon ECSクラスタは、ECSコンテナインスタンスのグループです。

  1. https://console.aws.amazon.com/ecs/home#/clustersにアクセスしてください。
  2. クラスターの作成]をクリックします。
  3. Networking onlytype]を選択します。次のステップ]をクリックします。
  4. 名前をtest-cluster にします(fargate.toml と同じ)。
  5. 作成」をクリックします。
  6. クラスターの表示]をクリックします。Cluster ARN の値から、リージョンとアカウントIDの部分に注意してください。
  7. クラスターの更新]ボタンをクリックします。
  8. Default capacity provider strategy の横にある [Add another provider] をクリックし、FARGATE を選択します。更新]をクリックします。

ECS Fargate上のクラスターのセットアップと作業に関する詳細な手順については、AWSのドキュメントを参照してください。

ステップ 6: ECSタスク定義の作成

このステップでは、CIビルドに使用するコンテナイメージを参照して、Fargate タイプのタスク定義を作成します。

  1. https://console.aws.amazon.com/ecs/home#/taskDefinitionsにアクセスしてください。
  2. 新しいタスク定義の作成」をクリックします。
  3. FARGATE]を選択し、[次のステップ]をクリックします。
  4. 名前はtest-task とします。(注:この名前はfargate.toml ファイルで定義された値と同じですが、:1 は含まれません)。
  5. タスク・メモリ(GB))と タスクCPU(vCPU)の値を選択します。
  6. コンテナの追加]をクリックします。次に
    1. Fargateドライバが環境変数SSH_PUBLIC_KEY をインジェクトできるように、ci-coordinator という名前を付けます。
    2. イメージを定義します(例えばregistry.gitlab.com/tmaczukin-test-projects/fargate-driver-debian:latest )。
    3. 22/TCPのポートマッピングを定義します。
    4. Addをクリックします。
  7. 作成」をクリックします。
  8. タスク定義の表示]をクリックします。
caution
1つのFargateタスクで1つ以上のコンテナを起動できます。Fargate ドライバは、ci-coordinator の名前を持つコンテナのみにSSH_PUBLIC_KEY 環境変数を注入します。Fargate ドライバが使用するすべてのタスク定義に、この名前を持つコンテナが必要です。この名前のコンテナは、前述のように SSH サーバーと GitLab Runner のすべての要件がインストールされているものでなければなりません。

タスク定義の設定と操作の詳細については、AWS のドキュメントを参照してください。

AWS ECR からイメージを起動するために必要な ECS サービスの権限については、AWS ドキュメントAmazon ECS タスク実行 IAM ロールを参照してください。

GitLab インスタンス上でホストされているものを含む非公開レジストリに対して ECS が認証を行うための情報については、AWS documentationPrivate registry authentication for tasksを参照してください。

この時点で Runner Manager と Fargate Driver は設定され、AWS Fargate 上でジョブの実行を開始する準備が整いました。

ステップ 7: 設定のテスト

これで設定が完了しました。

  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でカスタムエグゼキュータをテストした後にクリーンアップを行いたい場合は、以下のオブジェクトを削除します:

  • ステップ3で作成したEC2インスタンス、キーペア、IAMロール、セキュリティグループ。
  • ステップ5で作成したECS Fargateクラスター。
  • ステップ6で作成したECSタスク定義。

AWS Fargateタスクの非公開設定

高いセキュリティを確保するために、AWS Fargateの非公開タスクを設定します。この設定では、ExecutorはAWS内部のIPアドレスのみを使用し、AWSからのアウトバウンドトラフィックのみを許可するため、CIジョブは非公開のAWS Fargateインスタンス上で実行されます。

プライベートAWS Fargateタスクを設定するには、以下の手順でAWSを設定し、プライベートサブネットでAWS Fargateタスクを実行します:

  1. 既存の公開サブネットが VPC アドレス範囲のすべての IP アドレスを予約していないことを確認します。VPCとサブネットのCIRDアドレス範囲を検査します。サブネットのCIRDアドレス範囲がVPCのCIRDアドレス範囲のサブセットである場合は、手順2と4をスキップします。そうでない場合は、VPCに空きアドレス範囲がないため、VPCと公開サブネットを削除して再作成する必要があります:
    1. 既存のサブネットとVPCを削除します。
    2. 削除したVPCと同じ設定のVPCを作成し、CIRDアドレスを更新します(例:10.0.0.0/23)。
    3. 削除したサブネットと同じ設定で公開サブネットを作成します。VPCのアドレス範囲のサブセットであるCIRDアドレスを使用します(例:10.0.0.0/24)。
  2. 公開サブネットと同じ設定の非公開サブネットを作成します。公開サブネット範囲と重複しないCIRDアドレス範囲を使用します(例:10.0.1.0/24)。
  3. NATゲートウェイを作成し、公開サブネット内に配置します。
  4. 宛先0.0.0.0/0 がNATゲートウェイを指すように、非公開サブネットのルーティングテーブルを変更します。
  5. farget.toml 設定を更新します:

    Subnet = "private-subnet-id"
    EnablePublicIP = false
    UsePublicIP = false
    
  6. 以下のインラインポリシーをfargateタスクに関連するIAMロールに追加します(fargateタスクに関連するIAMロールは通常ecsTaskExecutionRole という名前で、すでに存在しているはずです)。

    {
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": [
                    "secretsmanager:GetSecretValue",
                    "kms:Decrypt",
                    "ssm:GetParameters"
                ],
                "Resource": [
                    "arn:aws:secretsmanager:*:<account-id>:secret:*",
                    "arn:aws:kms:*:<account-id>:key/*"
                ]
            }
        ]
    }
    
  7. セキュリティグループの “inbound rules “を、セキュリティグループ自身を参照するように変更します。AWSの設定ダイアログで:
    • Typesshに設定します。
    • SourceCustomに設定します。
    • セキュリティグループを選択します。
    • 任意のホストからの SSH アクセスを許可する既存の受信ルールを削除します。
caution
既存の受信ルールを削除すると、SSHを使用してAmazon Elastic Compute Cloudインスタンスに接続できなくなります。

詳細は以下のAWSドキュメントを参照してください:

トラブルシューティング

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クラスタがデフォルトのキャパシティ・プロバイダ・ストラテジで設定されている必要があります。

さらに読む

  • デフォルトのキャパシティ・プロバイダー戦略は、各Amazon ECSクラスターに関連付けられています。他のキャパシティプロバイダー戦略または起動タイプが指定されていない場合、タスクの実行またはサービスの作成時にクラスターはこの戦略を使用します。
  • capacityProviderStrategy が指定されている場合、launchType パラメータは省略する必要があります。capacityProviderStrategy またはlaunchType が指定されていない場合、クラスターの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"

ステップ6:ECSタスク定義の作成」の説明に基づいて、タスクコンテナのポート22が公開され、ポートマッピングが設定されていることを確認します。ポートが公開され、コンテナが設定されている場合:

  1. Amazon ECS > クラスター > タスク定義の選択 > タスクで、コンテナにエラーがないか確認します。
  2. ステータスがStopped のタスクを表示し、失敗した最新のものを確認します。コンテナに障害が発生した場合、ログタブに詳細が表示されます。

あるいは、Dockerコンテナをローカルで実行できることを確認してください。