1. Self-hosted GitHub Actions Runner運用における現実的な課題
チーム規模が20〜30名の開発者に達すると、GitHub-hosted Runnerのコストは急速に膨らみます。コスト最適化のため、AWS EC2、VPS、あるいはKubernetes上のSelf-hosted Runnerクラスタへ移行するのは合理的な選択肢です。しかし、インフラを自前で管理することは、運用上のさまざまなトラブルを抱え込むことにも繋がります:
- Runnerのサイレント停止やハングアップ:Docker build cacheの肥大化によるディスク枯渇(Disk 100%)や、OOM(Out Of Memory)による
Runner.Listenerプロセスのクラッシュが発生します。その結果、明確なエラー通知もないままパイプラインが無期限に停止してしまいます。 - ジョブキュー(Job Queue)の長期滞留:タイポ修正のような些細なコミットでも実行開始まで15〜20分待たされ、開発者から不満が噴出します。その一方でDevOpsチームは、インフラのリソース不足なのか、誰かが50ジョブのMatrix BuildをトリガーしてWorker Poolを占有しているのかを把握できません。
- ビルド時間の異常な増加:これまで5分だったパイプラインが突然18分に跳ね上がります。過去のメトリクスがなければ、Docker Base Imageのpull時にネットワーク帯域制限がかかっているのか、コードのコンパイルでCPUを使い果たしているのか、あるいはテストスイートが肥大化したのか、どのステップがボトルネックになっているかを特定できません。
かつてモニタリングがなかった頃は、開発者から「CIが壊れました」と連絡があるたびに、各サーバーへSSH接続してhtopやdf -hを実行し、手探りで調査していました。統合監視の導入はこの問題を根本から解決します。チームがSlackでメンションを飛ばしてくる前に、ダッシュボード上で問題を検知できるようになります。
2. 根本原因の分析
このような状況に陥る原因は、主に2つの情報の欠落に起因しています:
1. GitHub UIからはハードウェア情報が一切見えない
GitHub UIのRunner Settingsでは、Idle、Active、Offlineの3つのステータスしか確認できません。Active状態のインスタンスでCPU使用率が99%に張り付いてボトルネックになっているのか、キャッシュの読み書きでディスクIOPSが過負荷になっているのか、あるいはなぜArtifactsの展開だけに10分もかかっているのかまでは把握できません。
2. 時系列メトリクスとプロアクティブなアラートの欠如
GitHub側ではパフォーマンスメトリクスが時系列データとして保持されません。リリースが集中するピーク時間帯(金曜の16時〜17時など)にRunnerが深刻なリソース不足に陥っても、手元には定量的なデータがありません。平均で何件のジョブが滞留しているのか、キューの待ち時間(Queue Latency)はどれくらいか、Runner Poolごとのビルド失敗率はどう推移しているのかが分からないのです。
3. 代表的なアプローチの比較
DevOpsエンジニアが検討する主なアプローチには、次の3つがあります:
- GitHub REST APIを呼び出すCronジョブスクリプトを作成してSlack/Telegramへ通知:30分程度で素早く導入できます。ただし、GitHub APIのレート制限(5,000リクエスト/時)に引っかかりやすく、視覚的なグラフ表示や長期的なトレンド分析ができないという大きなデメリットがあります。
- すべてのRunnerログをELK/Lokiに集約:詳細なスタックトレースの調査には非常に有効です。しかし、リアルタイムなキュー時間や稼働率(Utilization)を算出するためにログを解析するのは、リソース消費が大きく構成も複雑になります。
- Prometheus + Exporter + Grafanaによる標準的な多層監視:インフラメトリクス(Node Exporter)とワークフロー/キューのメトリクス(github-actions-exporter)を並行して収集します。軽量で柔軟なアラート設定が可能な、本番環境におけるデファクトスタンダードの構成です。
4. 実践手順:Prometheusとgithub-actions-exporterによる網羅的監視環境の構築
ここでは、github-actions-exporter(GitHubからRunnerおよびJobのデータを取得)、Node Exporter(RunnerマシンのCPU、メモリ、ディスクを監視)、Prometheus(メトリクスの保存)、Grafana(ダッシュボードによる可視化)で構成される軽量なスタックを構築します。
ステップ1:GitHub Personal Access Token(PAT)の発行
GitHubの Settings > Developer settings > Personal access tokens にアクセスします。ClassicまたはFine-grainedトークンを選択し、以下の読み取り権限を付与します:
repo: 個別リポジトリ単位でRunnerを監視する場合。admin:orgまたはread:org: Organization配下のすべてのRunner Poolを監視する場合。
ステップ2:Docker Composeによる監視スタックのデプロイ
監視サーバー(またはRunnerクラスタ自体)上に docker-compose.yml を作成します:
version: '3.8'
services:
github-actions-exporter:
image: cmauto/github-actions-exporter:latest
container_name: github-actions-exporter
restart: unless-stopped
environment:
- GITHUB_TOKEN=ghp_yourPersonalAccessTokenHere123456
- GITHUB_ORGANIZATION=your-org-name
- REFRESH_INTERVAL=30s
ports:
- "9999:9999"
node-exporter:
image: prom/node-exporter:v1.8.1
container_name: runner-node-exporter
restart: unless-stopped
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
ports:
- "9100:9100"
prometheus:
image: prom/prometheus:v2.53.0
container_name: prometheus
restart: unless-stopped
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
volumes:
prometheus_data:
ステップ3:PrometheusのScrape Targets設定
Composeファイルと同じディレクトリに prometheus.yml を作成します:
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'github_actions'
static_configs:
- targets: ['github-actions-exporter:9999']
- job_name: 'runner_nodes'
static_configs:
- targets: ['node-exporter:9100']
サービスを起動します:
docker compose up -d
# ExporterがGitHub APIと正常に接続できているか確認
docker logs -f github-actions-exporter
ステップ4:Grafanaダッシュボード構築のための実践PromQLクエリ
GrafanaでPrometheusをData Sourceとして追加した後、以下のコアパネルを作成できます:
1. Runnerマシンの稼働状態監視(Health Check)
オフラインになったRunnerやクラッシュを即座に検知します:
# Pool別のオンライン状態のRunner数
sum by (os, status) (github_runner_status{status="online"})
# オフライン状態のRunnerに対するアラート(値が1の場合は切断を意味する)
github_runner_status{status="offline"} == 1
2. ジョブキューの滞留状況測定(Job Queue Latency)
リソース待ちになっているジョブ数を正確に把握し、適切なタイミングでAutoscalingを発動させます:
# リポジトリ別のQueued状態の総ジョブ数
sum(github_workflow_job_status_total{status="queued"}) by (repo)
# queuedに対するin_progressジョブの比率
sum(github_workflow_job_status_total{status="in_progress"}) /
sum(github_workflow_job_status_total{status="queued"})
3. ビルド時間の監視(Build Duration)
Build Cacheの最適化などを目的に、リポジトリごとのワークフロー平均実行時間を計測します:
# 直近30分間におけるジョブの平均実行時間(秒)
rate(github_workflow_job_duration_seconds_sum[30m]) / rate(github_workflow_job_duration_seconds_count[30m])
ステップ5:即時通知を送るAlert Ruleの設定
障害発生時にSlackやTelegramへ即座に通知を飛ばすため、PrometheusにAlert Ruleを追加します:
groups:
- name: github_runner_alerts
rules:
- alert: RunnerOffline
expr: github_runner_status{status="offline"} == 1
for: 3m
labels:
severity: critical
annotations:
summary: "Runner {{ $labels.name }} がオフラインです"
description: "Pool {{ $labels.runner_group }} 内のRunner {{ $labels.name }} が3分以上切断されています。"
- alert: HighJobQueueCount
expr: sum(github_workflow_job_status_total{status="queued"}) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "CI/CDジョブキューが滞留しています"
description: "空きRunner待ちのジョブが5分以上にわたり10件を超えています。Workerのスケールアウトを検討してください。"
わずか数十分の設定で、後手後手の障害対応からプロアクティブな運用管理へとシフトできます。リソースのボトルネック、ジョブの待ち時間、Runner Poolの稼働健全性をすべて手元で完全に可視化しましょう。
