2 ポイント 投稿者 GN⁺ 2025-03-21 | 1件のコメント | WhatsAppで共有
  • 複雑なCIをGitHub Actionsに戻す過程で、merge queue、複数のランナー、Rustビルド、Dockerイメージ、統合テストが絡み合い、設定そのものよりもデバッグのコストの方が大きくなった
  • main に入る変更はすべてテストを通過しなければならず、フォーマット・未使用依存関係・リンターのような 些細なエラーは自動修正 され、CI成果物はリリース成果物と同一である必要がある
  • merge queue の前後の検証をどちらも強制するには、2段階の ジョブ名を同一に そろえる必要があり、そうしないとチェックが止まったり、失敗した変更がマージされたりする可能性がある
  • GITHUB_TOKEN、workflow の permissions、カスタムトークン、fork・self-hosted runner の例外が絡み合い、セキュリティモデル を理解しづらく、ミスの余地が大きい
  • Dockerコンテナ実行、YAMLワークフロー、限定的なローカルテストのせいで開発速度は落ちたが、新しいCIスクリプトはマージ時間を大幅に短縮した

GitHub Actionsに戻った複雑なCI

  • この2週間、CIスクリプトをGitHub Actionsで書き直していた
  • 今回でCI構成の刷新は3回目
    • 最初はGitHub Actions
    • その後 Earthly に移行
    • Earthlyが終了したため、再びGitHub Actionsに戻った
  • 現在のCIは merge queue、複数のランナー、Rustビルド、Dockerイメージ、重い統合テストをまとめて扱っている
    • ランナーは self-hosted、blacksmith.sh、GitHub-hosted を併用している
    • 1つのPRをマージするたびに、複数の並列ランナーで合計約1時間のCI時間を消費する

CIが満たすべき条件

  • main に入るすべての変更は すべてのテスト を通過しなければならない
  • フォーマット、未使用依存関係、リンターの問題のような些細なミスは、失敗で終わるのではなく 自動修正 されるべき
  • CIでテストした成果物は、実際のリリース成果物と同一でなければならない
  • 開発者体験のため、CIは速く終わるべき
  • GitHub Actionsはこれらの条件を技術的にはサポートしているが、設定過程には隠れた落とし穴、一貫しない挙動、難しいデバッグがつきまとう

Merge queueとステータスチェック

  • クリーンな main ブランチを維持する鍵は、GitHubの merge queue にある
    • merge queue はCI実行前にPRを main の上へ rebase する
  • 必要なCI実行は2段階に分かれる
    • キューに入る前にCIを実行して些細な問題を自動修正する
    • キューの中で再度CIを実行し、最終的なマージ状態を検証する
  • GitHub Actionsで両方の実行を必須にするには、2段階の ジョブ名を同一に 指定しなければならない
    • GitHubが2つの実行を同じチェックとして扱い、両方が成功して初めてマージ可能になる
    • この方法は、数時間のデバッグの末に Stack Overflowの回答 から見つけた
  • 別のやり方では、ステータスチェックがキュー投入前に待機状態になってジョブが始まらなかったり、merge queue 内で失敗すべきジョブが失敗してもマージされたりする結果につながりうる

理解しづらいGitHub Actionsのセキュリティモデル

  • 最近、人気のGitHub Actionが 侵害された事件 の後、「依存関係をハッシュで固定せよ」という対応が出たが、コメント欄では実際にはほとんど誰もそうしていないという反応が続いた
  • GitHub Actionsにはデフォルトトークンである GITHUB_TOKEN がある
    • このトークンはデフォルト権限で初期化される
    • デフォルト権限はリポジトリ設定の Actions → General → Workflow Permissions で設定できる
  • GITHUB_TOKEN のデフォルト権限が制限的なら、必要な action やコマンド実行のために権限を引き上げる必要があり、デフォルト権限が寛容すぎるなら、workflow ファイル側で一部の権限を削ることができる
  • よりよいデフォルトは、権限なし から始めて必要な権限だけをユーザーが追加する方式だろう
  • 権限の種類は 多く、GitHubの専門家でなければ、それぞれが何を守っているのか把握しにくい

トークンと権限の例外

  • softprops/action-gh-release でGitHubリリースを自動生成するときは、カスタムトークン CI_RELEASE を使っている
- name: Release on GitHub
  if: env.version_exists == 'false'
  uses: softprops/action-gh-release@v2
  with:
    tag_name: v${{ env.CURRENT_VERSION }}
    generate_release_notes: true
    make_latest: true
    token: ${{ secrets.CI_RELEASE }}
  • デフォルトトークンでもリリース自体は完了するが、リリース後のワークフローはトリガーされない
    • 別に表示もないため、同じ問題に遭った人が残した issue を探して初めて原因が分かった
  • workflow YAML の中で権限を引き上げることもできる
    • 守りたいコードの中で権限を引き上げる構造には違和感がある
  • GitHubドキュメントによれば、permissions キーで fork リポジトリの読み取り権限を追加・削除できるが、通常は書き込み権限は付与できない
    • 例外は、管理者がGitHub Actions設定で Send write tokens to workflows from pull requests オプションを選んだ場合
  • 例外と落とし穴が多く、GitHub Actionsのセキュリティモデルは強力である一方、攻撃面とミスの可能性も広げている

Self-hosted runnerの不確実性

  • GitHubドキュメントは公開リポジトリで self-hosted runner を使うことを推奨していない
    • 公開リポジトリの fork がPR経由で self-hosted runner マシン上で危険なコードを実行できてしまうため
  • GitHubには、外部コントリビューターのPR実行を承認必須にする self-hosted runner 設定もある
  • この設定と self-hosted runner を併用すれば安全なのかについて、ドキュメントには明確な答えがなく、インターネット上でも合意がない
  • 複雑さが高く、100%の確信を持ちにくい状態のままになっている

DockerとGitHub Actionsの衝突

  • GitHub Actionsではジョブを コンテナ内で実行 できる
    • 毎回依存関係をインストールせず、dev container にあらかじめパッケージ化しておける利点がある
  • 実際に使うと ファイル権限 の問題が繰り返し起きる
    • コンテナはあるユーザーでファイルをビルドするが、GitHub runner は別の uid/gid で動くことがある
    • その結果、コンテナ内のファイルや GitHub workspace、一時ホストディレクトリにアクセスできなくなることがある
  • $HOME ディレクトリも食い違うことがある
    • dev container は /home/ubuntu にツールをインストールできる
    • GitHub Actions内では $HOME/github/home になることがある
    • $HOME 配下のファイルに依存するツールは、必要なファイルを見つけられないことがある
  • ホストシステムと相互作用する action も壊れうる
    • GitHubキャッシュは10GBに制限されているため、blacksmith の sticky disk action でキャッシュ用NVMeドライブをマウントしている
    • コンテナ内では動作せず、blacksmith.sh の修正後に解決した
  • container フィールド自体にも制限がある
    • entrypoint を override できない
    • 一部の step だけをコンテナ内で実行し、残りを外で実行する方式も不可能

YAMLワークフローの開発とデバッグ

  • GitHub ActionsのロジックはYAMLで書かれ、複雑になるほどミスしやすい
  • RustRoverのGitHub YAML linter チェックは役立ったが、よりよい 静的検査 が必要
  • ローカルでは実際のCI挙動を十分に試しにくい
    • act は有名なツールだが、CIでやりたいことのうち一部しか対応していない
  • 最善のデバッグ方法は、同じリポジトリをもう1つ作り、CIが期待通りに動くまで git commit -a -m "wip" && git push test-ci branch を繰り返すことだった

ワークフロー分割と成果物の再利用

  • 毎回CIパイプライン全体を回さないように、個々の workflow は小さく保っている
  • 各 workflow の最後で成果物をアップロードし、その後の workflow がそれをダウンロードして最初から再ビルドしないようにしている
  • 前回実行の成果物をダウンロードして workflow を分離テストすることもできる
    • ただし前回実行から取得する際は、download-artifact action にトークンを渡す必要がある
    • このトークンはデフォルトトークンでもよいが、なぜそれでも明示が必要なのかは未解決の疑問として残っている
  • メインの workflow ファイルは、他のYAMLファイルを呼び出すチェーンになる
jobs:
  invoke-build-rust:
    name: Build Rust
    uses: ./.github/workflows/build-rust.yml
  invoke-build-java:
    name: Build Java
    uses: ./.github/workflows/build-java.yml
  invoke-tests-unit:
    name: Unit Tests
    needs: [invoke-build-rust, invoke-build-java]
    uses: ./.github/workflows/test-unit.yml
  invoke-tests-adapter:
    name: Adapter Tests
    needs: [invoke-build-rust]
    uses: ./.github/workflows/test-adapters.yml
    secrets: inherit
  invoke-build-docker:
    name: Build Docker
    needs: [invoke-build-rust, invoke-build-java]
    uses: ./.github/workflows/build-docker.yml
  invoke-tests-integration:
    name: Integration Tests
    needs: [invoke-build-docker]
    uses: ./.github/workflows/test-integration.yml
  invoke-tests-java:
    name: Java Tests
    needs: [invoke-build-java]
    uses: ./.github/workflows/test-java.yml
  • 一部のジョブには secrets: inherit が必要
    • workflow が別の workflow を呼び出すと、secret はデフォルトでは共有されない
    • CIパイプライン全体では失敗するのに、個別の step 実行では動く問題の原因だった

速くなったマージ、それでも高いデバッグコスト

  • 新しいCIスクリプト によってマージ時間は大幅に短縮され、結果には満足している
  • ただし、その状態にたどり着くまでにあまりにも多くの時間がかかり、問題が起きたときはもっとデバッグしやすくあるべきだ

1件のコメント

 
GN⁺ 2025-03-21
Hacker Newsの意見
  • GitLabのほうがましだという話がもう出ているけれど、それは正しい。ただし別の形でいまいちではある
    Jenkins、GitLab、GitHubを経験した結論は、CIロジックはできるだけ自分たちのコードとして持つのがよい、ということ
    shell script、make、just、doit、mage、何でも構わないが、保守可能なきちんとしたコードであるべき
    パイプラインは可能な限り開発者のローカルマシンでも実行できるよう投資すべきで、そうでないとテストとデバッグが悪夢になる
    YAMLはできるだけ避け、いずれ収益化しなければならないVC投資ベースの「CIを永遠に解決する」新しいツールに縛られないほうがよい
    可能ならオンプレミスで自前のランナーを使うほうがよい

    • 数年前、最初のTech Leadの役割で、偶然まったく同じ結論にたどり着いた
      大規模なエンタープライズCMSプロジェクトで、クライアントはホスティングプラットフォームのセキュリティ上、デプロイ自動化は不可能だと言い、特定のサポートエンジニアが複雑なrun sheetに従ってコードと設定をすべて手動デプロイしていた
      最初は自分のローカルサーバーにパッケージング・デプロイするbashスクリプトを作って自分の苦痛を減らし、その後チームに共有して彼らの問題も解決した
      バグが取れた後は、ローカルから開発インスタンスへデプロイするよう変更し、Jenkinsにも同じbashスクリプトを呼ばせて、dev/testまで完全なCI/CDが動くようになった
      最終的にはプラットフォームサポート担当者も手動のrun sheetにうんざりし、成熟したスクリプトでstage/prodデプロイを自動化した。クライアントが後で方針を無視したことを知ったときには、ローカルからprodまで反復可能でエラーのない自動デプロイができていて、とても満足していた
    • NixとBazelのように強く隔離されたシステムは、ローカルでの再現性を簡単に確保するのに優れている
      どのCI「プラットフォーム」も、並列実行の可視化、Groovy/JSでのAPI呼び出し、通知・バッジ生成といったもののために、作業をステップに分割するよう誘惑してくる
      かわいらしくはあるが、結局は尻尾が犬を振っているようなもので、ビルドを管理し順序を決めるべきなのはGUIではなく、基本のビルドツールであるべき
      次世代のCIは、ローカルファーストのツールに深くフックできるとよい
      CIに複数の「ステップ」を定義させるのではなく、ビルドツールと通信して、ビルドツールが何をしているのか、その順序と個別ログを見せればよい
      テストランナーも同じで、自前の不透明な並列化方式を使い、最後にXMLで吐き出されたものだけ見られるという現状はもどかしい
      テストランナーがforkしたプロセス、各ログファイルの場所、終了ステータスをCIに伝えられるべき
    • 半導体業界では、すべてがローカルホスティングのJenkins + bashスクリプトだった
      JenkinsジョブはPerforceに保存されたbashスクリプトを実行するだけなので、ローカルで動かすには同じスクリプトを実行すればよかった
      初めてWeb SaaSスタートアップに入ったときはカルチャーショックが大きく、すべてがそれぞれ独自の設定・言語を持つサードパーティサービス上で動いていた
      POSIX/Linuxのような基礎知識はほとんど役に立たなくなり、今では慣れたものの、より良くなったと確信はできない
      抽象化レイヤーが多すぎて、全体を本当に理解している人がいるのか分からない
    • ローカルランナー基盤のCI/CDフローにかなり投資してきた
      最初はgogsとdroneを使い、今はforgejoとwoodpecker CIのフォークを使っている
      分散したハードウェアに複製しやすい構成なので、複数の冗長性を持たせて運用している
      少し痛い点は認証とシステム間のpull requestで、feature branchのマージとコードレビューには依然として単一障害点が必要になる
      すべてをGoでビルドするため、常に/toolchain/build.goを置くことにしており、CI/CDのpodman/dockerイメージでもbashなしで単一言語だけを使うようにしている
      FROM scratchにGoだけを入れる形で、例外はeBPFカーネルモジュールをコンパイルするか再ビルドする必要がある場合だけ
      個人的には2024年8月のGitHub Actions CVEが決定打で、技術的により詳しくhttps://cookie.engineer/weblog/articles/malware-insights-git...に書いた
      先週TJ actionsが侵害された理由も、GitHubが1年後でも修正していない同じ攻撃面だった
      こうした脆弱性まで検証するツールは、知る限りzizmorだけで、ほかのツールはスキーマを検証するだけで脆弱性や弱点は検証しない: https://github.com/woodruffw/zizmor
    • 可能ならGitHub ActionsはMakefileを包む薄いラッパーとしてだけ使っており、それ以降、体験はずっと良くなった
      Makefileが必要な依存関係のインストールと関連するビルド・テストコマンドを処理するので、このスレッドのほかのコメントで言われているような長いフィードバックループなしに、ローカルでも再テストできる
  • HNの面白さはまさにこういうところにある
    GitHub ActionsとDevOpsがそんなに広く嫌われていると読むのは、本当に興味深かった
    どちらも何年もほとんど問題なく使ってきたし、実際に提供してくれる価値を気に入っている
    このスレッドの外では、こんなに多くの人が嫌っているとは考えたこともなかった
    Actionsの設定とテストが少し面倒なのは確かだし、再実行するためにほとんど意味のないコミットをしなければならないのもいらだたしい
    ただ、一度動けばそのまま設定して忘れておけるし、Nodeのバージョン更新以外では4年ほどワークフローをほとんど触っていない
    たぶん自分の要件が十分に単純なので、もっと複雑な問題にぶつかっていないのだと思う

    • 何をするかによって大きく変わる
      GitHub CIは、コンパイルがないか非常に安上がりな言語、比較的よく隔離されたプロジェクト、複雑な統合テストがない場合、強いコンプライアンス強制が不要な場合、開発者権限がおおむね同じ場合、そして高速なCIではうまく合うように設計されているほうだ
      ここから外れた瞬間にだんだん崩れ始め、上の制約に合わない会社がGitHub Actionsで問題を抱え続けるのを見てきた
      さらに悪いのは、すでにGitHubに費用を払っていると安い、あるいは無料のように見え、追加契約・請求・サードパーティ審査・自前のCIサービス運用が不要に見える点だ
      そのため、問題を起こし続けても、初期段階では会社にとってより「安い」解決策のように見える
      後になって自前のGitHub runnerを構成する必要があると気づくと、開発者がCI問題の修正に費やす時間まできちんと計算した場合、まったく安くないこともある
      それでも、すでにGitHub Actionsを動かすのに多くの時間を使っており、移行するにはすべて移植しなければならないので、サンクコストの誤謬も生じる
      現実的には、他のCIソリューションも少しマシな程度であることが多い
    • GH actionsを使っているが、すべてのビルドシステムと同じく、得意なことだけを任せ、残りはshell scriptや別のDockerコンテナに置くべきだ
      複雑になったら「このスクリプトを実行する」に単純化するほうがよく、数千行のYAMLよりスクリプトのほうがはるかに書きやすくデバッグしやすい
      問題はGitHub Actionsそのものではなく、ビルド・CIシステムにあらゆるカスタムの寄せ集めを過剰に詰め込む人たちだ
      20年前のAntやHudsonでも同じことをしようとすれば大変だっただろうし、理由も同じだ
      こうしたシステムはbashの代替としてはあまり良くない
      AntはJavaでMavenやGradleに移る前に人気があったビルドシステムで、Mavenファイルがプラグインで複雑なことをしようとして、bashなら2〜3行で済むことを作ってしまう例をよく見た
      Gradleも別に良くはなく、Antには少なくとも「何かをする」ための単純なプリミティブ操作があったが、それをすべてXMLで書かなければならなかった
      ビルドとCIシステムは主にソフトウェアのビルドのような単純なことをするべきで、条件ロジック・カスタムの副作用・月や星の位置次第で起きるかもしれず起きないかもしれない妙な動作が増えると、失敗時のデバッグが本当に苦痛になる
      YAMLにはYAMLジェネレーターが役に立つ
      Kotlinベースのジェネレーターをしばらく使っていたが、補完、構文の安定性、型チェック、コンパイルできれば実行されるという利点があり、新しいパラメータやプラグインのバージョン更新も見つけやすかった
    • 複数の組織を内側から数多く見る事業をしていると、DevOpsで苦労している現実を目にしたとき、まずできるだけ単純化し、サードパーティプロバイダーへの依存を取り除いて、GitHub Actionsのhello worldレベルのように再び動く状態に戻そうとする
      不満を言うチームはたいてい、HN、subreddit、Discordで見たものの影響で過剰設計し、不要なものを入れていることが多い
      苦労しているチームに入ってコミットや構成を見ると、過去1年でパッケージマネージャーやバンドラーを5回変えた痕跡のようなものが出てくることもある
      10年以上のコードベースにJS、TS、Python、Go、Rustが混在していて、理由を聞くと性能のためだと言うが、ほとんどの業務アプリで言語がボトルネックだったことはほぼない
      複数部門または全社規模の数百万ドル級プロジェクトでも、データベース構成がどれほどひどいかを見れば驚く人は多いだろう
      大企業の「悪いJava/Oracle IT」が許可しないため、部門予算で自前運用していることが多く、こうした長続きしない新しいツールに多額の資金が流れている
    • 他の人たちと同じ痛点を経験しているようで、ただそれをよりうまく無視しているだけに見える
      現状より良くできるという原文筆者の側に同意する
    • 誤解していなければ、Actionsをトリガーするために無駄なコミットを作る必要はなく、workflow_dispatchを使える
  • まだ触れられていないことが一つある。それは最悪のフィードバックループ
    30〜60秒のフィードバックループほど悪いものはないと感じる
    画面に張り付かせるのに、生産性はまったくない時間だ
    GHA環境をローカルに再現しようと長いこと試したが、自分の文脈では不可能だった。そのため、すべての変更が「pushし、GHが拾うまで待ち、些細なタイプミスや不一致に対処し、繰り返す」になってしまう
    「今回こそは回るだろう」というスロットマシンのように、集中力と時間を食いつぶす
    5秒のビルドプロセスを得るのに25分かかり、GHAで素朴にビルドすると依存関係のせいで3分かかる
    キャッシュを追加してみようとなると10時間が飛ぶ
    失敗のコストと集中力の低下がものすごい

    • このつらさにはとても共感する
      GitHub Actionのコンテナビルドをデバッグするのに、ビルドごとに40分以上かかると、一日中、5〜6回の変更をテストするだけで終わってしまうことがある
      もっと良い方法があるはずなのに、なぜまだ誰も解決できていないのかわからない
    • GitLabでは https://github.com/firecow/gitlab-ci-local がGitLabパイプライン作業で途方もない時間節約ツールだった
      GitHubの https://github.com/nektos/act に似ている
      ただ、GitLab/GitHubが標準でこうしたやり方を提供してくれるとよいのだが
    • 大きなフラストレーションのかなりの部分は、まさにひどいフィードバックループと、それが浪費する精神的な余白だ
      原文も終盤で、終わりのない「wip」コミットの面倒を見ざるを得ない問題を扱っている
    • nektos/actを強くおすすめする
      十分に複雑なジョブなら、サーバーにSSHで入って調査することもできるし、それを助けるactionもたくさんある
    • 最近はLLMを多くのことに使っているが、もしかすると最も重要な用途は、こうした中くらいの長さの非同期作業における集中力維持装置として使うことかもしれない
      数分より長ければ待っている間に別の作業へ切り替えられるが、3〜10分の待ち時間は耐えがたい
      集中を失うには十分長いが、コンテキストスイッチするには十分長くないからだ
      今では、待っている間にLLMと作業に関する話をして集中を保てる
  • 10年間GitLabを使う会社で働き、runnerに慣れていた
    最近GitHubを使う会社に移り、規模が大きいので提供機能に感心するだろうと思っていた
    実際に驚きはしたが、期待していた方向ではなく、比べると本当にひどくて、どうしてこんな状態になったのか混乱している
    会社を運営していてどちらかを選ばなければならないなら、GitHub Actionsのせいで毎回GitLabを選ぶだろう

    • 自分だけではなくてよかった
      GitLab runnersはただ理解しやすい
      スクリプトを実行するコンテナだ
      個人プロジェクトでGitHub actionsを少し使っているが、なぜか設定がはるかに混乱する
    • 数年前にあきらめて、もっと単純なGHA互換エージェントを作りたいと思った
      最初の目標の一つは一次的な進捗UI体験を持つことだったが、コード内でGitHub APIをどこで呼んでいるのかすら見つけられなかった
      どこで聞いたのか覚えていないので強い伝聞注意が必要だが、GitHub内部でも誰も把握していないという話があった
  • GitHub Actionsは高速に反復改善していた初期には良かったが、今はGitHubが関心を失い、改善がほとんど止まったように見える
    最近Actionsがどれほど少ない関心しか受けていないか、本当に残念だ
    <https://github.com/orgs/community/discussions/categories/act...>を見ると、最も人気のあるIssueがまったく回答されないままになっている
    Earthlyが開発を中止し、DaggerがAI列車に乗ったのも残念だ
    きちんとした代替が出てくることを願っている
    関連して https://www.blacksmith.sh/ を検討するなら、https://depot.dev/ も必ず見るべきだ
    どちらも評価したがDepotを選んだ理由は、チームが非常に賢く、かなり見事な難問を解決していたからだ
    特にキャッシュが標準の actions/cache actionと動作するので、人気のあるサードパーティactionをパッチ済みのものに置き換える必要がまったくない点が良い

    • DaggerのCEOです
      Daggerの新しいユースケースであるAIエージェント実行を宣伝していますが、本来のユースケースである複雑なビルドやテスト実行も引き続きサポートしています
      Daggerは常に汎用エンジンであり、コミュニティもCI以外の用途で使い続けてきました
      エンジン、CLI、SDK、可観測性スタックはそのままで、製品を中止するのではなく、むしろプラットフォーム上のワークロードを増やして、すべてのユーザーに利益が出る方向です
    • 11月のレイオフでGH actionsチームの多くが影響を受けた
      例: https://github.com/actions/runner/pull/2477#issuecomment-244...
    • おそらくGHがActionsの価格を低く設定しすぎたため、利用量がさらに増えても売上が伸びず、改善する価値が低くなったのが問題に見える
      そして誰もがActionsの価格を基準にしたことで、他社の価格も下がることになったようだ
    • Earthlyが開発を中止したというニュースを見逃したのかもしれないが、関連する内容を見つけられなかった
      何があったのか気になる
    • 先週Depotに移行した
      Rustのビルドが20分以上から4〜8分に短縮され、簡単な設定と高速なキャッシュが付いたDockerビルドが本当に良い
  • 「フォーマット、未使用の依存関係、lint の問題のような些細なミスは失敗にせず、自動修正すべきだ」というのを本当にベストプラクティスだと見なすのか? 同意しない
    CI に自分のコードを触ってほしいとはまったく思わない
    CI が自分のコードに加えたかもしれないし、加えていないかもしれない変更の上に rebase しなければならない、ということを覚えていたくない
    すべての linter が自動修正できるわけでもないので、結局一部は自分のノート PC で直さなければならない
    些細なチェックなら、そもそも pre-commit hook で回すべきだ
    次は CI が失敗したテストケースを LLM で自動修正すべきなのか?
    実際に CI が何かを自動修正するのを好む人がいるのか気になる

    • この点で原文は脱線したように思う
      CI が走っている同じブランチに再度コミットするのは、どの CI システムでも落とし穴が多すぎる
      最初の問題は、リモートブランチがローカルブランチから予期せず即座にずれること、そしてそのコミットが追加の CI 実行をトリガーしないよう気を配らなければならないことだ
    • そういうことは pre-commit で処理する
      CI でやるのは、コミットを push した後にリモートブランチへ再び合わせる複雑さを増やすだけに聞こえる
      最悪の場合、ローカルでは動くコードを実際に壊してしまうこともあり得る
    • その部分を見た瞬間に記事から離脱した
      災いを招くやり方で、新しい失敗モードを大量に持ち込む不要な複雑さに聞こえる
      ベストプラクティスではない
      PR の些細なミスは、ほとんど常にもっと大きな誤りの兆候だ
    • CI の自動修正は初めて試しているところで、初期の印象はまちまちだ
      コードに手を入れられるのは腹立たしいが、ときには PR が CI システムをさらに通過して、後でより有用なフィードバックを生むこともある
      その後、別のやり方で修正したブランチを force push することが多いので、CI の自動修正内容は squash するか、別の方法で取り込んで一緒に畳み込む
      自動修正の大半は単に cargo fmt の実行だ
  • 興味深い記事で、著者がすぐに思い浮かべた痛点や荒い部分をよく示していた
    ただ、自分の経験ではこのリストは決して完全ではなく、実際にはもっと多い
    GitHub の 10GB キャッシュ制限、runner の種類に基づく同時実行制限、より大きな GitHub runner の高額な価格などがあり、セキュリティ問題はその次だ
    この 2.5 年間 Depot[0] を作りながら、人々が YAML ワークフローを無理やり曲げて使う様子を見るまで気づけない GitHub Actions の落とし穴が本当に多いことを知った
    特に container job には驚いた
    人々はビルドのために再現可能な CI サンドボックスを作ろうとして使うが、権限は奇妙で、Docker layer caching は遅く制限があり、パスも思った通りに動かない
    Depot は GitHub Actions をはるかに高速にし、こうした荒い部分をできるだけ取り除くことに注力してきた
    最初は Docker イメージのビルドを大幅に高速化することから始め、今ではそのアーキテクチャと性能を自社の GHA runners[1] にも持ち込んでいる
    runner 周辺のコンピュートとプロセスを構築・最適化してジョブを非常に高速にし、別途 cache action を置き換えたり使ったりしなくてもキャッシュを 2〜10 倍速くする
    Docker イメージビルダーも高速キャッシュを備えた専用コンピュートのすぐ隣にあるため、イメージを素早くビルドでき、そのイメージをレジストリからそのままビルドジョブで使えるので、container job ははるかに良くなる
    全体として GHA は非常に人気があるが、最大のファンでさえ、もっとずっと良くできると感じている雰囲気だ
    [0] https://depot.dev/
    [1] https://depot.dev/products/github-actions

    • どの基準で「指数関数的に速い」というのか気になる
      GH がワークフローのステップ数に対して指数時間を要するわけではないはずだ
    • Depot は良さそうだが、かなり高く見える
      開発者 10 人の小さな B2B 企業でも、ビルドと CI だけで 200+500、つまり月 $700 になる
      速度向上が本当に必要なら妥当かもしれないが、より良い GHA 体験も提供しているなら、ものすごい速度を必ずしも必要としないチーム向けの別料金プランがあるとよさそうだ
    • Depot は素晴らしい
      強くおすすめできる
      runner を変えただけでビルドが 5 分以上から 1分 に縮むのは魔法のようだ
    • 「GitHub の 10GB キャッシュ制限」だって
      10,000,000,000 バイトなら誰にとっても十分なはずだ
      本当に大量のバイトだ
  • 数日前、人気のある GitHub Action が侵害され、対策は「依存関係を hash に固定せよ」だった
    しかしコメントでも指摘されていたように、ほとんど誰もそうしていない
    金融サービスのアプリを作る際に GitHub Actions を使っていたので、Action の依存関係は当然 hash で指定していた
    これがデフォルト、あるいは必須の方法であるべきだという点には同意するが、自分のリスクを語りながら「ほとんど誰もやっていない」と言うのは弱い言い訳
    他人が何をしているかは、自分が選べる選択肢とは無関係
    Actions を取り込むときは hash に固定すべきで、そのほうがはるかに安全

    • HN コミュニティは数日前、かなり学ぶところがあったように思う
      「デフォルトは重要だ」とはよく言われるが、誰もが推奨するパターンやサンプル文書も重要だというのも同じくらい真実
      より深く理解している、あるいは痛い目を見た賢い個人なら正しい判断ができても、ユーザー集団はデフォルトの利用パターンに大きく影響され、苦労して学ぶか教育される必要がある
      データを食いつぶしてしまう MySQL のよくある利用パターンを批判できるのと同じように、GH Actions の使い方も批判できる
    • 依存関係の固定は、ある問題を別の問題と引き換えること
      しばらくの間、ビルドは期待どおりに動くだろうが、その期間はいずれ終わる
      ある日、固定した依存関係を更新しなければならなくなったとき、複数のメジャーバージョンを一気にまたぎ、破壊的変更やパイプラインの残りに及ぶ連鎖的な影響に対処しなければならないかもしれない
      依存関係の rolling update を許可すると、ソフトウェアの寿命を通じてこうした保守作業を小さく管理可能なまま保つ助けになる
    • パッケージマネージャーの lockfile のような 推移的ロック がない
      good/foo@hash に依存していても、その先が bad/hacked@v1 に依存しており、V1 が悪意あるバージョンへ差し替えられたらやられる
      composite actions の場合がそうで、JS actions でも依存関係をロックせず、action の設定時点で最新パッケージを取得するなら同じ問題が起きる
      すべてを推移的に fork して自分で固定し、更新し続ける必要がある
    • renovate を使っていて、hash を自動で固定し更新してくれる
      怠けていても renovate の PR が開いたら新しい hash だけ確認すればよい: https://docs.renovatebot.com/modules/manager/github-actions/...
  • CI をローカルでテストできるようにする最善の方法は、今のところチェックを Nix で実行可能にし、CI 設定自体はできるだけ単純に保って Nix だけを呼び出すことだった
    CI 設定の boilerplate を減らすという意味で、GitHub Actions は関数をサポートするプログラミング言語
    ただし関数呼び出しはプログラム内の非常に限られた場所、つまり steps の中でしか使えず、関数を定義するには Git リポジトリを作らなければならない
    関数呼び出しの構文も少し独特で、uses キーワードを使う
    そのため、この方法では取り除けない boilerplate が多いが、GitHub Actions の中には一部を解決する複数の YAML eDSL が隠れている
    たとえば matrix で繰り返しを作れるが、汎用的な繰り返しではなく、非常に特定の構文位置にしか現れない
    YAML ブロックをコピー&ペーストせず本当に重複をなくすには、以前は Nix と Python で JSON を生成していた
    今は RCL を使っている: https://rcl-lang.org
    これらはいずれも、ループや関数呼び出しを望む場所ならどこにでも置ける汎用 YAML 重複排除器

  • たいてい GitHub Actions を使うのは、使わざるを得ないから
    私の経験では、最善の戦略は利用を最小限にすること
    バイナリや shell script を呼び出し、GHA の世界への依存をできるだけ減らせば、ローカルテストもしやすくなる

    • 私もそうしている
      ロジックの 90% を Go バイナリに入れ、GitHub Actions は特定の段階でそれを呼び出すだけ
      結局 GHA には、まだ得意な仕事である パイプライン用のローカル UI 提供だけが残る
      一番良い点は、単体テストができ、自分のパイプラインでそのツールを直接試せて、CLI さえ手元にあればローカルでも実行できること
    • マイグレーションも楽になる
      GitHub や GitLab などはソースコードホスティングとイベントトリガーのプラットフォームとしてだけ置き、そのイベントをどう処理するかは自分で決めるほうがよい
      CI 自体は、アプリケーションコードの薄い CI レイヤーが呼び出して使う機能を提供する、別のソース管理リポジトリであるべき
      そうすれば、かなり現実的な方法で CI をローカル実行できる
      Jenkins pipeline で使う Groovy CI ライブラリで似たことをやってみたが、多くの部分が Jenkins 前提で、それほど単純ではなかった
      特定の基盤プラットフォームを前提にしない、もっとすっきりしたオープンソースの選択肢があるのか気になる
    • 「たいてい強制されるから使う」というのは Teams のようなケースに似ている