GitHub Actionsがエンジニアリングチームをじわじわ殺している
(iankduncan.com)- リポジトリに標準で含まれるCIという理由で広く使われているが、構造的な非効率と不安定なユーザー体験が開発者の生産性を低下させる
- ログビューアの遅い読み込みとブラウザクラッシュ、複雑なYAML構文と式エラーが反復的なデバッグを招く
- コンピュート資源を所有できない構造により、性能・拡張性・環境制御の面で限界が露呈する
- 多くの問題を回避しようとすると、複雑なYAMLや巨大なBashスクリプトでCIそのものを作り直す状況が繰り返される
- これに対してBuildkiteは、単純なYAML構造、セルフホスト可能なエージェント、実用的なログ体験を通じて、長期的に維持可能なCIの代替となる
GitHub Actionsの問題点
- GitHub Actionsのログビューアは非効率: 単純なエラー確認にも複数段階のクリックとページ読み込みが必要
- ビルド失敗時はチェック要約ページ → ワークフロー実行ページ → ジョブページ → 折りたたまれたステップのクリックまで、3〜4段階のページ遷移が必要で、各段階ごとに別々の読み込みが発生
- 大規模なビルドログでブラウザを繰り返しクラッシュさせ、検索機能を使うとChromeが固まる現象が再現できるレベル
- 長いログではスクロール自体が機能せず、結局生ログのアーティファクトをダウンロードしてテキストエディタで開く羽目になる
- 戻るボタンを押すと元のPRページではなく、予測不能なGitHub Actions UIページへ移動し、ブラウザ履歴がActionsのURLで埋まっていく
- デバッグ過程の非生産性
- 環境変数を確認するために
run: envステップを追加して再度プッシュすると、20分のフィードバックループが発生し、1行の変更にこの過程を十数回繰り返すことになる - 20分単位のフィードバックループが繰り返され、1日の業務がCI待ち時間に消えていく
- 環境変数を確認するために
- YAMLの構造的限界
- GitHub ActionsのYAMLは、独自の式言語、コンテキストオブジェクトモデル、文字列補間ルールが結びついた特殊な形
${{ }}式で引用符を1つ間違えると、ランナーが起動するまで4分待った後でようやく文字列が失われていたことに気づく- 式構文は設定として書くには複雑すぎ、正式なプログラミング言語として使うには制約が多すぎる**境界領域(liminal space)**に存在する
- 構文をドキュメントではなく、失敗体験を通じて学習する構造になっている
- Marketplaceのセキュリティリスク
uses:構文で外部アクションを呼び出すと、リポジトリ、シークレット、ビルド環境へのアクセス権を検証されていないサードパーティに与えることになる- SHA固定(pinning)は可能だが実際にやる人はほとんどおらず、固定したとしても読んでいない不透明なコードを
GITHUB_TOKENアクセス権とともに実行する構造 - Marketplaceには品質がさまざまなコミュニティ管理アクションが混在し、その多くはシェルスクリプトとDockerfileで構成されている
- 依存関係の管理は不透明で、安全でないコードが実行される可能性がある
- コンピュート環境の制約
- GitHub Actionsの標準ランナーはMicrosoft所有の共有ランナーで、遅く、リソースが限られ、意味のあるカスタマイズはできない
- より大きなランナーのコストは、財務チームから「ちょっと話そうか」という会議招集が来るレベルで、それでも環境制御は不可能
- Namespace, Blacksmith, Actuated, Runs-on, BuildJetなど、GitHub Actionsランナーの遅さだけを解決することに特化したスタートアップが少なくとも6社以上存在し、それ自体が標準コンピューティング環境の不足を示している
- Self-hosted runnerを設定すればコンピュートの問題は解決できるが、YAML式、権限モデル、Marketplace、ログビューアなど残りの問題はそのまま残る
細かいが積み重なる問題
actions/cache: キャッシュキーが分かりにくく、キャッシュミスは静かに発生し、キャッシュ削除(eviction)は不透明で、キャッシュのデバッグに節約できる以上の時間を費やす- 再利用可能ワークフロー: 一定の深さ以上にはネストできず、呼び出し側ワークフローのコンテキストにきれいにアクセスできず、分離されたテストもできない
GITHUB_TOKEN権限モデル:permissions: write-allは広すぎる一方、細かな権限設定はリポジトリ・ワークフロー・ジョブレベル設定間の相互作用が迷路のように複雑- 同時実行(concurrency)制御: 同じブランチで進行中の実行を取り消すことは1行でできるが、それ以上の細かな制御はサポートされない
- シークレットを
if条件に使えない:if: secrets.DEPLOY_KEY != ''のような条件付き実行はできず、セキュリティ上は合理的だが、forkとメインリポジトリの両方で動くワークフローを書く際には回避策が必要
「とにかくBashスクリプトを書こう」の罠
- CIのYAMLに疲れたエンジニアには、すべてを
run:のbashスクリプトで置き換えようという誘惑があるが、時間が経つと条件分岐、関数、引数解析、並列処理が追加される - 3か月後には、800行のbashが
waitとPIDファイルでジョブ並列化を再実装し、独自の再試行ロジックや出力解析ロジックまで備えるようになる - 結局CIシステムから脱出したのではなく、テストフレームワークもなく誰も追えない、さらに悪いCIシステムをbashで自作しただけになる
- Bashは接着剤(glue)用途には適しているが、ビルドシステムやテストハーネスとして使うと、ガードレールのある場所からない場所へ複雑さを移す結果になる
Buildkiteの代替アプローチ
-
安定したログビューア
- Buildkiteのログビューアはブラウザをクラッシュさせず正常にログを表示し、ANSIカラーやテストフレームワークのフォーマットもそのままレンダリングされる
- Annotation機能により、ビルドステップがテスト失敗の要約、カバレッジレポート、デプロイリンクなどをビルドページに直接Markdownで出力できる
- エージェントが自前インフラで動くため、SSHでビルドマシンに接続して直接デバッグできる
-
単純なYAML構造
- BuildkiteのYAMLはパイプラインを記述する純粋なデータ構造で、ステップ、コマンド、プラグインだけを宣言する
- 実際のロジックが必要なら、ローカルで実行できる本物のプログラミング言語でスクリプトを書く
- 「オーケストレーションは設定に、ロジックはコードに」という境界を明確に保ち、GitHub Actionsが曖昧にしてしまうまさにその境界を守る
-
コンピュート環境の完全な制御権
- Buildkiteエージェントは単一バイナリとして、自社クラウド、オンプレミス、カスタムハードウェアのどこでも実行できる
- インスタンスタイプ、キャッシュ、ローカルストレージ、ネットワークを完全に制御でき、NVMeドライブと20GBのDockerレイヤーキャッシュを持つ大型EC2インスタンスからRaspberry Piまで対応する
- 「Buildkiteだけどもっと速い」サードパーティ産業は存在せず、単により大きいマシンを回せばよい
- 小規模なオープンソースライブラリを維持する個人メンテナーにとっては、GitHub Actionsのパブリックリポジトリ向け無料枠は依然として価値がある
- この記事の主な対象は、本番システムを運用するチームであり、CI時間が週あたりのエンジニアリング時間の損失として測定され、45分かかるビルドがコンピュート費用と人件費の両面でコストを生む環境
- そうした環境では、Buildkiteエージェント運用のオーバーヘッドはすぐに元が取れる
-
動的パイプラインのサポート
- Buildkiteではパイプラインステップはデータであり、スクリプトが実行時に動的に追加ステップを生成(emit)してアップロードできる
- モノレポで変更されたファイルに基づいて必要なビルド・テストステップだけを正確に生成できるため、ハードコードされたマトリクスや
if: contains(...)のスパゲティは不要 - GitHub Actionsの
matrix、if条件、再利用可能ワークフローはこれを近似(approximate)しようとするが、表現力の乏しい宣言的言語でルーブ・ゴールドバーグ・マシンを作ることになる
-
プラグイン構造の単純さ
- 構造的にはGitHub Actions Marketplaceと似ていて、サードパーティリポジトリからコードを取得する方式
- 違いは、BuildkiteプラグインはたいていDockerイメージではなく**薄いシェルフック(thin shell hook)**であり、表面積が小さく、数分で全体を読めること
- 自前インフラで動くため、**爆発半径(blast radius)**を利用者が制御できる
-
ユーザー体験重視の細かな機能
- Buildkiteはカスタム絵文字(
:parrot:,:docker:など)をパイプラインステップの横に表示でき、一見些細だが、製品利用体験への細やかな配慮を示している - GitHub Actionsは「これは楽しいか?」という問いを一度も発していないかのような委員会設計の産物
- Buildkiteはカスタム絵文字(
結論: CIシステム選定の基準
- GitHub Actionsは**デフォルト搭載(default)という利点で市場を支配しており、パブリックリポジトリは無料で、すでに皆が使っているプラットフォームに組み込まれていて、「十分に良い(Good Enough)」**水準にある
- CIのInternet Explorerのようなもので、移行コストは現実的で時間は有限であるため使われ続ける
- Buildkiteは継続的な使いやすさと開発者体験の面で優れている
- 単純なオープンソースプロジェクトにはGitHub Actionsで十分だが、大規模な本番環境ではBuildkiteのほうが適している
- CIシステムの歴史で市場シェアを取るのは、最も優れたシステムではなく、最も始めやすいシステムである
- GitHub Actionsは始めるのが最も簡単なCIであり、Buildkiteは使い続けるのに最も良いCIで、長期的には後者が重要
- CIツールが開発者の時間を浪費する構造なら、問題は開発者ではなくツールそのものにある
3件のコメント
CI が複雑化すること自体が問題のようですね。
同じ文章が二度投稿されたような気もしますが。AIが構成するには悪くない組み合わせに見える最近ですね..
Hacker Newsの意見
私はいくつものCIシステムを使ってきた。CircleCIとGitHub Actionsを多く使ったが、筆者とは結論が異なる
昔はJenkinsがJava専用、TravisがRails専用だったが、こうした特化型CIは結局行き止まりだった。今ではCIは単なるワークフローオーケストレーターへと進化している
CircleCI 2からGitHub Actionsへ移行した理由も、CircleCIがこの転換にうまく対応できなかったからだ。GHAには十分な表現力があった
ログブラウザやYAML文法のようなものは些細な問題だ。重要なのは計算資源の所有と動的パイプラインで、前者はどのCIでも可能で、後者はBuildkiteの強みだ
私の結論は、Actionsは実用上かなり良く、新しい会社を始めるならBuildkiteを、オープンソースにはActionsを使うということだ
ビルドグラフを理解していないと増分ビルドの状態を維持する必要があり、それが一時的なバグを引き起こす。だからUnrealEngine HordeやUBAのようにビルド構造を深く理解するシステムが必要だ
汎用CIを使うと、ビルドに1日以上かかることもある
私はGHAの良い部分だけを使うようにしている。たとえばGitHubはイベントディスパッチャーとしては優秀だが、ワークフローオーケストレーターとしては今ひとつなので、その部分は別のシステムに委ねている
1日に何十回も見るログが使いにくいと生産性が落ちる。生ログでエスケープコードを無視しながら読むのは本当に苦痛だ
私はシンプルさを保っている。すべてのオーケストレーションをdeploy.shスクリプトに入れ、ローカルのMacやAWS CodeBuildで実行している
YAMLは単に
bash deploy.shの1行だけだ。DockerコンテナさえあればAzureでもGitHub Actionsでもどこでも同じように動くすべてのCI環境における中核戦略は、ローカルと同じビルドシステムを用意することだ
私は常にMakefileから始める。Docker、CIビルド、lintなど、すべてをMakefileが駆動する。プロジェクトが大きくなれば別のツールへ移ることもあるが、基本は単一のトリガーツールだ
私はモバイルでFastlaneをよく使うが、ボイラープレートを減らし、構造を与えてくれる。結局Rubyなので必要なら抜け道もある
(GNU Make文書リンク)
結局「ただBashスクリプトを書け」という話で、不要な複雑さが追加されているだけだ
現在の会社でもローカルで全パイプラインを回せなくなり、MRを1つテストするのにビルドを10回ずつ回す巨大なCIインフラができている
私にはこの記事がNix/Buildkiteの広告のように感じられた
CIは単にスクリプトやビルドターゲットを実行する程度で十分だ。CIは環境と設定だけを提供し、ロジックはコード側にあるべきだ
こうすればCI独立性が生まれ、システム間の移行が容易になる
GitLab CIはこうした複雑さをうまく扱うほうだ。テンプレートとジョブ構成機能は優れているが、デバッグは難しく、条件ロジックがしばしば予期せず失敗する
.shファイルに入れていたチームは移行が非常に簡単だった一方、UIで細かく分割していたチームは苦労していた
問題はCI/CDそのものではなく、設定ファイルでプログラミングする文化だ
git commit -m "try fix"のあと10分待つループがあまりにも一般的だ。ローカルで再現可能なCI環境はいまだに不足している環境分離をポリシーとして設定すれば、どんなツールを使っても問題ない。結局はツールと方法論の調和が鍵だ
actのようなツールは、ローカルでCIを再現するのに大いに役立つ「エンジニアチームを殺す」というタイトルは大げさだ。GitHub Actionsは十分に良い
BitbucketやGitLabより好んでいる
最近はGitHubの信頼性低下が深刻だ
actions/checkoutが理由もなく失敗したり、リリースジョブが2回実行されたり、40分間ただ待たされることもある何年も使っているが、基本的な安定性が落ちてきている。Buildkiteを見逃したのが惜しい
GitHub Actionsは、私が使った中でも最悪クラスのCIツールの1つだ(Jenkinsと同格)
一方でBuildkiteは最高だ。動的パイプラインのおかげで、テスト失敗時に自動で再試行ステップを作成したり、コード変更に応じて並列テストを調整したりできる
各CIジョブごとに異なるマシン構成を使えるのも大きな利点だ。強く勧める
「シンプルなスクリプトベースCI」を主張する人たちは、大規模な実プロジェクトの経験がないのではないかと思う