【ITニュース解説】How Docker networking broke checkout under load: a container ecommerce infrastructure case study
2026年09月09日に「Dev.to」が公開したITニュース「How Docker networking broke checkout under load: a container ecommerce infrastructure case study」について初心者にもわかりやすく解説しています。
ITニュース概要
DockerコンテナのECサイトで、負荷時にチェックアウトが遅延。CPUやメモリではなく、Dockerネットワークの非効率な設定(デフォルトブリッジ、接続管理、DNS解決)が複合原因だった。カーネルチューニングや専用ネットワーク導入で改善し、コンテナの負荷問題はネットワーク層に注目すべきと学んだ。
ITニュース解説
あるECサイトで、顧客が購入手続きをする際のチェックアウト画面が、特定のタイミングで非常に遅くなるという問題が発生した。具体的には、普段は280ミリ秒程度で完了する処理が、アクセスが集中する時間帯には2.1秒以上かかるようになり、最悪の場合はタイムアウトして購入が完了しない事態に陥っていた。サイトの利用者は「サイトが固まる」と感じ、不満が募っていたという。
このサイトは、約1年前に従来のシステムをDockerという技術を使ってコンテナ化し、単一のサーバー上で運用していた。コンテナとは、アプリケーションとその実行に必要な環境をひとまとめにした、軽量で独立したソフトウェアパッケージのようなもので、これによりアプリケーションのデプロイや管理が容易になる。このサイトでは、PHPで書かれたメインのアプリケーション、データを保存するRedis、バックグラウンド処理を行うワーカーキューなどがそれぞれコンテナとして動作し、Docker Composeというツールで管理されていた。1日に約4万人の利用者が訪れ、ピーク時には毎秒900件のアクセスを処理する規模だったが、当初は問題なく稼働していた。しかし、フラッシュセールなどの高負荷時に問題が表面化し始めたのだ。
チームは当初、サーバーのCPUやメモリが不足していると考え、アプリケーションのコンテナ数を増やして対応しようとした。しかし、これは状況をさらに悪化させた。データベースへの問い合わせ時間は安定しており、データベース自体が原因ではないことも判明した。問題は、普段は見えにくいコンテナ間の「ネットワーク」の層に隠されていたのである。
詳しい調査の結果、遅延を引き起こしていたのは、単独では致命的ではないが、三つの問題が複合的に絡み合っていたことが判明した。
一つ目の問題は、「デフォルトのブリッジネットワークによるオーバーヘッド」である。コンテナ化したアプリケーションでは、複数のコンテナが連携して動作する。例えば、メインのアプリケーションコンテナがRedisコンテナにデータを要求したり、データベースコンテナにデータを保存したりする。これらのコンテナ間の通信は、Dockerが提供する「ブリッジネットワーク」という仮想的なネットワークを通じて行われる。しかし、デフォルトの設定では、このコンテナ間の通信が、効率的ではない「ユーザーランドプロキシ」という仕組みを介して処理されていた。これはまるで、近所の友人宅に行くのに、一旦役所を経由して許可をもらってから向かうようなものだ。この無駄な経路により、コンテナ間の通信1回あたり約1.8ミリ秒の遅延が追加されていた。毎秒900件のアクセスがあり、1回のアクセス処理で3~4回もの内部通信が発生すると、このわずかな遅延も積み重なり、合計で大きな時間ロスとなっていた。
二つ目の問題は、「Conntrackテーブルの枯渇」である。サーバーがネットワーク上で多くの通信を効率的に処理するためには、「Conntrackテーブル」と呼ばれる、現在確立されている接続状況を記録する管理台帳のようなものを使っている。この管理台帳の最大記録数(nf_conntrack_max)が、Linuxカーネルのデフォルト値である65,536のままだった。アクセスが集中すると、RedisやPostgreSQLのようなデータベースへの短命な(すぐに切れる)接続が大量に発生し、この管理台帳がすぐに満杯になってしまった。管理台帳が満杯になると、カーネルはそれ以上新しい接続を記録できなくなり、新規のパケットを黙って破棄し始める。これは、電話回線がパンクして、誰も電話がつながらなくなってしまうような状況だ。しかも、このパケット破棄はエラーとして明確に通知されなかったため、ログを確認しても異常に気づきにくかった。
三つ目の問題は、「DNS解決のオーバーヘッド」である。コンテナは、他のコンテナと通信する際に、その名前(例:「redis」や「postgres」)を使って相手を識別する。この名前を、ネットワーク上の実際のIPアドレスに変換する作業を「DNS解決」と呼ぶ。Dockerには、このDNS解決を行うための組み込みDNSサーバー(127.0.0.11)が用意されているが、アプリケーションがこの解決結果をキャッシュ(一時的に記憶)していなかったため、新しい接続が発生するたびに毎回DNS解決を行っていた。アクセス集中時には接続の生成が頻繁に行われるため、DNS解決のリクエストが次々と発生し、順番待ちが発生して解決に時間がかかるようになっていた。
これらの問題は、少人数のテスト環境では表面化しないものだった。同時に900人の利用者がアクセスするような実運用環境で初めて、その深刻さが露呈したのである。
この問題に対して、チームは「Kubernetesへの移行」や「アプリケーションコードの書き換え」といった根本的な解決策ではないアプローチは排除した。Kubernetesはコンテナオーケストレーションの強力なツールだが、今回のようなネットワークの問題はKubernetesにも存在する。ツールを変えるだけでは、問題の場所を移すだけで根本解決にはならないと判断された。また、アプリケーションの内部呼び出しパターンは一般的なものであり、アプリケーション側で通信回数を減らすのではなく、ネットワーク層が効率的に処理できるようにすべきだと考えられた。
そして、問題解決のために四つの層にわたる具体的な修正が施された。
まず、一つ目の解決策は、「Conntrackのカーネルチューニング」である。前述の接続管理台帳の枯渇を防ぐため、サーバーのLinuxカーネル設定が調整された。具体的には、Conntrackテーブルの最大エントリ数(net.netfilter.nf_conntrack_max)をデフォルトの65,536から262,144へと4倍に増やした。また、確立されたTCP接続のタイムアウト時間(net.netfilter.nf_conntrack_tcp_timeout_established)を600秒に設定し、TCP接続を効率的に再利用するための設定(net.ipv4.tcp_tw_reuse = 1)も有効にした。さらに、同時に受け入れ可能な接続要求の数を増やす設定(net.core.somaxconn = 4096)も行った。これにより、Conntrackテーブルが満杯になるのを防ぎ、効率的な接続管理が可能になった。同時に、Conntrackテーブルの使用状況を常時監視する仕組みも導入し、将来的な問題の早期発見に努めることになった。
二つ目の解決策は、「内部トラフィックのデフォルトブリッジからの分離」である。コンテナ間の通信における非効率な経路を解消するため、Dockerのデフォルトブリッジネットワークとは別に、内部通信専用のカスタムブリッジネットワークを作成した。この新しいネットワークには、アプリケーションコンテナ、Redisコンテナ、検索用のサイドカーコンテナが接続された。これにより、コンテナ間の通信は、DockerのデフォルトのNAT(ネットワークアドレス変換)経路を通らず、より直接的で高速な経路でやり取りされるようになった。これは、住宅街の交通渋滞を避けるために、幹線道路の裏に専用の抜け道を作ったようなイメージだ。外部からのアクセスを受け付けるロードバランサーは引き続き別のネットワークに接続され、セキュリティに影響はなかった。
三つ目の解決策は、「dnsmasqによるローカルDNSキャッシング」である。コンテナが通信相手のIPアドレスを毎回DNS解決するオーバーヘッドを解消するため、「dnsmasq」という軽量なDNSキャッシュサーバーを、各アプリケーションコンテナの「サイドカー」(主となるコンテナの横で補助的に動作するコンテナ)として導入した。dnsmasqはDockerの組み込みDNSサーバーから名前解決を行い、その結果を一時的に記憶(キャッシュ)する。キャッシュの有効期間(local-ttl)は10秒、キャッシュサイズは1000件に設定された。これにより、アプリケーションは同じサービス名への問い合わせを繰り返す際に、毎回DNSサーバーに問い合わせる手間が省け、迅速にIPアドレスを取得できるようになった。10秒という短い有効期間は、コンテナが再起動などでIPアドレスが変わった場合でも、すぐに新しい情報が反映されるように考慮された。
四つ目の解決策は、「PgBouncerによるコネクションプーリング」である。これは最後の手段として導入された。PgBouncerは、PostgreSQLデータベースへの接続を効率的に管理するためのツールである。アプリケーションがデータベースに接続する際、毎回新しい接続を確立するのではなく、PgBouncerがあらかじめ多くの接続をデータベースに確立しておき、アプリケーションからの要求に応じてそれらの接続を使い回す仕組み(コネクションプーリング)を提供する。これにより、データベースへの短命な接続の数が大幅に削減された。結果として、Conntrackテーブルに記録されるエントリの数や、DNS解決の必要性がさらに減少し、それまで実施したネットワーク層の改善効果をさらに高めることができた。
これらの対策を講じた結果、ECサイトのチェックアウト処理は安定し、高負荷時でも問題なく動作するようになった。この事例から得られる教訓は、コンテナ化されたアプリケーションが、CPUやメモリに余裕があるにもかかわらず負荷時のみ性能が低下する場合、原因は多くの場合、見えないネットワーク層、特にConntrackテーブルの管理、ブリッジネットワークの構成、そしてDNS解決の挙動といった部分に隠されているということだ。闇雲にコンテナ数を増やすのではなく、これらの低レベルなネットワークの仕組みを理解し、適切に設定することが、安定したシステム運用には不可欠なのである。