ネットワークが「風邪を引いた」ら何が起きる?
DevやDevOpsの皆さんなら、ローカル環境ではサクサク動き、ステージングでのテストも問題なかったアプリが、本番環境にデプロイした途端、ユーザーから「動作が重い」とクレームが来る状況を経験したことがあるでしょう。
以前、忘れられないトラブル対応がありました。マイクロサービスが安定稼働していた時、データセンターのスイッチに不具合が発生し、約5%のパケットロスが生じたのです。直ちにService AからService Bへの呼び出しでタイムアウトが多発。スレッドが次々とブロックされ、わずか10分でドミノ倒しのようにシステム全体がダウンしました。その時、私は痛感しました。「コンピュータネットワークは決して完璧ではない」ということを。
実際、ネットワークには常に遅延(Latency)、パケットロス(Packet Loss)、あるいは順序の入れ替わり(Reordering)などのリスクが潜んでいます。アプリがこれらの「荒波」と共に生きる設計になっていなければ、些細なトラブルで簡単に崩壊してしまいます。
なぜネットワークが不安定だとアプリは「死ぬ」のか?
多くの場合、原因はビジネスロジックではなく、接続管理の方法にあります。代表的な3つのシナリオを挙げます:
- タイムアウト設定が長すぎる: 多くのライブラリでデフォルトのタイムアウトが30秒、あるいは無制限になっています。ネットワークが遅延するとリクエストが滞留し、ワーカーを使い果たしてリソースが枯渇します。
- 「ナイーブ」なリトライメカニズム: ネットワークが混雑している時に高頻度でリトライを繰り返すのは、自システムに対するDoS(サービス拒否)攻撃と同じです。
- サーキットブレーカーの欠如: 下流のサービスが遅延しているのに、上流がリクエストを送り続けると、エラーがシステム全体に波及します。
神頼みをする代わりに、テスト段階から意図的に障害を発生させる「Chaos Engineering(カオスエンジニアリング)」を導入すべきです。Docker環境において、Pumbaは最も実戦的で効果的なツールです。
従来のネットワーク障害シミュレーション手法
Pumbaが登場する前、エンジニアはいくつかの手動の手法を使っていました:
tc(Traffic Control) コマンド: Linux標準の強力なツールですが、構文が非常に難解です。設定を一つ間違えるだけでホスト全体のネットワークを遮断するリスクがあります。- プロキシ/ゲートウェイの設定: プロキシを介して帯域を制限する方法。セットアップに時間がかかり、CI/CDプロセスでの自動化が困難です。
- LANケーブルを抜く: 物理的な手法であり、コンテナやクラウド環境には適しません。
Pumba – Dockerコンテナのための秘密兵器
Pumbaは、Docker専用のオープンソースChaos Testingツールです。コンテナの停止(kill/stop)や、ネットワークインターフェースへの直接的な干渉が可能です。最大の利点は、アプリのコードを一行も修正する必要がないことです。
Pumbaは独立したコンテナとして動作し、docker.sockをマウントして他のコンテナを制御します。実体は、tcの強力な機能をシンプルで分かりやすいコマンドにパッケージ化したものです。
コンテナのIP範囲を制限するためにサブネット計算が必要な時は、よく toolcraft.app/ja/tools/developer/ip-subnet-calculator を使います。CIDRを入力するだけでネットワーク範囲やホスト数が分かり、テスト環境の分離に非常に便利です。
1. Pumbaのクイックインストール
ホストマシンにインストールする必要はありません。Dockerを使って直接Pumbaを実行し、バージョンを確認してみましょう:
docker run --rm gaiaadm/pumba pumba --version
2. レイテンシ(ネットワーク遅延)のシミュレーション
web_appという名前のコンテナがあるとします。このコンテナの送信パケットに、5分間、3000ms(3秒)の遅延を発生させたい場合は以下の通りです:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
gaiaadm/pumba pumba netem \
--duration 5m \
delay --time 3000 \
web_app
より現実的にするために、--jitterパラメータを追加しましょう。例:delay --time 3000 --jitter 500。これで遅延が2500msから3500msの間で変動し、実際のネットワーク環境に近づきます。
3. パケットロスのシミュレーション
パケットロスはTCPプロトコルにとって悪夢です。再送(Retransmit)が発生し、レイテンシが劇的に悪化します。20%のパケットロスを発生させるには:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
gaiaadm/pumba pumba netem \
--duration 3m \
loss --percent 20 \
web_app
4. 帯域制限 (Bandwidth Limit)
2Gモデム時代のような低速回線での動作を確認したい場合は、rateコマンドを使用します:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
gaiaadm/pumba pumba netem \
--duration 5m \
rate --rate 100kbit \
web_app
実践シナリオ:自己修復能力のテスト
私は通常、以下のようなテストフローにPumbaを組み込んでいます:
- Docker Composeでスタック全体(App, Redis, Postgres)を起動。
- Pumbaを使用してデータベースへの接続を完全に遮断、または10秒の遅延を発生させる。
- ログを観察:アプリは明確なエラーを出力しているか?コネクションプールは溢れていないか?そして最も重要なのは、Pumbaを停止した後、アプリは自動的に再接続するか、あるいはフリーズしたままか?
例えば、api_serviceに対して、10秒ごとに2秒間、500msのラグを発生させるシナリオ:
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
gaiaadm/pumba pumba \
--interval 10s \
netem --duration 2s delay --time 500 api_service
このテストにより、maxLifetimeやidleTimeoutが適切に設定されていない場合、多くのコネクションプールライブラリが「デッドコネクション」を永久に保持し続ける問題を特定できました。
まとめ
インフラが常に100%安定していることを期待することはできません。しかし、障害に対してアプリがどう反応するかをコントロールすることは可能です。Pumbaを使えば、より「タフ」でレジリエンスの高いシステムを構築できます。
本番環境がダウンしてから原因を探すのではなく、インテグレーションテストやステージング環境でPumbaを活用しましょう。ネットワークが不安定でも、揺るがないシステムを目指しましょう!

