LinuxでのECMPルーティング設定:ip routeコマンドで複数のネットワークパスにトラフィックを分散する

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

本番サーバーがネットワークのボトルネックに遭遇したことがある。CPUには余裕があり、ディスクIOも問題なかったのに、2枚のNICで2つの異なるアップリンクに接続しているにもかかわらず、スループットが800Mbpsにとどまっていた。原因は、両方のリンクがアクティブなのに実際にトラフィックを運んでいたのは片方だけで、もう一方はフェイルオーバー待ちだったのだ。そこでECMPを調べることになった。

デフォルトルートが1つでは不十分な理由

Linuxに複数のアップリンクがある場合、デフォルトではシステムはデフォルトルートを1つしか使用しない。残りのルートはバックアップとして、メインリンクが落ちたときにのみ使われる。これにより、実際に3つの問題が生じる:

  • 帯域幅が1つのパスに制限される — 2×1Gbpsあっても、使えるのは1Gbpsのみ
  • メインリンクが障害を起こすと、システムが検知してバックアップリンクへ切り替えるまでに時間がかかる
  • トラフィックパターンが不均一 — 一部のフローが輻輳しているのに、もう一方のインターフェースは完全にアイドル状態になる

ECMP(Equal-Cost Multi-Path)は、パケットヘッダーのハッシュ値に基づいてカーネルが複数のパスに同時にコネクションを分散させることで、これら3つの問題をすべて解決する。

LinuxカーネルにおけるECMPの仕組み

単一のネクストホップを選択する代わりに、カーネルは各パケットのタプル(src IP, dst IP, src port, dst port, protocol)からハッシュを計算してパスを決定する。同じTCPコネクションは常に同じネクストホップを通る — 同一コネクションのパケットが異なるパスを通るとパケットの順序が狂い、スループットが大幅に低下するため、これは重要な特性だ。

カーネル3.6以降のデフォルトハッシュポリシーはL3(IPアドレスのみ)を使用する。カーネル4.4以降では、同一の宛先への多数のコネクション時により均等に分散させるため、L4ハッシュ(IP+ポート)を有効にできる。

準備:カーネルの確認とハッシュポリシーの設定

この記事で使用するトポロジー:

  • サーバー:192.168.1.100(eth0)と192.168.2.100(eth1)
  • ゲートウェイ ISP A:192.168.1.1(インターフェースeth0経由)
  • ゲートウェイ ISP B:192.168.2.1(インターフェースeth1経由)

ラボのサブネット設計には、toolcraft.app/ja/tools/developer/ip-subnet-calculatorをよく使っている — CIDRを入力するだけで、ネットワーク範囲、ブロードキャスト、利用可能なホスト数が即座に分かり、手計算が不要だ。

# カーネルバージョンを確認(3.6以上が必要)
uname -r

# 現在のハッシュポリシーを確認(0 = L3、1 = L4)
sysctl net.ipv4.fib_multipath_hash_policy

# L4ハッシュを有効化 — 特に同一IPへの多数のコネクション時により均等に分散
sysctl -w net.ipv4.fib_multipath_hash_policy=1

# 再起動後も設定を維持
echo "net.ipv4.fib_multipath_hash_policy=1" > /etc/sysctl.d/99-ecmp.conf
sysctl -p /etc/sysctl.d/99-ecmp.conf

ip routeコマンドでECMPを設定する

基本的なECMPルートの追加

nexthop構文を使うと、1つのコマンドで複数のパスを宣言できる:

# 現在のルートを確認
ip route show

# 古いデフォルトルートを削除
ip route del default

# 同等の2つのネクストホップでECMPルートを追加
ip route add default \
  nexthop via 192.168.1.1 dev eth0 weight 1 \
  nexthop via 192.168.2.1 dev eth1 weight 1

実際の帯域幅に応じてウェイトを調整する

ISP Aが100Mbps、ISP Bが200Mbpsの場合、ウェイト1:2を使って比率に応じてトラフィックを分散する:

# ウェイト比1:2 — ISP BはISP Aの2倍のトラフィックを受信
ip route add default \
  nexthop via 192.168.1.1 dev eth0 weight 1 \
  nexthop via 192.168.2.1 dev eth1 weight 2

Netplanを使った永続的な設定(Ubuntu)

ip routeコマンドの設定は再起動すると失われる。Ubuntuの場合はNetplanを使用する:

# /etc/netplan/01-ecmp.yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 192.168.1.100/24
    eth1:
      addresses:
        - 192.168.2.100/24
  routes:
    - to: default
      via: 192.168.1.1
      metric: 100
    - to: default
      via: 192.168.2.1
      metric: 100
netplan apply

注意:Netplanはweightの構文を直接サポートしていない — 同じメトリックを持つ2つのルートを作成し、カーネルがECMPとして自動的に処理する。異なるウェイトが必要な場合は、スタートアップスクリプトを使用する:

#!/bin/bash
# /etc/rc.local または systemd ExecStartPost
ip route del default 2>/dev/null || true
ip route add default \
  nexthop via 192.168.1.1 dev eth0 weight 1 \
  nexthop via 192.168.2.1 dev eth1 weight 2
exit 0

確認とモニタリング

ECMPルートのアクティブ状態を確認

# ルーティングテーブルを確認 — 出力にnexthop構文があることを確認
ip route show

# 期待される出力:
# default
#     nexthop via 192.168.1.1 dev eth0 weight 1
#     nexthop via 192.168.2.1 dev eth1 weight 2

パケットの経路を確認する

# ip route getで各宛先のネクストホップを確認
ip route get 8.8.8.8
ip route get 1.1.1.1
ip route get 208.67.222.222

# 異なる宛先で実行すると — 2つのインターフェースに交互に振り分けられることを確認できる
# 例:
# 8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.100
# 1.1.1.1 via 192.168.2.1 dev eth1 src 192.168.2.100

各インターフェースのリアルタイム帯域幅をモニタリング

# ip -sでパケット/バイト数を確認
watch -n 1 'ip -s link show eth0 && echo "---" && ip -s link show eth1'

# またはnload(別途インストールが必要)
nload eth0 eth1

# vnstatで時間/日別トラフィックを確認
vnstat -i eth0 -i eth1 --live

実際の問題:非対称ルーティングとフェイルオーバー

非対称ルーティングは、マルチISP ECMP環境で最もよく遭遇する問題だ。サーバーからのパケットがeth0を通るが、応答がeth1から戻ってくる場合(ISPのルーティングが異なるため)、ステートフルファイアウォールは最初のSYNパケットを確認できないためパケットをドロップする。ポリシールーティングで解決できる:

# 各ISP用に独立したルーティングテーブルを2つ作成
echo "100 isp1" >> /etc/iproute2/rt_tables
echo "200 isp2" >> /etc/iproute2/rt_tables

# isp1テーブル:eth0経由の送信トラフィックはすべてeth0で応答
ip route add default via 192.168.1.1 table isp1
ip route add 192.168.1.0/24 dev eth0 src 192.168.1.100 table isp1

# isp2テーブル:eth1についても同様
ip route add default via 192.168.2.1 table isp2
ip route add 192.168.2.0/24 dev eth1 src 192.168.2.100 table isp2

# ルールのルーティング:送信元IPに応じて対応するテーブルを使用
ip rule add from 192.168.1.100 table isp1
ip rule add from 192.168.2.100 table isp2

ECMPにはヘルスチェック機能が組み込まれていない。ゲートウェイが停止しても、カーネルはそこへのトラフィック送信を継続する。ゲートウェイ障害時に自動でルートを削除するシンプルなモニタースクリプト:

#!/bin/bash
# /usr/local/sbin/ecmp-monitor.sh — cronで1分ごとに実行
GW1="192.168.1.1"; GW2="192.168.2.1"

ping -c 3 -W 2 $GW1 > /dev/null 2>&1; GW1_UP=$?
ping -c 3 -W 2 $GW2 > /dev/null 2>&1; GW2_UP=$?

if [ $GW1_UP -eq 0 ] && [ $GW2_UP -eq 0 ]; then
  # 両方がアップ — フルECMPを確保
  ip route replace default \
    nexthop via $GW1 dev eth0 weight 1 \
    nexthop via $GW2 dev eth1 weight 2
elif [ $GW1_UP -ne 0 ]; then
  ip route replace default via $GW2 dev eth1
  logger "ECMP: Gateway 1 down, switched to single path via $GW2"
elif [ $GW2_UP -ne 0 ]; then
  ip route replace default via $GW1 dev eth0
  logger "ECMP: Gateway 2 down, switched to single path via $GW1"
fi
# crontabに追加
echo "* * * * * root /usr/local/sbin/ecmp-monitor.sh" > /etc/cron.d/ecmp-monitor
chmod +x /usr/local/sbin/ecmp-monitor.sh

ECMPは軽量で効果的なツールだ — 追加のデーモンや複雑なソフトウェアは不要で、カーネルがすべての分散処理を担う。ポリシールーティングと組み合わせて非対称ルーティングを回避し、シンプルなモニタースクリプトを加えれば、ほとんどの本番環境のユースケースに対応できる。

Share: