プッシュ通知
新記事をすぐにお知らせ
🎙️ 音声: ずんだもん / 春日部つむぎ(VOICEVOX)
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つのパターンで提示します。
Cloudflareは単なるCDNサービスではなく、インターネット基盤層に深く組み込まれています。
Cloudflareが提供する機能層
CDN・コンテンツ配信
registry.npmjs.org)のキャッシュ配信DNS・トラフィックルーティング
セキュリティ・DDoS対策
TLS終端・暗号化
Zero Trust・アクセス制御
npm registryの場合、Cloudflareはこれら全機能を組み合わせて、npmjs.orgの前に立ち塞がる共通インフラとして機能しています。そのため、Cloudflareが落ちると:
といった複数の障害が同時に発生し、結果として開発環境から本番環境まで全てが影響を受けるのです。
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パイプラインでは、通常以下のようなステップが実行されます。
# 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障害時に起こる連鎖:
npm ci ステップで失敗 → パイプライン全体が止まる特に問題なのが、監視・ステータス配信もCloudflare経由の場合、「何が落ちているのか分からない」という二重の障害になることです。
Cloudflare以外のインフラサービス障害でも、同様にCI/CDが止まる事例が複数あります。業界による影響の違いを理解することで、冗長化の優先度付けがしやすくなります。
障害の概要
GitHub.comのリポジトリ・GitHub Actionsが一時的に不安定になった障害では、以下が同時に発生しました。
clone/push ができないCI/CDへの具体的な影響
GitHub Actionsに完全依存していたチームでは:
main ブランチへのマージ時の自動デプロイが全て手動に対策の方向性
# 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にスイッチして最低限のビルド・デプロイを継続できます。
障害の概要
GitLab.comのパフォーマンス低下やDBトラブルが発生した際、以下が起こりました。
業界特有のインパクト
金融システムはメンテナンスウィンドウが限定的です。「この2時間でリリースしないと1週間延期」という制約が多く存在します。
GitLab CIに完全依存していたチームでは:
対策の方向性
# セルフホストGitLabのバックアップ運用例
# オンプレ or クラウド上にセルフホストGitLabを構築し、
# 定期的にプロジェクトをエクスポート/インポート
# プロジェクトエクスポート(毎日実行)
gitlab-backup create
# 別環境へのインポート(障害時)
gitlab-backup restore BACKUP=<backup_timestamp>
このような構成により、GitLab.com障害時も、セルフホスト側で最小限のCI/CDパイプラインを実行できます。
障害の概要
特定AWSリージョン(例:ap-northeast-1)が障害に陥った際、そのリージョンに閉じたCI/CD基盤は全て停止しました。
業界特有のインパクト
対策の方向性
# 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)はクロスリージョンレプリケーションを設定し、リージョン障害時は別リージョンのパイプラインにトラフィックを切り替えます。
インフラ冗長化は「理想」ですが、実務ではコスト・構築工数・運用負荷とのバランスを取る必要があります。以下は、中規模サービス(月間トラフィック5TB、エンジニア10名)を想定した試算です。
構成
追加コストの目安
| 項目 | 月額 |
|---|---|
| CloudFront データ転送(5TB) | 8~15万円 |
| AWS WAF ルール・リクエスト | 3~5万円 |
| Route53 ホストゾーン・クエリ | 0.5~1万円 |
| 合計 | 11~21万円 |
構築工数
月次運用負荷
構成
追加コストの目安
| 項目 | 月額 |
|---|---|
| GKE クラスタ+最小ノード | 10~15万円 |
| クロスクラウド監視(Datadog等) | 5~10万円 |
| ネットワーク・VPN | 2~5万円 |
| 合計 | 17~30万円 |
構築工数
月次運用負荷
1時間あたりの停止コストをモデル化する
例:ECサイト(月間売上3,000万円)の場合
Cloudflareレベルの大規模障害で「年1回、4時間止まる」と仮定:
冗長化コストとの比較
| パターン | 年間インフラ費 | 年間運用費 | 合計 | ROI判定 |
|---|---|---|---|---|
| マルチCDN | 132~252万円 | 48~80万円 | 180~332万円 | 年3~4回の障害でペイ |
| マルチクラウド | 204~360万円 | 96~192万円 | 300~552万円 | 年5回以上の大規模障害必要 |
導入判断の目安
以下の「Yes/Noチャート」で判定します。
| 判定 | 推奨アクション |
|---|---|
| Yes×3 | マルチクラウド含むフル冗長化を検討 |
| Yes×2 | マルチCDN以上を検討 |
| Yes×1 | CI/CD冗長化から始める |
| No×3 | 部分的なSPOF排除(後述)から開始 |
実際の導入を想定して、難易度と効果で分類した5つのパターンを詳細に解説します。
難易度:低~中 | 効果:高
社内にキャッシュ付きnpmプロキシを立て、CI/CDは常にそこだけを見るようにします。npmjs.org / Cloudflareが落ちても、過去に取得済みのパッケージはローカルキャッシュから即座にinstallできます。
# 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
# .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
メリット
注意点
難易度:低 | 効果:中
複数のnpmレジストリを使い分け、特定レジストリ障害時に手動or自動でフェイルオーバーできるようにします。
# デフォルト設定(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
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
# .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
メリット
注意点
難易度:中~高 | 効果:高
GitHub Actions等のSaaS CI + 自前のself-hosted runner を併用することで、SaaS側の障害やCloudflare経由の通信が詰まっても、最低限のビルド・デプロイを継続できます。
# 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.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:
# .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 }}
メリット
注意点
難易度:高 | 効果:非常に高
GitLab Runner / Jenkins / Argo Workflows などをKubernetes上で稼働させ、複数AZ・複数クラウドに分散配置して、クラウド障害にも対応できるようにします。
# 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
# 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")
]
}
メリット
注意点
難易度:中 | 効果:中~高
Cloudflare障害で CI/CD が止まるケースには、「CI ツール本体」ではなく DNS / Webhook / 監視が詰まるパターンも含まれます。これらの経路を冗長化することで、見えない障害を防ぎます。
# 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
# ヘルスチェック定義
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'
メリット
注意点
Cloudflare障害で困った読者が、今日から実施できる対策を優先度順にまとめました。
実装例
# 現在のCloudflareのレコード一覧を取得
curl -X GET "https://api.cloudflare.com/client/v4/zones/ZONE_ID/dns_records" \
-H "Authorization: Bearer TOKEN" | jq .
# レジストラのDNS管理画面で同じレコードを追加
# または Terraform で管理
ci-origin.example.com)を用意実装例
# 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;
}
}
実装例
# 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
ci-webhook.example.com)を監視ci-origin.example.com等)も監視実装例
# 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."
実装例
# 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 ..."
Cloudflareの2025年11月大規模障害は、単一サービスへの過度な依存がもたらす脆弱性を、多くの企業に突きつけました。npm installが通らず、Firebase Functionsがデプロイできない状況は、決して他人事ではなく、どのチームにも起こり得る現実です。
しかし、本記事で紹介した5つの冗長化パターンを段階的に導入することで、Cloudflareの障害はもはや「開発を止める理由」ではなく、「自動フェイルオーバーをテストする機会」に変わります。
インフラの冗長化は、単なる「保険」ではなく、ビジネス継続性と開発生産性を守るための投資です。Cloudflareのような大規模サービスでも障害は起こります。その時に「止まらないシステム」を持つことが、競争力の源泉になる時代が来ています。
本記事のチェックリストから、今日から実装できる対策を1つ選んで、始めてみてください。
参考情報
記事数の多いカテゴリから探す