GeekFactory

int128.hatenablog.com

Terraform Moduleを用いたAWSにおけるGitLab Runnerの運用

GitLabにはCI/CDの機能が統合されています.GitLab CI/CDではGitLab本体とは別のGitLab Runnerと呼ばれるノードでビルドを実行します.GitLab RunnerはJenkinsでいうJenkins Agentと同様に,普通のPCで実行したり,クラウドでDockerコンテナとして実行したりできます.ドキュメントによるとGitLab Runnerの実行方法は以下が用意されています.

https://docs.gitlab.com/runner/#selecting-the-executor

Jenkins Agentと同様にGitLab Runnerは手軽にインスタンスを構築できる反面,スケールアウトやアップデートなどを考えておかないと運用が泥沼化しがちです.

本項では,GitHubで公開されているTerraform Moduleを利用してAWSでGitLab Runnerを運用する方法を紹介します.このモジュールを利用すると以下のメリットがあります.

  • AWSコンソールやSSHによる手動構築が不要になる
  • EC2インスタンスのメンテナンスが不要になる(Immutableな構成になっている)
  • ビルドキューに応じて実行ノードをスケールアウトできる

ただし,GitLabが公式にサポートしているモジュールではないので,今後も継続的にメンテナンスされていくかは分かりません.OSSなので積極的にcontributeしていきましょう.

terraform-aws-gitlab-runner

https://github.com/npalm/terraform-aws-gitlab-runner はGitLab Runnerに必要なリソースをプロビジョニングしてくれるモジュールです.具体的には以下のリソースが作成されます.

  • Auto Scaling Group(GitLab Runnerの実行用)
  • Launch Configuration(GitLab Runnerの実行用.GitLab Runnerのインストールスクリプトや設定ファイルが含まれる)
  • S3 Bucket(ビルドキャッシュ用)
  • CloudWatch Logs(EC2インスタンスのログ)
  • 上記で必要なSecurity GroupやIAM Roleなど

このモジュールはGitLab Runnerのauto scaling modeを利用しており,ビルドキューに応じてBuild RunnerのEC2インスタンスを作成および削除する仕組みになっています.具体的には,以下の2種類のEC2インスタンスが作成されます.

  • GitLab Runner(Build Runnerを作成するためのノード.Auto Scaling Groupがライフサイクルを管理する)
  • Build Runner(実際にビルドを実行するノード.GitLab Runnerがライフサイクルを管理する)

GitLab Runnerのauto scaling modeは内部でDocker Machineを利用しており,Docker Machineが新しいBuild Runnerを作成します.

このモジュールはGitLabオフィシャルの Autoscale GitLab CI/CD runners and save 90% on EC2 costs で紹介されている構成をベースにしているとのことですが,このモジュールではAuto Scaling Groupを利用してGitLab Runner自体もスポットインスタンスにすることでさらなる費用削減を実現しています.例えば,us-west-2リージョンで以下のインスタンスタイプを利用した場合,8〜9割引になります.(私の大好きな激安インスタンスタイプです)

  • GitLab Runner t3.micro $0.0104/h → $0.0031/h (80% OFF)
  • Build Runner m3.medium $0.0670/h → $0.0067/h (90% OFF)

今のところSpot Fleetには対応していないようですが,今後に期待です.(詳しくはIssue#77を参照)

使ってみよう

https://github.com/npalm/terraform-aws-gitlab-runner/tree/develop/examples にサンプルが用意されています.本稿では,パブリックサブネットにGitLab Runnerを配置する例を示します.

AWSリソースの定義

まず,新しいVPCとサブネットを定義します.IPアドレスブロックや名前は適当で構いません.

resource "aws_vpc" "main" {
  cidr_block = "172.21.0.0/16"
}

resource "aws_internet_gateway" "public" {
  vpc_id = "${aws_vpc.main.id}"
}

resource "aws_route_table" "public" {
  vpc_id = "${aws_vpc.main.id}"
}

resource "aws_route_table_association" "public" {
  route_table_id = "${aws_route_table.public.id}"
  subnet_id      = "${aws_subnet.public.id}"
}

resource "aws_route" "public_internet_gateway" {
  route_table_id         = "${aws_route_table.public.id}"
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = "${aws_internet_gateway.public.id}"
}

resource "aws_subnet" "public" {
  vpc_id            = "${aws_vpc.main.id}"
  cidr_block        = "172.21.0.0/20"
  availability_zone = "us-west-2a"
}

次に,terraform-aws-gitlab-runnerモジュールを読み込みます.ここでは https://gitlab.com にGitLab Runnerを登録しますが,独自にホスティングしているGitLabに対しても同様の方法で登録できます.

data "local_file" "public_ssh_key" {
  filename = ".sshkey.pub"
}

variable "gitlab_runner_registration_token" {
  type        = "string"
  description = "Registration token for GitLab Runners"
}

module "gitlab_runner" {
  source  = "npalm/gitlab-runner/aws"
  version = "3.5.0"

  aws_region                  = "us-west-2"
  environment                 = "gitlab_runner"
  ssh_public_key              = "${data.local_file.public_ssh_key.content}"
  vpc_id                      = "${aws_vpc.main.id}"
  subnet_ids_gitlab_runner    = ["${aws_subnet.public.id}"]
  subnet_id_runners           = "${aws_subnet.public.id}"
  aws_zone                    = "a"
  runners_use_private_address = false

  # GitLab Runnerインスタンスの設定
  instance_type              = "t3.micro"
  runner_instance_spot_price = "0.01"

  # Build Runnerインスタンスの設定
  docker_machine_instance_type  = "m3.medium"
  docker_machine_spot_price_bid = "0.02"

  runners_name                = "aws.m3"
  runners_gitlab_url          = "https://gitlab.com"

  # GitLab本体にGitLab Runnerを登録する際の設定
  # https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner を参照
  gitlab_runner_registration_config = {
    registration_token = "${var.gitlab_runner_registration_token}"
    description        = "GitLab Runner on AWS"
    tag_list           = "aws"  # GitLab Runnerのタグ
    locked_to_project  = "true"
    run_untagged       = "false"
    maximum_timeout    = "3600"
  }
}

AWSリソースのプロビジョニングとGitLabへの登録

https://gitlab.com を開き,新しいプロジェクトを作成します.プロジェクトのCI/CD設定を開き,Registration Tokenを取得します.

f:id:int128:20190620174128p:plain

Terraformを実行します.ここでは環境変数でRegistration Tokenを指定します.

export TF_VAR_gitlab_runner_registration_token=dvc3717mx7RksPZCbe7a
terraform init
terraform apply

GitLab Runnerの登録に成功すると,以下のようにプロジェクトのCI/CD設定にRunnerが現れます.

f:id:int128:20190620174708p:plain

ビルドの実行

プロジェクトに .gitlab-ci.yml を作成します.このとき以下のように tags にGitLab Runnerのタグ名を指定します.先ほどTerraformの定義で aws というタグを付けたので,ここでも aws というタグを指定しています.

image: golang:1.12
stages:
  - test
format:
  stage: test
  script:
    - go test -race ./...
  tags:
    - aws

コミットをpushするとビルドが実行されます.AWSコンソールを確認すると,以下のようにBuild Runnerのインスタンスが作成されているはずです.

f:id:int128:20190620175729p:plain

Build Runnerライフサイクルの変更

デフォルトでは,ビルドが完了してから10分間が経過するとBuild Runnerのインスタンスが削除されます.また,Build Runnerのインスタンス数の上限は設定されていません.

ここでは,ビルドが完了してから1分後にインスタンスが削除されるように変更します.また,インスタンス数の上限も2つに変更します.

module "gitlab_runner" {
  # 以下を追記
  runners_idle_time = 60
  runners_limit     = 2
}

AWSリソースの変更を反映します.

terraform apply

上記のコマンドを実行するとLaunch Configurationが変更されます.すでにGitLab Runnerが起動している場合は,既存のインスタンスを削除すると新しい設定でインスタンスが起動されます.

プロジェクトを開き,連続してビルドを実行してみましょう.同時実行数が2つに制限されているはずです.

f:id:int128:20190620184604p:plain

また,1分間が経過するとBuild Runnerのインスタンスが削除されているはずです.

auto scaling modeの仕組みや設定は https://docs.gitlab.com/runner/configuration/autoscale.html で詳しく説明されているので,ぜひ参照してください.

まとめ

https://github.com/npalm/terraform-aws-gitlab-runner を利用すると,AWSでGitLab Runnerを簡単に運用できます.さらに,スポットインスタンスを利用すると大幅な費用削減を実現できます.