GitHub Actionsをどこでも！self-hosted runnersがアツかった

# はじめに
こちらは[長野高専 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/nnct) 4日目の記事です！
@shun-shobon さんが開催するNNCTアドカレもこれで3回目の参加ですね〜。現在後続の主催者を探しているみたいなので、来年度以降も在籍予定の方はぜひ！

## 自己紹介
卒業してしまった身なので書いておきます！
18sで昨年電子情報工学科を卒業した**Bony_Chops**と申します！現在は大学に3年次編入して、各所でインターン等々をやっています。

https://bonychops.com

## 宣伝
同日付で別のアドカレ記事を書いているので、ぜひそちらもご覧ください！以下の記事ではこの記事の内容を実際に使っています。

https://qiita.com/BonyChops/items/8488d39694847a2ed681

# 前置き
突然ですが皆さんはGitHub Actionsを使っていますか？ GitHubに高度にインテグレートされたActionsは2018年にリリース[^1]されて以来、その手軽さから様々なCI/CDに取り込まれていきました。  
そんなActionsですが、実は**持ち前の環境でも実行できる**ことをご存知でしたか？「あーはいはい、[act](https://github.com/nektos/act)のことでしょ？」と思ったそこのあなた、実は**GitHubが公式に提供する仕組み**があります！それが、**self-hosted runners**です。

:::note

[**act**](https://github.com/nektos/act)と**self-hosted runners**は似ていますが、両者趣旨が異なるものです。
- [**act**](https://github.com/nektos/act): ローカルでActionsをテストする
- **self-hosted runners**: GitHubで実行されるActionsの環境の一つとして任意の環境を使えるようにしたもの

:::

https://github.com/nektos/act

# self-hosted runners
前述しましたが、self-hosted runnersは**GitHubで実行されるActionsの環境の一つとして任意の環境を使えるようにしたもの**です。つまり...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601799/b3233977-3c01-53cf-36cd-f224ae960127.png)

一見普通のGitHub Actionsに見えると思いますが、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601799/b2258420-98d7-08dc-a09e-92aeba40bd74.png)

**これ全部自前環境で動かしました**。

## メリット
自前で発動させられるActionsには、次のようなメリットがあります。
- **自前IPでリクエストを飛ばせる**
  - ワークフローによっては外部にアクセス/リクエストを通したい、けどgithub.comから出るリクエストだとアクセスできない...みたいなケースがあると思います。self-hostedを使えば、そういった制限を回避できます。
- **ケースによっては格段にフローを早くできる**
  - 大きいモデル等を使ってビルドしている、みたいなケースのとき、それを落とすのにすごい時間がかかることがあると思います。self-hostedはインストールされた環境で**そのまま動く**ため、環境に予めモデルを置いておけば、モデルをダウンロードする時間をスキップできます
- **レートリミットを回避できる**
  - フローがどれほど時間がかるものであっても、持ち前の環境の電気代やらを負担すれば良いだけなので、気兼ねなく重めのフローを組めます
- **ポート開放不要**
  - 後述するセットアップ方法を見ればわかるとおり、**設定がめちゃくちゃ楽です**。**ポート開放が不要**なので、自分みたいに集合住宅に住んでいる民でも使うことができます(とても嬉しい)。

## セットアップ
「セットアップ、大変なんでしょ」というそこのあなた！設定はめちゃくちゃ楽です。

:::note alert
後述しますが、**self-hostedはDocker環境ではない**ので、フローを試す際は気をつけてください。実行するコマンドは**実行環境に直接影響します**[^2]。
不安な方は後述のDocker化をお試しください。
:::

リポジトリのSettings -> Actions -> Runnersに移動します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601799/20ff6a16-1137-a3da-c8a9-5b3fa60719ff.png)

使いたいOSとアーキテクチャを選択します。もちろんM1(macOS arm64)やラズパイ4-(Linux arm64)にも対応してますし、Linux armに対応しているのでラズパイZeroや3などでも(おそらく)実行できます。
Windowsを選べるのも、人によってはかなりありがたいんじゃないでしょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601799/f9a8466c-d106-2726-6292-09eebf946bec.png)

あとは表示されているコマンド郡を実行すればOKです。登録用のTOKENも含まれているので、実行するだけで勝手にアカウントにひも付きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/601799/f78aa6bf-feee-3133-399b-c31c375fdbf0.png)

最後に、使いたいWorkflowの`runs-on`を書き換えれば完了です。

```diff_yaml
  jobs:
    node-build-and-deploy:
-     runs-on: ubuntu-latest
+     runs-on: self-hosted
```

## Docker化[^3]
前述した通り、self-hosted runnersはデフォルトだと**環境でそのままコマンドを実行します**。それだと困るという人もいると思うので、Docker化していきます。

以下は**Linux arm64**での例です。それ以外の人は [#セットアップ](#セットアップ) で提示されるコマンド郡を参考に直してください^^;

:::note
**Docker環境でのLinux arm64**ですので、アーキテクチャが同じ人(ラズパイ4-, Apple Siliconなど)はそのまま使えます。
:::

```dockerfile:Dockerfile
FROM ubuntu:22.04

RUN apt-get update && \
    apt-get install \
    curl \
    expect \
    jq \
    ssh \
    rsync \
    gettext-base \
    -y

# Linux arm64向け、それ以外の人は #セットアップ で提示されるコマンド郡を参考に直してください^^;
RUN mkdir actions-runner && cd actions-runner && \
    curl -o actions-runner-linux-arm64-2.311.0.tar.gz -L \
    https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-arm64-2.311.0.tar.gz && \
    echo "5d13b77e0aa5306b6c03e234ad1da4d9c6aa7831d26fd7e37a3656e77153611e  actions-runner-linux-arm64-2.311.0.tar.gz" | sha256sum -c - && \
    tar xzf ./actions-runner-linux-arm64-2.311.0.tar.gz && \
    ./bin/installdependencies.sh

COPY exec-runner.sh /actions-runner/exec-runner.sh
RUN chmod +x /actions-runner/exec-runner.sh
WORKDIR /actions-runner

CMD ["/actions-runner/exec-runner.sh"]
```

:::note
デフォルトでは1フローを実行したらコンテナを停止/削除する設定になっているため、不要であれば`exec-runner.sh`内の`--ephemeral`を削除してください
:::

```bash:exec-runner.sh
#!/bin/bash

# repo scopeのあるアクセストークン
if [ -z "${GITHUB_ACCESS_TOKEN}" ]; then
  echo "GITHUB_ACCESS_TOKEN must be set" 1>&2
  exit 1
fi

if [ -z "${GITHUB_API_DOMAIN}" ]; then
  echo "GITHUB_API_DOMAIN must be set" 1>&2
  exit 1
fi

if [ -z "${GITHUB_DOMAIN}" ]; then
  echo "GITHUB_DOMAIN must be set" 1>&2
  exit 1
fi

if [ -z "${GITHUB_REPOSITORY_NAME}" ]; then
  echo "GITHUB_REPOSITORY_NAME must be set" 1>&2
  exit 1
fi

if [ -z "${GITHUB_REPOSITORY_OWNER}" ]; then
  echo "GITHUB_REPOSITORY_OWNER must be set" 1>&2
  exit 1
fi

# ランナー設定用トークンを取得する
export GITHUB_RUNNER_REGISTRATION_TOKEN=$(curl \
    -X POST \
    -H "Accept: application/vnd.github+json" \
    -H "Authorization: Bearer $GITHUB_ACCESS_TOKEN" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    https://$GITHUB_API_DOMAIN/repos/$GITHUB_REPOSITORY_OWNER/$GITHUB_REPOSITORY_NAME/actions/runners/registration-token \
    | jq .token | sed -e 's/"//g')

export RUNNER_ALLOW_RUNASROOT=1
runnerName=`hostname`

# config.shを実行する
# 不要であれば--ephemeralを消してください
expect -c "
set timeout 10
log_user 0
spawn ./config.sh --url https://${GITHUB_DOMAIN}/${GITHUB_REPOSITORY_OWNER}/${GITHUB_REPOSITORY_NAME} --token ${GITHUB_RUNNER_REGISTRATION_TOKEN} --ephemeral
log_user 1
expect  -re \"Enter the name of the runner group to add this runner to:.*\"
send \"\n\"
expect  -re \"Enter the name of runner:.*\"
send \"${runnerName}\n\"
expect  -re \"Enter any additional labels.*\"
send \"\n\"
expect  -re \"Enter name of work folder:.*\"
send \"\n\"
expect \"#\"
exit 0
"

./run.sh
```
:::note
`GITHUB_ACCESS_TOKEN`は、Settings -> Developer Settings -> Personal Access Tokens -> Fine-granted TokensからRepository permissions
の`Administration`と`Actions`を付加したTokenを生成して適用してください
:::

```ini:.env
GITHUB_ACCESS_TOKEN=(token)
GITHUB_REPOSITORY_OWNER=(repository owner name)
GITHUB_REPOSITORY_NAME=(repository name)
GITHUB_DOMAIN=github.com
GITHUB_API_DOMAIN=api.github.com
```

```shell
# ビルド
docker build -t self-hosted-runner .
# 起動
docker run --rm -it --env-file=./.env self-hosted-runner
```

# 家のRaspberry Piで実行
家に転がっていたRaspberry Pi 3, 4と、ブラックフライデーで分裂させた4で起動させてみました...と言いたいのですが、設定が間に合わず現状3のみで実行しています^^; (遅い^^;)

https://x.com/BonyChops/status/1729516151015928256?s=20

https://qiita.com/BonyChops/items/8488d39694847a2ed681

↑ここで実際に使っています。

# 結論[^4]
今年7月にActions runner controller(runners k8s管理)がGAになった[^6]そうなので近いうちにそっちも試したいな〜
みんなも**self-hosted runners**を使ってCI/CDの可能性を広げよう。

[^1]: https://github.blog/2018-10-16-future-of-software/
[^2]: 自分はこれを知らずに実行し、(WorkflowにSSHの設定を弄るものが含まれていたため)`~/.ssh/config`が吹き飛んで絶望しました
[^3]: https://note.com/shift_tech/n/n199fd81ce315
[^4]: 自分の記事は毎回締まりが悪いなと感じていましたが、**毎回結論がないな**ということに気づいたので、若干適当ですが書くようにしました。
[^6]: https://github.blog/changelog/2023-06-30-github-actions-actions-runner-general-availability/