はじめに
こんにちは。ニフティ株式会社入会システムチームのtakahataです。 GitHub ActionsからCodeBuildをAWS CLIで呼ぶ構成から、GitHub Actions self-hosted runnerでCodeBuildを呼ぶ構成とECS on Fargateを実行する構成への変更を検証した話を共有します。また、ECSを利用したself-hosted runnerの検証も実施しました。
背景
所属しているチームのWebサービスではデプロイ前のリグレッションテストとして、E2EライブラリであるPlaywrightを利用して検証環境上でブラウザテストを実行しています。
検証環境では外部からのアクセスを遮断していますが、GitHub ActionsのIPアドレスはIPアドレスリストのどのIPアドレスからアクセスされるかわからないため(参考)、GitHub Actionsが持つホストからテストを実施することができません。
そのため、現在は、GitHub Actions から AWS CLI を使ってAWS CodeBuildを実行することによって、Playwrightを実行していました。CodeBuildはVPC内で実行できるため検証環境にアクセスすることができます。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/traditiona-1.png)
課題
現状の構成ではGitHub Actionsから見るとCodeBuildの実行に成功したことしかわからず、テストに成功したかどうかはCodeBuild上でしか確認できませんでした。
ニフティではAWSをマルチアカウントで運用しているため、テストが終わるとAWSアカウントにログインしてCodeBuildからテスト結果のレポートを確認する必要があります。
また、テスト結果をGitHub側に連携することができていないため、テスト結果を元にプルリクエストのマージを許可するといった運用ができていませんでした。
解決手段
上記課題について2通りの方法で検証を実施しましたので、以降は概要と手順について説明します。
- CodeBuildのマネージド型のself-hosted runnerを利用する
- ECS on Fargateの実行環境を構築する
CodeBuildのマネージド型のself-hosted runnerを利用する
GitHub Actionsのself-hosted runnerとは、GitHubがホストしているサーバを使わず利用者が用意したサーバ上でGitHub Actionsのワークフローを実行する仕組みです。2024年4月にマネージドなself-hosted runnerの実行環境としてCodeBuildを容易に利用できるようになりました(参考)。
この対応を受け、AWS CLIでCodeBuildを実行する構成からself-hosted runnerで実行する構成に変更することを検証しました。
VPC上で動作するCodeBuildを実行環境としてGitHub Actionsのワークフローを実行することにより、GitHubのプルリクエスト等の機能と連携しながらGitHub ActionsとしてVPC内でテストを実行することができます。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/future-1.png)
対応手順
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/action-runner.html を元に設定をしました。対応手順は大きく2つあります。
①CodeBuildプロジェクトを作成する
②GitHubのワークフローを作成する
①CodeBuildプロジェクトを作成する
CodeBuildのソースに利用したいGitHub Actionsのリポジトリを指定します。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/Untitled-3.png)
ウェブフックイベントを以下のように設定します。ここでは複数のリソースは指定できずプライマリリソースとして上記で指定したGitHubのイベントを設定できるようです。
公式ドキュメントによるとGitHub Actionsで実行されるワークフローは workflow_jobのqueuedイベントによって実行されますので(参考)、フィルタグループのイベントタイプに WORKFLOW_JOB_QUEUED
を指定します。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/Untitled-1-2.png)
その他設定としてイメージファイルはPlaywrightの実行要件に指定されているUbuntuを指定しました。
それ以外にはVPC経由で実行したいためVPCやセキュリティグループを指定しました。
②GitHubのワークフローを作成する
CodeBuildのbuildspec.ymlファイルをGitHub Actions用のyamlファイルに移植しました。
self-hosted runnerの指定をするためにrunnerにCodeBuildのプロジェクト名を指定します。
1 2 3 4 5 6 7 8 9 10 11 |
name: Playwright Tests (Self-Hosted Runner) on: push: ... jobs: test: runs-on: codebuild-Codebuildのプロジェクト名-${{ github.run_id }}-${{ github.run_attempt }} ... |
実行結果
GitHubが提供するホストと特に違いなくワークフローを実行することができ、VPC内部のサーバにもアクセスすることができました。
GitHub-hosted runnerとの違いとしては、CodeBuildがself-hosted runnerの実行環境を構成するために90秒ほど待ち時間が発生していました。
CodeBuildがself-hosted runnerを構成する実行ログの様子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
Downloading GHA self-hosted runner binary % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 13 179M 13 24.9M 0 0 63.5M 0 0:00:02 --:--:-- 0:00:02 63.4M 55 179M 55 100M 0 0 73.7M 0 0:00:02 0:00:01 0:00:01 73.7M 100 179M 100 179M 0 0 80.5M 0 0:00:02 0:00:02 --:--:-- 80.6M Configuring GHA self-hosted runner -------------------------------------------------------------------------------- | ____ _ _ _ _ _ _ _ _ | | / ___(_) |_| | | |_ _| |__ / \\ ___| |_(_) ___ _ __ ___ | | | | _| | __| |_| | | | | '_ \\ / _ \\ / __| __| |/ _ \\| '_ \\/ __| | | | |_| | | |_| _ | |_| | |_) | / ___ \\ (__| |_| | (_) | | | \\__ \\ | | \\____|_|\\__|_| |_|\\__,_|_.__/ /_/ \\_\\___|\\__|_|\\___/|_| |_|___/ | | | | Self-hosted runner registration | | | -------------------------------------------------------------------------------- # Authentication √ Connected to GitHub # Runner Registration √ Runner successfully added √ Runner connection is good # Runner settings √ Settings Saved. Running GHA self-hosted runner binary √ Connected to GitHub |
GitHub ActionsがCodeBuildの実行を待っている様子
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/Untitled-2-1.png)
ECS on Fargateの実行環境を構築する
続いてECS on Fargateを実行する方法になります。こちらの方法では上記CodeBuildで指定していたWebhookの向き先となるサービスを自身で構築し、構築したサービス上でECSタスクの起動とself-hosted runnerへの登録をする必要があります。
また、常時ECSタスクを実行する必要はないため、コスト削減のために必要に応じてタスクを実行するように設定しています。
対応手順
AWSが提供するサービスを組み合わせて構築しています。手順は以下のようになります。
- ワークフロー実行に合わせてAPI GatewayにWebhookでHTTPリクエストを実行するようGitHub上で設定
- API GatewayのAWS統合でStep Functionsを実行
※Step Functionsを利用することでタスク起動失敗時に再実行できるようにしています - Lambdaを実行しWebhook検証とself-hosted runnerへの登録に必要なトークンを取得
- ECSタスクを実行
- ECSタスク上のコンテナでself-hosted runnerへの登録
- ECSタスク上のコンテナを利用してブラウザテストを実行
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/route.png)
①ワークフロー実行に合わせてAPI GatewayにWebhookでHTTPリクエストを実行するようGitHub上で設定
リポジトリのSettings→WebhooksからAdd Webhookで追加できます。
Payload URLにAPI Gatewayのエンドポイント、Content typeにapplication/json、Secretに後にWebhook検証をするためのランダムな文字列を入力します。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/Untitled-3-2.png)
トリガーにWorkflow jobsのみ選択します。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/Untitled-4.png)
②APIGatewayのAWS統合でStep Functionsを実行
Step Functionsを実行するようAPIGatewayを実行します。
ここで、統合リクエストのマッピングテンプレートを適切に設定する必要があります。ここではHTTPリクエストヘッダに含まれる「X-Hub-Signature-256」とHTTPリクエストボディを文字列にエスケープしたJSONに変換しています。「X-Hub-Signature-256」はWebhookの検証に必要です。
1 2 3 4 5 6 |
{ "stateMachineArn": "arn:aws:states:[Step FunctionsのARN]", "input" : "{\\"secret\\": \\"$input.params().get('header').get('X-Hub-Signature-256')\\", \\"body\\": $util.escapeJavaScript($input.json('$')) }" } |
Step Functionsは以下のようなフローになっています。Webhookからイベントを特定し、イベントがqueued(ワークフロー作成された際に発行されるイベント)である場合のみLambda実行に分岐し、それ以外のイベントの場合はなにもせずに終了します。
![](https://engineering.nifty.co.jp/wp-content/uploads/2024/06/Untitled-5.png)
③Lambdaを実行しWebhook検証とself-hosted runnerへの登録に必要なトークンを取得
作成したAPI Gatewayはどこからでもアクセスできてしまうため、最悪の場合大量アクセスにより大量にECSタスクが実行できてしまいます。そのため、WebhookがGitHub Actionsから呼ばれたものか検証する必要があります。
詳細についてはhttps://docs.github.com/ja/webhooks/using-webhooks/validating-webhook-deliveriesを参照ください。
API Gatewayから取得されたHTTPリクエストボディに ①で設定したSecretを利用してハッシュ値を計算し、「X-Hub-Signature-256」と一致しているか検証しています。SecretはSSM Parameter Store等で管理します。
ここで単純にLambdaのeventでHTTPリクエストボディを渡してしまいますと各Lambdaランタイム言語の辞書型に変換されてしまいますので、文字列として渡してあげる必要があります。一度辞書型になってしまうと文字列に再変換したときに検証がうまくいきませんでした。
また、self-hosted runnerに登録するには一時的なトークンを取得する必要があります。トークンを発行するにはREST APIを実行する必要があります(参考)。REST APIの実行に必要なGitHubのPersonal Access TokenもSSM Parameter Storeから取得します。
④ECSタスクを実行
ECSタスクを実行します。この際に③で取得したトークンを連携します。
⑤ECSタスク上のコンテナでself-hosted runnerへの登録
ECSタスク上でトークンを利用してself-hosted runnerへ登録を行います。
詳細については https://docs.github.com/ja/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners を参照ください。
コンテナイメージ上で依存関係のインストール、設定更新、self-hosted runnerへの登録を用意されたシェルスクリプトを使って実行します。
詰まったポイントとしては実行者をrootユーザーと一般ユーザーで実行を分ける必要があること、今後GitHub Actions上で利用するコマンドが使えるようにしておくことです。
今回のケースではgit checkoutをするためにgitのパッケージが必要でした。また、一般ユーザーからsudoする場合にパスワードが求められないようにするためNOPASSWDの設定であったり、ホームディレクトリの作成が必要であったりしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
# サンプル FROM ubuntu:24.04 RUN apt update && apt-get -y install curl sudo git RUN mkdir /actions-runner RUN useradd runner -m RUN echo 'runner ALL=NOPASSWD: ALL' >> /etc/sudoers RUN chown runner /actions-runner USER runner WORKDIR /actions-runner RUN curl -o actions-runner-linux-x64-2.317.0.tar.gz -L <https://github.com/actions/runner/releases/download/v2.317.0/actions-runner-linux-x64-2.317.0.tar.gz> RUN tar xzf ./actions-runner-linux-x64-2.317.0.tar.gz USER root RUN ./bin/installdependencies.sh USER runner COPY ./register.sh /actions-runner/ CMD [ "bash", "/actions-runner/register.sh" ] |
register.shでは以下のように設定しています。必要なオプションを指定しないとインタラクティブに入力を求められますのでコマンド内で全て指定します。
ここで「 --ephemeral
」オプションをつけることでタスク実行完了時に自動でself-hosted runnerからの登録を解除してくれるため、使い回されることなく使い捨てでコンテナを利用できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/bin/sh NOW=`date '+%Y%m%d%H%M%S'` ./config.sh \\ --url $GITHUB_URL \\ --token $TOKEN \\ --ephemeral \\ --disableupdate \\ --name runner_$NOW \\ --runnergroup Default \\ --labels self-hosted \\ --work _work ./run.sh |
⑥ECSタスク上のコンテナを利用してブラウザテストを実行
ブラウザテストの実行はCodeBuildと同じですので割愛します。
実行結果
結果についてもCodeBuild同様特に問題なく動作することができましたので割愛します。
まとめ
今回VPC内部でPlaywrightをしたいという課題に対して、「CodeBuildのマネージド型のself-hosted runnerを利用する」「ECS on Fargateの実行環境を構築する」という2通りの手段を検証しました。
これまでself-hosted runnerをVPC内で実行するためにはEC2やECS等で環境を用意する必要がありましたが、self-hosted runnerにマネージドで対応したCodeBuildを利用することで簡単に実行環境を構築することができましたので、構築の手軽さという点ではCodeBuildを使う利点があると思いました。
一方で、ECS on Fargateの実行環境を構築する手法は構築が大変であるものの、コストの点で利点があります。東京リージョンのx86アーキテクチャで比較するとCodeBuildの最も安価であるgeneral1.smallが1分あたり0.005USDであるのに対して(参考)、同等スペックの2vCPU、メモリ4GBのFargateは(0.05056×2vCPU+0.005534×4GB)/60分 = 0.002054USDと単純計算ではありますが半額程度のコストで実行できます。またFargateの場合はより低いスペックでも稼働できますのでスペックを必要としない場合はさらにコスト削減できると思います。
実際にself-hosted runnerを構築することでマネージド型のありがたみを時間しました。両手法についてぜひ試してみてください。
![](https://engineering.nifty.co.jp/wp-content/uploads/2023/08/hiring.png)
ニフティでは、
さまざまなプロダクトへ挑戦する
エンジニアを絶賛募集中です!
ご興味のある方は以下の採用サイトより
お気軽にご連絡ください!
ニフティに興味をお持ちの方は
キャリア登録をぜひお願いいたします!
connpassでニフティグループに
参加いただくと
イベントの
お知らせが届きます!