背景と導入の理由
午前2時15分、オンコール用のポケットベルが鳴り響き、40台ものノードで一斉にアラートが発生しました。ハイパーバイザークラスター上のミッドティア企業テナントが3台のデータベースノードを起動した直後、そのクラスターの半分が即座にオフラインになったのです。根本原因は典型的なVLANの競合でした。
テナントAとテナントBの双方が、社内環境をVLAN 100上に構築していました。ディストリビューションレイヤーがすべてのフレームを標準のシングルタグ802.1qトラフィックとして処理していたため、両者のブロードキャストドメインが瞬時に統合されてしまったのです。90秒も経たないうちに、Top-of-RackのAristaスイッチは14,000件以上のMACフラッピング通知を記録し、顧客のパケットが何千個もドロップする事態に陥りました。
IEEE 802.1qは12ビットのVLAN識別子を使用します。これにより利用可能なブロードキャストドメインは4,094個に限られます。50社以上のクライアントを抱えるマルチテナントのコロケーションやマネージドホスティング環境では、このプールはあっという間に枯渇します。さらに重大なのは、顧客の内部アドレッシングを事業者がコントロールできない点です。テナントAがWeb層にVLAN 100を要求する一方で、テナントBがCephストレージにVLAN 100を使用することもあります。タグを勝手に書き換えれば顧客の自動化スクリプトが破損し、タグを剥離すればトポロジーが崩壊してしまいます。
この問題を解決するのが、一般にQinQとして知られるIEEE 802.1adです。この標準規格では、内側の802.1qカスタマーVLANタグ(C-TAG、EtherType 0x8100)を、外側のサービスVLANタグ(S-TAG、EtherType 0x88a8)の中にカプセル化します。コアネットワークは外側のS-TAGのみに基づいてスイッチングを行います。顧客の内側タグは、トラフィックが特定のVMやゲートウェイルーターに到達するまで一切変更されません。
ハイパースケールクラウドではVXLANやGeneveなどのソフトウェアオーバーレイが主流ですが、ベアメタル環境やキャリアグレードのLinuxエッジノードでは依然としてQinQが重宝されています。純粋なレイヤー2デュアルタギングは、VXLANのような50バイトのUDPカプセル化オーバーヘッドを排除し、CPUリソースを消費することなく真のワイヤーレートスループットを実現します。
インストールと前提条件
バージョン3.10以降のすべてのモダンなLinuxカーネルには、802.1adのサポートが含まれています。しかし、最小構成のサーバーディストリビューションでは、ブート時に必要なカーネルモジュールが自動的に読み込まれないことがよくあります。
まず、8021qモジュールを即座にロードします。
sudo modprobe 8021q
計画的なホストの再起動で接続が切断されないよう、再起動後もモジュールを永続化させます。
echo "8021q" | sudo tee /etc/modules-load.d/8021q.conf
必要なネットワークツールおよびパケット検証ツールセットをインストールします。
# Debian、Ubuntu、またはProxmox VE
sudo apt-get update && sudo apt-get install -y iproute2 ethtool tcpdump
# RHEL、AlmaLinux、またはRocky Linux
sudo dnf install -y iproute ethtool tcpdump
次に、物理インターフェースの性能を確認します。標準的なタグなしイーサネットフレームは、ヘッダーとFCSを含めて1518バイトです。4バイトのタグを2つスタックすると、最小フレームサイズは1526バイトに増加します。物理NICまたはスイッチポートが1500バイトを超えるフレームを破棄する場合、QinQのペイロードはサイレントにドロップされます。アップリンクインターフェース(ここではeth0を使用)のリンク状態を確認してください。
ethtool eth0 | grep -i speed
ip link show eth0
設定手順
スタックは常に、基礎となるハードウェアから顧客の名前空間へと下から順に設定してください。親リンクの準備状況を確認する前にサブインターフェースを定義してはいけません。
1. 物理インターフェースのMTUを調整する
最初に親インターフェースのMTUを引き上げます。標準の1500バイトIPペイロードを維持しつつ2つの4バイトタグを収めるには、最低でも1508バイトが必要です。物理スイッチがジャンボフレームをサポートしている場合は、直接9000バイトに設定します。
sudo ip link set dev eth0 mtu 9000
sudo ip link set dev eth0 up
2. 外側のサービスVLAN(802.1ad)を作成する
テナントAに外側のサービスVLAN IDとして1000を割り当てます。proto 802.1adを渡すことで、標準の0x8100ではなくEtherType 0x88a8を外側ヘッダーに書き込むようLinuxに強制します。
sudo ip link add link eth0 name eth0.1000 type vlan proto 802.1ad id 1000
sudo ip link set dev eth0.1000 mtu 8996 up
3. カスタマーVLAN(802.1q)をスタックする
外側のS-TAGインターフェースの直上に、内側のカスタマーVLANを構築します。このインターフェースは標準の802.1qタグ(EtherType 0x8100)を処理します。このシナリオでは、テナントAは内部VLAN 100を使用します。
sudo ip link add link eth0.1000 name eth0.1000.100 type vlan proto 802.1q id 100
sudo ip link set dev eth0.1000.100 mtu 8992 up
4. ネットワーク名前空間を使用した競合テナントの分離
次にテナントBを考えます。テナントBも同一のプライベートIP(192.168.100.1/24)でVLAN 100を設定しています。同一の物理サーバー上で、テナントBに外側のS-TAG 2000を割り当てます。
# テナントB用のアウターS-TAG
sudo ip link add link eth0 name eth0.2000 type vlan proto 802.1ad id 2000
sudo ip link set dev eth0.2000 mtu 8996 up
# テナントB用のインナーC-TAG(競合なしにVLAN ID 100を再利用)
sudo ip link add link eth0.2000 name eth0.2000.100 type vlan proto 802.1q id 100
sudo ip link set dev eth0.2000.100 mtu 8992 up
デフォルトのルーティングテーブルに重複したIPサブネットをバインドすると、カーネルのARPフラックスやルーティングテーブルの破損を引き起こします。ネットワーク名前空間(network namespaces)を使用してクリーンに分離します。
# 独立したルーティングドメインを作成
sudo ip netns add tenant-a
sudo ip netns add tenant-b
# 顧客インターフェースを各名前空間に移動
sudo ip link set dev eth0.1000.100 netns tenant-a
sudo ip link set dev eth0.2000.100 netns tenant-b
# 重複するIP体系を各名前空間内で安全に設定
sudo ip -n tenant-a addr add 192.168.100.1/24 dev eth0.1000.100
sudo ip -n tenant-a link set dev eth0.1000.100 up
sudo ip -n tenant-b addr add 192.168.100.1/24 dev eth0.2000.100
sudo ip -n tenant-b link set dev eth0.2000.100 up
これで両テナントは内部VLAN 100上で192.168.100.0/24を同時に運用できるようになりました。両者のトラフィックパスが交差することは決してありません。
5. systemd-networkdによる設定の永続化
ip linkで実行したコマンドは再起動すると消失します。デュアルタグの階層構造を恒久的に維持するには、systemd-networkdを使用します。
外側のS-TAG netdevを/etc/systemd/network/10-eth0.1000.netdevに定義します。
[NetDev]
Name=eth0.1000
Kind=vlan
[VLAN]
Id=1000
Protocol=802.1ad
スタックされたC-TAG netdevを/etc/systemd/network/20-eth0.1000.100.netdevに定義します。
[NetDev]
Name=eth0.1000.100
Kind=vlan
[VLAN]
Id=100
Protocol=802.1q
/etc/systemd/network/05-eth0.networkで外側VLANを物理インターフェースにバインドします。
[Match]
Name=eth0
[Network]
VLAN=eth0.1000
LinkLocalAddressing=no
検証とトラブルシューティング
インターフェースが起動したら、顧客のワークロードをルーティングする前にスタックをレイヤーごとに検証します。
1. インターフェースのプロトコルフラグを確認する
ip -d link showでインターフェースのメタデータを照会します。
ip -d link show eth0.1000
プロトコルフィールドが802.1adと一致していることを確認します。
vlan protocol 802.1ad id 1000 <REORDER_HDR>
ネストされた顧客インターフェースを確認します。
ip -n tenant-a -d link show eth0.1000.100
標準の802.1qカプセル化が表示されるはずです。
vlan protocol 802.1q id 100 <REORDER_HDR>
2. tcpdumpによるワイヤーレベルの検証
パケットキャプチャによって真実が明らかになります。下位のインターフェースで-eオプションを付けてtcpdumpを実行し、802.1ヘッダーフィールドを直接検査します。
sudo tcpdump -e -n -i eth0 vlan
テナントAの名前空間からテストトラフィックを生成します。
sudo ip netns exec tenant-a ping -c 2 192.168.100.2
キャプチャ結果には、フレームヘッダーに両方のタグが存在することが示されます。
02:42:10.198302 52:54:00:12:34:56 > 52:54:00:ab:cd:ef, ethertype 802.1Q-QinQ (0x88a8), vlan 1000, p 0, ethertype 802.1Q (0x8100), vlan 100, p 0, IPv4 (0x0800), length 98: 192.168.100.1 > 192.168.100.2: ICMP echo request, id 412, seq 1
この出力により、内側の0x8100タグを包む外側の0x88a8カプセル化が機能していることが確認できます。
3. 本番環境の落とし穴:ハードウェアオフロードとMTUブラックホール
送信側のキャプチャにはパケットが表示されるものの、リモートピアが何も受信できない場合は、まず次の2点を確認してください。
- NICハードウェアオフロード:古い10G/25Gコントローラー(
ixgbeドライバーを使用するIntel 82599ESや初期の仮想化virtio_netデバイスなど)は、スタックされたVLANの解析に失敗することがよくあります。これらはハードウェア側で外側の0x88a8タグを勝手に剥離したり破損させたりします。Linuxカーネルがタグ操作を直接処理できるよう、ハードウェアVLANオフロードを無効化してください。
sudo ethtool -K eth0 rxvlan off txvlan off
- MTUブラックホール:小さな56バイトのICMP pingは成功するのに、サイズの大きなデータ転送がハングアップすることがあります。Don’t Fragment(DF)ビットを立ててテストを実行してください。
sudo ip netns exec tenant-a ping -s 1472 -M do 192.168.100.2
このpingがタイムアウトした場合、上流のスイッチポートが1508バイトのオーバーサイズフレームを破棄しています。対処法は2つあります。スイッチポートのMTUを最低1508バイト(ジャンボフレームの場合は9000バイト)に引き上げるか、顧客のサブインターフェースMTUを1492バイトに下げるかのいずれかです。

