AWS EC2上のGitLab Runnerの自動スケーリング

GitLab Runnerの最大の利点の一つは、ビルドがすぐに処理されるようにVMを自動的にスピンアップ/ダウンできることです。これは素晴らしい機能で、正しく使えば、Runnerを24時間365日使うわけではなく、費用対効果が高くてスケーラブルなソリューションが欲しいという状況で非常に役立ちます。

導入

このチュートリアルでは、AWSでGitLab Runnerを適切に設定する方法を探ります。AWSのインスタンスは、必要に応じて新しいDockerインスタンスを生成するRunnerマネージャとして機能します。これらのインスタンス上の Runner は自動的に作成されます。このガイドで説明するパラメータを使用するので、作成後に手動で設定する必要はありません。

さらに、AmazonのEC2 Spotインスタンスを利用することで、GitLab Runnerインスタンスのコストを大幅に削減しつつ、非常に強力な自動スケーリングマシンを利用することができます。

前提条件

Amazon Web Services(AWS) 、ほとんどの設定がここで行われるため、Amazon Web Servicesに精通している必要があります。

この記事の後半で設定するパラメータに慣れるために、Docker Machineamazonec2 ドライバのドキュメント をざっと読むことをお勧めします。

GitLab Runnerはネットワーク経由でGitLabインスタンスと通信する必要があります。AWSのセキュリティグループの設定やDNSの設定をするときに考えておく必要があります。

例えば、EC2リソースを公開トラフィックから分離して別のVPCに置くことで、ネットワークのセキュリティを強化することができます。あなたの環境はおそらく異なるので、あなたの状況に最適なものを検討してください。

AWSのセキュリティグループ

Docker Machineは、Dockerデーモンとの通信に必要なポート2376 と SSH22 のルールを持つデフォルトのセキュリティグループを使おうとします。Dockerに依存する代わりに、必要なルールを持つセキュリティグループを作成し、後述するようにGitLab Runnerのオプションで指定することができます。こうすることで、ネットワーク環境に応じて事前にカスタマイズすることができます。ポート237622Runner Manager インスタンスからアクセスできることを確認する必要があります。

AWSの認証情報

スケーリング(EC2)とキャッシュ更新(S3経由)の権限を持つユーザーに紐付いたAWSアクセスキーが必要です。EC2(AmazonEC2FullAccess)とS3のポリシーを持つ新しいユーザーを作成します。S3に必要な最小権限の詳細については、runners.cache.s3を参照してください。よりセキュリティを高めるために、そのユーザーのコンソールログインを無効にすることができます。GitLab Runnerの設定時にセキュリティ認証情報を使うので、タブを開いたままにしておくか、エディターにコピーペーストしておきましょう。

必要なAmazonEC2FullAccessAmazonS3FullAccess ポリシーを設定したEC2 インスタンスプロファイルを作成することもできます。このインスタンスプロファイルを Runner Manager の EC2 インスタンスにアタッチして、ジョブの実行用に新しい EC2 インスタンスをプロビジョニングできるようにします。

ランナー・マネージャー・インスタンスの準備

最初のステップは、GitLab RunnerをEC2インスタンスにインストールすることです。Ubuntu、Debian、CentOS、RHELなど、DockerとGitLab Runnerの両方がサポートしているディストリビューションを選びましょう。

ランナーマネージャーのインスタンスはジョブ自体を実行しないので、強力なマシンである必要はありません。初期設定のために、小規模なインスタンスから始めることができます。このマシンは常に稼働している必要があるため、専用のホストです。そのため、継続的なベースラインコストがかかる唯一のホストです。

前提条件をインストールします:

  1. サーバーにログイン
  2. GitLab RunnerをGitLab公式リポジトリからインストールします。
  3. Dockerをインストールします。
  4. GitLabフォークからDocker Machineをインストール(DockerはDocker Machineを廃止しました)

Runnerのインストールが完了したら、いよいよ登録です。

GitLab Runner の登録

GitLab Runnerを設定する前に、まずGitLabインスタンスと接続するために登録する必要があります:

  1. ランナートークンの取得
  2. ランナーの登録
  3. Executor type を聞かれたら、次のように入力します。docker+machine

最も重要な部分である GitLab Runner の設定に移ります。

note
インスタンス内のすべてのユーザーがオートスケールされた Runner を使えるようにするには、Runner を共有ランナーとして登録します。

ランナーの設定

Runnerが登録されたので、設定ファイルを編集し、AWSマシンドライバに必要なオプションを追加する必要があります。

まずはそれを分解してみましょう。

グローバルセクション

グローバルセクションでは、すべてのRunnerで同時に実行できるジョブの上限を定義することができます (concurrent)。これは、GitLab Runner が対応できるユーザー数やビルドにかかる時間など、あなたのニーズに大きく依存します。10 のような低い値から始めて、徐々に値を増やしたり減らしたりすることができます。

check_interval オプションは、Runner が GitLab に新しいジョブがないかチェックする頻度を秒単位で定義します。

使用例:

concurrent = 10
check_interval = 0

その他のオプションもございます。

runners セクション

[[runners]] セクションから、最も重要な部分はexecutor で、docker+machine に設定する必要があります。こ れ ら の 設 定 の ほ と ん ど は 、ラ ン ナ ー を 初 め て 登 録 す る 際 に 行 わ れ ま す 。

limit は、このランナーが起動するマシン(実行中およびアイドル状態)の最大数を設定します。詳細については、limitconcurrentIdleCount](../autoscale.md#how-concurrent-limit-and-idlecount-generate-the-upper-limit-of-running-machines)の[関係を確認してください。

使用例:

[[runners]]
  name = "gitlab-aws-autoscaler"
  url = "<URL of your GitLab instance>"
  token = "<Runner's token>"
  executor = "docker+machine"
  limit = 20

[[runners]] 以下の他のオプションもご利用いただけます。

runners.docker セクション

[runners.docker] セクションでは、.gitlab-ci.ymlで定義されていない場合に子ランナーで使用するデフォルトの Docker イメージを定義することができます。privileged = trueを使用することで、すべてのランナーがDocker で Docker を実行できるようになります。これは GitLab CI/CD を使って独自の Docker イメージをビルドする場合に便利です。

次に、disable_cache = true 、Docker Executorの内部キャッシュ機構を無効にします。次のセクションで説明する分散キャッシュモードを使用するためです。

使用例:

  [runners.docker]
    image = "alpine"
    privileged = true
    disable_cache = true

[runners.docker] 以下の他のオプションもご利用いただけます。

runners.cache セクション

ジョブを高速化するために、GitLab Runnerは選択したディレクトリやファイルを保存し、後続のジョブ間で共有するキャッシュ機構を提供します。このセットアップでは必須ではありませんが、GitLab Runnerが提供するディストリビューションキャッシュメカニズムを使うことが推奨されます。新しいインスタンスは必要に応じて作成されるので、キャッシュが保存される共通の場所を持つことが不可欠です。

以下の例では、Amazon S3を使います:

  [runners.cache]
    Type = "s3"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "s3.amazonaws.com"
      AccessKey = "<your AWS Access Key ID>"
      SecretKey = "<your AWS Secret Access Key>"
      BucketName = "<the bucket where your cache should be kept>"
      BucketLocation = "us-east-1"

ここでは、キャッシュの仕組みをさらに詳しく説明します:

runners.machine セクション

これは設定の中で最も重要な部分で、GitLab Runnerに新しいDocker Machineインスタンスをいつどのように生成し、古いDocker Machineインスタンスを削除するかを指示するものです。

ここではAWSマシンのオプションに焦点を当てます:

runners.machine セクションの例です:

  [runners.machine]
    IdleCount = 1
    IdleTime = 1800
    MaxBuilds = 10
    MachineDriver = "amazonec2"
    MachineName = "gitlab-docker-machine-%s"
    MachineOptions = [
      "amazonec2-access-key=XXXX",
      "amazonec2-secret-key=XXXX",
      "amazonec2-region=us-central-1",
      "amazonec2-vpc-id=vpc-xxxxx",
      "amazonec2-subnet-id=subnet-xxxxx",
      "amazonec2-zone=x",
      "amazonec2-use-private-address=true",
      "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true",
      "amazonec2-security-group=xxxxx",
      "amazonec2-instance-type=m4.2xlarge",
    ]
    [[runners.machine.autoscaling]]
      Periods = ["* * 9-17 * * mon-fri *"]
      IdleCount = 50
      IdleTime = 3600
      Timezone = "UTC"
    [[runners.machine.autoscaling]]
      Periods = ["* * * * * sat,sun *"]
      IdleCount = 5
      IdleTime = 60
      Timezone = "UTC"

Docker Machineドライバはamazonec2 に設定され、マシン名には標準のプレフィックスが付き、%s (必須)は子ランナーのIDに置き換えられます:gitlab-docker-machine-%s

さて、あなたのAWSインフラストラクチャに応じて、MachineOptions で設定できるオプションはたくさんあります。以下に、最も一般的なものを示します。

マシンオプション説明
amazonec2-access-key=XXXXEC2 インスタンスを作成する権限を持つユーザーの AWS アクセスキー。
amazonec2-secret-key=XXXXEC2インスタンスを作成する権限を持つユーザーのAWSシークレットキー。
amazonec2-region=eu-central-1インスタンス起動時に使用するリージョン。省略可能で、デフォルトのus-east-1 が使用されます。
amazonec2-vpc-id=vpc-xxxxxインスタンスを起動するVPC ID
amazonec2-subnet-id=subnet-xxxxAWS VPCのサブネットID。
amazonec2-zone=x指定がない場合、のアベイラビリティゾーンがa の場合、指定したサブネットと同じアベイラビリティゾーンに設定する必要があります。例えば、ゾーンがeu-west-1b の場合、次のように設定する必要があります。amazonec2-zone=b
amazonec2-use-private-address=trueDocker Machinesの非公開IPアドレスを使用しても、公開IPアドレスを作成します。内部トラフィックを維持し、余分なコストを避けるのに便利です。
amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,trueAWS エクストラタグのキーと値のペアで、AWS コンソール上でインスタンスを識別するのに便利です。Name “タグはデフォルトでマシン名に設定されています。runner-manager-name “を[[runners]] で設定したランナー名と一致するように設定し、特定のマネージャーセットアップによって作成されたすべてのEC2インスタンスをフィルタリングできるようにします。
amazonec2-security-group=xxxxセキュリティグループIDではなく、AWS VPCセキュリティグループ名。AWSセキュリティグループを参照してください。
amazonec2-instance-type=m4.2xlarge子ランナーが実行するインスタンスタイプ。
amazonec2-ssh-user=xxxxインスタンスに SSH アクセスするユーザー。
amazonec2-iam-instance-profile=xxxx_runner_machine_inst_profile_nameRunner マシンに使用する IAM インスタンスプロファイル。
amazonec2-ami=xxxx_runner_machine_ami_id特定のイメージの GitLab Runner AMI ID。
amazonec2-request-spot-instance=trueオンデマンド価格よりも安い価格で利用可能な予備のEC2容量を使用します。
amazonec2-spot-price=xxxx_runner_machine_spot_price=x.xxスポットインスタンスの入札価格(米ドル)。--amazonec2-request-spot-instance flagtrue に設定する必要があります。amazonec2-spot-price を省略すると、Docker Machineは最大価格を1時間あたりのデフォルト値$0.50 に設定します。
amazonec2-security-group-readonly=trueセキュリティグループを読み取り専用に設定します。
amazonec2-userdata=xxxx_runner_machine_userdata_pathRunner マシンuserdata のパスを指定します。
amazonec2-root-size=XXインスタンスのルート・ディスク・サイズ(GB)。

注釈

  • MachineOptions 、AWS Docker Machineドライバがサポートするものであれば何でも追加できます。Dockerのドキュメントを読むことを強くお勧めします。インフラストラクチャのセットアップによって、適用するオプションが異なる可能性があるからです。
  • amazonec2-ami を設定して別のAMI IDを選択しない限り、子インスタンスはデフォルトでUbuntu 16.04を使用します。Docker Machineにはサポートされているベースオペレーティングシステムのみを設定してください。
  • マシンオプションの1つとしてamazonec2-private-address-only=true を指定した場合、EC2インスタンスには公開IPが割り当てられません。これは、VPCがインターネットゲートウェイ(IGW) 、ルーティングが問題なく正しく設定されている場合は問題ありませんが、より複雑な構成になっている場合は考慮する必要があります。VPCの接続性についてはこちらをご覧ください。

[runners.machine] 以下の他のオプションもご利用いただけます。

全て揃うまで

/etc/gitlab-runner/config.toml の完全な例です:

concurrent = 10
check_interval = 0

[[runners]]
  name = "gitlab-aws-autoscaler"
  url = "<URL of your GitLab instance>"
  token = "<runner's token>"
  executor = "docker+machine"
  limit = 20
  [runners.docker]
    image = "alpine"
    privileged = true
    disable_cache = true
  [runners.cache]
    Type = "s3"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "s3.amazonaws.com"
      AccessKey = "<your AWS Access Key ID>"
      SecretKey = "<your AWS Secret Access Key>"
      BucketName = "<the bucket where your cache should be kept>"
      BucketLocation = "us-east-1"
  [runners.machine]
    IdleCount = 1
    IdleTime = 1800
    MaxBuilds = 100
    MachineDriver = "amazonec2"
    MachineName = "gitlab-docker-machine-%s"
    MachineOptions = [
      "amazonec2-access-key=XXXX",
      "amazonec2-secret-key=XXXX",
      "amazonec2-region=us-central-1",
      "amazonec2-vpc-id=vpc-xxxxx",
      "amazonec2-subnet-id=subnet-xxxxx",
      "amazonec2-use-private-address=true",
      "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true",
      "amazonec2-security-group=XXXX",
      "amazonec2-instance-type=m4.2xlarge",
    ]
    [[runners.machine.autoscaling]]
      Periods = ["* * 9-17 * * mon-fri *"]
      IdleCount = 50
      IdleTime = 3600
      Timezone = "UTC"
    [[runners.machine.autoscaling]]
      Periods = ["* * * * * sat,sun *"]
      IdleCount = 5
      IdleTime = 60
      Timezone = "UTC"

Amazon EC2 Spotインスタンスによるコスト削減

Amazonの説明によると

Amazon EC2スポットインスタンスでは、Amazon EC2の予備のコンピューティング容量を入札できます。スポットインスタンスは多くの場合、オンデマンド価格よりも割安で利用できるため、アプリケーションの実行コストを大幅に削減し、同じ予算でアプリケーションの計算能力とスループットを向上させ、新しいタイプのクラウドコンピューティングアプリケーションを実現できます。

上記で選択したrunners.machine オプションに加えて、/etc/gitlab-runner/config.tomlMachineOptions セクションに以下を追加します:

    MachineOptions = [
      "amazonec2-request-spot-instance=true",
      "amazonec2-spot-price=",
    ]

amazonec2-spot-priceamazonec2-spot-price空にしたこの設定では、amazonec2-spot-priceAWSはSpotインスタンスに対する入札価格を、そのインスタンスクラスのデフォルトのOn-Demand価格に設定 amazonec2-spot-priceします。amazonec2-spot-priceamazonec2-spot-price完全にamazonec2-spot-price省略 amazonec2-spot-priceすると、Docker Machineは最大価格を1時間あたり$0.50のデフォルト値に設定します。

Spotインスタンス要求をさらにカスタマイズすることができます:

    MachineOptions = [
      "amazonec2-request-spot-instance=true",
      "amazonec2-spot-price=0.03",
      "amazonec2-block-duration-minutes=60"
    ]

この設定では、Docker MachineはSpotインスタンスを使用して作成され、Spotリクエストの最大価格は1時間あたり$0.03、Spotインスタンスの持続時間は60分が上限となります。上記の0.03 は一例ですので、選択したリージョンに基づく現在の価格を必ずご確認ください。

Amazon EC2 Spotインスタンスの詳細については、以下のリンクを参照してください:

スポットインスタンスの注意点

スポットインスタンスは、未使用のリソースを利用し、インフラストラクチャのコストを最小限に抑える素晴らしい方法ですが、その意味するところを認識しておく必要があります。

Spotインスタンスの価格モデルのため、Spotインスタンス上でCIジョブを実行すると障害率が上昇する可能性があります。指定した最大Spot価格が現在のSpot価格を超えた場合、要求されたキャパシティを得ることができません。スポット価格は時間単位で改定されます。最大価格が改定後の Spot インスタンス価格を下回る既存の Spot インスタンスは、2 分以内に終了し、Spot ホスト上のジョブはすべて失敗します。

その結果、オートスケールランナーは新しいインスタンスのリクエストを続ける一方で、新しいマシンの作成に失敗します。これは最終的に60回リクエストされ、AWSはそれ以上受け付けません。そして、Spotの価格が受け入れられると、コール量の制限を超えたため、少しの間ロックアウトされます。

そのようなケースに遭遇した場合、Runner Managerマシンで以下のコマンドを使用すると、Docker Machinesの状態を見ることができます:

docker-machine ls -q --filter state=Error --format "{{.NAME}}"
note
GitLab Runnerがスポット価格の変更を優雅に処理することに関するいくつかのイシューがあり、docker-machine Docker Machineを継続的に削除しようとするレポーターがいます。GitLabはアップストリームプロジェクトで両方のケースに対するパッチを提供しています。詳しくはイシュー#2771と #2772をご覧ください。

結論

このガイドでは、AWS 上で GitLab Runner をオートスケールモードでインストール・設定する方法を学びました。

GitLab Runnerのオートスケール機能を使うことで、時間とコストの両方を節約することができます。AWSが提供するSpotインスタンスを使えばさらに節約することができますが、その意味するところを知っておく必要があります。入札額が十分高ければ、イシューはないはずです。

このチュートリアルが(大きく)影響を受けた以下のユースケースを読むことができます: