以前書いた「GitHubActionsのSelf-hosted runnerで、SpringNativeのビルド時間を短縮する」の続きです。
SpringNativeでNativeイメージ化する際のビルド時間の短縮に「Self-hosted runner」に強めのEC2インスタンスを使用してビルド時間の短縮した話を書きました。
https://qiita.com/renave/items/561904b2988ebb6f0534
今回は費用の節約のために「Self-hosted runnerを必要な時だけ起動する」ようにしたお話です。
こちら、強めのEC2インスタンスなので起動しっぱなしにすると結構お金がかかります。
使うたびに手動で起動/停止するのも大変です。
そこで、
1:GHA実行時に標準のUbuntuのrunnerを使用し、Self-hosted runnerインスタンスを起動。
2:Self-hosted runnerでSpringNativeのビルド処理を実行。
3:ワークフローが終了したらインスタンスを停止する。
4:念のため、業務終了後の夜間にも停止をスケジュール。
するようにしました。
「actions-runner/run.sh」自動起動設定
インスタンスを起動する際に、自動で「Self-hosted runner」の「actions-runner/run.sh」を実行させる必要があります。
これには「rc.local」を利用します。
rootでrc.local作成
sudo su -
sudo vi /etc/rc.local
既にインストール済みの「actions-runner/run.sh」を絶対パスで記述
rc-local.serviceで実行する場合は、rc.localファイルに「RUNNER_ALLOW_RUNASROOT="1"」を設定するのがポイントです。
そうしないとrc-local.serviceで実行できません。
#!/bin/bash
export RUNNER_ALLOW_RUNASROOT="1"
/home/****/actions-runner/run.sh &
実行権限を与えましょう
chmod +x /etc/rc.local
続いて、rc-local.serviceの自動起動設定、サービスリスタート、ステータス確認をします。
systemctl enable rc-local.service
systemctl restart rc-local.service
systemctl status rc-local.service
rc-local.serviceが以下のように正常起動していればOKです。
● rc-local.service - /etc/rc.local Compatibility
Loaded: loaded (/etc/systemd/system/rc-local.service; enabled; vendor preset: enabled)
Drop-In: /usr/lib/systemd/system/rc-local.service.d
└─debian.conf
Active: active (running) since Mon 2021-11-22 15:32:43 UTC; 12s ago
Process: 1643 ExecStart=/etc/rc.local start (code=exited, status=0/SUCCESS)
Main PID: 1644 (run.sh)
Tasks: 15 (limit: 37282)
Memory: 35.0M
CGroup: /system.slice/rc-local.service
├─1644 /bin/bash /home/***/actions-runner/run.sh
└─1648 /home/***/actions-runner/bin/Runner.Listener run
Nov 22 15:32:43 ********* systemd[1]: Starting /etc/rc.local Compatibility...
Nov 22 15:32:43 ********* systemd[1]: Started /etc/rc.local Compatibility.
Nov 22 15:32:45 ********* rc.local[1648]: √ Connected to GitHub
Nov 22 15:32:46 ********* rc.local[1648]: Current runner version: '2.284.0'
Nov 22 15:32:46 ********* rc.local[1648]: 2021-11-22 15:32:46Z: Listening for Jobs
Self-hosted runnerのEC2インスタンスを停止→起動してみましょう
EC2コンソールから停止
Githubでrunnerの確認
GithubActionsのworkflow
こんな感じの起動スクリプトをworkflowに書いて動かしてみましょう。
name: Self-hosted runner startup workflow
~中略
env:
SHR_INSTANCE_ID: '<YOUR_SELF_HOSTED_RUNNER_INSTANCE_ID>'
jobs:
startup:
name: startup
runs-on: ubuntu-20.04
timeout-minutes: 10
~中略
steps:
#AWS Credentials
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{secrets[matrix.cfg.access-key-id]}}
aws-secret-access-key: ${{secrets[matrix.cfg.secret-access-key]}}
aws-region: ${{secrets[matrix.cfg.default-region]}}
- name: startup self-hosted runner
run: |
SHR_INSTANCE_STATUS=$(aws ec2 describe-instance-status --instance-ids ${SHR_INSTANCE_ID} | jq -r .InstanceStatuses[].InstanceStatus.Status)
echo "SHR_INSTANCE_STATUS=${SHR_INSTANCE_STATUS}"
if [ -z "${SHR_INSTANCE_STATUS}" ] ; then
echo "Starting Self-Hosted runner. Please wait."
aws ec2 start-instances --instance-ids ${SHR_INSTANCE_ID}
sleep 10
for ((i=1; i<100; i++)); do
SHR_WAIT_STATUS=$(aws ec2 describe-instance-status --instance-ids ${SHR_INSTANCE_ID} | jq -r .InstanceStatuses[].InstanceStatus.Status)
echo "[Check status...${i}] ${SHR_WAIT_STATUS}"
if [ "${SHR_WAIT_STATUS}" = "ok" ] ; then
echo "Self-Hosted runner started."
break;
fi
sleep 10
done
else
echo "Self-Hosted runner already is running."
fi
exit 0
※ forで回している理由は、whileだとエラーになってしまうみたい。GHAでは禁止されているのかな?
起動後
actions-runnerのスクリプトが起動すると、Idle状態になります。
後続のビルド処理は「runs-on: self-hosted」で「needs: [startup]」のworkflowを作ればOK
build:
name: build
needs: [startup]
runs-on: self-hosted
以下、省略~
ジョブ終了後に「Self-hosted runner」を停止する
こちらもbuild後に実行させるように「shutdown」ワークフローを書きます。
shutdown:
name: shutdown
needs: [build]
runs-on: ubuntu-20.04
timeout-minutes: 10
~中略
steps:
#AWS Credentials
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{secrets[matrix.cfg.access-key-id]}}
aws-secret-access-key: ${{secrets[matrix.cfg.secret-access-key]}}
aws-region: ${{secrets[matrix.cfg.default-region]}}
- name: shutdown self-hosted runner
run: |
SHR_WAIT_STATUS=$(aws ec2 describe-instance-status --instance-ids ${SHR_INSTANCE_ID} | jq -r .InstanceStatuses[].InstanceStatus.Status)
echo "SHR_INSTANCE_STATUS=${SHR_INSTANCE_STATUS}"
if [ "${SHR_WAIT_STATUS}" = "ok" ] ; then
aws ec2 stop-instances --instance-ids ${SHR_INSTANCE_ID}
echo "Self-Hosted runner is stopping."
sleep 10
for ((i=1; i<100; i++)); do
SHR_WAIT_STATUS=$(aws ec2 describe-instance-status --instance-ids ${SHR_INSTANCE_ID} | jq -r .InstanceStatuses[].InstanceStatus.Status)
echo "[Check status...${i}] ${SHR_WAIT_STATUS}"
if [ -z "${SHR_WAIT_STATUS}" ] ; then
echo "Self-Hosted runner stopped."
break;
fi
sleep 10
done
echo "Self-Hosted runner stopped."
else
echo "Self-Hosted runner already stopped."
fi
exit 0
業務終了時間に停止させる
停止忘れが無いように、念のため業務に影響のない夜間に自動で停止をスケジュールしておきます。
自動停止には、AWSのEvent Bridgeで「Stop-Instances」のイベントを使用します。
こんな感じで設定できます。
16:00 UTC(JSTで25時)に停止するようにしてます。
この辺は運用に合わせて適宜設定するといいと思います。
公式ドキュメントも参考にしてください
https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-create-rule-schedule.html
今後の改善
「停止処理」は、ビルド処理がひととおり完了した後に実施していますが、複数人で同時にworkflowを走らせた場合などに困ることがありそうなので、この点はランナーの状態を確認したり、複数台用意して、offlineのランナーを選出して使ったり。
不都合があれば、そんな処理を入れていこうかなと思っています。
P.S.
いかがでしょうか?
Self-hosted runnerにハイスペックなマシンを使うことでCIの処理時間(今回はSpringNativeのビルド)を短くすることができますが、費用も気にした方がいいのでこういう形にしてみました。
今回の処理を入れることでビルド時間はインスタンス起動時間分遅くなりますが、お金のことなのでそこはどちらを取るか。といったところでしょうか。
なお、EC2インスタンスを停止していてもEBSボリューム代はかかるので、ご留意ください。
参考になれば幸いです。