Mobile Factory Tech Blog

技術好きな方へ!モバイルファクトリーのエンジニアたちが楽しい技術話をお届けします!

GitHub ActionsのSelf-hosted Runnerで複数設定のRunnerを使う

駅メモ!開発基盤チームの id:xztaityozx です!今回は CI/CD のお話です。

現在、駅メモ!チームでは Jenkins を使った CI/CD が構築されています。今回ここに GitHub Actions を加えることとなりました。チームでは段階的に GitHub Actions に移行していく計画です。 GitHub Actions を採用した理由としては、技術スタックの変化による需要の増加と Jenkins で抱えていた問題を解決するためという 2 点が主です。この記事では後者について書こうと思います。

現在の Jenkins 構成で困っていたこと

現在の Jenkins 構成では、起動できるサーバのスペックを柔軟に切り替えたり、追加できるようにしたいというリクエストに答えづらい状態でした。仕組み上の制約などから、追加の作業はそこそこ時間のかかる作業となっていたからです。

以前 CI でカバレッジ計測をしたいというリクエストがありましたが、提供まで 3 ヶ月の時間を要しました。
他のタスクに対応しつつだったので、かかりっきりというわけではなかったのですが、カバレッジ計測に合った別パイプラインの追加などが必要で、その作業すべてが手作業だったため、時間を取られてしまい提供が遅れてしまったのです。

このように、新しいワークフローを追加する際に、気軽さと柔軟性の欠如が問題となっていました。似たような事例でも直列に繋いだり、git hook 使用して対応したりといった対策が必要だった状況です。

もっと気軽に、自由に自動チェックやテストを追加したいですね…。

philips-labs/terraform-aws-github-runnerの Multi runner モジュールを使っていろんな設定の Runner を使えるようにする

この問題に対する解決策として、GitHub Actions と philips-labs/terraform-aws-github-runnerMulti runnerモジュールを活用する方法を紹介します。philips-labs/terraform-aws-github-runner はオートスケールする GitHub Actions の Self-Hosted Runner を AWS 上に構築してくれる Terraform モジュールです。id:Eadaeda が以前にもこのテックブログで紹介していますので、興味があれば参照してみてください。

tech.mobilefactory.jp

さて、このMulti runnerモジュールの README には以下のように書かれています。

This module replaces the top-level module to make it easy to create with one deployment multiple type of runners.

This module creates many runners with a single GitHub app. The module utilizes the internal modules and deploys parts of the stack for each runner defined.

どうやらこのモジュール、1 つの GitHub App に様々な設定の Runner を複数個構築できるというもののようです。コミット履歴やプルリクエストを辿ってみると、実装のきっかけになった議論を見つけることができました。

github.com

ざっくりとした内容は runs-on に与えた値で起動するインスタンスのインスタンスタイプや OS を制御できると良いよねということです。これの具体的な実現方法としては、単純に Runner の仕組みを複数個作るというもので、図にすると以下のような感じでしょうか。

multi-runner

Multi runner モジュールを使ってみる

早速試してみましょう。今回の場合はt3.smallt3a.smallを起動できるsmallという Runner の設定がほしいというリクエストが来たとして、Multi runner モジュールで Runner を作ってみます!

module "multi_runner" {
  source = "philips-labs/github-runner/aws//modules/multi-runner"
  # 実装当時のLatest
  version = "3.6.0"

  prefix = "hoge-multi-runner"

  aws_region = data.aws_region.current.name
  vpc_id     = data.aws_vpc.vpc.id
  subnet_ids = data.aws_subnets.subnets.ids

  github_app = {
    id             = "ここにAppID"
    key_base64     = "ここに秘密鍵"
    webhook_secret = random_id.random.hex
  }

  webhook_lambda_zip                = "./download-lambda/webhook.zip"
  runners_lambda_zip                = "./download-lambda/runners.zip"
  runner_binaries_syncer_lambda_zip = "./download-lambda/runner-binaries-syncer.zip"

  multi_runner_config = {
    "small" = {
      # t3.smallかt3a.smallなRunner
      matcherConfig = {
        # このRunnerを呼び出すのに使う runs-on の値
        labelMatchers = [["self-hosted", "linux", "x64", "ubuntu-20.04", "small"]]
        exactMatch    = true
      }
      runner_extra_labels = "ubuntu-20.04,small"
      runner_name_prefix  = "linux-x64-small_"

      # エファメラルランナーの場合は0でよい
      # https://github.com/philips-labs/terraform-aws-github-runner#ephemeral-runners
      delay_webhook_event = 0

      runner_config = {
        runner_os           = "linux"
        runner_architecture = "x64"

        # エファメラルランナー設定を有効にしてRunnerは1つのJobを実行したら終了するように
        enable_ephemeral_runners = true

        ami_filter = {
            name  = ["hoge-github-runner-*"]
            state = ["available"]
        }
        ami_owners = [data.aws_caller_identity.current.account_id]

        # このRunner設定はt3.smallかt3a.smallを起動する!
        instance_types = ["t3.small", "t3a.small"]

        # 起動速度を考えて事前ビルドしたAMIから上げることにしたので、Syncerでactions/runnerのバイナリをSyncする機能はOFFにした
        enable_runner_binaries_syncer = false
      }
    }
  }
}

あとはterraform planの出力を読んでterraform apply、GitHub App 側の設定を行って終了です。更に Runner の設定を増やしたい場合は、"small"と同じように増やしていけば良いだけです!簡単!

module "multi_runner" {
  source = "philips-labs/github-runner/aws//modules/multi-runner"
  version = "3.6.0"

  ...

  multi_runner_config = {
    "small"  = { ... }
    "medium" = { ... }
    "large"  = { ... }
  }

  ...

}

Perl::Critic を動かしてみる

テストの一部として組み込まれている Perl::Critic による静的解析を GitHub Actions で動かしてみます。smallぐらいのスペックがあれば十分なので、最初の移行としてぴったりな題材です。

# https://json.schemastore.org/github-workflow.json
name: Perl Critic

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths:
      - "**.pl"
      - "lib/**.pm"
      - "t/**.t"
      - "t/**.pm"
      - ".github/workflows/critic.yaml"

concurrency:
  group: critic-${{github.ref}}
  cancel-in-progress: true

jobs:
  critic:
    # 動かすRunnerの指定をする部分
    runs-on: [self-hosted, linux, x64, ubuntu-20.04, small]
    steps:
      - uses: actions/checkout@v3
      - name: Get delta
        id: changed-files
        run: |
          echo diffs="$(gh pr diff --name-only ${{ github.event.number }} | grep -Pe '^(.*\.(pl|pm)|t/.*\.t)$' | tr '\n' ' ')" >> $GITHUB_OUTPUT
        env:
          # 認証に secrets.GITHUB_TOKEN が使えるのが本当に便利
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Run Check
        if: ${{ steps.changed-files.outputs.diffs != '' }}
        run: |
          /usr/local/bin/perlcritic --profile config/dev/perlcriticrc \
            ${{steps.changed-files.outputs.diffs}}

Perl ファイルの差分だけを検査するような簡単なワークフローです。ポイントは runs-onに Runner のlabelMatchers設定の値が書かれているところです。

注意点として、runs-onのラベルは部分一致するもののうちのどれかが選ばれるという GitHub の仕様があります。例えば以下のように複数の Runner の設定があるとき

  • [self-hosted, linux, x64, ubuntu-20.04, small]
  • [self-hosted, linux, x64, ubuntu-20.04, large]
  • [self-hosted, linux, x64, ubuntu-20.04, xlarge]

runs-on[self-hosted, linux, x64, ubuntu-20.04]と設定するだけではsmall,large,xlargeのどれにもマッチするため、どの Runner がジョブを拾うかはわからないのです。このことはMulti runnerモジュールの README にも書いてあるので、詳しくはそちらをお読みください。 すこしruns-onを間違えただけで意図しないコスト増加がありえるので、ワークフローのコードレビューでは必ず見ておきたい項目になりそうです。

まとめ

  • Self-Hosted Runner な GitHub Actions を構築することになった
  • 起動できる Runner のスペックを柔軟に切り替えたり、追加できるようにしたかった
  • philips-labs/terraform-aws-github-runnerの Multi runner できそうだったのでやってみた
  • うまく動いたのでよかった

今後、利用が活発になると見える問題点や、JIT Runnerも試してみたいなと思っているので、また知見が溜まったらこのブログで記事を書きたいと思います。以上です。