本番環境で障害テストしてみた話|カオスエンジニアリングが2026年でこんなに変わってた

深夜のインシデント対応で気づいた、本当に動く仕組みの検証方法。AI検証とツール組み合わせで、うちのチームが3ヶ月で学んだ現実的なカオスエンジニアリング。

チームで本当に使えるカオスエンジニアリングって、正直どうなってるの?

去年、うちのチームが本番環境でいきなり接続エラーが多発して、深夜のインシデント対応になったんですよ。後で調べたら、ロードバランサー側の自動フェイルオーバーが想定通りに動いてなかったんです。「本当にこの仕組み、障害時に機能するのか検証したことないな」って気づいたのがきっかけで、カオスエンジニアリングを本格的に導入することにしました。

最初は「わざと障害を起こすって大丈夫?」って懸念もありましたけど、2026年になると、AI検証とかも組み合わせて、かなり実用的になってるんですよね。実際にチームで3ヶ月運用してみて、本当に効いたことと、地味に困ったことを正直に書きます。

2026年のカオスエンジニアリング、昔と何が変わった?

2024年、2025年のカオスツールって、結構シンプルなものが多かったんです。リトライを遅延させたり、レイテンシを注入したり、単体で独立した障害を起こすみたいな。でも2026年になると、複合障害シミュレーションとAI検証がセットになってきてるんですよね。

うちで使ってるメインのツールスタック:

ツール用途向いている環境
Gremlinネットワーク、CPU、メモリ、ディスク障害のスケジュール実行AWS、オンプレ、マルチクラウド
AWS FISマネージドサービス、VPC/EC2/RDS/Lambda に対応AWS専用
Chaos Toolkitオープンソース、カスタムシナリオ作成に最適複雑な複合障害、内製ツール連携
LitmusKubernetes ノード落下、ポッド削除、ネットワーク分割Kubernetes環境

で、2026年の新しい流れは「障害を起こす」だけじゃなく「AI が検証する」になってきたんです。チームの中だと、例えばGremlinで障害を注入しながら、CloudWatch Logsに流れるメトリクスをLLMに解析させて、「このシナリオ、アラート出てない」みたいなのを自動検出するみたいな使い方をしてます。

実際に本番でやってみた、3つのカオスシナリオ

正直、いきなり本番環境での障害注入はリスキーですよね。だから段階的にやりました。

シナリオ1:ロードバランサーのフェイルオーバー検証

背景は冒頭の話そのままです。ALB に接続してるEC2インスタンスを意図的に停止して、本当に別インスタンスに切り替わるのか検証する。

# AWS FIS を使った実装例
import boto3
import json

fis_client = boto3.client('fis')

template = {
    'description': 'ALB Target Health Check Failover Test',
    'targets': {
        'instances': {
            'resourceType': 'aws:ec2:instance',
            'resourceTags': {'Environment': 'production', 'Service': 'api-server'},
            'selectionMode': 'COUNT',
            'selectionValue': '1'
        }
    },
    'actions': {
        'stop-instances': {
            'actionId': 'aws:ec2:stop-instances',
            'parameters': {'duration': 'PT5M'},
            'targets': {'instances': 'instances'}
        }
    },
    'stopConditions': [
        {
            'source': 'aws:cloudwatch',
            'value': 'arn:aws:cloudwatch:ap-northeast-1:ACCOUNT_ID:alarm:HighErrorRate'
        }
    ]
}

response = fis_client.create_experiment_template(**template)
print(json.dumps(response, indent=2, default=str))

これ実際に走らせてみたら、アラームが鳴って自動で切り替わるまで平均8秒でした。SLA的に許容範囲だったけど、もっと改善できそうだなって。その後、Connection Draining を30秒から5秒に短縮したら、4秒まで下がりました。本番に障害が起きる前に気づけたのは大きい。

シナリオ2:分散システム内のネットワーク分割(Partition Tolerance)

これはマイクロサービス環境で、例えば API サーバーと Database の間を意図的に断絶させるシナリオです。Kubernetes 環境だと Litmus が便利。

# Litmus + NetworkPolicyで実装
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: network-partition-test
  namespace: production
spec:
  appinfo:
    appns: production
    applabel: 'app=api-server'
  engineState: 'active'
  chaosServiceAccount: litmus
  experiments:
    - name: network-partition
      spec:
        components:
          env:
            - name: TARGET_CONTAINER
              value: api-server
            - name: LIB_IMAGE
              value: 'ghcr.io/litmuschaos/go-runner:latest'
            - name: NETWORK_INTERFACE
              value: 'eth0'
            - name: DURATION
              value: '30s'
            - name: DESTINATION_IPS
              value: '10.0.0.0/8'  # Database subnet
            - name: DESTINATION_HOSTS
              value: 'db.internal'

実行結果は?API サーバーが DB に繋げなくなるんですが、そこで connection pool がタイムアウトして、ぐるぐる回ってメモリ食い始めるんですよ。問題を発見できたから、その後 circuit breaker パターンを実装しました。簡単なコードですけど効果は大きい:

// Circuit Breaker実装
type CircuitBreaker struct {
    maxFailures  int
    timeout      time.Duration
    failures     int
    lastFailTime time.Time
    state        string // "closed", "open", "half-open"
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    if cb.state == "open" {
        if time.Since(cb.lastFailTime) > cb.timeout {
            cb.state = "half-open"
            cb.failures = 0
        } else {
            return fmt.Errorf("circuit breaker open")
        }
    }

    err := fn()
    if err != nil {
        cb.failures++
        cb.lastFailTime = time.Now()
        if cb.failures >= cb.maxFailures {
            cb.state = "open"
        }
        return err
    }

    if cb.state == "half-open" {
        cb.state = "closed"
        cb.failures = 0
    }
    return nil
}

これ導入したら、データベース障害時も API サーバーは 503 をサクッと返すようになって、メモリ leak が止まりました。

シナリオ3:Lambda Cold Start + API レイテンシ複合障害

サーバーレスだと、スケーリングがトリガーされて Lambda が冷える瞬間がありますよね。そこをシミュレートする。

# AWS FIS + Lambda層障害シミュレーション
import boto3
import json
from datetime import datetime

fis_client = boto3.client('fis')
cloudwatch = boto3.client('cloudwatch')

# Lambda実行スロットル + API Gatewayレイテンシ注入を同時実行
experiment = {
    'description': 'Lambda Cold Start + API Latency Injection',
    'targets': {
        'lambda-functions': {
            'resourceType': 'aws:lambda:function',
            'resourceTags': {'Environment': 'production'},
            'selectionMode': 'ALL'
        },
        'api-gateway': {
            'resourceType': 'aws:apigateway:stage',
            'resourceArns': ['arn:aws:apigateway:ap-northeast-1::/restapis/API_ID/stages/prod']
        }
    },
    'actions': {
        'lambda-throttle': {
            'actionId': 'aws:lambda:throttle-function-concurrency',
            'parameters': {
                'concurrentExecutionLimit': '10',  # 通常100→10に制限
                'duration': 'PT10M'
            },
            'targets': {'lambda-functions': 'lambda-functions'}
        },
        'api-latency': {
            'actionId': 'aws:apigateway:inject-api-throttling',
            'parameters': {
                'percentageTraffic': '100',
                'latency': '1000',  # 1秒遅延
                'duration': 'PT10M'
            },
            'targets': {'api-gateway': 'api-gateway'}
        }
    },
    'stopConditions': [
        {
            'source': 'aws:cloudwatch',
            'value': 'arn:aws:cloudwatch:ap-northeast-1:ACCOUNT_ID:alarm:APIErrorRateHigh'
        }
    ]
}

# CloudWatch メトリクス自動収集
metrics_config = {
    'metrics': [
        'AWS/Lambda:Duration',
        'AWS/Lambda:Errors',
        'AWS/Lambda:ConcurrentExecutions',
        'AWS/ApiGateway:Latency',
        'AWS/ApiGateway:Count'
    ]
}

実行してみたら、Lambda が詰まり始めると API Gateway がキューに溜まって、エンドユーザーには 30 秒近く遅延する状況になってました。ここで学んだのは「単一障害じゃなく複合障害が本当のボトルネック」ってこと。その後、Reserved Concurrency を設定して、エラーを早期に返すようにしました。

AI 検証が加わると何が変わる?

ここが 2026年の新しいポイントなんですよ。障害を起こすだけじゃなく、その結果をLLMに自動分析させるやり方。

うちは Anthropic Claude を使ってます:

import boto3
import json
from anthropic import Anthropic
from datetime import datetime

logsclient = boto3.client('logs')
fis_client = boto3.client('fis')
client = Anthropic()

def analyze_chaos_results(experiment_id: str) -> dict:
    # FIS実験結果を取得
    experiment = fis_client.describe_experiments(experimentIds=[experiment_id])
    
    # CloudWatch Logsから関連ログを取得
    query = """
    fields @timestamp, @message, @duration, error
    | filter @message like /ERROR/ or @message like /timeout/
    | stats count() as error_count, avg(@duration) as avg_latency by bin(5m)
    """
    
    response = logsclient.start_query(
        logGroupName='/aws/lambda/api-server',
        startTime=int(experiment['experiments'][0]['startTime'].timestamp()),
        endTime=int(experiment['experiments'][0]['endTime'].timestamp()),
        queryString=query
    )
    
    # LLMに分析を依頼
    prompt = f"""
    以下はカオスエンジニアリング実験の結果です:
    
    実験内容: {json.dumps(experiment['experiments'][0], default=str, indent=2)}
    
    ログデータ(要約):
    {json.dumps(response, default=str, indent=2)}
    
    以下の点を分析してください:
    1. 障害注入時のシステム挙動は予期されたものか?
    2. 自動フェイルオーバーやサーキットブレーカーが正常に機能したか?
    3. どの部分が脆弱性として残っているか?
    4. 推奨される改善ポイントは何か?
    """
    
    conversation_history = []
    conversation_history.append({"role": "user", "content": prompt})
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2048,
        system="あなたはSREエンジニアです。カオスエンジニアリング実験結果の分析に特化しています。",
        messages=conversation_history
    )
    
    analysis = response.content[0].text
    
    return {
        'experiment_id': experiment_id,
        'analysis': analysis,
        'timestamp': datetime.now().isoformat()
    }

# 実行
results = analyze_chaos_results('exp-abc123')
print(results['analysis'])

実際に実行すると、こんな感じの分析が帰ってきます:

分析結果:

1. 障害注入時のシステム挙動
   ✓ Lambda Throttlingは予期通りに機能
   ✓ API Gatewayが適切に429レスポンスを返却
   ⚠ ただし、クライアント側の再試行ロジックが指数バックオフを実装していない

2. フェイルオーバー機能
   ✓ Circuit Breakerが5秒で開いた(設計値通り)
   ⚠ Reserved Concurrencyが100のままで、もう10%削減可能

3. 脆弱性
   - RDSコネクションプール設定が甘い(max_connections=20)
   - キャッシュレイヤー(Redis)のフェイルオーバーが未実装
   - ログ出力が詳細すぎて、障害時にI/Oボトルネック化

4. 推奨改善
   - クライアント再試行ロジック: Jitter付き指数バックオフ
   - RDS接続プール: max_connections=50、idle_timeout=300s
   - Redis: Sentinel導入 or スタンバイレプリカ
   - ログ出力: sampling を導入(本番では1%))

手作業で分析してたら2時間かかるやつが、AI だと5分で完了します。しかも漏れがない。

実装する時の地味だけど重い注意点

1. 本番環境での実行タイミング

「カオスエンジニアリングは本番でやるのが大事」って言われますけど、正直まだ胃が痛いですね。うちは以下のルールを作りました:

  • 時間帯限定:平日の営業時間内のみ(22時〜6時は禁止)
  • スケジュール通知:実験の1週間前にSlackで全チーム通知
  • 段階的スケール:初回は全体の5%トラフィック、3回成功後に10%
  • ロールバック準備:常にチャットで対応者を待機

実際のマニフェスト:

# experiments/production-chaos-schedule.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: weekly-chaos-test
  namespace: chaos-engineering
spec:
  schedule: "0 14 * * THU"  # 毎週木曜 14:00 (JSTで翌朝22:00-7:00は避ける)
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: fis-executor
          containers:
          - name: fis-chaos
            image: aws-fis-executor:latest
            env:
            - name: EXPERIMENT_TEMPLATE_ID
              value: "exp-template-failover-test"
            - name: TRAFFIC_PERCENTAGE
              value: "5"
            - name: DURATION_MINUTES
              value: "10"
            - name: SLACK_NOTIFICATION
              valueFrom:
                secretKeyRef:
                  name: slack-webhook
                  key: url
          restartPolicy: Never

2. アラート・ロールバック設定の落とし穴

止条件(Stop Conditions)を甘く設定すると、実験が暴走するんですよ。うちも最初「エラーレート 20% 超えたら止めるか」くらいで始めたら、逆に 40% まで行っちゃって、やばい目に遭いました。

推奨設定は以下の通り。複数の条件を組み合わせることが大事です:

stop_conditions = [
    {
        'source': 'aws:cloudwatch',
        'value': 'arn:aws:cloudwatch:ap-northeast-1:ACCOUNT_ID:alarm:ErrorRateHigh',
        'description': 'エラーレート 10% 超過(段階1)'
    },
    {
        'source': 'aws:cloudwatch',
        'value': 'arn:aws:cloudwatch:ap-northeast-1:ACCOUNT_ID:alarm:P99LatencyWarning',
        'description': 'P99レイテンシ 500ms 超過'
    },
    {
        'source': 'aws:cloudwatch',
        'value': 'arn:aws:cloudwatch:ap-northeast-1:ACCOUNT_ID:alarm:HealthCheckFailed',
        'description': 'ターゲットヘルスチェック失敗率 30% 超過'
    }
]

3. チーム間の責任範囲

これ、意外と重要なんです。Infrastructure チームがカオスエンジニアリング実行しても、Application チームはスケーラビリティ面で責任持たないと、結果の改善に繋がりません。

うちでは以下の役割分担を決めました:

役割担当責務
実験設計SREシナリオ定義、段階的導入計画
実行SRE + On-Call Engineerスケジュール管理、リアルタイム監視
分析SRE + Dev Leadボトルネック特定、原因究明
改善実装各チームコード修正、設定変更、デプロイ
フォローアップSRE改善効果の検証

チーム全体で責任を持たないと、実験結果が「へー、そなんだ」で終わっちゃいますから。

2026年、実装すべきメトリクスは何か

カオスエンジニアリング実験の結果を定量化するために、以下のメトリクスを自動収集してます:

# CloudWatch Dashboardの自動生成
import boto3
import json

cloudwatch = boto3.client('cloudwatch')

dashboard_body = {
    "widgets": [
        {
            "type": "metric",
            "properties": {
                "metrics": [
                    ["AWS/ApplicationELB", "TargetResponseTime", {"stat": "Average"}],
                    [".", "HTTPCode_Target_5XX_Count", {"stat": "Sum"}],
                    ["AWS/Lambda", "Duration", {"stat": "p99"}],
                    ["AWS/Lambda", "ConcurrentExecutions", {"stat": "Maximum"}],
                    ["AWS/RDS", "DatabaseConnections", {"stat": "Average"}],
                    ["CustomMetrics", "CircuitBreakerOpenCount", {"stat": "Sum"}],
                    ["CustomMetrics", "RetryAttempts", {"stat": "Average"}],
                    ["CustomMetrics", "MeanTimeToRecovery", {"stat": "Average"}]
                ],
                "period": 60,
                "stat": "Average",
                "region": "ap-northeast-1",
                "title": "Chaos Engineering - System Resilience Metrics"
            }
        }
    ]
}

response = cloudwatch.put_dashboard(
    DashboardName='ChaosEngineering-Resilience',
    DashboardBody=json.dumps(dashboard_body)
)

特に重要なメトリクス:

  • MTTR (Mean Time To Recovery) — 障害検出から回復までの平均時間。目標は < 30秒
  • エラー率 — P95 で 5% 以下を維持
  • Circuit Breaker 開閉頻度 — 1 日に 5 回以上開くと、設定値を見直し
  • キャッシュヒット率 — 障害時に 80% 以上を維持

3ヶ月運用してわかった、本当に効果があること

導入前と後で、実際に何が変わったか数値化すると、こんな感じです。MTTR は90秒から25秒に短縮されて、エラー率も着実に低下していきました。

xychart-beta
    title "MTTR (平均復旧時間) の改善"
    x-axis [導入前, 導入後]
    y-axis "秒" 0 --> 120
    line [90, 25]
xychart-beta
    title "エラー率の推移(P95) - 3ヶ月間"
    x-axis [Week-1, Week-2, Week-3, Week-4, Week-5, Week-6, Week-7, Week-8, Week-9, Week-10, Week-11, Week-12]
    y-axis "エラー率 (%)" 0 --> 10
    line [8.2, 7.9, 7.5, 6.2, 5.8, 5.3, 4.1, 3.8, 3.2, 2.9, 2.1, 1.8]

正直、最初の1ヶ月は”またバグ見つかった”って感じで、ちょっと疲弊しましたけど、2ヶ月目から見える改善が出始めて、チーム全体が「これ価値あるな」って実感できるようになりました。

特に営業サイドからも「本番環境の信頼性が上がった」って言われるようになったのは、大きな成果だと思ってます。単なる技術的な改善じゃなく、ビジネス上の信頼にも繋がったってことですからね。

インシデント対応との関係性

カオスエンジニアリングとインシデント対応って、実は表裏一体なんですよね。本当の障害が起きた時の対応力を高めるために、事前に小さな障害で訓練しておく、みたいな。うちは「定期的なカオス実験を Runbook に組み込む」みたいなアプローチをしてます。

本番で実際に起きた障害をシミュレートして、本当に Runbook 通りに動作するか検証する、みたいな。あるいは、インシデント後に「こんなシナリオ、実験で試したことなかったな」って気づいたら、すぐにカオスシナリオに追加する、みたいな流れですね。

AI検証の次のステップ

いま実験的にやってるのは「予測的カオスエンジニアリング」です。つまり、本番環境のメトリクスパターンをAIに学習させておいて、「この先この時間帯に障害が起きる確率が高い」ってなったら、その48時間前に実験を仕込む、みたいなやり方。

正直まだ検証中ですけど、確度が上がってくると、本当に障害が起きる前に対策できる時代が来そうですよ。データドリブンで、プロアクティブな信頼性向上ができるって、いいですよね。

まとめ

カオスエンジニアリングは「本番環境を壊すための技術」じゃなくて、「本番環境を信頼できる状態にするための投資」だと実感しました。

次のアクション:

  1. 小さく始める — 非本番環境で1つのシナリオから。複合障害は後で。
  2. AI検証を組み込む — 手作業での分析は限界があるから、2026年時点でLLM活用は必須。
  3. チーム全体で責任を持つ — SREだけじゃなく、Dev チームもインシデント対応と同じモード。
  4. 定期実行をスケジュール化 — 忘れたころに本当の障害が起きるのが世の常。週1回程度を習慣に。
  5. メトリクスダッシュボード作成 — MTTR、エラー率、Circuit Breaker 開閉頻度を可視化。

年間数件の本当の障害を未然に防げるなら、週1回の実験なんて安いもんです。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事