完璧なネットワークでのテストをやめる:耐障害性UIのためのChaos Tunnels導入

完璧なネットワークでのテストをやめる:耐障害性UIのためのChaos Tunnels導入
現代のWeb開発ワークフローでは、「localhost」は嘘です。超低遅延の光ファイバー接続上にアプリケーションを構築し、32 GBのRAMを搭載したマシンで動作させ、サブミリ秒応答のローカルサーバーに対してテストしています。その後、混雑した地下鉄の中や高パケットロスの田舎の通勤者に向けてこれらのアプリケーションを配信します。
結果として、完璧なネットワークの”ハッピーパス”が消えると、UIはフリーズしたり点滅したりクラッシュしたりします。
真に耐障害性のあるソフトウェアを作るには、ネットワークを一定のものとみなすのをやめ、変数として扱う必要があります。そこでlocalhostでのchaos engineeringが役立ちます。”Chaos Tunnels” — 故意にローカル接続を劣化させるプロキシ — を導入することで、コードを本番環境にリリースする前にUIのエラーハンドリングや状態管理をストレステストできます。
localhostの偽の安全性
ローカルで開発しているとき、fetch()リクエストはインターネットを通じて送信されません。ループバックインターフェースを通じて、ジッターや輻輳、信号干渉のない環境で行われます。この無菌環境では、レースコンディションは隠れたままです。ローディングスピナーは一瞬だけ表示されるため、完璧に見えます。
しかし、実世界は異なります。2026年2月に公開された5Gスタンドアロン(SA)と非スタンドアロン(NSA)公共ネットワークの比較調査によると、NSA 5Gは平均約54 msの遅延を示し、プライベートなSAネットワークのほぼ10倍のジッターと、50 ms以上のスパイクも散見されます。Wikipediaの5G記事は、タワー間のハンドオーバー時に遅延が大きく増加し、ネットワーク状況により50〜150 msになると指摘しています。
これが、あなたのlocalhost環境が隠しているギャップです。
ブラウザのDevToolsだけでは不十分な理由
多くの開発者はChromeやFirefoxの「Throttling」タブを使って簡易的な動作確認をします。これらのツールは便利ですが、根本的な制限があります:
アプリケーションレベルのみ。 ブラウザのメインスレッドと送信リクエストにのみ影響します。ハードウェアレベルの問題やシステム全体の障害はシミュレートできません。
予測可能な遅延。 標準のスロットリングは「Fast 3G」などの一定速度を提供します。2秒間高速で、その後5秒間パケットの20%を喪失するような混沌とした状態は再現できません。
TCPレベルのシミュレーション不可。 DevToolsはDNS障害や大きなペイロードの途中でのTCPタイムアウトを再現できません。
Chaos Tunnelの定義:ネットワーク劣化プロキシ
「Chaos Tunnel」とは、ネットワーク劣化プロキシのことで、フロントエンドとバックエンドまたは外部APIの間に配置される中間者です。ブラウザのスロットリングとは異なり、トランスポート層で動作し、TCPデータの生のストリームを操作できます。
Toxiproxyのようなツールを経由してローカルトラフィックをルーティングし、「トキシック」を注入することで、以下のような効果を得られます:
- 遅延(Latency):すべてのリクエストに基本遅延(例:500 ms)を追加
- ジッター(Jitter):遅延にランダムな変動(例:±200 ms)を付与
- 帯域制限(Bandwidth limiting):エッジの2G接続を模擬
- 遅いクローズ(Slow close):接続終了を遅延させ、UIのハングをテスト
- スライサー(Slicer):データを小さなチャンクに分割し、ストリーミングやチャンクアップロードのエッジケースを誘発
- リセットピア(Reset peer):途中で接続を切断し、信号喪失をシミュレート
localhostでのChaos Engineering設定:ステップバイステップガイド
Toxiproxyは、Shopifyが開発したTCPプロキシフレームワークで、ネットワーク状況をシミュレートします。2014年以降積極的にメンテナンスされており、2025年のGitHub採用分析によると、Chaos MeshやNetflixのChaos Monkeyと並び、最も広く使われているchaos engineeringツールの一つです。これらはchaos engineeringツールを使用するリポジトリの64%以上で採用されています。
Toxiproxyは言語に依存せず、単一バイナリとして動作し、シンプルなHTTP管理APIを公開しているため、ローカル開発やCIパイプラインに最適です。
ステップ1:Toxiproxyのインストール
macOSでHomebrewを使ってサーバとCLIをインストール:
brew install toxiproxy
またはDockerイメージを取得(CI環境やDocker Composeに便利):
docker pull ghcr.io/shopify/toxiproxy:latest
サーバを起動します。デフォルトでポート8474をリッスンします — これがコントロールプレーンAPIです:
toxiproxy-server
ステップ2:プロキシトンネルの作成
例:バックエンドAPIがlocalhost:3000で動作している場合、localhost:4000にトンネルを作成し、トラフィックを通しつつ、必要に応じて劣化させます:
toxiproxy-cli create api_proxy --listen localhost:4000 --upstream localhost:3000
フロントエンドの環境変数を更新し、プロキシを指すようにします:
# .env.local
API_URL=http://localhost:4000
これ以降、アプリはToxiproxyを経由して実際のサーバに通信します。 chaosの制御はアプリコードに触れずに行えます。
ステップ3:Chaosの注入
次に、実用的な部分です。公開5Gの”不安定”な接続をシミュレートします — 表面上は高速でも、信号の影やハンドオーバースパイクに弱い状態です。
5G遅延とジッターのシミュレーション:
toxiproxy-cli toxic add api_proxy --type latency --attribute latency=100 --attribute jitter=500
これにより、100 msの遅延と500 msのジッターが追加され、UIは100 msから600 msの範囲で応答時間を経験します。これは混雑した環境でのNSA 5Gの実際の挙動にかなり近いです。
パケットロスのシミュレーション:
toxiproxy-cli toxic add api_proxy --type limit_data --attribute bytes=0
またはreset_peer toxicを使って突然の切断を模擬します。
複数のトキシックを同時に動かすことも可能です。帯域制限と遅延を重ねて、エッジネットワークのシナリオを再現できます。
ステップ4:Docker Composeとの連携
コンテナ化されたスタックを運用するチーム向けに、ToxiproxyはDocker Composeのサイドカーサービスとして便利に組み込めます。アプリケーションサービスは、依存先に直接アクセスするのではなく、Toxiproxyのポートを指すように設定します:
services:
toxiproxy:
image: ghcr.io/shopify/toxiproxy:latest
ports:
- "8474:8474" # コントロールプレーンAPI
- "4000:4000" # API用のプロキシ
api:
build: ./api
environment:
- BACKEND_URL=http://toxiproxy:4000
depends_on:
- toxiproxy
この方法では、アプリコードの変更は必要なく、接続文字列だけを更新します。
具体的なシナリオ:何をテストしているのか?
“ゾンビ”接続(高パケットロス)
接続がダウンしているわけではなく、ただロスが多すぎてほぼ死んでいる状態です。
- 実験:
limit_dataやreset_peertoxicsを使って15%のパケットロスを設定 - 観察ポイント: UIはタイムアウトをトリガーするか、永遠に”ローディング”状態を維持するか?耐障害性の高いUIは、一定閾値を超えたリクエストは死んだと検知し、「再試行」ボタンを表示すべきです。
5Gハンドオーバースパイク
ユーザが5Gタワー間を移動したり、mmWaveからミッドバンドに切り替わると、遅延は20 ms未満から150 ms以上に跳ね上がります。
- 実験: 1秒間の遅延スパイクを30秒ごとに発生させるトキシックをスクリプト化
- 観察ポイント: スパイク中にリクエストを送信した場合、UIはどう反応するか?重複送信やスケルトンスクリーンの遷移はスムーズか?
DNSブラックホール
APIは稼働しているが、ユーザのDNSプロバイダが失敗したり、サードパーティのスクリプトが解決できない場合はどうなるか?
- 実験: プロキシを使って特定のアップストリーム(例:解析やA/Bテスト用)へのトラフィックをブロック
- 観察ポイント: アプリは起動できるか?非必須のスクリプトがブロックされてもコア機能は動作するか?
遅いクローズ / ハングソケット
データを送信せずに”開いた”ままの接続は、特にモバイル環境での実世界のシナリオです。電波状態の管理により、接続をサスペンドするためです。
- 実験:
slow_closetoxicを遅延付きで適用(数秒) - 観察ポイント: UIは適切なタイムアウトを設定しているか?無限にブロックしないか?
耐障害性設計:フロントエンドのパターン
Chaos TunnelでUIが崩壊したら、自信を持って防御パターンを実装し、実証的に測定できます。
1. 楽観的UIとロールバック
“いいね”やフォーム送信をサーバーの確認を待たずに即時反映。だが、chaos engineeringはロールバックパスをテストさせます。接続が切断された場合、UIはどうリバートし、エラーを明示するか?それとも黙って壊れた状態を放置するか?
2. インテリジェントなSkeleton Screen
高遅延ネットワークでは、標準のローディングスピナーはイライラさせることも。Skeletonはパフォーマンス向上に役立ちます。高遅延を想定し、タイミングを調整。リクエストが2秒以上かかる場合は、Skeletonから”まだ作業中…“に切り替えるなど、ユーザに行動可能な情報を提供します。
3. フロントエンドのサーキットブレーカー
NetflixのHystrixに代表されるパターンで、フロントエンドもサーキットブレーカーを持つべきです。API呼び出しが3回連続失敗したら、リトライを停止し、”劣化モード”に入る。キャッシュされたデータを表示するなど。
クライアント側のサーキットブレーカーは状態遷移型:Closed(正常)、Open(高速失敗)、Half-Open(試験運用)。opossumなどのライブラリもありますが、軽量な実装は自作も容易です。
4. 明示的なリクエストタイムアウト
Chaos Tunnelはfetch()にタイムアウト設定がないと露呈します。AbortControllerを使い、5〜10秒のタイムアウトを設定しましょう。これにより、ハングしたソケットがUIを長時間塞ぐのを防ぎます。
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 8000);
try {
const response = await fetch('/api/data', { signal: controller.signal });
// 応答処理
} catch (err) {
if (err.name === 'AbortError') {
// 再試行UIを表示
}
} finally {
clearTimeout(timeoutId);
}
5. 非必須スクリプトはメインスレッドをブロックしない
解析ツールやA/Bテスト、広告スクリプトは非必須です。deferやasyncで読み込み、DNSブラックホールシナリオでの検証も行い、これらの失敗がコアアプリの起動を妨げないことを確認しましょう。
Chaos Frontend Toolkit
Toxiproxy以外にもツールは増えています。GitHubのawesome-chaos-engineeringリポジトリは、フロントエンドに特化したChaos Frontend Toolkitを含むツール群を追跡しています。ブラウザレベルのシミュレーションには、Mock Service Worker (MSW)がAPIレスポンスに遅延やエラーを注入し、コンポーネント単体のテストに役立ちます。
主要ツールの比較表:
| ツール | 最適用途 | 主な特徴 |
|---|---|---|
| Toxiproxy | localhost / CI | 高度にスクリプト可能なTCPプロキシ。自動テストやDockerセットアップに最適 |
| Pumba | Docker環境 | Dockerコンテナとネットワークの停止・スロットル |
| Chaos Mesh | Kubernetes | クラスター全体の障害注入。CNCFのインキュベーションプロジェクト |
| MSW (Mock Service Worker) | コンポーネント / 単体テスト | fetch呼び出しをインターセプトするブラウザのサービスワーカー |
| Network Link Conditioner | macOS全体 | システムレベルのスロットル。ネイティブアプリや全ブラウザトラフィックのテストに |
| Chaos Frontend Toolkit | フロントエンド専用 | UI耐障害性実験に特化 |
成功の測定:重要な耐障害性指標
chaos実験を行うだけでは不十分です。変化を追跡し、各主要機能の「耐障害性プロファイル」を定義し、実施前後の指標を測定しましょう:
- ストレス下でのインタラクティブ時間(TTI-S):ジッター200 ms時のTTIは?基準値と比較
- エラー回復率:失敗リクエストのうち、ユーザの再試行成功率は?良好なUIは多くのユーザを取り戻せる
- ゾンビ状態の持続時間:ネットワーク断時にユーザが反応なしの画面にどれだけ長くいるか?リクエストタイムアウトとUI更新により制限
- 非必須スクリプトの失敗影響:解析スクリプトのブロックがTTIに影響するか?しないはず
CIへのChaosテスト統合
chaos tunnelは自動化が最も効果的です。ToxiproxyのHTTP APIとクライアントライブラリを使えば、以下のようなテストスクリプトを書けます:
- Docker ComposeのテストスタックにToxiproxyを起動
- APIへのプロキシを設定
- toxic(例:500 ms遅延)を注入してエンドツーエンドテストを実行
- UIが正しいSkeletonやタイムアウトメッセージ、再試行ボタンを所定の時間内に表示するか検証
- toxicを除去し、正常動作を確認
これにより、耐障害性は手動の作業から自動回帰テストに進化します。
結論:ChaosをDoneの定義に組み込もう
chaos engineeringの目的は、単に壊すことではありません。確かな自信を築くことです。5GのハンドオーバースパイクやDNSブラックホール、15%のパケットロスを模擬した環境でUIが耐えられることを知れば、リリース時の驚きは減ります。
静的なlocalhostと実世界の公共5Gのギャップは、デプロイ後に発見すべきものではありません。最初から設計・エンジニアリングすべきです。
完璧なネットワークでのテストはやめましょう。プロキシを設定し、トキシックを注入し、ネットワークの不安定さを開発の第一級の課題としましょう。
チェックリスト
- [ ] Toxiproxy(バイナリまたはDockerイメージ)をインストールし、コントロールプレーンをポート8474で公開
- [ ] ローカルポートからバックエンドAPIへのプロキシを作成
- [ ] フロントエンドの環境変数を更新し、トンネルを指す
- [ ] ジッター(±500 ms)を注入し、レースや重複送信を露呈
- [ ] 5Gハンドオーバースパイク(1秒間の遅延を5秒間隔で発生)をテスト
- [ ] パケットロス / リセット toxicを適用し、タイムアウトとリトライUIを検証
- [ ] DNSブラックホールシナリオを実行し、解析ツールのブロック後もアプリが起動するか確認
- [ ] すべてのユーザ向け
fetch()にAbortControllerのタイムアウトを設定 - [ ] 頻繁に失敗するエンドポイントを呼び出すコンポーネントにサーキットブレーカーを導入
- [ ] 解析やA/Bテスト用の非必須スクリプトは
deferやasyncで読み込み - [ ] CIにchaosシナリオを組み込み、耐障害性の回帰を自動化
Related Topics
Keep building with InstaTunnel
Read the docs for implementation details or compare plans before you ship.