電通総研 テックブログ

電通総研が運営する技術ブログ

コスト安なCI環境を目指してオートスケールするCI環境を構築する

こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター の山下です。
今回はユーザーに合わせてオートスケールするGitHub ActionsのRunnerについて紹介しようと思います。

課題と目的

GitHub Actionsを使ってCIを実施するのは一般的になってきています。
ISIDでもGitHub Actionsを活用してCIを実施しています。 しかし、GitHub社が提供しているrunners(GitHub-hosted runners)では困る場合があります。「GitHub Actionsでオンプレミス環境のCI/CDを実行する方法」の記事では、オンプレミス環境のような外部ネットワークに接続できないような環境でGitHub Actionsを利用したCIを実施する場合に、self-hosted runnersを紹介しました。

オンプレ環境で実行できないこと以外にも、GitHub Actionsの標準環境にはいくつか不満があります。

  • CIの待ち時間が長い
  • CIに使うマシンの性能を調整したい
  • CIの費用が高い

といった問題です。

GitHub-hosted runnersでは、GitHub社が提供しているインフラに依存しています。このため、GitHub側の調子が悪いときにCI開始されるまでの待ち時間が長くなったりします。 また、CIの実行に使うマシンもGitHub社が決めたものの範囲で選ぶことになってしまいます。 以前は、選択の余地がなかったのですが、Larger Runnersがbeta版ですが公開されて状況は改善しつつあります。Larger Runnersに関する公式の記事はこちら です。

また、GitHub Actionsの料金はLinuxの場合、1分当たり$0.008 となっていて安くはないです。 単純計算すると1時間あたり $0.48 になってしまい、AWSだとt3.2xlargeを借りることが出来てしまいます。
以下のドキュメントによると、GitHub標準のrunnerの性能は、2コア、7GBメモリのマシンのようなので若干コスト高になってしまいます。
https://docs.github.com/ja/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources

self-hosted runnerを導入することで、自前の環境を構築すれば待ち時間や性能の問題は解決します。しかし、高性能なマシンをずっと起動しているとコストが高くなってしまいます。
必要な時、必要な分だけEC2を起動しGitHub Actionsのself-hosted runnersを動作できれば、コストパフォーマンスに優れるself-hosted runners環境を構築できます。

今回はその環境(Autoscale self-hosted runners)について解説します。

公式の推奨している方法について

GitHub公式の資料にオートスケールするself-hosted runnersについての記載があります。

基本的に、この手順に従って構築すれば問題ないです。公式では以下の2種類の方法が提供されています。

actions-runner-controller/actions-runner-controllerkubernetes上にself-hosted runnersを構築する物になっています。既にkubernetesを運用している場合にはこちらを導入するのが良いですが、GitHub Actionsのためだけにkubernetesを運用するのは運用負荷を考えると難しいので今回は採用しませんでした。

philips-labs/terraform-aws-github-runner は、AWSのAutoScaling Groupを利用して self-hosted runners環境を構築するものとなっています。構築はterraformを利用することになります。今回はこちらを使って環境構築を行います。

構築の手順

それでは早速構築の手順について解説します。
GitHub Appの設定と terraform の実行と両方を行っていく必要があるので順番に気を付けて実施してください。

事前準備

環境構築に際していくつか事前に準備しておく物があります。

terraformは各自インストールする必要があります。公式サイトから適宜インストールしておいてください。

terraform-aws-github-runnerのマニュアルに記載がありますが、GitHub上でGitHub Appを作成する必要があります。いくつか注意が必要です。
まず、最初に作成する際は、WebhookをActiveにしないで作成します。

今回は特定のプロジェクトで利用するような事を想定し、GitHub Appに与える権限をRepositoryレベルで設定します。以下のように、Actions、Checks、MetadataをRead-onlyにします。それに加えて、AdministrationをRead & writeとしました。

terraformの実行

terraformファイルの作成

今回は以下のような2つのtfファイルを作成して実行しました。片方がVPCを作成するもの、片方が今回のself-hosted runner用の物です。

// vpc.tf
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "3.11.2"

  name = "vpc-${local.environment}"
  cidr = "10.0.0.0/16"

  azs             = ["${local.aws_region}a", "${local.aws_region}c", "${local.aws_region}d"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_dns_hostnames    = true
  enable_nat_gateway      = true
  map_public_ip_on_launch = false
  single_nat_gateway      = true

  tags = {
    Environment = local.environment
  }
// main.tf
locals {
  version     = "v1.8.0"
  environment = "default"
  aws_region  = "ap-northeast-1"
}

provider "aws" {
  region = "ap-northeast-1"
}

variable "github_app_key_base64" {}
variable "github_app_id" {}

resource "random_id" "random" {
  byte_length = 20
}

data "aws_caller_identity" "current" {}

module "runners" {
  source  = "philips-labs/github-runner/aws"
  version = "1.8.0"

  create_service_linked_role_spot = false
  aws_region                      = local.aws_region
  vpc_id                          = module.vpc.vpc_id
  subnet_ids                      = module.vpc.private_subnets

  prefix = local.environment
  tags = {
    costcenter = "p7-ci-template"
    Project = "AutoScaleTest"
  }

  github_app = {
    key_base64     = var.github_app_key_base64
    id             = var.github_app_id
    webhook_secret = random_id.random.hex
  }

  webhook_lambda_zip                = "webhook.zip"
  runner_binaries_syncer_lambda_zip = "runner-binaries-syncer.zip"
  runners_lambda_zip                = "runners.zip"
  enable_organization_runners = false

  # ランナーのラベル
  # github actionsの設定ファイルで指定するラベルとなる
  runner_extra_labels         = "default,<CIで指定するラベル>"

  # enable access to the runners via SSM
  enable_ssm_on_runners = true
  instance_types = ["m5.large", "c5.large"]


  # override delay of events in seconds
  delay_webhook_event   = 5
  runners_maximum_count = 1

  # set up a fifo queue to remain order
  fifo_build_queue = true

  # override scaling down
  scale_down_schedule_expression = "cron(* * * * ? *)"
}

また、変数の設定ファイル(terraform.tfvars)も作成します。
以下のような内容です。秘密鍵base64に変換することに注意してください。

github_app_id = "<<github appのid>>"
github_app_key_base64 = <<EOF
<<取得したgithub appの秘密鍵をbase64エンコードしたもの>>

EOF

terraformの実行

ここまで準備すれば、あとは terraform を実行するだけです

terraform init
terraform apply

すると、AWS上に環境が構築されます。

GitHub Appにhookの設定を追加

先ほど、terraform applyをした出力に GitHub appで利用するwebhookのURLが含まれています。 terraform output -json を実行しても取得できます。

以下のような出力が得られます。

{
  "runners": {
    "sensitive": false,
    "type": [
      "object",
      {
        "lambda_syncer_name": "string"
      }
    ],
    "value": {
      "lambda_syncer_name": "default-syncer"
    }
  },
  "webhook_endpoint": {
    "sensitive": false,
    "type": "string",
    "value": "<webhookのURL>"
  },
  "webhook_secret": {
    "sensitive": true,
    "type": "string",
    "value": "<secret>"
  }
}

これらの <webhookのURL><secret> の内容が必要です。 この値を、GitHub Appのwebhookのendpointとsecretとして登録します。画面では以下のようになります。

そして、eventのサブスクライブが必要なので、Permission & eventsから workflow job にチェックを入れてください。これで設定完了です。

実際に利用する場合

GitHub Actionsを設定するyamlファイルで

runs-on: [self-hosted, default, <設定したラベル> ]

と記載しておけば、実行時に自動的にインスタンスの起動などが行われてそのインスタンス上で実行されます。

まとめ

今回は、オートスケールするGitHub self-hosted runnersの構築手順について紹介しました。 これを使ってどんどんCIを回していきたいですね。


私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 - ソリューションアーキテクト

執筆:@yamashita.tsuyoshi、レビュー:@sato.taichiShodoで執筆されました