GitHub Actionsを意図せず大量実行させて社内CIを止めた話
はじめに
KINTOテクノロジーズでインフラエンジニアをしているyassanです。
先日、GitHub Actionsのワークフローを意図せず大量に起動してしまい、社内のCI/CDパイプラインを約1時間にわたって止めてしまうという事故を起こしました。
この記事では、小さなミスがどう連鎖して大きな障害になったのか、そしてそこから何を学んだのかをお話しします。
前提:コメント駆動のCI/CDパイプライン
私たちのチームでは、Terraformのインフラコードを管理するリポジトリでGitHub Actionsを活用しています。
仕組みはシンプルで、PRにコメントを投稿すると、そのPRで変更されたディレクトリを検出して自動的に terraform plan を実行してくれるというものです。
ワークフローの概要を簡略化すると、以下のようなイメージです。
name: Terraform Plan
on:
issue_comment:
types: [created, edited] # コメントの新規作成・編集時に発火
jobs:
plan:
# PRへのコメントで、本文にコマンド文字列を含む場合に実行
if: |
github.event.issue.pull_request
&& contains(github.event.comment.body, '/command')
runs-on: ubuntu-latest
steps:
- name: PRの変更ディレクトリを検出
# ...
- name: 対象ディレクトリごとに terraform plan を実行
# ...
- name: 結果をPRにコメント
# ...
通常であれば、PRの変更範囲は1〜2ディレクトリ程度。数分で完了する軽い処理です。
やらかしの連鎖
火種:いつもの感覚でリベースしたら、対象が35ヶ所に膨れ上がった
普段のPRは main ブランチに向けて作成しています。しかしこの日に限って、別の作業ブランチをベースにしたPRを作っていました。
ここで、いつもの癖で何も考えずにリベースを実行。すると、そのブランチにあった他のメンバーのコミットが差分に混入してしまいました。
本来1ディレクトリだったplanの対象が、一気に35ディレクトリに膨れ上がりました。
延焼:消火しようとしたらガソリンだった
35ディレクトリ分のplanが走ってしまったことに気づき、「余計な結果コメントを非表示にして整理しよう」と考えました。
そこでGitHub APIを使って、不要な34件のコメントのうち20件を非表示(minimize)にしていきました。
その操作がワークフローのトリガーになるとも知らずに、非表示にするだけだと軽い気持ちで実施しました。
結果として、思いがけず20件 × 35ディレクトリ = 約700回のワークフローが一斉に走り出しました。
種明かし:大量のトリガー
GitHub APIの minimizeComment でコメントを非表示にすると、GitHub上では 「コメントの編集」イベント として扱われます。ちなみに、Web UIから手動でhideした場合はこのイベントは発生しません。
そして、非表示にしたコメントの本文には、ワークフローのトリガーとなるコマンド文字列が含まれていました。
つまり、1件非表示にするたびに、35ディレクトリ分のplanが再び起動してしまう状況だったのです。
誤判断:PRを閉じれば止まると思った
約10分後、大量のワークフローが走っていることに気づきました。パニックになった私は「PRを閉じれば止まるはず」と考え、すぐにPRをクローズしました。
「これで大丈夫」と安心して、別の作業に戻りました。
発覚:社内から悲鳴が上がる
さらに約10分後。社内のチャットに「GitHub Actionsが動かない」「CIがずっとキュー待ちになっている」という報告が上がり始めました。
慌ててGitHubを確認すると、クローズしたはずのPRにまだ結果コメントが投稿され続けていました。
実は、PRをクローズしても 実行中のワークフローはキャンセルされません。
それどころか、クローズされたPRに対してもコメントイベントは発火するため、PRクローズ自体にワークフローを止める効果はないのです。
これにより、共有ランナーの枠を食いつぶしてしまい、他チームのCIが動かなかったわけです。
私はすぐにGitHub Actionsの画面から、実行中のワークフローを手動で片っ端からキャンセル。ようやくキュー溜まりが解消し、社内のCI/CDが正常に戻りました。
あとから確認したところ、恐ろしいことに約3,000分(50時間相当)のActions実行時間を、わずか1時間の間に消費していたことがわかりました。
何が起きていたのか
今回の事故は、4つのミスが連鎖して起きました。
| # | やったこと | 何が起きたか |
|---|---|---|
| 1 | 別ブランチベースのPRでリベース | 他人のコミット混入で対象35ディレクトリに膨張 |
| 2 | 結果コメントを非表示にして整理 | 非表示=編集イベント → ワークフロー再起動 × 20回 |
| 3 | PRをクローズして安心 | 起動済みワークフローは止まらない |
| 4 | 20分間気づかず放置 | 社内CI/CDが1時間停止 |
一つ一つは「ちょっとした判断ミス」や「仕様を知らなかった」程度のことですが、それが連鎖することで大きな障害になりました。
ワークフロー変更による再発防止
1. トリガー条件の見直し
ワークフローのトリガーから edited(編集)イベントを削除し、created(新規作成)のみに限定しました。これにより、コメントの編集や非表示でワークフローが起動することはなくなりました。
on:
issue_comment:
- types: [created, edited]
+ types: [created]
2. コマンド判定ロジックの厳格化
コメント本文にコマンド文字列が「含まれているか」ではなく、「先頭から始まっているか」で判定するように変更しました。さらに、イベント種別の二重チェックも追加しています。
jobs:
run_plan:
if: |
github.event.issue.pull_request
+ && github.event.action == 'created'
- && contains(github.event.comment.body, '/command')
+ && startsWith(github.event.comment.body, '/command')
3. 同時実行の制御
concurrency グループを設定し、同一PRでのワークフローの並列実行を防止しました。後から起動したワークフローが、先行するものをキャンセルして最新のplanだけが実行されるようになっています。
concurrency:
group: plan-${{ github.event.issue.number }}
cancel-in-progress: true
組織としての課題
今回の事故で、ワークフロー単体の修正だけでは防ぎきれない課題も見えてきました。
- 共有ランナーの同時実行数が急増しても気づく仕組みがない
- ワークフローのトリガー設計に関する共通のガイドラインがない
- 暴走に気づいたとき、誰がどう止めるかの手順が整備されていない
これを踏まえてコーポレートITグループと連携して以下による改善を進めていきたいと考えています。
- ランナー使用状況の監視強化(同時実行数がしきい値を超えた際の Slack アラート)
- ARMランナーやハイスペックランナーへの切り替えによる処理効率の改善
- ワークフロートリガー設定のベストプラクティス策定・既存ワークフローの一括監査
この経験から学んだこと
「止めたつもり」が一番怖い。
PRを閉じればワークフローも止まると思い込んでいましたが、実際にはそうではありませんでした。慌てているときほど、思い込みで行動してしまいがちです。
ワークフローのトリガー条件は、「最悪のケース」で考える。
GitHub APIを使ったコメントの非表示は編集イベントとして扱われること、結果コメントの本文にトリガー文字列が含まれること。どちらも普段は問題にならない仕様ですが、組み合わさったときに暴走を引き起こしました。
小さなミスは連鎖する。
リベースのミス、コメント整理の操作、PRクローズへの過信、確認不足。どれか一つでも正しく対処できていれば、ここまでの事故にはなりませんでした。失敗が起きたとき、焦らずに「今何が動いているのか」を確認することが大事だと痛感しました。
おわりに
今回の事故は、自分の操作で社内の開発フローを止めてしまうという、なかなかにつらい経験でした。
ただ、この失敗をきっかけにワークフローのトリガー設計を見直し、同様の暴走が起きない仕組みに改善できました。外注開発なら責任問題になりかねない失敗も、内製開発なら改善のきっかけにできる。それがこの経験で得た一番の実感です。
この記事が、同じようなCI/CDの落とし穴を避けるための参考になれば幸いです。
関連記事 | Related Posts
Migrating from Docker Hub to ECR Public Gallery
Docker HubからECR Public Galleryへ移行する
Streamlining iOS app development with Bitrise
![Cover Image for [iOS][CI/CD] Integrating a Private Repository as a Library in Xcode Cloud](/assets/blog/authors/HiroyaHinomori/xcode-cloud_x_private-repository_top.jpg)
[iOS][CI/CD] Integrating a Private Repository as a Library in Xcode Cloud
A system for efficiently reviewing code and blogs: Introducing PR-Agent (Amazon Bedrock Claude3)
![Cover Image for [iOS][CI/CD] Xcode Cloudでプライベートリポジトリをライブラリとして取り込む](/assets/blog/authors/HiroyaHinomori/xcode-cloud_x_private-repository_top.jpg)
[iOS][CI/CD] Xcode Cloudでプライベートリポジトリをライブラリとして取り込む



