Pumbaでネットワーク障害をシミュレーション:1%のパケットロスでアプリを落とさないために

Network tutorial - IT technology blog
Network tutorial - IT technology blog

ネットワークが「風邪を引いた」ら何が起きる?

DevやDevOpsの皆さんなら、ローカル環境ではサクサク動き、ステージングでのテストも問題なかったアプリが、本番環境にデプロイした途端、ユーザーから「動作が重い」とクレームが来る状況を経験したことがあるでしょう。

以前、忘れられないトラブル対応がありました。マイクロサービスが安定稼働していた時、データセンターのスイッチに不具合が発生し、約5%のパケットロスが生じたのです。直ちにService AからService Bへの呼び出しでタイムアウトが多発。スレッドが次々とブロックされ、わずか10分でドミノ倒しのようにシステム全体がダウンしました。その時、私は痛感しました。「コンピュータネットワークは決して完璧ではない」ということを。

実際、ネットワークには常に遅延(Latency)、パケットロス(Packet Loss)、あるいは順序の入れ替わり(Reordering)などのリスクが潜んでいます。アプリがこれらの「荒波」と共に生きる設計になっていなければ、些細なトラブルで簡単に崩壊してしまいます。

なぜネットワークが不安定だとアプリは「死ぬ」のか?

多くの場合、原因はビジネスロジックではなく、接続管理の方法にあります。代表的な3つのシナリオを挙げます:

  • タイムアウト設定が長すぎる: 多くのライブラリでデフォルトのタイムアウトが30秒、あるいは無制限になっています。ネットワークが遅延するとリクエストが滞留し、ワーカーを使い果たしてリソースが枯渇します。
  • 「ナイーブ」なリトライメカニズム: ネットワークが混雑している時に高頻度でリトライを繰り返すのは、自システムに対するDoS(サービス拒否)攻撃と同じです。
  • サーキットブレーカーの欠如: 下流のサービスが遅延しているのに、上流がリクエストを送り続けると、エラーがシステム全体に波及します。

神頼みをする代わりに、テスト段階から意図的に障害を発生させる「Chaos Engineering(カオスエンジニアリング)」を導入すべきです。Docker環境において、Pumbaは最も実戦的で効果的なツールです。

従来のネットワーク障害シミュレーション手法

Pumbaが登場する前、エンジニアはいくつかの手動の手法を使っていました:

  1. tc (Traffic Control) コマンド: Linux標準の強力なツールですが、構文が非常に難解です。設定を一つ間違えるだけでホスト全体のネットワークを遮断するリスクがあります。
  2. プロキシ/ゲートウェイの設定: プロキシを介して帯域を制限する方法。セットアップに時間がかかり、CI/CDプロセスでの自動化が困難です。
  3. 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を組み込んでいます:

  1. Docker Composeでスタック全体(App, Redis, Postgres)を起動。
  2. Pumbaを使用してデータベースへの接続を完全に遮断、または10秒の遅延を発生させる。
  3. ログを観察:アプリは明確なエラーを出力しているか?コネクションプールは溢れていないか?そして最も重要なのは、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

このテストにより、maxLifetimeidleTimeoutが適切に設定されていない場合、多くのコネクションプールライブラリが「デッドコネクション」を永久に保持し続ける問題を特定できました。

まとめ

インフラが常に100%安定していることを期待することはできません。しかし、障害に対してアプリがどう反応するかをコントロールすることは可能です。Pumbaを使えば、より「タフ」でレジリエンスの高いシステムを構築できます。

本番環境がダウンしてから原因を探すのではなく、インテグレーションテストやステージング環境でPumbaを活用しましょう。ネットワークが不安定でも、揺るがないシステムを目指しましょう!

Share: