GitHub Actions のコスト戦略
TLDR
- 開発体験が良くなると CI のコストも減る
- 不必要なジョブ実行を減らし、割れ窓を直すことから始めると良い
- Self-hosted runners ではクラウドコスト最適化の一般的なプラクティスも併用する
GitHub Actions のコスト構造
- GitHub-hosted runners
- Self-hosted runners
CI ジョブの特徴
- リソース使用量のバラツキが大きい
- 所要時間のバラツキが大きい
- CI では多様な処理が実行されるため、ジョブの所要時間は均一にならない。例えば、数秒で終わるジョブと数十分かかるジョブが同一ノードに配置される場合もある
- 待ち行列の増減が激しい
- Pull Request の作成やマージなどのアクションを契機に多数のジョブが実行される
- 一方で、夜間や休日などほとんどジョブが実行されない時間帯もある
- 成功するまで再実行される
- 一般的なブランチ戦略では CI が品質ゲートとなっているので、CI が成功するまで Pull Request をマージできない
- Flaky な CI は開発体験を阻害し、コストを浪費する
Self-hosted runners のコスト戦略
ここでは Kubernetes (AWS EKS / EC2) で actions-runner-controller を運用している前提を考える。他のパブリッククラウドでも同様の考え方が使えると思う。
AWS のコスト構造は以下のようになる。
- 固定費
- Kubernetes クラスタやその運用に必要なコンポーネントの実行コスト
- 変動費
- EC2 インスタンスの実行コスト
- ネットワークコスト
ネットワークコストは EC2 インスタンスを Public subnet に配置することで大幅に削減できる。そのため、支配的な EC2 インスタンスの実行コストについて考えていく。
コストの計測
同一インスタンスに多数なジョブが配置されて頻繁に入れ替わるため、ジョブ単位の厳密なコスト計算は難しい。
- インスタンスの開始から終了までの実行コストを、ジョブの所要時間やリソース割り当てで按分する
- 全インスタンスの実行コストを、各ジョブの所要時間で按分する
- (対象ジョブの所要時間 / 全ジョブの所要時間) × 全インスタンスの実行コスト
- 全インスタンスの実行コストは AWS Budget で簡単に計測できる
- ジョブの所要時間は https://github.com/int128/datadog-actions-metrics で計測できる
コストの最適化
EC2 インスタンスの実行コストは以下の契機に依存する。
コストを削減するには以下のアプローチがある。
- ジョブの不必要な実行を減らす
- 最も簡単にできる
- ワークフローの paths や branches を見直す
- Dependabot や Renovate などの Bot は大量のジョブが実行される原因になりうる
- ワークフローはコピペで増えていくので、割れ窓を直して conftest で違反を防ぐと、長期的に効いてくる
- Flaky なジョブの再実行を減らす
- 一般的なブランチ戦略では CI が失敗すると Pull Request をマージできない。成功するまでジョブを再実行する必要があるので、Flaky な CI はコストを浪費する
- テストケースの失敗率をモニタリングし、継続的にテストを改善する
- OOM もジョブが失敗する原因になるので、OOM をモニタリングし、継続的にリソース割り当てを改善する
- ジョブの所要時間を短くする
- テストケースの所要時間をモニタリングし、継続的にテストを改善する
- キャッシュが有効に活用されるように見直す
- ジョブに割り当てるリソースを減らす
- リソースを削減しすぎると所要時間が伸びる、OOM による再実行が増える、といった副作用がある
- 前述のようにモニタリングツールでリソース使用量のスパイクを捕捉することは難しい
- OOM 失敗率をモニタリングしながらメモリサイズを減らしていく
- インスタンスサイズや集積率の最適化