- 真野隼記
- 約 6,500 文字
- 1,100 View
目次
※画像はGemini Pro 2.5で作成しました。
CI/CD連載 3本目です。
はじめに
TIG 真野です。
GitHub Actionsをローカル環境で実行できるnektos/actをMakefileやTaskfileなどのタスクランナーの代わりとして使えるのか、試してみた記事です。
nektos/act とは
act は GitHub Actionsのワークフローをローカル上で実行できる、Go言語で実装されたツールです。Dockerを利用してGitHub Actionsの実行環境をエミュレートしてくれ、GitHubのリポジトリにプッシュすることなくワークフローのテストやデバッグを行うことができます。actの名前の由来は、actionsからもらっているんだろうなと思っています。
act のREADMEには以下のようにactを使うべき理由が書かれています。
Run your GitHub Actions locally! Why would you want to do this? Two reasons:
- Fast Feedback - Rather than having to commit/push every time you want to test out the changes you are making to your .github/workflows/ files (or for any changes to embedded GitHub actions), you can use act to run the actions locally. The environment variables and filesystem are all configured to match what GitHub provides.
- Local Task Runner - I love make. However, I also hate repeating myself. With act, you can use the GitHub Actions defined in your .github/workflows/ to replace your Makefile!
日本語訳:
GitHub Actions をローカルで実行しましょう!これを行うべき理由は2つあります:
- フィードバックを早くする - .github/workflows/ ファイルに加えている変更(または埋め込まれた GitHub Actions への変更)をテストしたいと思うたびにコミット/プッシュをする代わりに、act を使えばアクションをローカルで実行できます。環境変数やファイルシステムは、GitHub が提供するものと一致するようにすべて設定されます
- タスクランナーとして動かす - 私は make が大好きです。しかし、同じことを繰り返すのも嫌いです。act を使えば、.github/workflows/ で定義された GitHub Actions をあなたの Makefile の代わりに利用できるのです!
2つ目の理由として書かれていた、タスクランナーとして使うという点が意外でした。MakefileかTaskfileあたりで十分良い気もしますが、GitHub Actionsとローカル実行でも利用するコマンド定義を共有できれば確かに便利そうです。
この点で評価している記事が、自分の観測範囲で見つけることができなかったため、CI/CD連載の1ネタとして試します。
actの使われどころ
Gitea という、GitHubのセルフホスト可能なGo言語製のプロダクトがあります。類似のツールにGogsも存在しますが、Giteaは元々Gogsからフォークされたものです。理由は公式の発表ブログがあります。
そのGiteaのGitea Actionsは、act (のソフトフォークで、ライブラリ呼び出しなどをできなくしている)で動いているとのことです。Giteaからさらに派生した Forgejoでも Forgejo Runnerで利用されています。
複数のプロダクトからCI/CDランナーとして採用されていることから、完成度も高まっているのではないでしょうか。なお、2025年6月時点ではバージョン v0.2.78 でした。そもそもがGitHub Actionsの互換を謳っているため、安定性は高いと言えるのではないでしょうか。
アーキテクチャ
act のアーキテクチャは少し特殊です。Makefileの代わりにと説明があったので、最初はステップの中で uses: actions/checkout@v4 のような別途コンテナ起動が必要な場合のみ、Docker呼び出しし、その他はホスト上で直接コマンドを実行するのかと思っていました。
実際は、GitHub Actionsと同じくRunner(ランナー)のコンテナイメージをローカルに取得し、そのランナー上でジョブを処理します。ステップの中でDocker呼び出しが必要な場合のみ、ランナーとは別にコンテナイメージを起動させます。データのやり取りはボリューム共有機能を経由して行われます。Docker呼び出しが不要な場合は、ランナーで直接実行します(そのために必要な、Node.jsなどの最低限のセットアップはされています)。
上記の構造のため、ちょっとしたスクリプト実行も、上図でいうジョブコンテナの呼び出しが必要です。
事前準備
act を利用する上でDockerのインストールは前提条件です。もし、未構築の場合はMac・Windowsの場合はDocker Desktop(Linuxの場合はDocker Engine)をインストールします。
actのインストール
公式ドキュメントにインストールについて独立したページがあり、様々なパッケージマネージャーに対応しています。
私はGo環境が構築済みだったため、 go install で対応します。
go install github.com/nektos/act@v0.2.78 |
helpを見ると、オプションが豊富なことも分かります。
act --help |
サンプルスクリプト
プロジェクトルートに移動し、GitHub Actionsのワークフローファイルを配置する .github/workflows ディレクトリを作成します。
mkdir -p .github/workflows |
今回は、ローカルタスクランナーとしての act を試すため、CI用のワークフローとは別に、ローカル実行専用のワークフローファイル local-tasks.yaml (名前は任意)を作成してみましょう。作成後は git commit をしておくと良いです。act側でgitのリビジョン情報などを取得しようとするため、未コミットの場合はWARNログなどでコンソールが埋まってしまうためです。
|
act -j greet で実行します。
初回はMicro/Medium/Largeのうち、どのランナーで動かすか? と聞かれますが、Medium で良いでしょう。
time act -j greet |
echo だけで3秒…。タスクランナーとしてこの時点で利用する可能性が厳しいのでは? とすでに感じますが、続けます。
続いて、リポジトリ上で ls -la をします。ローカル実行とは言え、実体はDockerコンテナ上で動作するため actions/checkout@v4 をする必要があります。先程の local-tasks.yaml の最後に以下を追加します。
+ list-files: # ファイル一覧を表示するジョブ |
act -j list-files で実行します。
time act -j list-files |
カレントディレクトリのファイル一覧が表示されました。微妙に実行時間は長くなりました。
ローカルでの実行にあたり、GitHubのシークレット (secrets.GITHUB_TOKEN など) が必要なワークフローの場合、act では –secret MY_SECRET=value や .secrets ファイルを使用してこれらを提供できます。タスクランナーとして使う場合、必ずしもシークレットが多用されるわけではありませんが、覚えておくと良いでしょう。
他のランナーで動かすとどうなるのか
Runners - act - User Guide でmicro, largeで利用しているイメージが記載されています。-P オプションで指定できるようです。
おそらく、最軽量のmicroで動かします。
time act -P ubuntu-latest=node:16-buster-slim -j greet |
少しだけ早くなりましたが、劇的に高速化とはならないようです。
続いて、large を動かしたかったのですが、イメージが上手く取得できなかったので試していません。ご存じの方がいましたら、Xなどで教えて下さい。
オフライン実行
act にはオフラインモードが存在します。ローカルにジョブコンテナやアクションのイメージがキャッシュされていれば、オフラインでも動作可能。原理的に高速化もされます。
time act --action-offline-mode -j greet |
2-3倍、性能が改善しました。体感上もこれなら待てます。続いて、checkout@v4 を含んだlist-filesを動かします。
e$ time act --action-offline-mode -j list-files |
こちらも2-3倍高速化しています。これならまだなんとかなるかもしれません。
それなりに歴史を重ねたリポジトリで動かしてみる
試したリポジトリサイズは以下です。size-pack がリモートサーバーにpushされた時のサイズとのことで、1.3GiB程度です。
git gc |
これで試してみます。 先程の act --action-offline-mode -j list-files を試してみます。
time act --action-offline-mode -j list-files |
チェックアウトだけで5秒程度が追加となり、全体で6.5秒程度。リポジトリサイズが増えると厳しい感じがしますね。
プロキシ、カスタム証明書の読み込みが難しい?
例えば、Goの環境を構築したい場合、以下のように actions/setup-go などを呼び出します。しかし、ローカル環境によってはエラーになります。
+ lint: |
エラーの例です。 failed to verify certificate: x509: certificate signed by unknown authority とカスタム証明書を利用している環境において、あるあるなエラーが出ています。
[Local Development Tasks/Format Go Files] Unable to clone https://github.com/actions/setup-go refs/heads/v5: Get "https://github.com/actions/setup-go/info/refs?service=git-upload-pack": tls: failed to verify certificate: x509: certificate signed by unknown authority |
カスタムイメージをビルドしたり、ルート証明書を読み込ませたり、SSL VERIFYを無効化などいろいろ試しましたが、残念ながら私の実力では未解決でした。もちろんこの課題が発生すること自体が組織のネットワークポリシー次第であり万人がハマるわけではありません。しかし、ランナーのコンテナが起動する分、環境セットアップが難しくなることは間違いなく、構造上、難易度が高くなるなという印象です。
ちなみに、 setup-go@v5 を利用せず、個別に定義を書けば成功できました(もはや、GitHub Actionsのお作法からは外れていますが)。
lint: |
なお、上記は curl の部分で -k をつけて簡易的に実装しています。プロダクションで用いる場合はカスタム証明書を読み込ませた方がよいでしょう。
actでリンター(go vet) を実行して成功した結果です。
time act -j lint |
厳しいのは、実行時間が1分40秒かかったというところでしょう。これは go vet を実行するためにコンパイルが必要なので、 go mod download 相当の処理が動くためです。go mod 側のキャッシュをボリュームマウントすれば高速化できると思いますが、逆に言うとそういったチューニングが必要だということです。
ちなみにもし、go vet が失敗(違反コードが存在)した場合は exit 1 でジョブが以下のように失敗します。
| # github.com/.../... |
成功/失敗の表示は、タスクランナーとして、特段大きな違和感は無いと思います。
フォーマットする場合
フォーマットやコード生成などの場合は、ホスト側のコードに反映させる必要があります。この場合、checkout 経由ですと、フォーマット結果を反映できず困ってしまいます。そのため、ボリュームマウントで対応します。ボリュームマウントするので、 checkout のステップは無くすことができます。
defaults.run.working-directory に適当なマウント先のフォルダを定義します。
+ fmt: |
実行時は --container-options でボリュームマウント定義を渡します。このオプションはドキュメントで探せなかったのですが、 https://github.com/nektos/act/issues/1548 のIssueから見つけました。
act コマンドを実行します。 --container-options "-v $(pwd):/workdir" の /workdir の値は、さきほどの local-tasks.yaml で指定した値と一致させます。
time act -j fmt --container-options "-v $(pwd):/workdir" |
checkout が無くなった分、高速化したのと、単純に gofmt を呼ぶだけ(コンパイルなどは不要)であるため、20秒で終わりました。--pull=false や --action-offline-mode をつければ、数秒早くできる可能性があります。ちなみに、ホスト上で直接 gofmt を呼び出す場合は1~2秒で終わります。
ボリュームマウントですが、ローカルでの実行速度を最優先に考えるのであれば、actions/checkout@v4 を呼び出さなくて済むため必須かもしれないと思いました。もちろん、代償として GitHub Actions とのコード共有・再利用性は下がります。
Makefileとの棲み分けは?
リンターやフォーマッタなど、具体的なコマンドはMakefile(Taskfile)に記載し、ローカル開発時にはそれらのタスクコマンドを単純呼び出しできるようにしておく方がデバッグもやりやすいかなと思います。CI/CD定義からはそれらのコマンドを呼び出すだけ、という構成にすると、定義が重複せず保守性を保てるでしょう。
このような棲み分けの概念を壊せないかと、CI/CD定義を直接ローカルで動かせる act を、タスクランナーとして使ってみようという試みでしたが、現時点ではプロキシ・カスタム証明書の問題が解決したとしても、実行時間のオーバーヘッドが大きく微妙です。そもそも、定義の共有自体が私の技術力では微妙な結果に終わってしまいました。そのため、この記事の結論としては、よくローカル開発で実行するコマンドは、 act 経由ではなく引き続きMakefileやTaskfileを利用する方が無難でしょう。
ボリュームマウントの定義などは煩雑なので、何ならMakefileにactの呼び出しコマンドを書いてしまいたいくらいです。
まとめ
act をタスクランナーとして試しました。
現時点で得た課題感は以下です。
- actでは全てのタスクが、コンテナ上で動くため、起動のオーバーヘッドが1~3数秒かかる
- 1GiB超えのリポジトリの場合は、チェックアウトのみでさらに5秒程度かかる
- 依存ライブラリの解決など毎回実行するには重い処理は、キャッシュが有効だが、そうするとホストとのボリュームマウントなど面倒なチューニングが必要となり、管理コストが上がる
- フォーマットやコード生成など、ホスト側のファイルを書き換えたい場合は
actions/checkout@v4を行わず、直接リポジトリごとボリュームマウントする必要があり、管理コストが上がる - プロキシ・カスタム証明書などを前提とする組織ネットワークでは、
actions/setup-goなどのコマンドがうまく動作しない可能性。そのため、プロキシ問題をトラブルシュート&解決できる人材・時間が必要
上記、チューニングや環境構築に成功したとしても、GitHub Actions側の workflows 定義の共有は難しく、結局、別のファイルとして管理することになりそうということでした。
act 側のナレッジをチームで積んでいけば、性能その他の課題は潰せそうですが、タスクランナーとして利用するのは、それなりの意思決定が必要になりそうな印象です。GitHub Actionsにある程度習熟した人であればもう少し別の見方になるかもしれません。積極的に導入しているよーという方やチームがいらっしゃいましたら、Xなどで教えてください。