GitHub Actionsのセルフホステッドランナーを使う時のノウハウ

## はじめに

[KLab Engineer Advent Calendar 2022](https://qiita.com/advent-calendar/2022/klab) 11日目の記事になります.

GitHub actionsのセルフホステッドランナーの運用に関するノウハウ集です。
オンプレとクラウドを混ぜて利用していますが、ややオンプレ寄りのノウハウが多めです。
主にGitHub Enterprise Server 3.3を利用して溜めた知見を元にしています。
プランによっては異なる箇所があるかもしれない点と、3.3はサポート終了が近い点にご注意ください。

:warning: この記事で出てくる `ラベル` は `セルフホステッドランナーに付与するラベル` を指します。PullRequestに付けるラベルではありません。

## セルフホステッドランナーを構築する時のノウハウ

### organizationでランナーを構築して複数のリポジトリで共有する

ランナーを追加できる場所は3種類。repository、organization、enterpriseがあります。
複数のリポジトリ向けにそれぞれランナーを作ると、保守コストが上がるのはもちろん、消費メモリも多くなります。
環境にもよると思いますが、ランナー1つで待機中は250MB程、実行中は500MB程のメモリを使用していました。

もちろん1リポジトリで完結するプロジェクト専用であればrepositoryでの作成で十分です。

### ランナー名にはマシン名を含めておく

セルフホステッドランナーを構築するマシンが複数あると、どのマシンにあるランナーなのか確認したいケースが多々あります。

* エラー発生時の原因調査
* マシンが落ちた時の状態確認
* マシンのメンテのために止める必要があるランナーの特定
* etc...

ランナー名からマシン名が分かると便利です。

### ランナーをサービス化しておく

ランナーのセットアップ手順はGitHubが提示してくれますが、提示された手順ではrun.shを使っているためサービス化してくれません。
ソフトウェア更新等でマシンを再起動したい時に起動しなおすのは手間なので、サービス化しておくと良いです。

`run.sh` の代わりに `sudo svc.sh install` を実行すればサービス化できます。

停止などの方法は[サービス化の公式Docs](https://docs.github.com/ja/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service)をご確認ください。

なおrun.shを使う場合、少しだがログを確認できるというメリットがあります。
しかしログの内容はかなり限られているので、サービス化してしまって良いと思います。

### PATHを通すときは `.path` に記載する

マシン自体の環境変数を更新してPATHを通してもランナーには反映されません。
ランナーのフォルダの中を見ると `.path` というファイルがあり、セットアップ時にPATHを保存しておいて使う仕組みのようです。
`.path` ファイルを更新してランナーを再起動することで反映されます。

## ランナーのラベル運用ノウハウ

### インストールソフトウェア名のラベルを使って部分メンテしやすくする

ランナーのラベルはセットアップ時だけでなく、Settings画面からも簡単につけ外し可能です。
インストールされた全てのソフトウェア名を網羅する必要はないですが、実行したいCIのコアとなるソフトウェア名は登録しておくと、特定の機能を使うジョブ以外だけを実行可能にできます。

例えばUnityをインストールしたマシンには `unity` をつけて、Xcodeをインストールしたマシンには `xcode` をつけておきます。

ワークフローのラベル指定は `jobs.<job-id>.runs-on` で記載します。
カンマ区切りで複数のラベルを指定した場合、全てのラベルがついたランナーで実行されます。

:bulb: 例
UnityのiOSビルドをするにはUnityとXcodeが必要になるので、ワークフロー側は以下のように指定します。

```
name: build ios
on:
  workflow_dispatch:
jobs:
  build:
    runs-on: [ unity, xcode ]
    steps:
      ...
```

### インストールソフトウェア名のラベルを削除してメンテとする

メンテのために一時的に特定のランナーで実行させたくない場合、事前にランナーにつけておいたラベルを削除します。
一部のラベルを削除するだけにすれば部分メンテも可能です。

:bulb: 例
UnityのAndroidビルドとiOSビルドをできるようにUnityとXcodeが使えるrunnerがあり、Xcodeだけアップデート作業のためにiOSビルドだけメンテにしたい。というケースを考えます。

ランナー構成は以下の状態とします。

| マシン名 | runner名 | ラベル | 
|-|-|-|
| マシンA | runner-1 | unity, xcode |
| マシンA | runner-2 | unity, xcode |
| マシンB | runner-3 | unity, xcode |

またワークフロー側は以下の設定です。

| ワークフロー | runs-on指定 |
|-|-|
| Androidビルド | [unity] |
| iOSビルド | [unity,xcode] |

マシンBのiOSビルドだけをメンテ中とするために、runner-3からxcodeラベルを削除します。

| マシン名 | runner名 | ラベル | 
|-|-|-|
| マシンA | runner-1 | unity, xcode |
| マシンA | runner-2 | unity, xcode |
| マシンB | runner-3 | unity |

マシンAのrunner-1とrunner-2では引き続きiOSビルドを実行できます。
マシンBのrunner-3でもAndroidビルドは実行することができるので、マシン資源を有効活用することができます。

### 排他制御したい場合は専用のラベルを作る

GitHubホステッドランナーで排他制御するのは少々手間がかかりますが、ホステッドランナーであれば簡単に実現できます。

job専用のラベルを作り、実行させたいランナーにだけ追加します。
jobのruns-onには専用ラベルを指定します。

:bulb: 例

各ランナーのラベルを以下のように設定したとします。

| マシン名 | runner名 | ラベル | 
|-|-|-|
| マシンA | runner-1 | unity, xcode, mutex-job |
| マシンA | runner-2 | unity, xcode |
| マシンB | runner-3 | unity, xcode |

runs-onに `mutex-job` を指定したjobはrunner-1でのみ実行されます。
実行中に新たにもう1つ実行してもキューに積まれるだけです。

:warning: 別のjobがrunner-1を使っていても待たされます。ある程度の反応速度も求められる場合は専用のランナーを構築したほうが良いかもしれません。

## ストレージ管理

### ストレージの使用率を定期的に確認する

ストレージの使用率を取得し、slackなどのチャットツールに通知するスクリプトを定期的に実行しておくと良いです。

明示的に削除しない限り、チェックアウトしたリポジトリは実行後もストレージに残っています。
特にコミット履歴を扱うジョブのために `depth=0` でチェックアウトしている場合はストレージ消費が大きくなりやすいです。
ランナーを多く構築したマシンでは掛け算で増えていくので特に注意です。

MacOSでは空き容量が無くなった時にrmできなくなることもあり、昨今のリモートワーク状況だと対応しづらかったりするので、事前に検知して対処することが重要です。

## おわりに

セルフホステッドランナーを使い倒すための運用ノウハウについて書きました。
ラベルについてはやや珍しい使い方をしているかと思いますが、使い方次第ではこんなこともできるよ。という参考になれば幸いです。