2 ポイント 投稿者 GN⁺ 2023-09-24 | 1件のコメント | WhatsAppで共有
  • GitHub Actionsは生産性と機能面で有用だが、実際のワークフロー作成では デバッグの遅さ、セキュリティ上のミス、型の欠如、公式アクション不足が繰り返し時間のロスにつながる
  • 小さな修正でも git add; git commit; git push とブラウザでのログ確認を繰り返す必要があり、単純なリリースワークフローでも 4回のコミットと失敗したリリース が必要だった事例がある
  • ${{... }} 展開、pull_request_target、広すぎる GITHUB_TOKEN のデフォルト権限、fork の SHA 参照はいずれもミスしやすいセキュリティ上のポイントで、とくに fork SHA は意図したリポジトリのコミットのように見えることがある
  • アクション入力には type: 検証がなく、workflow_callworkflow_dispatch とサポート範囲も異なるため、配列・オブジェクト入力の代わりに CSV風文字列 やフラット化した入力を自分で処理しなければならない
  • push 時点の検証、ランタイムのセキュリティチェック、個人リポジトリでのデフォルトトークン権限の縮小、より厳格な型チェック、さらに多くの公式・準公式アクションが、ワークフローの信頼性を高められる

有用だが繰り返し足を引っ張る GitHub Actions

  • GitHub Actionsは2019年から、プロのプロジェクトでも趣味のプロジェクトでも毎日使うほど、生産性と安心感に大きく貢献してきたツール
  • 機能拡張も継続的に行われてきた
  • それでも実際の開発プロセスでは 大きなフラストレーションと時間の損失 の原因にもなる

デバッグは小さなミスにも重すぎる

  • リリースワークフローを設定する過程で、小さなミスを直すために 4回のコミット4回の失敗したリリース が必要だった事例がある
    • 必要な箇所で ${{ ... }} を使っていなかった
    • needs: の関係を入れ忘れた
  • 現在のデバッグのサイクルは、単純なミス1つを確認するだけでも手順が多い
    • 開発環境からブラウザに切り替える必要がある
    • 正しいタブを探さなければならない
    • Actions の概要とステータス画面をクリックして開かなければならない
    • バッファされたコンソールログを更新しながら次のエラーを探さなければならない
  • 小さな変更でも全体で 30秒以上 かかることがある
  • 改善の方向性

    • 対話型デバッグシェル を提供するか、少なくとも git add; git commit; git push なしで小さな変更を加えてワークフローを再実行できるようにすべき
    • リポジトリ設定で、明らかに誤ったワークフローを push 時点で拒否できるべき
      • 実行不能な構文
      • 存在しない job や step への参照
      • 不正な YAML のせいでワークフローが静かに実行されない状況

セキュリティ上のミスはあまりに起こりやすい

  • GitHub Actionsでは、ユーザーが書いたワークフローが意図せず脆弱になりやすい
  • ${{ ... }} 展開をシェルや別の実行コンテキストで使う場合、展開される値がユーザー制御の入力なら、悪意あるコード注入につながり得る
    • 例では inputs.frob を通じてコードが注入され、$MY_IMPORTANT_SECRET が漏えいし得る
  • pull_request_target は、単純ではないワークフローで安全に使うのが非常に難しい
    • 想定される用途は、fork から来た PR にラベルを付けたりコメントしたりするような限定的なものに見える
    • しかし実際には大きな foot-gun のように露出している
  • ワークフローおよび job レベルの権限も過剰に設定されやすい
    • 通常の GITHUB_TOKEN のデフォルトアクセス権は非常に広い
    • 個人アカウントのリポジトリでは、デフォルトトークン権限の縮小を忘れがち
    • 必要な権限範囲を正確に把握できず、トークン権限を過剰に付与してしまいがち
  • GitHub の権限モデルが read/write/none という単一軸に押し込められていることも混乱を増やしている
    • id-token: write でワークフローの OpenID Connect トークンを読めるようになる
    • 一部の security advisory の GET 操作は write 権限を要求し、別の操作は read のみで済む

SHA 固定アクションは fork のコミットと見分けがつきにくい

  • actions/checkout@c7d749a2d57b4b375d1ebcd17cfbfb60c676f18e のように見える参照が、実際には actions/checkout リポジトリのコミットではないことがある
  • その SHA は actions/checkout のネットワーク内にある fork 上のコミットであり、GitHub の alternates の使用によって親リポジトリに属しているように見えることがある
  • Chainguard の関連記事 では問題を3つに分けている
    • fork の SHA 参照と対象リポジトリの SHA 参照が見た目で区別できない
    • GitHub REST API の /repos/{user}/{repo}/commits/{ref} は、{ref} が fork にしかなくても {user}/{repo} のみを参照する JSON 応答を返す
    • fork と非 fork の SHA を区別できないと、GitHub Actions の信頼済みソース制限を回避できてしまう
  • GitHub の対応は今のところ、ドキュメントに 追記 を入れる程度にとどまっている
  • 改善の方向性

    • 明らかに安全でないワークフローを push 時点で拒否する paranoid workflow security モードが必要
    • AddressSanitizer のようなランタイム計測のように、ワークフロー実行中にセキュリティ上危険なパターンを失敗扱いにできるべき
      • 例: pull_request_target で対象リポジトリではない ref に対して actions/checkout を使った場合は失敗にする
    • pull_request_target を非推奨化または削除する案も検討できる
    • 個人リポジトリでも、組織・エンタープライズと同様にデフォルト GITHUB_TOKEN の範囲をより制限的に設定できるべき
    • 参照されたリポジトリには存在せず fork にしかない SHA 固定アクションは、デフォルトで拒否すべき

型システムは一貫性と表現力が不足している

  • カスタム GitHub Action は inputs: 配下に入力を定義できるが、アクション入力には 型の強制 がない
  • type: number のような宣言はアクション入力では機能しない
  • GitHub Actions 内部でも型サポートの場所に一貫性がない
    • workflow_call: boolean, number, string をサポート
    • workflow_dispatch: boolean, choice, number, string をサポート
    • action inputs: 型サポートなし
  • 型をサポートする入力でも、配列やオブジェクトのような複合データ構造は扱えない
    • paths: [foo, bar, baz] のような配列入力は不可
    • headers: の下に階層を持つオブジェクト入力は不可
  • そのためアクション作者は代わりに間に合わせの形式を要求することになる
    • paths: foo,bar,baz のように CSV 風のパースを自前で実装する
    • header-foo, header-baz のように自然な階層構造をフラット化する
  • こうしたやり方は保守性にもセキュリティにもよくない
    • 単一のフラットな入力名前空間を自分で管理しなければならない
    • 複雑な入力のために任意の非仕様言語を作ることになる
  • 改善の方向性

    • action と workflow の作者が、どこでも type: を使えるようにすべき
    • choiceworkflow_dispatch に限定せず、あらゆる場所で使えるべき
    • 静的に型推論できる場合は、誤った型のワークフロー変更を push 時点で拒否すべき
    • type: objecttype: array が必要
    • 内部型が異種混在になる可能性があるため完全ではなくても、現状よりは改善できる
    • 必要であれば JSON シリアライズ文字列として渡せる
    • GitHub Actions にはすでに fromJSON(...) 関数がある

公式アクションはプラットフォームへの信頼と直結する

  • GitHub Actions のサードパーティエコシステムには高品質なアクションが多い
  • その土台には GitHub が保守する公式アクションがある
    • 基本的な git 操作: actions/checkout
    • GitHub 操作とリポジトリ管理: actions/{upload,download}-artifact, actions/cache, actions/stale
    • 必須セットアップ: actions/setup-python, actions/setup-node
  • こうしたアクションは汎用性と重要性が高く、公式に保守された実装である価値が大きい
  • しかし公式アクションの数は少なく、GitHub 機能を直接つなぐ処理でさえ公式アクションとして提供されていない場合がある
    • 例: pull request を merge queue にプログラムから追加するアクション
    • GitHub CLI には gh pr merge があるが、アクションとしては公開されていない
  • メンテナンス終了した公式アクションの例

  • エコシステム改善の方向性

    • GitHub インフラの異なる部分を直接つなぎ、現在 REST API 呼び出しや gh 実行で手動対応している作業は、公式アクションとして提供される価値がある
    • 大規模なサードパーティアクションと協力して、community-actions のような 準公式組織 を作ることも考えられる
      • GitHub のレビューを受けたアクション
      • リポジトリのセキュリティベストプラクティス準拠
      • セマンティックバージョン更新
      • 主要エコシステムアクションに対する明確な信頼経路

既存ツールと GitHub のロードマップ

  • 多くの人がローカル GitHub Actions エミュレーションツール nektos/act を勧めている
    • 単純なワークフローを高速に反復・デバッグするのに有用
    • ただしイメージやイベントが GitHub の実環境と完全には一致しない場合があり、完全ではないツールと評価されている
  • rhysd/actionlint はローカル lint とセキュリティ lint に使われている
    • workflows だけを lint でき、actions 自体は lint できない点が限界
  • GitHub Actions extension for VS Code は、エディタ内 lint とリポジトリワークフロー統合を提供する
    • 公開 JSON schema に基づいている
    • 実行中の workflow 表示、変数補完などを提供する
    • ただし secrets のタイプミスや動的に生成された output 名のタイプミスのような問題は検知できず、add-commit-push デバッグは依然として必要
  • GitHub Actions プロダクト管理責任者の Julian Dunn は、いくつかの進行中機能と優先事項を共有している
    • 対話型デバッグ は GitHub Actions の 公開ロードマップ に載っている
    • タグ・SHA ベースの workflow pinning から、より不変性の高い方式へ移行する作業も 公開ロードマップ にある
    • GitHub は公式アクションエコシステムの健全性と品質を優先事項と見ており、各アクションに十分な保守と注意を注ぐため、公式アクション数を小さく保つ戦略を取っている

1件のコメント

 
GN⁺ 2023-09-24
Hacker News の意見
  • GitHub Actions のワークフローには2つのやり方がある。1) GitHub Actions で「プログラミング」し、メール送信のようなことまでマーケットプレイスのツールをつなぎ、YAML が500〜1000行に膨れ上がり、条件文が乱立して理解しにくくなるやり方。
    2) GitHub Actions は「設定」だけにして、「この YAML の複雑さをスクリプトへ押し出せないか?」と常に問い続けるやり方。メール送信もスクリプトに入れれば、ワークフローは50〜60行程度で済み、ローカルでスクリプトをデバッグできるので、愚かな push-debug-commit の繰り返しもほぼなくなる。新しいチームに行くたびに、1は狂気の道で、2が合理的だと言っているが、半分くらいは今でも1を選ぶ。デバッグツールの不足ベンダーロックインも、2を選べばはるかに問題になりにくい

    • この見方には大いに同意する。GitHub Actions を YAML でプログラミングしようとせず、タスク実行基盤としてだけ扱えば、多くの苦痛は消える。
      ただし、すべてをきちんとしたプログラミング言語へ押し込むだけでは十分でないことも多い。GHA のベンダー固有機能を使う必要があったり、ジョブ間の依存関係を示したり、すでにアクションとしてうまく抽象化されている REST API を呼び出したりする場合がある。好きな言語で再実装することはできるが、ベンダー依存が消えるわけでもなく、依然として脆い。結局、GHA のベンダーロックイン的な価値提案は非常に強く、人々を2番へ説得するには、より強い利点が必要になる
    • 2番のやり方は、開発者がビルドをローカルで実行するのも容易にする。ローカルのデバッグと、テスト/ステージング/本番環境で同じビルドチェーンを使えるようになり、異なるビルド手順を維持しなくて済む。
      これは GHA だけでなく、すべてのビルドサーバーに当てはまる。ビルドサーバーは履歴、成果物管理、権限/監査を加えるスクリプト実行器であるべきで、実際のビルド過程はビルド対象のリポジトリに委ねるべきだ
    • 良い視点だ。ただし GitHub 自体を自動化するなら、たとえばロール割り当てやタグ付けのような作業では、1番を避けるのは難しい。それでも今は、GHA のひどいデバッグループを経験するくらいなら、かなりの部分を手動で処理したい。
      参考までに、GHA の動作をローカルで再現しようとする nektos/act がある: https://github.com/nektos/act
    • アクションはどうやってデバッグするのか気になる。commit-action-debug-change ループにあまりに長い時間を費やすのは、ばかげているとしか言いようがない。2番のやり方がスクリプトのデバッグをずっと簡単にする、という点には全面的に同意する。CI はローカル実行可能であるべきだが、GitHub Actions はツールがいくつかあっても、そう使いやすいわけではない
    • 2番を志向する主な理由は、GitHub が落ちてもローカルでビルドを回せて、必要なら簡単に別の場所へ移したいからだ。
      ビルド/テスト/署名/デプロイなどをできるだけ移植性の高いスクリプトとして書き、このスクリプトは常にローカルで動作すべきだ。GitHub は、そのスクリプトを実行する環境を自動で用意し、実際に実行する役割としてだけ見る
  • git commit、push、wait のループはひどいユーザー体験だ。ユーザーはローカルマシンを含め、どこでも実行できる移植可能なパイプラインを享受する資格がある。Act はこの問題をある程度解決するが、たいてい本物の環境をそのまま代表しているわけではない。
    本番のようにローカルで実行できないパイプラインも多いが、重要度の低い開発段階では、こうしたワークフローをキャプチャしてローカルで実行できない理由はない。Garden は移植可能なパイプラインを提供し、依存関係グラフ全体にまたがるキャッシュも追加する。一部の顧客では実行時間が80%以上短縮され、開発者は先に git へ push しなくても、テストの成功/失敗を即座に確認できる。オープンソースだ: https://github.com/nektos/acthttps://docs.garden.io

    • 人々がアクションの中に bash スクリプトを詰め込むのではなく、アクションが make や bash スクリプトを呼び出すだけにしていれば、こうした問題はなかったはずだ。CI/CD と開発者はどちらも make release のような同じターゲット/コマンドを使うべきだ
    • 「CI は雪の結晶のように特別であってはならない」という原則は、DevOps や DevOps ツール専門チームを作り始める中で、かなり失われてしまったように思う。何かが職業になった瞬間、それが過度に複雑化する転換点に達したように感じる。大文字の Agile や、時間を埋めなければならないスクラムマスターたちにも同じ現象を見る
    • Act の不完全さのせいで、見当違いの場所を掘り下げたことが何度もある。今はいったん以前のループに戻っており、時間が経つにつれて良くなることを願っている
    • ビルドパイプラインにもTerraform のようなものが必要だ。Bitbucket を使っているなら、神の助けが必要だ
    • garden.io のランディングページは iOS で表示が崩れて見える。画面の右側にはみ出している
  • GH Actions の実行をデバッグする苦痛には全面的に共感する。使える道具といえば、デバッグをオンにして再実行する機能くらいしかない。パイプラインを直したりデバッグしたりするために作ったゴミコミットが多すぎて、そのほとんどは動くかどうかを見るために手当たり次第に投げているだけのレベルだ。
    再利用可能なロジックのようなごく基本的なことですら、不必要に複雑だったり、ドキュメントがひどかったりする。分かってしまえばかなり簡単だったが、GitHub のドキュメントはお粗末だった。再利用性を得るには Action を公開するか、別のリポジトリに置かなければならないように見えたが、実際には再利用ロジックを入れた新しい YAML ファイルを作れる。ただし、動作させるには workflows フォルダのルートに置く必要がある。GH Actions は作業するのが本当に苦痛だが、いったん動けば非常に便利だ。コミットして push する前に Action をテストできるローカルランナーのようなものがあるとよいのに。

    • こういうときは draft PR を使う方法がある。draft PR で Action の YAML 変更を実行してみて、満足したら実際のマージ用 PR で適当にコミットを squash すれば、汚さを残さず反映できる。GH Action のロジックをデバッグしたり開発したりするとき、draft PR はかなり良かった。
    • すべてをこなせるわけではないが、かなり使えるツールがある: https://github.com/nektos/act
    • CI を直すときは、いつも feature branch に上げて、終わったら squash merge している。一発で終わる素早い修正はほとんどなく、いつも 3〜10 コミットになる。
    • GitHub runner イメージ、あるいはその模倣イメージを実行してみたことがあるが、設定や一部機能の動作があまりにも苦痛だった。2日後に諦めた。
      GitHub だけの問題でもない。他の大手 CI プラットフォームも、ワークフローや統合の面で特に優れているわけではない。今はできるだけすべてをスクリプト化している。
    • 私たちが Earthly を作った主な理由がこれだ。ビルドをローカルで実行し、CI との一貫性を得るためだ。
  • GitHub Actions はひどい CI/CD システムだ。同じ VM 上でステップを並列実行できず、コンテナベースのジョブは二級市民扱いされている。
    1つ目の問題のせいで、ローカル認証情報や環境依存の設定を並列化できない。google-github-actions/setup-gcloud に1分以上かかるのを見ると腹が立つ。2つ目の問題のせいで、リポジトリ内の Dockerfile で CI 環境の設定を表現し、イメージ内容が変わったら CI がそのイメージを再ビルドして、そのイメージに依存するワークフローを待機させ、内容が変わっていなければ再ビルドせず、すでにインストール済みの依存関係とともに即座に実行する、という構成が非常に難しい。GitHub Actions では毎回キャッシュを再充填しようとすることになる。キャッシュには 5GB の制限があり、金を払っても増やせず、FIFO で押し出されるので、5GB を超えると実質的に無いのと同じだ。Concourse が本当に恋しい。並列ジョブ、ユーザー定義のパイプライントリガー、CI 環境に SSH で入ってデバッグ、パイプラインなしで一回限りのジョブを実行、成果物を作らずにジョブ間でディレクトリ内容を受け渡す、といったことが可能だった。GitHub Actions は .github/workflows にファイルを1つ置けばすぐ使えるという入りやすさのおかげで人気を得た、おもちゃの CI/CD に近い。参加させるには良いが、初期機能を超えると「最高」と呼ぶには十分に強力ではない。

    • 5GB のキャッシュ制限のせいで「ビルドキャッシュが妙に壊れる」問題を、これで調べてみられそうだ。教えてくれてありがとう。
    • Concourse は素晴らしい。チームが解体されたとは知らなかったが、本当に残念だ。Zito のコミュニケーションの仕方も最高だった。
    • Pivotal というより、買収後に VMWare が解体した側に近いと思う。社内には Concourse を好きな人が多かった。ただし、買収前には離れていた。
      SSH デバッグと一回限りのジョブは本当に夢のようだった。
    • Tekton は見たことがあるだろうか: https://tekton.dev/
    • こうした問題のかなりの部分を考慮した既存のソリューションがある。少し意見を聞きたい。メールアドレスを共有するか、lawnchair@lawnchair.net まで連絡してほしい。
  • GitHub が fork と non-fork の SHA 参照を区別できず、fork が GitHub Actions のセキュリティ設定を迂回できるという問題が、どうしてまだ解決されていないのか分からない。
    セキュリティ制御を簡単に迂回できるなら、重大なセキュリティ問題だ。誰かに自分の SHA を使うよう説得する必要はあるが、ソーシャルエンジニアリングはたいてい簡単な部類だ。

  • 正直、どれほどひどいか恥ずかしいくらいだ。無関係なビルド失敗の通知が、たまたま最後に merge した最新の maintainer だけに行くのもおかしい。新しく追加された maintainer が Matrix で知らせてくれるまで、自分の更新/ビルドが1週間失敗し続けていることに気づかないこともある。
    cache action は目で見ても明らかに壊れているのに、管理者がいないように見える。UI が壊れたりタブがアンロードされたりすると、ステップのビルドログを tail することすらできない。ワークフローをワーカーノード間で移植可能にする抽象化も事実上ない。ベースイメージは惨事に近い。最初はただ肥大化しすぎていて苛立っただけだったが、掘り下げるほど「Linux を知らない Microsoft の人が担当したんだな」と思うようになった。Nix や Docker を使う人向けに、もっと軽量なイメージも提供しようという努力がない。GitHub から離れつつあるが、主な理由は Actions で目が覚めたからだ。YAML でプログラミングしない理由は、YAML が、モダンで実際に有用な技術を知らないエコシステムの上に VC マネーが注ぎ込まれて生まれた不名誉だと思っているからだ。

    • Microsoft が GitHub を買収したとき、こうなるのは明らかだったのではないかと思う。
  • https://pre-commit.ciを強くおすすめします。関係者ではなく、満足しているユーザーにすぎません。
    考え方としては、コミット前にコードを検査し、可能なら修正する hook を書くか既存の hook を使い、push 後に CI が再度検査して、必要なら自動で修正コミットまで行うというものです。自動キャッシュが優れていて信じがたいほど高速で、ローカル開発マシンと CI で同じ設定を使うため、デバッグ上の問題が大きく減ります。オープンソースでは無料です。自動リリースのようなことはせず検査だけですが、その検査は非常にうまくやってくれます。

    • Python プロジェクトでは、これに tox を併用すると特に有効です。tox はテストしたい Python バージョンに合わせて virtualenv を作成し、その中でテストを実行してくれます。
      ソースコード自体を検査するには、skip_install = True で静的検査も走らせられます。グローバルツールとして tox がインストールされ、必要な Python バージョンがすべて入っているコンテナで実行すれば十分です。たとえば https://github.com/georgek/docker-python-multiversion のようなイメージがあります。メンテナンスはされていませんが、更新は簡単です。[tox] envlist = py{310,311}[testenv][testenv:check]pre-commit run --all-files --show-diff-on-failure を置く、といったボイラープレートで十分です。
  • git commit; git push; repeat ループをあまりに何度も経験した末に、https://github.com/mxschmitt/action-tmateを見つけました。ステップ間でシェル接続を開いてくれるので、すべての問題を解決するわけではありませんが、ときには苦痛を確実に減らしてくれます。

  • 個人的には、今のように actions/upload-artifact などを使わなくても、action run にHTML レポートを添付できるとよいと思います。
    特にテストビルドでは、出力された HTML レポートをすばやく見たいことがよくあります。現時点で知っている方法は upload-artifact か GH Pages ですが、GH Pages は同じリポジトリに複数のレポート出力がある場合や、最新ではなく過去のレポートをすぐ見たい場合にはいまひとつです。ジョブのサマリーに HTML レポートへのリンクを付け、クリックすると HTML をレンダリングしてくれるだけの単純な attach-report アクションがあるとよいです。他の自動化 CI/CD システムは、HTML レポートのキャプチャと閲覧を標準でずっと豊富にサポートしています。

    • HTML ではありませんが、成果物のアップロードなどを経由せずに $GITHUB_STEP_SUMMARYMarkdown 出力を直接追加できます。
  • GH Actions は実質的に Microsoft の「Azure Pipelines」のリブランディングに近いものだと見ています。TFS/VSTS/AzDO のビルドおよびリリースパイプラインの以前の形をすべて使ってきた立場からすると、このチームはこれが得意ではありません。
    Azure Pipelines がどうにか使えるようになったのも、それ以前に試したあらゆるアプローチが文字どおり失敗したからです。コミットなしで個人環境上の edit-run-debug ループを回せるよう、パイプラインをローカルで実行するプロジェクトもありましたが、当然ながらキャンセルされました: https://github.com/microsoft/azure-pipelines-agent/pull/2687/files#diff-570b52bcb927a5365c22d17a21e8e19e1ba3427c7f3ec16ea64b7b3d14e4ee20。それでも生活の質を上げるツールはあります。たとえば構文を認識する VS Code 拡張機能です: https://marketplace.visualstudio.com/items?itemName=ms-azure-devops.azure-pipelines。少し議論を呼ぶ言い方をすると、YAML の代わりに XML を使っていれば、先頭の xmlns 宣言だけで、まともなコードエディタのほとんどでユーザーが特に介入しなくても検証を受けられたはずです。XML はひどいものですが、私たちが全部捨てたときに一緒に失ってしまった便利な機能もたくさんあります。

    • GHA は Microsoft による買収前から GitHub 内部で独立してかなり進んでいたプロジェクトだと理解していました。GHA が AzP の上に作り直されたという意味なのか、単に AzP に新しい名前を付けたという意味なのか、それとも別の意味なのか気になります。
    • 多くの人が考えているより、その可能性は高いと思います。買収後、AzDo チームの相当数が GitHub Actions/Projects の作業へ移りました。
    • 最後の段落までは同意しました。XML は目が痛くなります。山括弧と camelCase の恐怖よりは、たとえ苦痛があっても見やすく整形された YAML ドキュメントを選びます。