ガジェットコンパス

ガジェット探求の旅に終わりはない
🔍
CloudflarenpmCI/CDインフラ冗長化DevOpsGitHub Actions冗長性ディザスタリカバリー

Cloudflareの障害で開発が止まるのはなぜ?npm・CI/CDの単一依存から脱却する5つの冗長化戦略

👤 いわぶち 📅 2025-12-06 ⭐ 4.8点 ⏱️ 18m

ポッドキャスト

🎙️ 音声: ずんだもん / 春日部つむぎ(VOICEVOX)

📌 1分で分かる記事要約

  • Cloudflareの障害は単なるCDN問題ではなく、npm registry・GitHub・Firebase等の共通インフラとして機能しているため、CI/CDが連鎖的に停止する
  • 2025年11月の大規模障害では、npm installが失敗し、Firebase Functionsデプロイが不可能になるなど、開発全体が麻痺した事例が報告されている
  • GitHub・GitLab・AWSなど他サービスの障害事例から見ると、単一サービス依存は金融・メディア・EC業界で直接的な売上損失につながる
  • Verdaccioによるnpmキャッシュ、マルチCDN構成、CI/CD二重化など、難易度別の5つの冗長化パターンで段階的に対応可能
  • 今日から実装できるDNS冗長化、フェイルオーバー設計、監視の多重化により、「Cloudflare障害=開発停止」の時代を終わらせることができる

はじめに:2025年11月のCloudflare大規模障害から学ぶ

2025年11月18日(火)夜、Cloudflareのグローバルネットワークで大規模な障害が発生しました。日本時間で20:48ごろから始まったこの障害は、完全復旧まで約6時間を要しました。

この障害の最大の特徴は、単なるCDNの配信停止に留まらず、npm registry、GitHub、Firebase Functions、その他多くのWebサービスとCI/CDパイプライン全体が同時に麻痺したことです。

実際、多くの開発者が以下のような状況に直面しました。

$ npm install
npm ERR! code E500
npm ERR! 500 Internal Server Error - GET https://registry.npmjs.org/...
npm ERR! network timeout at: https://registry.npmjs.org/...
$ firebase deploy --only functions
Error: HTTP Error: 500, Internal Server Error
Error: Failed to fetch function upload URL. Request timed out.

CI/CDパイプラインは赤く染まり、本番リリースは全て手動に。緊急パッチも出せない。こうした状況は、多くの企業にとって初めての「Cloudflare依存のリスク顕在化」となりました。

本記事では、なぜCloudflareの障害がここまで広範囲に波及するのか、その技術的構造を明らかにし、単一サービス依存を避けるための実践的な冗長化戦略を5つのパターンで提示します。


第1章:なぜCloudflareの障害がCI/CDを止めるのか

Cloudflareが担う「見えない共通インフラ」としての役割

Cloudflareは単なるCDNサービスではなく、インターネット基盤層に深く組み込まれています。

Cloudflareが提供する機能層

  1. CDN・コンテンツ配信

    • npm registry(registry.npmjs.org)のキャッシュ配信
    • Webサイト・API・静的アセットの世界中からの高速配信
  2. DNS・トラフィックルーティング

    • ドメイン名解決の高速化とトラフィック最適化
    • Anycastネットワークによる最寄りPoPへの自動誘導
  3. セキュリティ・DDoS対策

    • L3/L4/L7レベルのDDoS防御
    • WAF(Web Application Firewall)によるリクエスト検証
    • ボット対策・レート制限
  4. TLS終端・暗号化

    • HTTPS通信の終端処理
    • TLS 1.3、HTTP/2・3対応
  5. Zero Trust・アクセス制御

    • Cloudflare Access(ゼロトラスト認証)
    • Workers・KV・Durable Objectsなどのエッジコンピューティング

npm registryの場合、Cloudflareはこれら全機能を組み合わせて、npmjs.orgの前に立ち塞がる共通インフラとして機能しています。そのため、Cloudflareが落ちると:

  • DNS解決が失敗する
  • TLS接続がタイムアウトする
  • 5xxエラーが多発する
  • キャッシュが応答しない

といった複数の障害が同時に発生し、結果として開発環境から本番環境まで全てが影響を受けるのです。

npm installが失敗する技術的メカニズム

npm installが失敗する流れを、具体的に追ってみましょう。

正常時のnpm install フロー

npm cli
  ↓ (package.json解析)
registry.npmjs.org へのHTTPS接続
  ↓ (Cloudflareエッジで接続受け付け)
DNS解決 → TLS確立 → HTTP/2リクエスト
  ↓ (Cloudflareキャッシュから応答)
パッケージメタデータ取得

tarball URL取得

tarball ダウンロード

node_modules へ展開

Cloudflare障害時のnpm install フロー

npm cli
  ↓ (package.json解析)
registry.npmjs.org へのHTTPS接続
  ↓ (Cloudflareエッジで接続受け付け)
DNS解決 → TLS確立 → HTTP/2リクエスト
  ↓ (Cloudflareエッジで5xx返却 or タイムアウト)
❌ ENOTFOUND / ETIMEDOUT / E500

npm ERR! code E500
npm ERR! 500 Internal Server Error

重要なポイントは、npm側は「registry.npmjs.orgに接続している」つもりですが、実際にはCloudflareのエッジノードが最初の受け付け窓口になっているということです。Cloudflareが応答しなければ、npmjs.orgのオリジンサーバに到達することはできません。

CI/CDパイプラインが全面停止する連鎖

CI/CDパイプラインでは、通常以下のようなステップが実行されます。

# GitHub Actions の例
name: deploy

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci  # ← ここでCloudflareを経由
      
      - name: Build
        run: npm run build
      
      - name: Deploy
        run: npm run deploy  # ← デプロイ先がCloudflareの場合さらに依存

Cloudflare障害時に起こる連鎖:

  1. npm ci ステップで失敗 → パイプライン全体が止まる
  2. 再実行しても、Cloudflare側が不安定な間は確率的に失敗
  3. 本番リリースが全て手動に切り替わる
  4. 緊急修正も出せない
  5. ステータスページすら更新できない(ステータスページもCloudflareを経由している場合)

特に問題なのが、監視・ステータス配信もCloudflare経由の場合、「何が落ちているのか分からない」という二重の障害になることです。


第2章:業界別インパクト事例から見る単一サービス依存のリスク

Cloudflare以外のインフラサービス障害でも、同様にCI/CDが止まる事例が複数あります。業界による影響の違いを理解することで、冗長化の優先度付けがしやすくなります。

事例1:GitHub障害によるSaaS開発の完全停止(IT/SaaS業界)

障害の概要

GitHub.comのリポジトリ・GitHub Actionsが一時的に不安定になった障害では、以下が同時に発生しました。

  • ソースコードの clone/push ができない
  • GitHub Actions による自動テスト・自動デプロイが全面ストップ
  • GitHub Packages / Container Registry へのpush/pullが失敗
  • Dependabot等のセキュリティスキャンが走らない

CI/CDへの具体的な影響

GitHub Actionsに完全依存していたチームでは:

  • main ブランチへのマージ時の自動デプロイが全て手動に
  • 本番リリースの遅延
  • セキュリティスキャンが一時的に無効化され、脆弱性チェック機能が失われる
  • GitHub Packages を使っていた場合、コンテナイメージのpull/pushができず、Kubernetesへのデプロイパイプライン全体がブロック

対策の方向性

# Terraform で複数リポジトリホスティングを管理する例
resource "github_repository" "primary" {
  name            = "my-app"
  description     = "Primary GitHub repository"
  visibility      = "private"
  has_wiki        = false
  has_projects    = false
}

# GitLabにもミラーリポジトリを用意
resource "gitlab_project" "mirror" {
  name                    = "my-app-mirror"
  visibility              = "private"
  mirror                  = true
  mirror_user_id          = data.gitlab_user.service_account.id
  import_url              = github_repository.primary.clone_url
  mirror_trigger_builds   = true
}

このような構成により、GitHub障害時はGitLabのCI/CDにスイッチして最低限のビルド・デプロイを継続できます。

事例2:GitLab.com障害による金融リリースフリーズ(金融/フィンテック)

障害の概要

GitLab.comのパフォーマンス低下やDBトラブルが発生した際、以下が起こりました。

  • GitLab CIジョブの実行が大幅遅延・タイムアウト
  • 決済基盤や与信システムのリリースが止まる
  • メンテナンスウィンドウ内のリリース完了が不可能

業界特有のインパクト

金融システムはメンテナンスウィンドウが限定的です。「この2時間でリリースしないと1週間延期」という制約が多く存在します。

GitLab CIに完全依存していたチームでは:

  • ウィンドウ内にCIが通らず、重要な規制対応リリースが延期
  • 緊急パッチが適用できず、リスク管理・コンプライアンス上の説明責任が発生
  • 監査対象となる決済システムが更新できず、規制当局への報告が必要に

対策の方向性

# セルフホストGitLabのバックアップ運用例
# オンプレ or クラウド上にセルフホストGitLabを構築し、
# 定期的にプロジェクトをエクスポート/インポート

# プロジェクトエクスポート(毎日実行)
gitlab-backup create

# 別環境へのインポート(障害時)
gitlab-backup restore BACKUP=<backup_timestamp>

このような構成により、GitLab.com障害時も、セルフホスト側で最小限のCI/CDパイプラインを実行できます。

事例3:AWSリージョン障害によるEC/メディアのデプロイ停止(メディア・EC)

障害の概要

特定AWSリージョン(例:ap-northeast-1)が障害に陥った際、そのリージョンに閉じたCI/CD基盤は全て停止しました。

  • CodePipeline / CodeBuild が動作しない
  • ECR(Elastic Container Registry)へのpush/pullができない
  • パイプラインが進行しない

業界特有のインパクト

  • メディア:特集キャンペーン・大型イベント対応のリリースが時間指定で組まれており、リージョン障害で「特集ページ公開が遅延」「広告施策と連動できない」というビジネスインパクト
  • EC:価格改定・在庫ロジック修正・決済連携パッチなど、売上直結のリリースが止まる。セール期間中は数時間のリリース遅延が直接売上損失に

対策の方向性

# Terraform でマルチリージョンCI/CDを定義
provider "aws" {
  alias  = "primary"
  region = "ap-northeast-1"
}

provider "aws" {
  alias  = "secondary"
  region = "us-east-1"
}

# プライマリリージョンのCodePipeline
resource "aws_codepipeline" "primary" {
  provider = aws.primary
  name     = "my-app-pipeline-primary"
  # ... 定義
}

# セカンダリリージョンのCodePipeline
resource "aws_codepipeline" "secondary" {
  provider = aws.secondary
  name     = "my-app-pipeline-secondary"
  # ... 定義
}

アーティファクト(コンテナイメージ・zip)はクロスリージョンレプリケーションを設定し、リージョン障害時は別リージョンのパイプラインにトラフィックを切り替えます。


第3章:冗長化導入のコスト・運用負荷試算

インフラ冗長化は「理想」ですが、実務ではコスト・構築工数・運用負荷とのバランスを取る必要があります。以下は、中規模サービス(月間トラフィック5TB、エンジニア10名)を想定した試算です。

冗長化パターン別の月額コスト

パターンA:マルチCDN(Cloudflare+CloudFront)

構成

  • DNS:Route53(プライマリ)+Cloudflare(セカンダリ)
  • CDN/WAF:Cloudflare+AWS CloudFront+AWS WAF
  • 切り替え:Route53ヘルスチェック+Traffic Policy

追加コストの目安

項目月額
CloudFront データ転送(5TB)8~15万円
AWS WAF ルール・リクエスト3~5万円
Route53 ホストゾーン・クエリ0.5~1万円
合計11~21万円

構築工数

  • 設計:3~5人日
  • 構築:5~10人日
  • 検証:3~5人日
  • 合計:11~20人日

月次運用負荷

  • 設定変更の二重反映:4~8時間/月
  • 定期フェイルオーバーテスト:2~4時間/四半期
  • 平均:月5~10時間

パターンB:マルチクラウド(AWS+GCP Kubernetes)

構成

  • Kubernetesクラスタ:AWS EKS(本番)+GCP GKE(DR)
  • ルーティング:Global DNS+Geo/Weightedルーティング
  • CI/CD:GitHub Actions+両クラスタへのデプロイ

追加コストの目安

項目月額
GKE クラスタ+最小ノード10~15万円
クロスクラウド監視(Datadog等)5~10万円
ネットワーク・VPN2~5万円
合計17~30万円

構築工数

  • 設計:10~20人日
  • 構築:15~30人日
  • 検証(フェイルオーバー訓練含む):10~20人日
  • 合計:35~70人日

月次運用負荷

  • 両クラスタのバージョン管理・パッチ:10~20時間/月
  • フェイルオーバー訓練(年1~2回):平均+数時間/月
  • 平均:月12~25時間

ROI判断フレーム

1時間あたりの停止コストをモデル化する

例:ECサイト(月間売上3,000万円)の場合

  • 直接売上損失:3,000万円 ÷ 30日 ÷ 24時間 ≈ 4.1万円/時
  • エンジニア対応コスト:8,000円/人 × 10名 = 8万円/時
  • 機会損失・ブランド毀損:4.1万円/時
  • 合計:約16万円/時

Cloudflareレベルの大規模障害で「年1回、4時間止まる」と仮定:

  • 年間損失:16万円/時 × 4時間 = 64万円/年

冗長化コストとの比較

パターン年間インフラ費年間運用費合計ROI判定
マルチCDN132~252万円48~80万円180~332万円年3~4回の障害でペイ
マルチクラウド204~360万円96~192万円300~552万円年5回以上の大規模障害必要

導入判断の目安

以下の「Yes/Noチャート」で判定します。

  • Q1:1時間停止での推定損失は100万円以上か?
  • Q2:過去2年で「基盤サービス障害」による停止が2回以上あったか?
  • Q3:24時間365日でRTO(復旧目標時間)が1時間未満か?
判定推奨アクション
Yes×3マルチクラウド含むフル冗長化を検討
Yes×2マルチCDN以上を検討
Yes×1CI/CD冗長化から始める
No×3部分的なSPOF排除(後述)から開始

第4章:実装難易度別の冗長化パターン5選

実際の導入を想定して、難易度と効果で分類した5つのパターンを詳細に解説します。

パターン1:npm キャッシュ兼プライベートレジストリ(Verdaccio)

難易度:低~中 | 効果:高

概要

社内にキャッシュ付きnpmプロキシを立て、CI/CDは常にそこだけを見るようにします。npmjs.org / Cloudflareが落ちても、過去に取得済みのパッケージはローカルキャッシュから即座にinstallできます。

実装例(Docker Compose)

# docker-compose.yml
version: '3.8'
services:
  verdaccio:
    image: verdaccio/verdaccio:5
    container_name: npm-registry
    ports:
      - "4873:4873"
    volumes:
      - verdaccio_storage:/verdaccio/storage
      - ./conf/config.yaml:/verdaccio/conf/config.yaml
    environment:
      - NODE_ENV=production
    restart: unless-stopped

volumes:
  verdaccio_storage:
# conf/config.yaml(Verdaccio設定)
storage: ./storage
uplinks:
  npmjs:
    url: https://registry.npmjs.org/
    timeout: 30s
    max_retries: 3

packages:
  '@*/*':
    access: $all
    publish: $authenticated
    proxy: npmjs
  '**':
    access: $all
    publish: $authenticated
    proxy: npmjs

logs:
  - {type: stdout, format: pretty, level: http}

server:
  keepAliveTimeout: 60

CI/CDでの利用設定

# .npmrc(プロジェクトルート)
registry=http://verdaccio.internal:4873
always-auth=false

# GitHub Actions での設定例
# .github/workflows/build.yml
name: Build and Deploy

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Configure npm registry
        run: |
          echo "registry=http://verdaccio.internal:4873" > .npmrc
      
      - name: Install dependencies
        run: npm ci

効果と注意点

メリット

  • npmjs.org / Cloudflare障害時も、過去に利用したバージョンはCI/CDが通る
  • 外部レジストリ依存を減らし、ビルド速度・再現性も向上
  • 社内パッケージの管理・配信も一元化可能

注意点

  • Verdaccio自体が単一障害点にならないよう、ボリューム永続化・バックアップ・冗長化が必要
  • 未キャッシュの新バージョンは障害中は取得不可(=ビルドに使う依存バージョンは固定する運用が前提)
  • ストレージ肥大化対策として、古いパッケージ削除ポリシーを決めておく

パターン2:npm レジストリ多重化(public + private)

難易度:低 | 効果:中

概要

複数のnpmレジストリを使い分け、特定レジストリ障害時に手動or自動でフェイルオーバーできるようにします。

実装例(.npmrc)

# デフォルト設定(public registry)
registry=https://registry.npmjs.org/

# スコープ付きパッケージは社内レジストリへ
@company:registry=https://npm.company.internal/

# 認証トークン
//npm.company.internal/:_authToken=${NPM_TOKEN}

# ネットワークタイムアウト設定
fetch-timeout=30000
fetch-retry-mintimeout=10000
fetch-retry-maxtimeout=120000

GitHub Actions での環境変数切り替え

env:
  NPM_REGISTRY: ${{ vars.NPM_REGISTRY || 'https://registry.npmjs.org/' }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: ${{ env.NPM_REGISTRY }}
      
      - name: Configure npm registry
        run: |
          npm config set registry $NPM_REGISTRY
          npm config set fetch-timeout 30000
          npm config set fetch-retry-mintimeout 10000
      
      - name: Install dependencies
        run: npm ci --prefer-offline --no-audit

pnpm での実装例

# .npmrc
registry=${NPM_REGISTRY}

# pnpm-workspace.yaml
packages:
  - 'packages/*'

# CI での設定
# GitHub Actions
- name: Install pnpm
  uses: pnpm/action-setup@v4
  with:
    version: 9

- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'pnpm'

- name: Configure registry
  run: |
    pnpm config set registry ${NPM_REGISTRY}

- name: Install dependencies
  run: pnpm install --frozen-lockfile

効果と注意点

メリット

  • public registry障害時も、private レジストリにフェイルオーバー可能
  • 環境変数を変更するだけで全リポジトリのCI/CDが一斉に切り替わる
  • 実装が簡単で、すぐに導入可能

注意点

  • 切り替えを「手動オペレーション」で良しとするなら簡単だが、自動化には後述のDNS/ヘルスチェック連携が必要
  • 複数レジストリを使う場合、lockfile(package-lock.json等)の一貫性に注意

パターン3:CI/CD サービス二重化(SaaS + 自前 Runner)

難易度:中~高 | 効果:高

概要

GitHub Actions等のSaaS CI + 自前のself-hosted runner を併用することで、SaaS側の障害やCloudflare経由の通信が詰まっても、最低限のビルド・デプロイを継続できます。

GitHub Actions self-hosted runner の登録

# Runner の登録(Linux環境の例)
mkdir ~/github-runner && cd ~/github-runner

curl -o actions-runner-linux-x64-2.310.2.tar.gz \
  -L https://github.com/actions/runner/releases/download/v2.310.2/actions-runner-linux-x64-2.310.2.tar.gz

tar xzf ./actions-runner-linux-x64-2.310.2.tar.gz

# トークン取得(GitHub リポジトリ設定 → Actions → Runners)
./config.sh \
  --url https://github.com/your-org/your-repo \
  --token $GITHUB_RUNNER_TOKEN \
  --name self-hosted-runner-1 \
  --labels self-hosted,linux,production \
  --work _work

# サービスとしてインストール
sudo ./svc.sh install

# 起動
sudo ./svc.sh start

Docker Compose での self-hosted runner 運用

# docker-compose.yml
version: '3.8'

services:
  gh-runner:
    image: myorg/github-runner:latest
    container_name: github-runner-1
    environment:
      GITHUB_OWNER: your-org
      GITHUB_REPO: your-repo
      GITHUB_TOKEN: ${GITHUB_RUNNER_TOKEN}
      RUNNER_NAME: gh-runner-1
      RUNNER_LABELS: self-hosted,docker,production
      RUNNER_WORKDIR: /tmp/github-runner
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - runner_work:/tmp/github-runner
    restart: unless-stopped
    networks:
      - runner-net

  # 複数ランナーを起動する場合
  gh-runner-2:
    image: myorg/github-runner:latest
    container_name: github-runner-2
    environment:
      GITHUB_OWNER: your-org
      GITHUB_REPO: your-repo
      GITHUB_TOKEN: ${GITHUB_RUNNER_TOKEN}
      RUNNER_NAME: gh-runner-2
      RUNNER_LABELS: self-hosted,docker,production
      RUNNER_WORKDIR: /tmp/github-runner
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - runner_work_2:/tmp/github-runner
    restart: unless-stopped
    networks:
      - runner-net

volumes:
  runner_work:
  runner_work_2:

networks:
  runner-net:

ワークフローで self-hosted ランナーを使用

# .github/workflows/deploy.yml
name: Deploy (Self-Hosted)

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: [self-hosted, linux, production]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Deploy to production
        run: npm run deploy
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}

効果と注意点

メリット

  • GitHub Actions側がCloudflare経由の障害に巻き込まれても、self-hosted runnerで継続
  • さらに別プロバイダ(GitLab CI等)を併用すると、SaaS全体の障害にも対応可能
  • ビルド・デプロイの完全な自社管理が実現

注意点

  • Runner を置くネットワークも、Cloudflareや単一クラウドに閉じないように設計
  • 権限管理・シークレット管理を複数系統に分ける必要があり、運用負荷が上がる
  • Runner自体のメンテナンス・パッチ適用が必要

パターン4:自前 CI のマルチAZ / マルチクラウド冗長化

難易度:高 | 効果:非常に高

概要

GitLab Runner / Jenkins / Argo Workflows などをKubernetes上で稼働させ、複数AZ・複数クラウドに分散配置して、クラウド障害にも対応できるようにします。

Kubernetes上のGitLab Runner デプロイ例

# gitlab-runner-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gitlab-runner
  namespace: ci-system
spec:
  replicas: 3
  selector:
    matchLabels:
      app: gitlab-runner
  template:
    metadata:
      labels:
        app: gitlab-runner
    spec:
      # AZ分散
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - gitlab-runner
                topologyKey: topology.kubernetes.io/zone
      
      containers:
        - name: gitlab-runner
          image: gitlab/gitlab-runner:alpine
          imagePullPolicy: Always
          
          env:
            - name: CI_SERVER_URL
              value: https://gitlab.company.com/
            - name: REGISTRATION_TOKEN
              valueFrom:
                secretKeyRef:
                  name: gitlab-runner-secret
                  key: token
            - name: RUNNER_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.pod-name
            - name: RUNNER_TAG_LIST
              value: "docker,kubernetes,production"
          
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 2000m
              memory: 2Gi
          
          volumeMounts:
            - name: docker-socket
              mountPath: /var/run/docker.sock
            - name: runner-cache
              mountPath: /cache
      
      volumes:
        - name: docker-socket
          hostPath:
            path: /var/run/docker.sock
        - name: runner-cache
          emptyDir: {}

---
# Service for external access
apiVersion: v1
kind: Service
metadata:
  name: gitlab-runner-service
  namespace: ci-system
spec:
  selector:
    app: gitlab-runner
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
  type: LoadBalancer

マルチクラウド構成(Terraform)

# AWS 側
provider "aws" {
  alias  = "aws_primary"
  region = "ap-northeast-1"
}

# GCP 側
provider "google" {
  alias   = "gcp_secondary"
  project = "my-project"
  region  = "asia-northeast1"
}

# AWS EKS クラスタ
resource "aws_eks_cluster" "primary" {
  provider = aws.aws_primary
  name     = "ci-cluster-primary"
  
  # ... クラスタ定義
}

# GCP GKE クラスタ
resource "google_container_cluster" "secondary" {
  provider = google.gcp_secondary
  name     = "ci-cluster-secondary"
  
  # ... クラスタ定義
}

# 両クラスタに同じRunnerをデプロイ
resource "helm_release" "gitlab_runner_aws" {
  provider = helm.aws_primary
  name     = "gitlab-runner"
  chart    = "gitlab/gitlab-runner"
  
  values = [
    file("${path.module}/gitlab-runner-values.yaml")
  ]
}

resource "helm_release" "gitlab_runner_gcp" {
  provider = helm.gcp_secondary
  name     = "gitlab-runner"
  chart    = "gitlab/gitlab-runner"
  
  values = [
    file("${path.module}/gitlab-runner-values.yaml")
  ]
}

効果と注意点

メリット

  • クラウド / AZ 障害を前提にした構成になるため、CI が「完全に止まる」事態はかなり起こりにくくなる
  • ネットワーク経路もクラウドごとに異なるため、特定CDN(Cloudflare等)障害の影響を分散
  • 地理的分散により、DDoS等の攻撃耐性も向上

注意点

  • ネットワーク設計(VPN / Peering)と Secrets管理(Vault / KMS連携)など、インフラ設計の難易度は非常に高い
  • コストが大きく上がるため、「最低限どのジョブだけを多重化するか」の線引きが必要
  • 監視・ログ集約(Datadog等)の複雑度も増加

パターン5:DNS・監視・Webhook 経路の冗長化

難易度:中 | 効果:中~高

概要

Cloudflare障害で CI/CD が止まるケースには、「CI ツール本体」ではなく DNS / Webhook / 監視が詰まるパターンも含まれます。これらの経路を冗長化することで、見えない障害を防ぎます。

ローカル DNS / Route53 フェイルオーバー設定

# dnsmasq を Docker で運用する例
version: '3.8'
services:
  dnsmasq:
    image: jpillora/dnsmasq
    container_name: local-dns
    ports:
      - "53:53/udp"
      - "53:53/tcp"
    volumes:
      - ./dnsmasq.conf:/etc/dnsmasq.conf
    cap_add:
      - NET_ADMIN
    restart: unless-stopped
# dnsmasq.conf
# 複数の上流DNSを指定(フェイルオーバー)
server=1.1.1.1        # Cloudflare DNS
server=8.8.8.8        # Google DNS
server=9.9.9.9        # Quad9

# ローカルキャッシュ設定
cache-size=1000
min-cache-ttl=3600

# ログ
log-queries
log-facility=/var/log/dnsmasq.log

Route53 DNS フェイルオーバー(Terraform)

# ヘルスチェック定義
resource "aws_route53_health_check" "ci_primary" {
  fqdn              = "ci-primary.example.com"
  port              = 443
  type              = "HTTPS"
  failure_threshold = 3
  request_interval  = 30

  tags = {
    Name = "ci-primary-health-check"
  }
}

# プライマリレコード(Cloudflare経由)
resource "aws_route53_record" "ci_primary" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "ci.example.com"
  type    = "CNAME"
  ttl     = 60

  set_identifier       = "primary"
  failover_routing_policy {
    type = "PRIMARY"
  }

  records             = ["ci-primary.example.net"]
  health_check_id     = aws_route53_health_check.ci_primary.id
  set_identifier      = "primary"
}

# セカンダリレコード(オリジン直or別CDN)
resource "aws_route53_record" "ci_secondary" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "ci.example.com"
  type    = "CNAME"
  ttl     = 60

  set_identifier = "secondary"
  failover_routing_policy {
    type = "SECONDARY"
  }

  records = ["ci-secondary.example.net"]
}

監視・通知の冗長化

# Prometheus + AlertManager で複数チャネルに通知
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - localhost:9093

# AlertManager 設定
global:
  resolve_timeout: 5m

route:
  receiver: 'default'
  group_by: ['alertname']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 12h
  
  routes:
    - match:
        severity: critical
      receiver: 'critical-channels'
      continue: true

receivers:
  - name: 'default'
    slack_configs:
      - api_url: ${SLACK_WEBHOOK_URL}
        channel: '#alerts'
  
  - name: 'critical-channels'
    slack_configs:
      - api_url: ${SLACK_WEBHOOK_URL}
        channel: '#critical-alerts'
    pagerduty_configs:
      - service_key: ${PAGERDUTY_KEY}
    email_configs:
      - to: 'oncall@company.com'
        from: 'alerts@company.com'
        smarthost: 'smtp.company.com:587'

効果と注意点

メリット

  • CI/CD ツール自体は単一でも、DNS経路や監視が多重化されていれば「気づかない・たどり着けない」障害を減らせる
  • 比較的シンプルな構成で、大きな効果が期待できる
  • 既存インフラへの影響が最小限

注意点

  • DNS フェイルオーバーはTTLとキャッシュの影響を受けるため、切り替えに数分かかる可能性
  • 監視・通知の多重化は、アラート疲れにつながらないよう設計が必要

第5章:今日から実装できるチェックリスト

Cloudflare障害で困った読者が、今日から実施できる対策を優先度順にまとめました。

✅ チェックリスト(優先度順)

1. DNSのバックアップ経路を用意する 〈難易度:低 | 優先度:★★★★★〉

  • Cloudflare以外のDNSベンダー(レジストラ標準DNS or Route53等)を確認
  • ゾーン定義をコード化(Terraform等)して複製可能にする
  • TTLを60~300秒程度に設定し、障害時の切り替え速度を確保
  • DNS切り替え手順をドキュメント化し、検証済みにしておく

実装例

# 現在のCloudflareのレコード一覧を取得
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
  -H "Authorization: Bearer TOKEN" | jq .

# レジストラのDNS管理画面で同じレコードを追加
# または Terraform で管理

2. CI/CD用エンドポイントにCloudflareを経由しない経路を作る 〈難易度:低~中 | 優先度:★★★★☆〉

  • Webhook / API エンドポイントを洗い出す
  • オリジン直アクセス用ホスト名(例:ci-origin.example.com)を用意
  • または別CDN(Fastly / CloudFront等)でマルチCDN構成を検討
  • Nginx等で、Cloudflare経由とオリジン直を両方許可する設定にする

実装例

# Cloudflare経由の通常経路
server {
    listen 443 ssl;
    server_name ci-webhook.example.com;
    
    # CloudflareのIPレンジのみ許可
    allow 173.245.48.0/20;
    allow 103.21.244.0/22;
    # ... Cloudflare IPリストを定期更新
    deny all;
    
    location /webhook {
        proxy_pass http://ci_app;
    }
}

# バックドア用オリジン直アクセス
server {
    listen 443 ssl;
    server_name ci-origin.example.com;
    
    # 社内VPN/特定IPからのみ許可
    allow 10.0.0.0/8;
    deny all;
    
    location /webhook {
        proxy_pass http://ci_app;
    }
}

3. Cloudflare依存のCI/CDステップを明示し、フォールバック実装 〈難易度:中 | 優先度:★★★★☆〉

  • パイプライン定義を見直し、Cloudflare前提のステップをタグ付け
  • 失敗時のフォールバック手順を実装(代替DNS、オリジン直等)
  • Cloudflare依存ステップは手動承認ステージに逃がす設定を検討

実装例

# GitHub Actions でのフォールバック例
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy app
        run: ./deploy_app.sh
      
      - name: Update Cloudflare DNS
        id: cf_dns
        run: ./update_cloudflare_dns.sh
        continue-on-error: true
      
      - name: Fallback to Route53 if Cloudflare failed
        if: failure() && steps.cf_dns.outcome == 'failure'
        run: ./update_route53_dns.sh

4. Cloudflare経由とオリジン直を分けて監視 〈難易度:中 | 優先度:★★★★☆〉

  • Cloudflare経由のエンドポイント(ci-webhook.example.com)を監視
  • オリジン直・別CDN経由のエンドポイント(ci-origin.example.com等)も監視
  • Cloudflare Status / 自社監視 / 外形監視を組み合わせ、原因判定を自動化
  • 障害時の手順書に、X / StatusPage / ダウンディテクター確認ステップを含める

実装例

# Prometheus ルール
groups:
  - name: ci-endpoints
    rules:
      - alert: CiWebhookDownViaCloudflare
        expr: probe_success{job="blackbox-ci-webhook-cf"} == 0
        for: 5m
        annotations:
          summary: "CI webhook via Cloudflare is down"
          description: "Check Cloudflare status and consider switching DNS/CDN route."

5. WAF・ボット対策ルール変更を「即本番反映」しない運用 〈難易度:低 | 優先度:★★★☆☆〉

  • 緊急ルール(セキュリティパッチ等)もステージング環境で検証
  • ログモード(Block ではなく Count)から開始し、エラー率をモニタリング
  • CI/CD用パスを WAF 除外ルールに追加し、誤検知を防ぐ

実装例

# Cloudflare WAF ルールの段階的適用
# 1. ステージングゾーンにのみルール適用(log/count モード)
cfcli waf rules create \
  --zone-id "$STAGING_ZONE_ID" \
  --mode "count" \
  --expression "http.request.uri.path contains \"/api\" and ..."

# 2. 24時間モニタリング
sleep 86400

# 3. 問題なければ本番ゾーンへ適用
cfcli waf rules create \
  --zone-id "$PROD_ZONE_ID" \
  --mode "block" \
  --expression "http.request.uri.path contains \"/api\" and ..."

6. 重要なCIジョブに別CIサービス / セルフホストRunner を用意 〈難易度:中~高 | 優先度:★★★☆☆〉

  • 現在使っているCIサービスを確認(GitHub Actions / GitLab CI等)
  • 別CIサービスへのバックアップパイプラインを用意
  • または自社 Kubernetes / VM 上にセルフホストRunner を立てる
  • 定期的に両系統でビルドが通ることを確認

7. システム構成図と設計ルールに「Cloudflare依存ポイント」を明示 〈難易度:低 | 優先度:★★★☆☆〉

  • システム構成図に Cloudflare 依存箇所を赤色で明示
  • DNS / CDN / WAF / Workers / KV 等、依存機能を一覧化
  • 設計レビューで「単一サービス依存を避ける」をチェック項目に追加
  • 新規機能追加時に、フォールバック案を設計書に含める

まとめ:「Cloudflare障害=開発停止」の時代を終わらせる

Cloudflareの2025年11月大規模障害は、単一サービスへの過度な依存がもたらす脆弱性を、多くの企業に突きつけました。npm installが通らず、Firebase Functionsがデプロイできない状況は、決して他人事ではなく、どのチームにも起こり得る現実です。

しかし、本記事で紹介した5つの冗長化パターンを段階的に導入することで、Cloudflareの障害はもはや「開発を止める理由」ではなく、「自動フェイルオーバーをテストする機会」に変わります

導入の優先順位

  1. 今週中:DNSバックアップ + オリジン直アクセス経路の用意
  2. 今月中:npm キャッシュ(Verdaccio)の構築 + CI/CD フェイルオーバー設計
  3. 今四半期:監視・通知の多重化 + 定期的なDR訓練
  4. 今年中:CI/CD二重化 or マルチクラウド検討

最後に

インフラの冗長化は、単なる「保険」ではなく、ビジネス継続性と開発生産性を守るための投資です。Cloudflareのような大規模サービスでも障害は起こります。その時に「止まらないシステム」を持つことが、競争力の源泉になる時代が来ています。

本記事のチェックリストから、今日から実装できる対策を1つ選んで、始めてみてください。


参考情報

  • Cloudflare Status:障害情報のリアルタイム確認
  • npm registry mirror 運用ガイド:Verdaccio / Nexus 等の構築・運用
  • GitHub Actions self-hosted runner ドキュメント:複数ランナー構成
  • Terraform / CloudFormation:Infrastructure as Code による冗長化設計

🗂️ 人気カテゴリ

記事数の多いカテゴリから探す