LoginSignup
2
1

More than 1 year has passed since last update.

【GitHub Actions】EC2スポットインスタンスをSelf-hosted runnerとして使うための長い道のり

Posted at

はじめに

Amazon EC2 スポットインスタンスは余っているEC2のキャパシティを割安に利用できるサービスで、公式おすすめのユースケースとしてCI/CDが挙げられています。

確かにCI/CDは断続的なタスクなのでインスタンスが中断されてもさした問題は起こりませんし、テストをたくさん回すからとにかく台数が必要だ!という状況において70%程度のコスト減が見込めるのは大変魅力ですね。

というわけで今回はこのスポットインスタンスとGitHub Actionsを連携させる方法を書いていくのですが、若干不穏なタイトルの通り設定はかなり手間です。今から始められる方は予めまとまった時間を確保しておくことをおすすめします。

やりたいこと

スポットインスタンスは契約上インスタンスが不定期に入れ替わるため、それを前提とした仕組みが必要です。
即ちやりたいことはこんな感じ。

  1. インスタンスの起動時に自身をSelf-hosted runnerとして登録するためのスクリプトを組む
  2. 上記スクリプトが設定されたスポットインスタンスをリクエストする

とっても簡単ですね。

登場するサービス / 用語

道中で様々なサービスを横断するので事前にざっくり説明しておきます。

GitHub側

GitHub Apps

GitHubのAPIをユーザーの権限で叩くのは運用上よろしくない1のでOrganizationにインストールされたアプリの権限で叩きましょうね、という用途で使います。

AWS側

AWS Secrets Manager

上記Appからトークンを貰うための秘密鍵を安全に扱うために使用します。

スポットフリート

スポットインスタンスをリクエストする方法はここに記載の通りAuto Scalingグループを作る方法とスポットフリートを作る方法の2パターンあるのですが、どっちでもいい2です。お好みで選択してください。
当記事ではスポットフリートを使用します。

事前準備

ではこれらを踏まえて事前準備をしていきましょう。

GitHub Apps

まず https://github.com/organizations/${Organizationの名前}/settings/apps/new からアプリを作ります。

  • GitHub App nameはGitHub全体で重複しないようにつける必要があるのでOrganizationの名前とかを先頭につけると怒られない
  • Homepage URLは必須項目ですがどうせ公開しないので適当に
  • Expire user authorization tokensとWebhookは使わないのでチェックを外して大丈夫

権限はかなり細かく設定できます。使う分だけ付与しましょう。
今回はOrganization permissions > Self-hosted runnersをRead and writeにします。
image.png

作成ボタンを押すと作られたアプリの設定画面に飛ぶので
image.png
App IDを控えて…
image.png
秘密鍵を作って…
image.png
Organizationにインストールしてください。
(参考画像は都合上ユーザーにインストールしています)
image.png
インストールが終わるとインストール先の設定ページに飛びます。
アドレスの末尾に書かれている数字がInstallation IDと呼ばれるもので、後々使うためこちらもメモっておきます。

Secrets Manager

次に先ほど作ったアプリの秘密鍵をSecrets Managerに登録します。

マネジメントコンソールからSecrets Managerにアクセスして新規登録していただければいいのですが、
image.png
ご覧の通りこのサービスはシークレットをkey-valueで持つんですね。
秘密鍵の中身はこのようになっているので、

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAtSXnB3rWWS4OxVvmxMffl0fi1b5TgnNScBhPo3SiRoiH26Q0
WuLIdIKNIqYl+nyhE48W+psHJIJny7nMkyP2bSM8mrTANffyfP6ZqS1i0cmx60A1
9JqWjjthQMK2FVQBSlU8Mk+1YFOaiUfgqE6x4ZDmfQVHzvQAeFtfhUMZ8nEEv/C/

...

T/rEdQKBgQCrWBhX6IsL4a9oqtbICh/BO2D2WyqALur4xbHfLn7INiEO2qNE7xiI
R/CDwUkkwPX38D2ruVoJ9rlv/VRrSz3CgwWonfmZW5ncpKlRQsglOASXCLvTw/VJ
L/8Kdio2TV7Q++LEEId25SZkuOz5prGBugNSTKGg+sJvSgm2KTwbWA==
-----END RSA PRIVATE KEY-----

このままだと改行が邪魔です。
なので一旦改行コードごとBase64でエンコードしてしまいましょう。

base64 -w0 your-app.yyyy-mm-dd.private-key.pem | クリップボードに直接入れるのが一番安心

image.png
OK。

登録が完了するとシークレットを取得するためのサンプルコードが出てくるので
image.png
シークレット名とリージョンを控えておいてください。

IAM ロール

Secrets Managerを使ったことによりEC2インスタンスがAWSのサービスにアクセスする必要が出てきました。
というわけでインスタンスにアタッチする用のIAM ロールを作っていきます。

image.png
まずはポリシーから。

image.png

image.png
今作ったポリシーを追加して…

image.png
こんな感じでいいでしょう。

以上で準備は完了です!

スクリプティング

スクリプト(ユーザーデータ)については実際のコードを見ながら解説を入れていきます。

#!/bin/bash

yum update -y
yum install jq -y
yum install git -y

# 前準備でメモした諸々
ORGANIZATION_NAME=****
APP_ID=****
INSTALLATION_ID=****
SECRET_NAME=****
SECRET_REGION=****
SECRET_KEY=****

# Secrets Managerから秘密鍵を取得
private_key=mktemp
aws secretsmanager get-secret-value --region $SECRET_REGION --secret-id $SECRET_NAME \
| jq -r "select(.Name == \"$SECRET_NAME\") | .SecretString" | jq -r ".runner" | base64 -d > $private_key

# GitHub APIを叩くためのアクセストークンを取得するためのトークン(JWT)を作成
# 参考: https://zenn.dev/gorohash/articles/e2c5f6ce50f4e6
header=$(echo -n "{\"alg\":\"RS256\",\"typ\":\"JWT\"}" | base64 -w0)
now=$(date +%s)
iat=$(($now - 60))
exp=$(($now + 600))
payload=$(echo -n "{\"iat\":$iat,\"exp\":$exp,\"iss\":$APP_ID}" | base64 -w0)
unsigned_token=$header.$payload
signed_token=$(echo -n $unsigned_token | openssl dgst -binary -sha256 -sign $private_key | base64 -w0)
jwt=$unsigned_token.$signed_token
rm $private_key

# 作成したJWTを渡してアクセストークンを取得
access_token=$(
  curl -s -X POST \
    -H "Authorization: Bearer $jwt" \
    -H "Accept: application/vnd.github.v3+json" \
    https://api.github.com/app/installations/$INSTALLATION_ID/access_tokens \
  | jq -r ".token" \
)

# 最新のランナーアプリケーションのダウンロードURLと名前を取得
runner=$(
  curl -s -X GET \
    -H "Authorization: token $access_token" \
    -H "Accept: application/vnd.github.v3+json" \
    https://api.github.com/orgs/$ORGANIZATION_NAME/actions/runners/downloads \
  | jq -r ".[] | select(.os == \"linux\" and .architecture == \"x64\")"
)
runner_url=$(echo $runner | jq -r ".download_url")
runner_name=$(echo $runner | jq -r ".filename")

# このインスタンスをSelf-hosted runnerとして登録するためのトークンを取得
registration_token=$(
  curl -s -X POST \
    -H "Authorization: token $access_token" \
    -H "Accept: application/vnd.github.v3+json" \
    https://api.github.com/orgs/$ORGANIZATION_NAME/actions/runners/registration-token \
  | jq -r ".token"
)

# ユーザーデータはrootとして実行されるが
# ランナーは一般ユーザーで立ち上げる必要がある(rootでは起動できない)ためsu
# 参考: https://zenn.dev/k_i/articles/10b16875cb9fd6
su - ec2-user <<EOF
mkdir actions-runner && cd actions-runner
curl -o $runner_name -L $runner_url
tar xzf $runner_name
./config.sh \
  --url https://github.com/$ORGANIZATION_NAME \
  --token $registration_token \
  --name $(echo ip.$(hostname -i) | sed "s/\./-/g") \
  --runnergroup Default \
  --labels ec2 \
  --work _work

sudo ./svc.sh install
sudo ./svc.sh start
EOF

参考にさせていただいた偉大なる先達の元記事はこちらです。

シェルスクリプトで GitHub App のインストールアクセストークンを取得する @gorohash
AmazonLinux2022をGitHub self-hosted runnersとして動かす @k_i

またユーザーデータに関する細かな仕様については公式ドキュメントで確認してください。

起動時に Linux インスタンスでコマンドを実行する - Linuxインスタンス用ユーザーガイド

スポットインスタンスのリクエスト

ではいよいよスポットインスタンスを立ち上げていきます。
とは言え必要な準備はだいたい済んでいるので、あとは設定するだけですね。
image.png
AMIはスクリプトがAmazon Linux 2であることを前提としているため固定
image.png
セキュリティグループは作っていなければ作っておきましょう。
SSH(22)とHTTP(80)とHTTPS(443)を開ければOKです3
image.png
IAM インスタンスプロフィールに先ほど作ったロールを指定4
image.png
ユーザーデータを#!/bin/bashから入力

あとはお好みで設定してリクエストを作成しましょう。
インスタンスが立ち上がったあとGitHubを確認してランナーが登録されていれば成功です!
image.png

おわりに(言い訳)

ひとまずランナーの登録までを自動化することができました。
本当はインスタンスの終了時にランナーの登録を解除するところまで自動化して完成なのですが、実際のところ今はスポットと言えどもインスタンスが中断する事態は稀なので…まあ…

いつか必要に駆られたらその辺りの話と、Auto Scalingの話をしたいと思います。

それではまた。

  1. 何がよろしくないのかはカヤック様のこちらの記事に詳しいです

  2. スポットフリートも後からAuto Scalingできるので

  3. GitHub Actions > 自分のランナーをホストする > self-hosted runners > 必要な環境

  4. 正しくはロールを作る際に自動で生成された同名のインスタンスプロフィール

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1