Docker

スケーラブルなDinD Self-hosted Runner Ubuntu版

以前作成したDinDによるGitHub ActionsのセルフホステッドランナーのDocker Composeスタックをアップデートしました✨
ディストリビューションを以前の AlmaLinux からポピュラーな Ubuntu に変更し、さらにスケーリングに対応したことで、より使いやすくなったと思います。

今回の改修点

参考までに以前作成した記事はこちらです。

DinDによるGitHub Actionsのセルフホステッドランナーを構築する
Dockerコンテナ上でセルフホステッドランナー(self-hosted runner)を動かすには、いろいろハードルがあると聞いていたのですが、実際にやってみるとそんなことも無くなっていたので、令和最新版としてDocker Composeで手っ取り早く構築方法をメモっておきます。

基本構造は以前の記事で紹介したものと変わっていませんが、今回は主に以下の点をアップデートしています。

  1. Gitリポジトリ作成
  2. ベースOSをUbuntu 24.04に変更
  3. Docker Composeでのスケーリング(deploy.replicas)に対応
  4. 終了処理の猶予時間延長

1. Gitリポジトリ作成

初版はGitLabのスニペットを使用していましたが、今回はリポジトリを作成して、今後のアップデートに備えるようにしました。
GitHub Actionsのホステッドランナーが有料である限り、自分自身も継続して使い続けると思いますので。

Seizu Development Forge / Docker / GitHub Actions Self-Hosted Runner DinD · GitLab
GitLab.com

修正提案などあればこちらへお願いします。

2. ベースOSをUbuntu 24.04に変更

以前は個人的な理由で AlmaLinux をベースOSにしていましたが、最近は Ubuntu をサーバー用途として使うことが多くなったため、それに合わせてUbuntuのイメージを使用してランナーを構築することにしました。
実際、GitHubホステッドランナーもUbuntuが主体のようですし、取り回しが良いと思います。

これにあたっては導入パッケージがいくらか変わった程度で大きな変更はありません。

3. コンテナのスケーリングに対応

今回一番工夫したところです。
今まではcomposeファイルにべた書きで複数ランナーのサービスを記述していましたが、変数を変更するだけで、並列展開するランナー数を自在に変更できる形にしました。

Point 1: deploy.replicas の利用

deploy.replicas を使えば同じ内容のサービスコンテナを複数展開(スケーリング)できるので、今回はそれを取り入れました。
.env変数を利用しているので、composeファイルを直接いじらずに変更できます。

# compose.yaml
deploy:
      replicas: ${RUNNER_REPLICAS:-1}
deploy.replicas の効果はコマンドラインの docker compose up コマンドにおける --scale オプションと同様です。

Point 2: ホスト名を連番化

サービスをスケーリングした場合、ホストマシンから見たコンテナ名には連番が振られるのですが、コンテナ内からはそれを直接参照することができず、ホスト名はコンテナのIDハッシュとなってしまいます。

セルフホステッドランナーではホスト名がそのままランナー名としてGitHubに登録されるので、ハッシュだと見分けがつきにくいのが難点です。
特に compose up/down で設置・撤去が手軽かつ、スケーリングが自由だとハッシュが頻繁に変わるため、なおさら管理が難しくなります。

それを解決する奇策として、DockerネットワークのDNSから自身のホスト名を逆引きする方法を採用しました。

# entrypoint.sh
# Get container index number
CONTAINER_INDEX=$(nslookup $(hostname -i) | grep 'name =' | sed -n 's/.*-\([0-9]\+\)\..*/\1/p')

# Generate runner name
RUNNER_NAME=$(cat /host_hostname)-runner-${CONTAINER_INDEX}

自身のIPアドレスから nslookup でホスト名の逆引きを行うと、ホストマシンから見えるコンテナ名と同じものが取得できます。あとはコンテナ名に付いている連番部分を正規表現で取り出しています。

ランナー名は何台もホストマシンを設置した場合でも区別がつくように {ホストPC名}-runner-{ランナーコンテナ連番} というフォーマットにしました。
ホストマシン名は /etc/hostname をバインドマウントすることで取得しています。

# compose.yaml
volumes:
  - /etc/hostname:/host_hostname:ro

Point 3: ボリュームの可搬性改善

以前はランナーコンテナごとにワーキングディレクトリ用のボリュームを割り当てていましたが、そのままではスケーリングの足かせになってしまいます。
そこで、一つのボリューム(または任意のディレクトリ)をマウントして、連番に基づく名前のディレクトリで分けることにしました。
こうすることで、コンテナを破棄してもボリュームが残っていればワーキングディレクトリの再利用ができます。

# entrypoint.sh
# Generate work directory
RUNNER_WORKDIR="/var/actions-runner-workdir/_workdir_${CONTAINER_INDEX}"

4. 終了処理の猶予時間延長

以前と同じく、終了コード 130(SIGINT) または 143(SIGTERM) を受け取った時、自動的にランナー登録を抹消するという終了処理を組み込んでいます。

# Remove runner from repository
cleanup() {
    echo "Removing runner..."
    ./config.sh remove --pat ${GITHUB_ACCESS_TOKEN}
}

trap 'cleanup; exit 130' INT
trap 'cleanup; exit 143' TERM

しかしながら、猶予時間が短いせいか、度々正常に抹消ができておらず、GitHubに幽霊ランナーが残ってしまうことがありました。
ここは単純にタイムアウトの猶予が短すぎたのが原因です。

そこで、サービスのプロパティで stop_grace_period を長めに設定することで解決しました。

stop_grace_period: 30s
stop_grace_period のデフォルト値は10秒となっています。

おわりに

使い方については本記事では割愛させていただきます。
詳しくはリポジトリのREADMEを参照してください。

ご意見・ご質問はお気軽にどうぞ😊

コメント

タイトルとURLをコピーしました