Kubernetes上の通信がなぜか15分で途切れてしまう謎を解明した話

はじめに

はじめまして、スピーダ事業Product Teamの清水です。
私たちのチームではKubernetesを利用してアプリケーションの運用をしております。
今回はKubernetesのあまり知られていない設定により詰まる経験をしたので、供養のため記事にしたいと思います。

今回の経緯

私たちのチームではLLM(Agent)を利用して各企業の経営課題を各種公開資料から抽出し、課題一覧として取得するということを行なっています。
こちらの処理では、企業の実態を反映した高品質な課題を抽出するため、LLM(Agent)によるレビューを何度も繰り返して最終的な出力を行います。
そのためレスポンスが返ってくるまでの時間が15分以上かかってしまうことも多くありました。

起きた問題

ClientとなるPod(Job)から該当のAgentに経営課題の抽出をリクエストすると、一定確率でレスポンスが返ってこない問題が発生しました。
レスポンスの返ってこないリクエストに明確な法則性があるわけではなく、調査は早々に暗礁へ乗り上げることとなりました...

調査

まずはClientのログを確認すると、以下のようにレスポンスが返ってきていないことが確認できました。

※ログの内容はサンプルです。実際のものとは異なります

2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_01
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_02
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_03
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_04
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_05
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_06
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_07
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_08
2026-07-16T04:05:16 [INFO] START taskId=DUMMY_ID_09

2026-07-16T04:13:21 [INFO] DONE taskId=DUMMY_ID_05 elapsed=485.21s
2026-07-16T04:13:48 [INFO] DONE taskId=DUMMY_ID_08 elapsed=512.17s
2026-07-16T04:14:33 [INFO] DONE taskId=DUMMY_ID_03 elapsed=557.20s
2026-07-16T04:17:14 [INFO] DONE taskId=DUMMY_ID_04 elapsed=717.98s
2026-07-16T04:18:14 [INFO] DONE taskId=DUMMY_ID_09 elapsed=778.24s

2026-07-16T05:05:16 [WARNING] RETRY attempt=1 // Client側で設定した60分後の強制リトライが発火
2026-07-16T05:05:16 [WARNING] RETRY attempt=1 
2026-07-16T05:05:16 [WARNING] RETRY attempt=1 
2026-07-16T05:05:16 [WARNING] RETRY attempt=1 

この時点では、一部のリクエストにおいてエージェント側で何らかの問題が出てレスポンスが返ってこなくなっているのではないかと考えていました。しかしながら、Agent側のログをチェックすると以下のように正常にレスポンスが返っていることがわかります。

※ログの内容はサンプルです。実際のものとは異なります

2026-07-16 04:13:21,427 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=484.986s, error=0, http.status_code='200')
2026-07-16 04:13:48,427 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=511.983s, error=0, http.status_code='200')
2026-07-16 04:14:33,455 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=557.017s, error=0, http.status_code='200')
2026-07-16 04:17:14,195 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=717.751s, error=0, http.status_code='200')
2026-07-16 04:18:14,397 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=777.959s, error=0, http.status_code='200')
2026-07-16 04:23:00,742 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=1064.008s, error=0, http.status_code='200')
2026-07-16 04:25:43,097 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=1226.371s, error=0, http.status_code='200')
2026-07-16 04:26:18,373 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=1261.930s, error=0, http.status_code='200')
2026-07-16 04:26:56,022 DEBUG [ddtrace._trace.tracer] - finishing span - Span(name='fastapi.request', service='sample-agent', resource='POST /run', duration=1299.585s, error=0, http.status_code='200')

上記のようにAgent側は正しくレスポンスを返していることがわかったため、我々はKubernetesのネットワーク上の問題を疑って調査を続けました。

問題の特定

ClientとAgentのログを突き合わせると、レスポンスが返ってくるまでに大体13分以上かかるリクエストが失敗していることがわかります。

そこで我々はネットワーク上のどこかでタイムアウトがかかっているのではないかと考えました。そして、その前提でClaude Codeと壁打ちしながら調査を進めているとある一つの事実に突き当たりました。

我々が利用しているKubernetesクラスターはkube-proxyのIPVSモードという設定で、通信のルーティングを行っています。その上でConfigMapにてIPVSモードのTCPタイムアウトが0sのままになっており、この場合タイムアウトの時間としてデフォルト値が利用され、それが15分となっていました。

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
ipvs:
  excludeCIDRs: []
  minSyncPeriod: 0s
  scheduler: rr
  strictARP: true
  syncPeriod: 30s
  tcpFinTimeout: 0s
  tcpTimeout: 0s # ここがデフォルト値で15分になる
  udpTimeout: 0s
mode: ipvs

こちらの設定の影響で15分以上パケットの送受信が行われていないTCPコネクションは、IPVSのコネクション管理テーブル(どのコネクションをどのPodへ転送しているかをセッション単位で保持しているもの)から削除されます。この削除はClientにもAgentにも通知されず、RSTもFINも飛びません。そのため双方はコネクションが生きていると思い込んだままとなり、Agentが返した応答はClientまで届かず、Clientはひたすら応答を待ち続ける状態になっていました。

この問題に対し、我々のチームではPythonで書かれたClient側で通信を維持するための仕組みを導入して解決を行いました。具体的には通信維持用のパケットを定期的に送信して、それがTCPコネクション上を走ることで通信が維持されます。

_transport = httpx.AsyncHTTPTransport(
    socket_options=[
        (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
        (socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 60),
        (socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 30),
        (socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3),
    ]
)
client = httpx.AsyncClient(base_url=BASE_URL, timeout=3600, transport=_transport)

まとめ

今回は、Kubernetesのネットワーク設定によりうまくアプリケーションが動かないパターンのご紹介をしました。
ソフトウェアエンジニアをしているとアプリケーション側のことに関心が向いてしまいがちですが、ネットワークやインフラの知識も身につけていかなくてはと思う一件でした。 AIアプリケーションが一般的になり、レスポンスまで時間がかかるケースも増えてくると思うので、謎のネットワークエラーが起きた場合は思い出してもらえると幸いです。

Page top