大規模ネットワークで発生する「Neighbour table overflow: ARP table full」によるネットワーク障害の修正方法

intermediate🌐 Networking2026-04-07| Linux(Ubuntu 20.04/22.04、RHEL/CentOS 7/8、Debian)、大規模フラットレイヤー2ネットワーク、/24以上のサブネット、高トラフィックのサーバーまたはルーター

Error Message

Neighbour table overflow: ARP table full
#ネットワーキング#arp#テーブル#オーバーフロー#カーネル

午前2時のアラート

監視アラートが発火した。ユーザーはアプリにアクセスできない。サーバーの半分にSSHがタイムアウトする。インターフェースは正常、ルートも問題なし——そんな表面的な確認を終えたあと、dmesgの奥に埋もれたこのメッセージを見つける:

[ 4823.119472] neighbour: arp_cache: neighbor table overflow!
[ 4823.119512] Neighbour table overflow: ARP table full

カーネルのARPテーブルに空きがなくなった。新しいARPエントリを作成できないため、最近通信していないホスト宛のパケットが静かにドロップされる。数百台のアクティブホストが存在する/20以上のフラットネットワークでは、これが一瞬で起きる——とくにゲートウェイ、ロードバランサー、あらゆるホストと通信する監視サーバーで顕著だ。

内部で何が起きているか

LinuxカーネルはIPアドレスをMACアドレスに対応付けるためにネイバーテーブル(ARPキャッシュ)を保持している。デフォルトのサイズは小規模ネットワーク向けだ。上限を制御するカーネルパラメータは3つある:

  • gc_thresh1 — この値を下回るとガベージコレクションは実行されない(デフォルト: 128)
  • gc_thresh2 — ソフトリミット。超過後5秒でGCが起動する(デフォルト: 512)
  • gc_thresh3 — ハードリミット。この値を超えると新規エントリは一切追加できない(デフォルト: 1024)

アクティブホストが約1000台いる/22ネットワークに接続された負荷の高いサーバーでは、数分でgc_thresh3に到達する。カーネルはARPが解決できないホスト宛のパケットを静かにドロップし始める。だから帳面上はすべて正常に見えるのに、実際には何も機能しないのだ。

変更前に問題を確認する

ARPテーブルが満杯になってドロップが発生していることを、以下のコマンドで確認する:

# 現在のARPテーブルのサイズを確認
ip neigh show | wc -l

# 現在のカーネル上限を確認
cat /proc/sys/net/ipv4/neigh/default/gc_thresh1
cat /proc/sys/net/ipv4/neigh/default/gc_thresh2
cat /proc/sys/net/ipv4/neigh/default/gc_thresh3

# 最近のカーネルログからオーバーフローメッセージを確認
dmesg | grep -i 'neighbour\|arp' | tail -20
journalctl -k | grep -i 'neighbour table overflow' | tail -10

# ARPキャッシュの統計を確認(失敗したルックアップ)
netstat -s | grep -i 'arp\|fail'

ip neigh show | wc -lの結果がgc_thresh3に近い、または超えている場合、それが原因だ。FAILED状態のエントリも確認できるはずだ:

ip neigh show | grep FAILED | head -20

即時対応(再起動不要)

パケットドロップを止めるため、すぐに上限値を引き上げる:

sudo sysctl -w net.ipv4.neigh.default.gc_thresh1=4096
sudo sysctl -w net.ipv4.neigh.default.gc_thresh2=8192
sudo sysctl -w net.ipv4.neigh.default.gc_thresh3=16384

ndisc_cacheのオーバーフローも発生している場合はIPv6も対処する:

sudo sysctl -w net.ipv6.neigh.default.gc_thresh1=4096
sudo sysctl -w net.ipv6.neigh.default.gc_thresh2=8192
sudo sysctl -w net.ipv6.neigh.default.gc_thresh3=16384

目安はセグメントあたりの想定ユニークホスト数の2〜3倍だ。/20ネットワークの使用可能アドレスは4094個——gc_thresh3は少なくとも8192に設定する。アクティブなVMが数千台ある/16なら32768以上が妥当だ。

再起動後も設定を維持する

sysctl -wの変更は再起動すると消える。永続的な設定ファイルに書き込んでおく:

sudo tee /etc/sysctl.d/99-arp-table.conf <<EOF
net.ipv4.neigh.default.gc_thresh1 = 4096
net.ipv4.neigh.default.gc_thresh2 = 8192
net.ipv4.neigh.default.gc_thresh3 = 16384
net.ipv6.neigh.default.gc_thresh1 = 4096
net.ipv6.neigh.default.gc_thresh2 = 8192
net.ipv6.neigh.default.gc_thresh3 = 16384
EOF

sudo sysctl --system

修正が有効か確認する

# 新しい上限が適用されているか確認
sysctl net.ipv4.neigh.default.gc_thresh3

# ARPテーブルが上限を大きく下回っていることを確認
ip neigh show | wc -l

# カーネルログにオーバーフローメッセージがないことを確認
dmesg | grep -i 'neighbour table overflow'

# FAILEDエントリは自然に消えるが、すぐにフラッシュすることも可能
ip neigh flush nud failed
ip neigh flush nud stale

sysctlの変更を適用してから数秒以内に接続性は通常回復する。それでもホストに到達できない場合は、失敗したARPエントリを手動でフラッシュすること——カーネルはすぐに再ARPして新しいエントリを書き込む。

オプション:動的環境向けにガベージコレクションを調整する

VMやコンテナが頻繁に起動・停止する環境では、デフォルトのGC設定は静的なホスト向けに設計されているため最適ではない。以下で調整する:

# 未使用エントリがstaleになるまでの時間
# デフォルトは60秒——IPが頻繁に再利用される環境では30秒が効果的
sudo sysctl -w net.ipv4.neigh.default.base_reachable_time_ms=30000

# GCの実行間隔(秒)(デフォルト: 30、そのままで問題ない)
sudo sysctl -w net.ipv4.neigh.default.gc_interval=30

# staleエントリがGCで削除されるまでの保持時間
sudo sysctl -w net.ipv4.neigh.default.gc_stale_time=60

本来解決すべき根本原因

閾値を引き上げるのは時間稼ぎに過ぎない。そもそもなぜそれほど多くのARPエントリが存在するのかを問い直すべきだ:

  • フラットネットワークが大きすぎる/20を単一のレイヤー2ドメインに広げるのは設計上の問題だ。ルーテッドコアを持つ小さなVLANに分割すること。各セグメントは独自のブロードキャストとARPドメインに収まるため、4000台のホストを把握しなければならないサーバーがなくなる。
  • ARPスキャンや監視ツール — Nagios、Zabbix、またはカスタムのネットワークスキャナーが/20範囲の全IPを叩けば、数分でARPテーブルが溢れる。セグメント全体に広域pingを打っているものがないか確認すること。
  • ブロードキャストストームtcpdump -i eth0 arp | pv -l -r > /dev/nullを実行してリアルタイムのARPリクエストレートを確認する。毎秒数百件ならば、テーブルサイズの問題ではなくブロードキャスト増幅の問題だ。
  • KubernetesやコンテナのオーバーレイネットワークK — 大規模クラスターでは各Podが独自のIPを持つためARPテーブルがすぐに枯渇する。同じsysctl修正を適用しつつ、CNI(Calico、Flannel、Cilium)に独自のネイバーテーブル設定があるかも確認すること。

ネットワーク設計のヒント

ARPの負荷を下げるために大きなフラットネットワークを小さなVLANに分割する際、サブネットの計算を正確に行うことが重要だ。VLANの分割を計画するとき、CIDRの範囲、使用可能なホスト数、ブロードキャストアドレスの算出にはToolCraftのSubnet Calculatorを使っている。ブラウザ上で完結し、データが外部に送信されない点が、内部IPアドレスを扱う際に安心できる。

まとめ

  • デフォルトの1024エントリというARP上限は数十年前には理にかなっていた。今日では/22より大きなフラットネットワークなら、障害の夜中に発見するのではなく、プロビジョニング時にこれらの値を調整しておくべきだ。
  • ARPテーブルの飽和を監視に組み込むこと。ip neigh show | wc -lgc_thresh3で割った単一のメトリクスで、本番環境への影響が出る前に早期警告を得られる。
  • これらのsysctl値をAnsibleのPlaybookやcloud-initの設定に追加しておくこと。同じ問題を夜中の2時に二度追いかけるのは完全に防げる。
  • 閾値を上げても溢れ続けるなら、解くべき問題を間違えている。それはLinuxのチューニング問題ではなく、ネットワークアーキテクチャの問題だ。

Related Error Notes