午前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 -lをgc_thresh3で割った単一のメトリクスで、本番環境への影響が出る前に早期警告を得られる。 - これらのsysctl値をAnsibleのPlaybookやcloud-initの設定に追加しておくこと。同じ問題を夜中の2時に二度追いかけるのは完全に防げる。
- 閾値を上げても溢れ続けるなら、解くべき問題を間違えている。それはLinuxのチューニング問題ではなく、ネットワークアーキテクチャの問題だ。

