一部書きかけになってしまったので後ほど追記します🙇♂️
はじめに
これまでJenkinsを使ってたけどGitHub ActionsでUnityのAndroid/iOSビルドしたい!
でも...
- GitHubホステッドランナーは使いたくない
- コストがかかる
- スペックが固定
- (Androidビルドがなぜかうまくいかない)
- (途中でマシンとの通信が途絶える...)
- act は使いたくない(辛い過去がある)
- ジョブの実行後にマシンの状態を引き継ぎたくない
- 手元にAppleシリコンMacがある
ということで、
- GitHub Actionsセルフホステッドランナーを
- AppleシリコンMac上のmacOS VM上で
- Unityビルド用に
- エフェメラルランナーとして立ててみた
のでそのときのメモです。
そもそも
GitHub Actionsとは
GitHub Actions は、ビルド、テスト、デプロイのパイプラインを自動化できる継続的インテグレーションと継続的デリバリー (CI/CD) のプラットフォームです。 リポジトリに対するすべての pull request をビルドしてテストしたり、マージされた pull request を運用環境にデプロイしたりするワークフローを作成できます。
自分の理解を雑に書くと
- GitHubから一時的にマシンを借りて(Ubuntu,Windows,macOS)
- アプリをビルドしたり
- アプリをストアにアップロードしたり
- ソースコードをデプロイしたり
- テストを実行したり
- バッチ処理を実行したり
- リポジトリのissueとかPull Requestとかブランチとかを操作したり
- ... などの自動化ができて
- それらの一連の処理はYAMLファイルでワークフローとして定義して
-
git push
とかPull Requestの作成とか手動でとかいろんなトリガーで起動できて - 処理が終わったらGitHubにマシンを返す
やつ、という感じです。
GitHubホステッドランナーとセルフホステッドランナー
ランナーは、GitHub Actions ワークフローでジョブを実行するマシンです。 たとえば、ランナーはリポジトリをローカルにクローンし、テスト ソフトウェアをインストールしてから、コードを評価するコマンドを実行できます。
GitHub は、ジョブの実行に使用できるランナーを提供します。または、独自のランナーをホストすることもできます。 各 GitHub ホステッド ランナーは、ランナー アプリケーションとその他のツールがプレインストールされた GitHub によってホストされる新しい仮想マシン (VM) であり、Ubuntu Linux、Windows、または macOS オペレーティング システムで使用できます。 GitHubホストランナーを使用すると、マシンのメンテナンスとアップグレードが自動的に行われます。
セルフホストランナーとは、GitHub上のGitHub Actionsからジョブを実行するためにデプロイして管理するシステムのことです。GitHub Actions の詳細については、"Understanding GitHub Actions" を参照ください。
セルフホスト型ランナーは、GitHubホスト型ランナーよりもハードウェア、オペレーティングシステム、ソフトウェアツールのコントロールがしやすくなっています。セルフホスト型ランナーでは、より大きなジョブを実行するための処理能力やメモリ、ローカルネットワークで利用可能なソフトウェアのインストール、GitHubホスト型ランナーでは提供されていないオペレーティングシステムの選択など、ニーズに合わせたカスタムハードウェア構成を作成することができます。セルフホストランナーは、物理的、仮想的、コンテナ内、オンプレミス、クラウドのいずれでも利用できます。
(↑DeepL翻訳)
前節で「GitHubから一時的にマシンを借りて」と書きましたが、このマシンは GitHubホステッドランナー
(GitHub-hosted runner)と呼びます。
YAMLで定義したGitHub Actionsワークフローは自分で用意したマシン上でも実行可能で、自前のマシンをセットアップしてGitHub Actionsを実行できるようにしたものが セルフホステッドランナー
(self-hosted runner)です。
また、セルフホステッドランナーは組織とリポジトリにそれぞれ登録でき、組織に登録した場合は組織内のリポジトリ内すべてで使用でき、リポジトリに登録したランナーはそのリポジトリ内でしか使用できません。
通常のセルフホステッドランナーとエフェメラルランナー
通常のセルフホステッドランナー
ジョブが実行された後のマシンの状態はそのまま他のジョブに引き継がれるので、ワークフロー/アクションを実装する側が
- ジョブの実行が終わったらマシンを元通りにする
- 作った/ダウンロードしたファイルを削除する
- 使用したリソースを開放する
- シークレットや機密情報は残さない
- 安易にsudoでシステムに何かをインストールしたりしない
などに気をつける必要があります。
公開されているアクションはクリーンナップ処理で後片付けしてくれる場合が多いですが、自分でシェルスクリプトを書いたりする場合はきちんと後始末したほうが良いです。
逆に次回以降のジョブ実行時にも使いたいファイルを消さずに残しておけばキャッシュとして再利用できたります。
エフェメラルランナー(ephemeral self-hosted runner)
今回使用したいランナーです。
- GitHub Actions: Ephemeral self-hosted runners & new webhooks for auto-scaling
- セルフホステッド ランナーによる自動スケーリング
ephemeral
とは「一時的な,儚い」という意味で、エフェメラルランナーには以下の特徴があります。
- ジョブを1つ実行したら
- プロセスが停止する
- GitHubから登録解除される
これは workflow_job
などのイベント発生時に動的にランナーのマシンを用意することでランナーをオートスケールさせるような構成を念頭に置いたものです。
厳密には「エフェメラルランナー」に「ジョブが毎回クリーンなマシンイメージで実行される」の意味合いはないのですが、簡単のために本記事では
- ジョブを毎回クリーンなマシンイメージで実行するランナー
という意味で使うことにします。
やりたいこと
- 自前のmacOSマシンを使いたい
→ セルフホステッドランナーを使用 - ジョブ実行後の状態を他のジョブに引き継がない
→ ランナーをエフェメラルにする
→ 毎回新しくmacOSのVMを立てて使用
→ 用が済んだらVMを捨てて作り直す
シーケンス的には↓を繰り返す感じ。
使うもの
- AppleシリコンMac
-
Tart
- AppleシリコンMac上にmacOSのVMを立てられる
- Virtualization frameworkを使用
- CLIで操作が楽
- たった2コマンドでmacOSのVMが起動できる!
# VMのイメージDL $ tart clone {VMイメージ名} # VM起動 $ tart run {VM名}
- たった2コマンドでmacOSのVMが起動できる!
- 100CPUコアまで無料(詳細なライセンス)
- AppleシリコンMac上にmacOSのVMを立てられる
- Tartelet
- GitHub
macOSのVMを立てられるアプリケーションはTartの他にも
- UTM(Apache 2.0ライセンス)
- Parallels Desktop(有料)
などがありますが、自分は規模感と使いやすさからTartを選択しました1。
手順
物理マシン上での作業①
macOSをアップデートする
物理マシンのOSを仮想マシンで使用したいmacOSと同じかそれ以上にしておきます。
後述する理由によりSequoia以上がおすすめです。
Tartをインストール&セットアップ
# Homebrewをインストール
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Tartをインストール
$ brew install cirruslabs/cli/tart
# VMのダウンロード
$ tart clone ghcr.io/cirruslabs/macos-sonoma-xcode:latest macos-sonoma-xcode
# VMを起動
$ tart run macos-sonoma-xcode
# ↓VMを起動したターミナルとは別のタブ/ウィンドウで実行
# sshの疎通確認
$ ssh admin@$(tart ip macos-sonoma-xcode) echo "hello"
# 問題なければ先に起動したVMを閉じる
Tarteletをインストール
- リリースページ からzipをDLして解凍
-
Tartelet.app
を/Applications
以下に移動
GitHub上での作業
TarteletのWikiを参考に進めればOK。
GitHub Appを作成&インストールする
個人の場合
アプリを https://github.com/settings/apps から作成します。
また、以下のパーミッションが必要です。
- Repository Permissions
- Administration:Read and write
- Metadata:Read-only
組織の場合
アプリを
https://github.com/organizations/{YOUR_ORGANIZATION_NAME}/settings/apps
から作成します。
また、以下のパーミッションが必要です。
- Repository Permissions
- Administration:Read and write
- Metadata:Read-only
- Organization Permissions
- Self-hosted runners:Read and write
アプリ作成後のページに App ID
が表示されているので控えておきます。
秘密鍵をDLする
アプリ作成後の設定画面を下にスクロールしていくと秘密鍵を生成するボタンがあるので、クリックして秘密鍵のファイルをダウンロードします。
GitHub Appをインストールする
ページ左の Install App
をクリックすると組織一覧のページが開くので、組織を選んで [Install]
を押します。
リポジトリ内に閉じたセルフホステッドランナーを作る場合は Only select repositories
からリポジトリを選択してインストールします。
物理マシン上での作業②
Tarteletに各種情報を登録する
Tartelet.app
を開いてGitHubタブを開きます。
Runner Scope
の登録
組織全体のセルフホステッドランナーとして登録する場合は Runner Scope
で Organization
を選択して、 Organization Name
を入力します。
リポジトリ毎の個別のセルフホステッドランナーとして登録する場合は Repository
を選択して、 Owner Name
と Repository Name
を登録します。
App ID
と Private Key (PEM)
の登録
App ID
には先ほど控えておいたApp IDを、 Private Key (PEM)
には先ほどDLした秘密鍵を設定します。
登録した秘密鍵はKeychainに保存されるので、他で使う予定がなければファイルは削除してOKです。
ランナー用のVMイメージを作成
$ tart clone macos-sonoma-xcode runner
(必要に応じてVMのスペックを調整)
$ tart get runner
OS CPU Memory Disk Size Display State
darwin 4 8192 100 69.389 1024x768 stopped
# CPUを6コアに設定
$ tart set runner --cpu 6
# メモリを10240MBに設定
$ tart set runner --memory 10240
VMのディスク容量を増やす方法
ディスク容量変更は単純に tart set {VM名} --disk {容量}
とかではできません(参考)。
# 予めVMは閉じておく
# 以下物理マシン上で実行
# 「300gb」のところはお好みで
$ truncate -s 300gb ~/.tart/vms/runner/disk.img
$ tart run runner
# VMが起動する
$ ssh admin@$(tart ip runner)
# パスワード入力(admin)
$ diskutil repairDisk disk0
$ diskutil apfs resizeContainer disk0s2 0
ランナー用のVMを起動
$ tart run runner
# 以降、起動したVM上で設定の続きを行う
VM上での作業
ここまで手順通りに進めている場合、スクリーンセーバー無効化や画面共有,sshの許可などの基本的な設定はセットアップ済みなので不要です。
基本的な設定を手動で行いたい場合は以下を参考に。
- https://github.com/mirego/ekiden/blob/main/host/README.md#adjust-the-machines-preferences
- https://tart.run/integrations/vm-management/
-
https://github.com/cirruslabs/macos-image-templates/tree/master/templates
- tartが使用しているVM作成時の手順を自動化するためのファイル群
アカウントは管理者アカウントとして
- アカウント名:
admin
- パスワード:
admin
が最初から登録されています。
(お好みで)不要なファイルを削除する
Xcodeにいろいろ入ってるので不要なものは削除します。
- Flutter関連
- WatchOS関連
- VisionOS関連
- iOSのシミュレータ
etc..
macos-{sonoma,sequoia}-base
などのマシンイメージを使うとそもそも余計なものが入ってないので、マシンイメージを小さくしたい場合はそちらも検討してください。
本当にまっさらな状態のOSインストールから始めたい場合は *.ipsw
ファイルからマシンイメージを作成することもできます(参考)。
(お好みで)デフォルトのシェルをbashに変更
$ chsh -s /bin/bash
# ターミナルを閉じて開き直す
以降の手順はbashを前提としているのでzshを使う人などは適宜読み替えてください。
Homebrewをインストール
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
$ echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/admin/.bash_profile
$ eval "$(/opt/homebrew/bin/brew shellenv)"
(お好みで)bashをアップデート
$ brew install bash && sudo chsh -s /opt/homebrew/bin/bash && sudo chsh -s /opt/homebrew/bin/bash $(whoami)
(お好みで)bash_completionを有効化
$ brew install bash-completion
$ echo '[[ -r "/opt/homebrew/etc/profile.d/bash_completion.sh" ]] && . "/opt/homebrew/etc/profile.d/bash_completion.sh"' >> ~/.bash_profile
$ source ~/.bash_profile
(必要に応じて)/Users/runner
→ /Users/admin
のシンボリックリンクを張る
一部のsetupアクションでユーザ名がrunnerで決め打ちになっているらしく、その対応です。
これまでの手順通りにTartのマシンイメージを使用している場合は最初から対応されてるので不要です。
$ ln -s /Users/admin /Users/runner
(お好みで)Rosetta2をインストール
$ softwareupdate --install-rosetta --agree-to-license
(必要に応じて)Xcodeをインストール
macos-sonoma-xcode
のマシンイメージには最初からXcodeが含まれているので不要です。
# Xcodesのダウンロード高速化に必要
$ brew install area2
# XcodesをDLしてインストール
https://github.com/XcodesOrg/XcodesApp/releases
# Xcodesで
# 1. Apple IDにログイン
# 2. 必要なバージョンのXcodeをインストール
# 3. iOS Platformをインストール
# ↑エラーでうまくいかない場合はXcodeを開いて Settings... → Components からインストールする
macOS Sonoma以下の場合はVM上でApple IDにログインできないので、Xcodeを物理マシンにダウンロードしてVMに転送するなどのやり方になると思います。
Unity Hubをインストール
$ brew install --cask unity-hub
# 後々の作業を楽にするためにaliasを張る
$ echo 'alias unity-hub='\''/Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless'\' >> ~/.bash_profile
$ source ~/.bash_profile
Unity Hubを開いてログイン後、ライセンスキーなどでアクティベートします2。
Unityをインストール
Unity Hubで必要なバージョンのUnityをインストールします。ターミナルからでもOK。
$ unity-hub install --version {Unityのバージョン} --changeset {チェンジセット} -m android ios
# Apple siliconを選択
# OpenJDKを入れるか聞かれるのでyes
# Android SDKとNDK Toolsを入れるか聞かれるのでyes
ターミナルからマイクへのアクセスを許可
Unityでビルドする時にマイクへのアクセス許可を求めるダイアログが出て処理が止まってしまうので、予め許可しておきます3。
$ brew install sox
$ sox -d -d
# マイクへのアクセス許可を求めるダイアログが出るので許可する
$ brew uninstall sox
git-lfs
をインストール
$ brew install git-lfs
$ git lfs install
GitHub CLIをインストール
$ brew install gh
cocoapods
をインストール
$ sudo gem install cocoapods
# 以下のエラーが出たらターミナルを再起動してやり直すとうまくいくかも
# The last version of drb (>= 0) to support your Ruby & RubyGems was 2.0.6.
# Try installing it with `gem install drb -v 2.0.6` and then running the current command again
$ pod --version
# バージョンが正しく表示されればOK
fastlane
をインストール
$ sudo gem install fastlane
$ fastlane -v
# バージョンが正しく表示されればOK
物理マシン上での作業③
Tarteletのウィンドウを開く
Tartelet.app
を開く → グローバルメニューの Tartelet
→ Settings...
Tarteletの設定
項目 | 値 |
---|---|
Virtual Machine | runner を選択 |
Username | admin |
Password | admin |
他の項目はお好みで。
ランナーを起動
グローバルメニューの Virtual Machines
→ ▶ Start
をクリックすると、VMが立ち上がってGitHubに登録後、ランナーとしてジョブを待機する状態まで自動でやってくれます。
適当なワークフローを起動してテスト
以下のワークフローファイルをデフォルトブランチにコミットしてプッシュ後、GitHub上で手動実行します。
name: Test for self-hosted runner
on: workflow_dispatch
jobs:
build:
runs-on: [self-hosted, tartelet]
steps:
- run: echo "Hello, self-hosted runner!"
正しく動いたらセットアップ完了です。
エフェメラルランナーなのでジョブが完了するとGitHubからランナー登録解除されますが、Tarteletがよしなにもろもろをやってくれます。
これで使い放題&ぶっ壊し放題なmacOSのGitHub Actionsランナーが手に入ったぞ!うおおおおお!!
ってなりませんか?僕はなりました。
ランナーを停止したいとき
- グローバルメニューの
Virtual Machines
→■ Stop
- 開いてるランナーウィンドウを手動で閉じる
です(わかりにくい)。
1.の手順を踏まずに2.を行うとVMが無限に起動し続けるので注意!
キャッシュどうするか問題
CI/CDツール・サービス上でUnityのビルドを実行するとき、通常は実行時間の短縮のために Library/
をキャッシュして使い回すと思います。
GitHubホステッドランナーでは actions/cache を使用してGitHubにキャッシュを持たせるのが一般的です。
しかし、セルフホステッドランナー上でジョブを実行する際に actions/cache を使用してしまうと、キャッシュの保存先はジョブを実行するマシン上ではなくGitHub上になってしまいます。
- (ここにissueとか記事とか貼る)
- self-hosted runnerと actions/cache の噛み合わせが悪かった件
GitHub上にキャッシュを持たせるとGitHubのUIで一覧したり削除ができますが、通信量や実行時間的にもムダなので、ジョブを実行するマシンもしくはローカルネットワーク上にキャッシュできるようにしたいところです。
いろいろ試したわけではないですが、個人的には github-action-cache-local-fs を使っていて特に問題ないので、こちらを使った方法をご紹介します。
github-action-cache-local-fs を使ってローカルにキャッシュする
(ここに説明を追記)
その他の方法
ローカルにキャッシュサーバを立てて使う GitHub Actions Cache Server (非公式)や、Amazon S3を使うものなどがあるみたいです。
詳しくはググってみてください。
その他
Tarteletは便利だけど...
現状のTarteletにはtartにオプションパラメータを渡す方法は提供されていません。
例えばTartにはVM起動時のオプションでホスト側macOSのディレクトリをゲスト側macOSと共有する機能がありますが、Tarteletでは使用できません。
これは例えば
「物理マシン1台だけで運用するからホスト上にキャッシュを置いてゲストと共有したい」
みたいなときに困ります。
また、TartにはVMのUIを表示しない --no-graphics
というオプションがありますが、Tarteletでは使えないので描画周りでムダにリソースを使うのが気になります。
Tartelet以外の選択肢
Tartelet以外で比較的ラクにエフェメラルランナーを立てる選択肢としては以下があります。
Ekiden
Ekidenはシェルスクリプトで実装されたシンプルなツールで、環境変数を設定すればエフェメラルなself-hosted runnerを簡単に起動できます。
macOS VMをself-hosted runnerとしてGitHubに登録して待機、ジョブ終了後にVMを作り直して再度GitHubに登録し直すループを手軽に実現できます。
自分が最初に試したのがこれでした。
ただ、Ekidenはself-hosted runner登録に必要な一時的なトークンの再生成に対応してないため、そのあたりを自分で実装する必要があります。ドキュメントも割と不親切なのでソースコードを読んだほうが早い、ということもしばしばあります(分量は多くないので読むのは簡単ですが)。
基本的にただのシェルスクリプトなので楽に改造できます。Tarteletの簡単さより柔軟性がほしいならありかもです。
myshoes
Auto scaling self-hosted runner 🏃 (like GitHub-hosted) for GitHub Actions!
Cyber Agent所属の@whywaitaさん開発のツールです。CAのmacOS VMのセルフホステッドランナーではこれが使われてるそうです。
現状はmacOSのVMにそのまま使えるわけではなくproviderを自作する必要があるみたいですが、gRPCで実装する必要があるっぽかったので自分は試してません。
自作する
そこまで複雑な機能が必要なわけでもないので、工数と相談しつつ自作もありかなと思います。
Ekidenを参考に好きな言語で書いたらいいんじゃないでしょうか。
macOS VMの制限
台数制限
macOS VMというかホスト側の制限なのですが、一つのmacOS物理マシン上で立てられるmacOS VMの最大数は2台までです。
これはmacOSの利用規約にも書いてある制限であり、Virtualization frameworkを使用しているツールで3台目のmacOSを立ち上げようとするとエラーになります4。
Apple Accountへのログイン制限
macOS VM上ではApple Accountにログインできません(でした)。
ホスト・ゲストともにmacOS Sequoia以上ならVM上でApple Accountにログイン可能です。
XcodeをDLするにはApple Accountへのログインが必要で、VMでXcodeを使用するには物理マシンでDLしたものを持ってくる必要がありましたが、SequoiaからはXcodeのインストールや更新が楽になりました。
ネストした仮想化の制限
現状macOS VM上ではネストした仮想化ができません。
- macOS VM上で
- macOS VMを立てる → ❌️できない
- Dockerを使用する → ❌️できない
ただ、macOS Sequoia かつ M3チップ であればLinuxのネストは可能になったようです(未確認)。
今後やりたいこと
- macOSだと物理マシン1台につきVM2台の制限があるのでLinuxでビルドしたい
- Xcodeプロジェクトから
*.ipa
のビルドだけmacOSでやりたい - Docker in Dockerとかいうのでできるかもしれない
- Xcodeプロジェクトから
- セルフホステッドランナーの初期状態をGitHubホステッドランナーに近づけたい
- ランナーのイメージはここにある
- Packerを使って自分でビルドする必要がある?
上記についてアドバイスお待ちしてます🙇♂️
最後に
意外と書くことが多くてUnityでのビルドまでたどり着けなかったので、macOSセルフホステッドランナー + GameCIによるビルドについては別の記事にまとめる予定です。
いろいろ書いてきましたが内容が似てる記事が数日前に投稿されてたのでそちらも貼っておきます5。うぐぐ...