LoginSignup
12
3

More than 1 year has passed since last update.

GitHubActionsのSelf-hosted runnerを必要な時だけ起動する

Last updated at Posted at 2021-11-24

以前書いた「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コンソールから停止

image.png

Githubでrunnerの確認

停止したので「Offline」になりました。
image.png

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では禁止されているのかな?

こんな感じで起動処理が走ります
image.png

起動済みであればこんな感じ
image.png

起動後

actions-runnerのスクリプトが起動すると、Idle状態になります。
image.png

後続のビルド処理は「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

こんな感じで、停止処理がビルド処理完了後に動きます。
image.png

停止しました。
image.png

業務終了時間に停止させる

停止忘れが無いように、念のため業務に影響のない夜間に自動で停止をスケジュールしておきます。

自動停止には、AWSのEvent Bridgeで「Stop-Instances」のイベントを使用します。
こんな感じで設定できます。

16:00 UTC(JSTで25時)に停止するようにしてます。
この辺は運用に合わせて適宜設定するといいと思います。
image.png

公式ドキュメントも参考にしてください
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ボリューム代はかかるので、ご留意ください。

参考になれば幸いです。

12
3
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
12
3