0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GitHub Actionsのコストを見て、自宅にCI・セキュリティ基盤を建てた

0
Last updated at Posted at 2026-08-19

GitHub Actionsのコストを見て、自宅にCI・セキュリティ基盤を建てた

上原正吉(EarthLink Network Co., Ltd.)。Claude Codeを開発の主体に据え、20を超えるプロダクトを1人で同時に開発・運用しています。これは、その現場の実測記です。

GitHub Actionsのコストを見て、自宅にCI・セキュリティ基盤を建てた

2026 年 6 月下旬、組織の GitHub Actions が課金の上限で止まりました。PR(プルリクエスト)の CI(継続的インテグレーション。変更のたびに自動でテストを回す仕組み)チェックは毎回 2 秒で FAILURE、ログは空。同じころ、プラグインのリリース通知も Slack に届かなくなっていました。コードは 1 行も悪くありません。外部サービスが、課金を理由にある日インフラを止めたのです。

止まった Actions は、お金を払えば戻せます。ただ、払って元に戻すだけでは、同じコストが毎月続きます。開発を続ける以上、CI でテストを回すのは前提で、そこを削るつもりはありません。だから問いは「テストを減らして安くするか」ではなく、「必要なテストを、どこで一番安く回すか」でした。試算すると、GitHub Actions を使い続けるコストは無視できない額です。しかも、これから入れたいセキュリティ検査ツール(DefectDojo など)にも、動かす場所が要ります。ならば、テストもセキュリティ検査も監視も、自宅に 1 つのインフラ基盤を建てて全部そこで回すのが一番いい。そう判断して動いた記録です。当時の記録では、この基盤の monorepo(複数のリポジトリを 1 つに束ねた構成)は初コミットから約 1 か月で 330 コミット・ADR 35 本まで育ちました。

結論を先に置きます(本文の読了は約 6 分)。

  • どの run も一瞬(2 秒)で失敗し、ログが空になるのは、GitHub Actions の課金ブロックの目印だった — 外部サービスはある日、無音でインフラを止める。異常を知らせる仕組みを、止まる仕組みに依存させない
  • 止まらない基盤を自分で建て、古い方も捨てなかった — self-hosted(自前で持つ)ランナーへ切り替えつつ、GitHub Actions 側の workflow は残し、課金が戻れば無改修で戻せるようにした
  • 配布したセキュリティ用の workflow が、全 PR を止めた — gitleaks(コードに紛れ込んだ秘密情報を検出するツール)の全履歴スキャンは、健全な PR まで落とす。ゲートは「落とせる」だけでなく「正しく通す」ことも検証しないと、全員の作業を止める
  • 高可用(HA、High Availability。1 台落ちても止めない構成)の合格判定は、実機で電源やネットワークを実際に落とすこと — ユニットテストが全て緑でも、実機で落として初めて出たバグが 3 件あった

症状: どの run も一瞬で失敗し、ログが空になる

最初、私は自分たちの workflow を疑いました。しかし CI チェックは 2 件とも決まって 2 秒で FAILURE。ログを開いても中身が無い。切り分けのため、まず Slack 通知の経路を疑って webhook を直接叩きました。

# webhook 単体は生きているか? → HTTP 200 で Slack に着信した = コードは無罪
curl -s -o /dev/null -w "%{http_code}\n" -X POST "$SLACK_WEBHOOK" \
  -H 'content-type: application/json' \
  -d '{"text":"probe from local"}'
# => 200

webhook は 200。ローカルの通知スクリプトも正常。つまり通知ロジックは無罪でした。次に CI 側を gh run view で覗くと、全 run が 2 秒・空ログで終わっている。ここで像が結びます。ランナーそのものが起動していない。原因は GitHub Actions の課金ブロックで、組織の GitHub Actions が丸ごと止まっていました。どの run も、動き出す前に一瞬(2 秒)で失敗し、ログには何も残らない——この挙動そのものが、いま思えば課金ブロックの目印でした。そして厄介なのは、止まった仕組みが無音だったこと。Slack 通知という「異常を知らせる仕組み」自体が Actions 依存で沈黙していたため、気づくまでに数日かかりました。

gh run view で見ると、どの run も一瞬(2 秒)で失敗しログが空。ランナーが起動せず、課金ブロックで組織の GitHub Actions が無音で止まっていた(実端末の再構成・ホスト名/ID は匿名)

転換: hosted に依存しない CI を「コードで」建てる

外部 SaaS(サービスとして提供されるソフトウェア)の課金事情は、ある日突然インフラを止める。ならば止まらない基盤を自分で持つしかない。しかも自宅に基盤を建てるなら、CI だけを載せるのはもったいない。テストも、これから入れるセキュリティ検査も、監視も、同じ 1 つのインフラに集約する。それが一番いいと考えました。方針は「SSH(サーバーへ遠隔ログインしてコマンドを打つ仕組み)で直接コマンドを打つ運用をやめ、Ansible / Docker Compose / Terraform(いずれも構成をコードで定義して適用するツール)で自宅サーバー群を宣言的に管理する」ことにしました。既存の CI サーバー 1 台の状態を回収(harvest)して monorepo 化し、self-hosted ランナー、脆弱性台帳、Grafana/Loki/Prometheus(可視化・ログ・メトリクスを担う監視スタック)による observability(可観測性。稼働状態を外から見えるようにする仕組み)、ログ基盤までを初日〜2 日でほぼ一気に codify(コードとして定義)しました。

各プロダクトの workflow は、hosted 前提から self-hosted ランナーへ機械的に張り替えます。

# hosted (ubuntu-latest) 依存をやめ、自宅ランナーのラベルへ寄せる
jobs:
  test:
    runs-on: [self-hosted, linux, docker]   # 自前ランナー群のラベル
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile && pnpm test

Actions 側の workflow は消さずに残しました。課金が復活したら無改修で戻せるようにするためです。「壊れたら別の基盤に切り替え、壊れた方も捨てない」——これが基本方針になりました。

罠 1: 配った security workflow が組織全体の PR を止めた

移行して間もなく、共通配布していたセキュリティ用 workflow が、配布先の全 PR をブロックする事故を起こしました。当時この workflow は組織の非アーカイブ 102 リポジトリのうち 25 に配布済みで、影響範囲は広い。本命の原因は gitleaks の使い方でした。

gitleaks detect --source . は、対象を全 ref (実質 git log --all) までスキャンします。つまり過去ブランチに残ったテスト用のダミーシークレットや古い漏洩痕跡を拾って、健全な PR まで必ず落ちる。さらに self-hosted ランナーのワークスペース汚染で、「その PR には存在しないはずのファイル」を検出する誤検知も出ました。修正は、スキャン範囲を PR の差分に限定することです。

# 全 ref をスキャンして PR を必ず落とす、をやめる。差分だけ見る
gitleaks detect --source . --redact \
  --log-opts="origin/${BASE_REF}..HEAD"

念のため、PASS すべきブランチと FAIL すべきブランチの両方で E2E(端から端まで通しで動かす)検証を行う self-test の workflow まで用意しました。セキュリティゲートは「落とせる」だけでは駄目で、「正しく通す」ことも検証しないと、全員の作業を止めてしまうという学びです。

罠 2: self-hosted 化の代償で Docker ビルドが崩れる

移行にはツケもありました。hosted ランナーを全廃した直後から、複数プロダクトで CDK(コードでクラウド構成を定義するツール)の Docker bundling が動かなくなったのです。「コンテナ内ランナー + ホストの docker socket」という構成で、bundling が使うボリュームマウントが空振りする。あるプロダクトではデプロイ workflow がしばらく全滅し、ローカル Mac から手動デプロイする生活になりました。対処は、Docker bundling をランナー上の pip install --target によるローカル依存展開に置き換えることで、最終的にデプロイ workflow の 5 連続成功を実測で確認してクローズしました。基盤を移すと、その上のビルド前提が静かに崩れる。ここでも「動いた」は実測でしか確認しない方針が効きました。

HA: split-brain を起こし、その日のうちに実機で落として直す

ランナーが 1 台だと、それが落ちれば、また CI が止まります。そこで 2 ノード構成にしたのですが、ここで最大のインシデントが起きました。NVMe(高速なストレージ規格)の増設のため ci1 から ci2 へ failover したあと、ci1 を再起動すると 両ノードが active になり、同一のジョブキューを非協調に奪い合う split-brain(両系が同時に主系になってしまう状態)を深夜に観測したのです。web からのジョブ投入は ci2、実行は ci1、という気持ちの悪い状態でした。

原因は「起動系に、自分が designated active かを判定する gate が無い」という構造問題。ci1 は ci2 が active であることを知らずに自動起動していました。同日中に atomic lease fencing(古い主系を確実に締め出して二重起動を防ぐ仕組み)(ハートビート 2 秒 / TTL(有効期限) 6 秒 / grace 6 秒の fence-agent を両ノードに常駐)を実装し、実機の kill-test(電源やネットワークを実際に落とす試験)を通しました。自動 failover 18 秒、NFS(ネットワーク越しの共有ファイルシステム)分断時の self-fence + takeover 17 秒、データ差分は完全一致。ここで一番の教訓は、failover ≠ fencing です。手動 failback は忘れられる。そして、ユニットテスト(bats。bash 用のテストフレームワーク)が全緑でも、実機で電源やネットワークを実際に落として初めて出たバグが 3 件ありました(grace 中にハートビートが互いに譲り合って進まない livelock、hard-mount による I/O の無限ブロック、他系の lease を確認せず主系を奪う盲目的 steal)。合格ラインは設計文書ではなく実機の kill-test だ、と Mission に明記したのはこの日です。

Infra Portal: 全プロダクトを 1 画面に集約する

最後に、CI 状態・デプロイ昇格・複数プロバイダのコスト・バックアップ充足・サーバとローカル LLM(大規模言語モデル)の死活を 1 画面に集約する自作ポータル(Infra Portal と呼んでいます)を育てました。設計思想は 2 つあります。ひとつは「登録情報を信じず、リポジトリとライブ環境の実測で capability を自動検出する」。もうひとつは「HTTP 200 で生きていることにせず、実応答の意味を見る」。前者は自己申告のズレを、後者は「サーバは応答するが中身は壊れている」を捕まえるためです。ここでも笑える障害があり、あるノードのカードが 62 時間前で凍結していたのは、HA の非対称インストール——passive 側に必要なレポート用スクリプトを入れ忘れていた——が原因でした。両ノードを対称化して直しました。

自宅CI基盤の Infra Portal。HA の2ノード(ci1 active / ci2 standby)と、1基盤に集約したCIランナー・セキュリティ検査・監視。ノード名は伏せた再構成、HAの実測値は検証済み

まとめ — 転用できる教訓

  • テストは省けない。だからコストを見て、自前の 1 つの基盤に集約した。無料枠に収めるのではなく、回し続ける前提でコストを見積もり、CI・セキュリティ・監視を 1 台のインフラに寄せた
  • どの run も一瞬で失敗しログが空になるのは、課金ブロックの目印。外部サービスはある日、無音でインフラを止める。異常を知らせる仕組みを、止まる仕組みに依存させない
  • 基盤は自分で建て、古い方は捨てない。self-hosted へ切り替えつつ hosted の workflow は残し、無改修で戻せるようにする
  • 配布するゲートは「正しく通す」ことまで検証する。gitleaks の全 ref スキャンのように、落とせるだけのゲートは全員の作業を止めてしまう
  • HA の合格判定は実機 kill-test。failover ≠ fencing。bats 全緑でも、実機で実際に落とさないと出ないバグがある

筆者について

上原正吉。EarthLink Network Co., Ltd. でAI開発をしています。2025年からClaude Codeを開発の主体に据え、今は20を超えるプロダクトを1人で同時に開発・運用しています。この連載では、その現場で実際に起きたこと(うまくいったことも、失敗も)を、数字と一緒に書いていきます。

また、AIで業務や開発を組み替えたい会社・チーム向けに、AI活用のコンサルティングも受け付けています。ご相談は www.eln.ne.jp からどうぞ。


EarthLink Network は、会社の全業務を AI で回すために、必要になったものを自社で作っています。いま作っているプロダクトの一覧と概要は、こちらにまとめています。

EarthLink Network が自社でつくっている18のプロダクト

会社と各プロダクトの詳細は、公式サイト www.eln.ne.jp をご覧ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?