カオスエンジニアリングを手軽にはじめてみる
はじめに
サービスを運用していると、こんな不安を感じることはありませんか?
「もしデータベースが落ちたら、本当に自動でフェイルオーバーするの?」
「APIサーバーの一部が応答しなくなったとき、サービス全体は正常に動き続けるの?」
「ネットワークが不安定になったら、どこまで耐えられるの?」
障害は必ず起きます。でも、実際に起きるまで「本当に大丈夫か」は分からない。
そんな不安を解消するために、あえて障害を起こして検証するのがカオスエンジニアリングです。
この記事では、難しそうに聞こえるカオスエンジニアリングを、できるだけシンプルに始めて、ほんの少しでも理解を進めることを目的としています。
1. カオスエンジニアリングとは
Principles of Chaos Engineeringによると、カオスエンジニアリングとは:
Chaos Engineering is the discipline of experimenting on a system in order to build confidence in the system's capability to withstand turbulent conditions in production.
訳:カオスエンジニアリングとは、システムが本番環境の混沌とした状況に耐える能力に対する確信を構築するために、システムに対して実験を行う規律である
もっと簡単に言うと:
「わざと壊して、壊れないことを確認する」手法です。
カオスエンジニアリングの5原則
Principles of Chaos Engineeringでは、以下の5つの原則が定義されています:
-
定常状態の仮説を立てる
- システムの「正常な振る舞い」を定義
- スループット、エラー率、レイテンシなどの測定可能な出力に注目
-
現実世界のイベントを多様化する
- ハードウェア障害、ソフトウェア異常、トラフィックスパイクなど
- 実際に起きうる障害を幅広く再現
-
本番環境で実験を実行する
- 実際のトラフィックとシステム挙動を正確に検証
- テスト環境では再現できない問題を発見
-
実験を自動化し継続的に実行する
- 手動実験は非効率的
- 自動化により常にシステムの耐障害性を検証
-
影響範囲を最小限に抑える
- 顧客への悪影響を最小化
- 小さく始めて徐々に範囲を拡大
なぜ今、カオスエンジニアリングが重要なのか?
-
システムの複雑化
- マイクロサービス
- 分散システム
- クラウドネイティブ
-
ビジネスへの影響
- 1分のダウンタイムが数億円の損失に
- ユーザー体験の重要性
-
予防から発見へ
- 「障害を防ぐ」から「障害に強くなる」へ
- レジリエンス(回復力)の重視
つまり、システムが複雑になればなるほど想定外が増えます。
だからこそ、あえて壊して「想定内」に変えることが重要なのです。
2. めちゃくちゃ簡単にカオスエンジニアリング
後述するツールなどは使わずに、まずはシンプルにカオスエンジニアリングを体験してみます。
今回作ったもの:シンプルなWeb APIサーバー
まず、簡単なWebアプリケーションを作りました。
RubyのSinatraというフレームワークを使った、データを返すだけのシンプルなAPIです。
アプリケーションの構成
test-chaos/
├── app.rb # メインのWebアプリケーション
├── config.ru # アプリケーション起動設定
└── Gemfile # 依存関係
app.rbの中身は下記の通りです。
# frozen_string_literal: true
require 'sinatra'
require 'json'
# カオスエンジニアリングのデモ用Webアプリケーション
class ChaosApp < Sinatra::Base
configure do
set :port, 4567
set :chaos_enabled, false
set :chaos_delay, 0
set :chaos_error_rate, 0
end
get '/' do
content_type :json
{ message: 'Welcome to Chaos Engineering Demo!', status: 'healthy' }.to_json
end
get '/api/data' do
inject_chaos if settings.chaos_enabled
content_type :json
{
data: {
timestamp: Time.now.to_s,
random_value: rand(100),
message: 'This is sample data'
},
status: 'success'
}.to_json
end
get '/health' do
content_type :json
{
status: 'healthy',
chaos_mode: settings.chaos_enabled,
chaos_delay: settings.chaos_delay,
chaos_error_rate: settings.chaos_error_rate
}.to_json
end
post '/chaos/enable' do
params_data = JSON.parse(request.body.read)
settings.chaos_enabled = true
settings.chaos_delay = params_data['delay'] || 0
settings.chaos_error_rate = params_data['error_rate'] || 0
content_type :json
{ message: 'Chaos mode enabled', settings: chaos_settings }.to_json
end
post '/chaos/disable' do
settings.chaos_enabled = false
settings.chaos_delay = 0
settings.chaos_error_rate = 0
content_type :json
{ message: 'Chaos mode disabled', settings: chaos_settings }.to_json
end
private
def inject_chaos
sleep(settings.chaos_delay / 1000.0) if settings.chaos_delay.positive?
return unless settings.chaos_error_rate.positive? && rand(100) < settings.chaos_error_rate
halt 500, { error: 'Chaos error injected!' }.to_json
end
def chaos_settings
{
enabled: settings.chaos_enabled,
delay_ms: settings.chaos_delay,
error_rate_percent: settings.chaos_error_rate
}
end
end
普通に動かすとどうなる?
まずは通常の動作を見てみましょう。
1. アプリケーションの起動
$ ruby app.rb
2. APIにアクセスしてみる
$ curl http://localhost:4567/
{
"message": "Welcome to Chaos Engineering Demo!",
"status": "healthy"
}
3. データ取得APIを叩いてみる
$ curl http://localhost:4567/api/data
{
"data": {
"timestamp": "2025-10-19 15:30:45 +0900",
"random_value": 42,
"message": "This is sample data"
},
"status": "success"
}
ひとまず動きとしては問題なさそう。
カオス機能の仕組み
ここからが本題。このアプリにカオスを注入します(カオスを注入するという言葉は正しいのだろうか)
カオス注入のコード
class ChaosApp < Sinatra::Base
# カオス設定(初期値はすべてOFF)
configure do
set :chaos_enabled, false # カオスモードのON/OFF
set :chaos_delay, 0 # 遅延時間(ミリ秒)
set :chaos_error_rate, 0 # エラー率(%)
end
# データ取得API
get '/api/data' do
inject_chaos if settings.chaos_enabled # ←ここでカオスを注入!
...
end
private
# カオス注入の実装
def inject_chaos
# 1. 遅延注入:指定されたミリ秒だけ待機
if settings.chaos_delay.positive?
sleep(settings.chaos_delay / 1000.0)
end
# 2. エラー注入:指定された確率でエラーを返す
if settings.chaos_error_rate.positive? && rand(100) < settings.chaos_error_rate
halt 500, { error: 'Chaos error injected!' }.to_json
end
end
end
カオス(障害)を有効にする方法
障害は、APIを通じてON/OFFできます:
# 障害を有効化(500ms遅延、30%エラー率)
$ curl -X POST http://localhost:4567/chaos/enable \
-H "Content-Type: application/json" \
-d '{"delay": 500, "error_rate": 30}'
{
"message": "Chaos mode enabled",
"settings": {
"enabled": true,
"delay_ms": 500,
"error_rate_percent": 30
}
}
カオス有効後の動作を確認
カオスを有効にした後、同じAPIを叩くとエラーであったり、遅延といったものが確認できます。
1. データ取得APIが遅くなる
$ time curl http://localhost:4567/api/data
{
"data": {
"timestamp": "2025-10-19 15:33:12 +0900",
"random_value": 73,
"message": "This is sample data"
},
"status": "success"
}
curl http://localhost:4567/api/data
0.00s user
0.00s system
45% cpu
0.512 total # ← 500ms以上かかっている
通常は1ms以下だったレスポンスが、500ms以上かかるようになりました。
2. エラーが返ってくる
# 何度か実行するとHTTP ステータスコード500でエラーに
$ curl -i http://localhost:4567/api/data
HTTP/1.1 500 Internal Server Error
...
{
"error": "Chaos error injected!"
}
30%の確率でエラーが返るようになりました。
このようにすることで、簡単なWebアプリでわざと障害を起こすことができました。
しかし、実際のプロダクトにこんな危ないコードは入れられません。
そこで、プロダクトコードに手を加えずとも障害を起こせるツールを使ってみます。
プロダクトコードに手を加えず障害を起こしたい
より本格的なカオスエンジニアリングには専用ツールがあるとより便利そうです。
ツールを用いることで下記のようなことを実現できそうです。
-
より現実的な障害を再現したい
- ネットワークの分断
- パケットロス
- CPUやメモリの枯渇
- ディスクI/Oの遅延
-
システム全体で検証したい
- 複数のサービス間の通信
- データベースとの接続
- ロードバランサーの動作
-
安全に実験したい
- 影響範囲の制御
- 自動的な復旧
- 実験の記録と分析
どんなツールがあるの?調べてみた
「カオスエンジニアリング ツール」でググってみたら、いろいろ出てきました:
-
Chaos Monkey(Netflix製)
- 元祖カオスエンジニアリングツール
- EC2インスタンスをランダムに停止させる
- AWS環境が前提
-
Litmus(CNCF Sandbox Project)
- Kubernetes環境専用のカオスツール
- ポッドの削除、ノード障害、ネットワーク遅延など
-
Gremlin(商用ツール)
- GUI付きで使いやすそう
- CPU負荷、メモリリーク、ディスクI/O障害など幅広い
- 有料
-
Toxiproxy(Shopify製OSS)
- プロキシとして動作し、通信を妨害
- 遅延、タイムアウト、帯域制限などができる
-
Pumba(Docker向け)
- Dockerコンテナを対象にしたカオスツール
- コンテナの停止、ネットワーク遅延、CPU/メモリ制限
- docker runコマンドみたいに使えるらしい
今回はシンプルに使えそうなToxiproxyを触ってみます。
4. Toxiproxyを使ってみる
Toxiproxyとは?
Toxiproxyは、アプリケーションとデータベースやAPIの間に入って、通信を邪魔するプロキシサーバーです。
[アプリ] → [Toxiproxy] → [データベース/API]
↓
ここでカオスを注入!
インストール方法
macOSの場合:
# Homebrewでインストール
$ brew install toxiproxy
### 起動してみる
```bash
# Toxiproxyサーバーを起動
$ toxiproxy-server
{"level":"info","version":"2.12.0","caller":"server.go:78","time":"2025-10-19T13:36:25Z","message":"Starting Toxiproxy"}
{"level":"info","address":"localhost:8474","caller":"api.go:57","time":"2025-10-19T13:36:25Z","message":"Starting Toxiproxy HTTP server"}
# CLIツールも使えるようにする
$ brew install toxiproxy-cli
# プロキシの一覧を確認
$ toxiproxy-cli list
Name Listen Upstream Enabled Toxics
======================================================================================
no proxies
実際に使ってみる
先ほど作ったRubyアプリに対してToxiproxyを設定してみます!
1. プロキシの作成
# 5555番ポート → localhost:4567 へのプロキシを作成
$ toxiproxy-cli create -l localhost:5555 -u localhost:4567 ruby-app
Created new proxy ruby-app
toxiproxy-cli listで確認すると
Name Listen Upstream Enabled Toxics
======================================================================================
ruby-app 127.0.0.1:5555 localhost:4567 enabled None
プロキシが作成されていることが確認できます。
各列の意味:
- Listen: Toxiproxyが待ち受けるアドレス(クライアントはここに接続させる)
- Upstream: 実際のサービスが動いているアドレス(Toxiproxyが転送する先)
- Toxics: 現在適用されている障害の数
2. 通常アクセスの確認
# Toxiproxy経由でアクセス(ポート5555を使う)
$ time curl http://localhost:5555/api/data
{"data":{"timestamp":"2025-10-19 16:56:56 +0900","random_value":10,"message":"This is sample data"},"status":"success"}curl http://localhost:5555/api/data 0.00s user 0.00s system 45% cpu 0.014 total
この時点では問題なく動作します。そして処理全体で0.014sかかっています。
3. 遅延を注入してみる
# 1000msの遅延を追加
$ toxiproxy-cli toxic add -t latency -a latency=1000 ruby-app
Added downstream latency toxic 'latency_downstream' on proxy 'ruby-app'
コマンドの詳細:
-
toxic add: 障害(toxic)を追加するサブコマンド -
-t latency: 障害の種類を「遅延(latency)」に指定 -
-a latency=1000: 遅延時間を1000ミリ秒(1秒)に設定 -
ruby-app: 対象のプロキシ名
この設定により、クライアントからのリクエストが実際のサーバーに届くまでに1秒の遅延が発生します。
# 再度アクセス
$ time curl http://localhost:5555/api/data
{"data":{"timestamp":"2025-10-19 22:57:55 +0900","random_value":97,"message":"This is sample data"},"status":"success"}curl http://localhost:5555/api/data 0.00s user 0.01s system 0% cpu 1.023 total
レスポンスは返ってきましたが、処理全体で1.023sかかっています。
正しく遅延が発生したことがこれで確認できました。
ちなみに設定した遅延は下記コマンドで削除できます。
toxiproxy-cli toxic remove -n latency_downstream ruby-app
また、toxiproxy-cli inspect ruby-appを実行することで現在どんな障害を設定しているかが確認できます。
$ toxiproxy-cli inspect ruby-app
Name: ruby-app Listen: 127.0.0.1:5555 Upstream: localhost:4567
======================================================================
Upstream toxics:
Proxy has no Upstream toxics enabled.
Downstream toxics:
latency_downstream: type=latency stream=downstream toxicity=1.00 attributes=[ jitter=0 latency=1000 ]
他にも下記のような障害を設定することも可能です。
# タイムアウト(1秒後に接続を切断)
$ toxiproxy-cli toxic add -t timeout -a timeout=1000 ruby-app
# 帯域制限(1KB/秒に制限)
$ toxiproxy-cli toxic add -t bandwidth -a rate=1 ruby-app
# 接続クローズの遅延(3秒かけて接続を閉じる)
$ toxiproxy-cli toxic add -t slow_close -a delay=3000 ruby-app
このようにToxiproxyを使うことで、プロダクトコードに一切手を加えずに様々な障害を注入できます。
より実際の障害時に近い状態を再現したい場合、例えばデータベース接続の障害をテストするなら
# PostgreSQLへのプロキシを作成
$ toxiproxy-cli create -l localhost:15432 -u localhost:5432 postgres
# 接続タイムアウトをシミュレート
$ toxiproxy-cli toxic add -t timeout -a timeout=5000 postgres
# アプリがどう振る舞うか確認
$ ruby app_with_database.rb # タイムアウトエラーが出るはず
このようにすることで、実際に障害が起きた時を再現して動作確認が行えそうです。
Toxiproxyを使ってみた感想
- プロダクトコードを変更しなくて良いので、既存のコードを汚さなくてすむ
- CLIで簡単に障害を ON/OFF できる
- いろんな障害パターンが試せる(遅延、タイムアウト、帯域制限など)
- 実際の本番環境でも使えそう
今後
今回はカオスエンジニアリングを体験するため、シンプルなアプリケーションとToxiproxyを使用しました。
ネットワーク障害を手軽に発生させることができ、APIサーバーの障害時の挙動を効果的にシミュレートできました。
実際のプロダクト環境では、DockerやKubernetesなどのコンテナ技術が主流です。
より本番環境に近い障害テストを行うため、今後はPumbaやLitmusといったツールも検証したいと考えています。
Discussion