Linuxネットワーク名前空間とvethペア:ゼロから構築するコンテナネットワーク

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Linuxがコンテナネットワークを分離する仕組み

docker runを実行したりKubernetesのPodをスケジュールしたりするとき、containerdやCRI-Oなどのコンテナランタイムは魔法を使っているわけではありません。Linuxカーネルに直接組み込まれている標準のネットワーキングプリミティブを設定しているに過ぎません。テナント間でネットワークトラフィックを分離するため、インフラチームは主に以下の3つのアーキテクチャから選択します。

  • ホストネットワークの共有:コンテナがホストのネットワークスタックを直接共有します(例:Dockerの--net=host)。このアプローチではオーバーヘッドがほぼゼロ(多くの場合5マイクロ秒未満のレイテンシ)になりますが、セキュリティ境界が失われ、マイクロサービス間でのポート衝突が発生しやすくなります。
  • 完全な仮想マシン分離:KVMなどのハイパーバイザーが、専用の仮想NIC、TAPインターフェース、ゲストカーネルを備えた仮想マシンを起動します。この手法はハードウェアレベルの強固な分離を実現しますが、メモリ消費が大きくなります。軽量なゲストOSであってもベースラインで150MB〜512MBのRAMを消費し、起動に数秒かかります。
  • 仮想イーサネット(veth)ペアを用いたネットワーク名前空間:カーネルがルーティングテーブル、ファイアウォールルール、インターフェース、ソケットなどのネットワークリソースを論理的なサンドボックスに分割します。これらのサンドボックスを仮想イーサネット(veth)ケーブルで接続することで、ミリ秒未満の起動時間と無視できるほどのオーバーヘッドでコンテナレベルの分離を提供します。

これらのカーネルメカニズムを理解することは、本番環境での壊れたCNIプラグインのトラブルシューティング、Dockerブリッジ間でのパケットドロップのデバッグ、そして重厚なサードパーティ製ツールを使わないワークロードの分離に役立ちます。

手動によるネットワーク名前空間管理のトレードオフ

独自にインターフェースを配線する前に、カーネルのネットワークプリミティブを手動で管理するメリットとデメリットを比較検討しましょう。

メリット

  • 無視できるほどわずかなリソース消費:ネットワーク名前空間の作成に割り当てられるカーネルメモリはわずか数キロバイト(struct net)です。ホストのCPUやRAMに負荷をかけることなく、数千個の名前空間を実行できます。
  • 独立したネットワークスタック:各名前空間は、分離されたループバックデバイス、個別のARPテーブル、プライベートなソケットリスト、独立したiptables/nftablesフィルタチェーンを持ちます。
  • 普遍的な互換性:すべての設定はiproute2やiptablesなどの標準コアユーティリティに依存しています。コンテナデーモンやCNIバイナリは一切不要です。

デメリット

  • 揮発性の状態:ip netnsで作成された名前空間はRAM上にのみ存在します。systemdユニットやカスタムのブートストラップスクリプトでバックアップしない限り、ホストの再起動によって即座に消去されます。
  • ルーティングのスケーラビリティ:数十を超えるエンドポイントにスケールアップすると、静的ルートやブリッジ設定は破綻しやすくなります。本番環境のセットアップには、CalicoやCiliumなどの自動化されたコントロールプレーンが必要です。
  • 権限の要件:インターフェースやルーティングテーブルの設定には、rootアクセスまたはCAP_NET_ADMINケーパビリティが必要です。

アーキテクチャ:仮想ブリッジ、デュアル名前空間、およびNAT

Dockerなどのランタイムがデフォルトのbridgeネットワークを構築する方法を再現するため、Linuxソフトウェアブリッジを介して2つの分離された名前空間を接続し、NATマスカレードを使用してエグレストラフィックをパブリックインターネットにルーティングします。

運用上の注意点として、リモートVPS上でiptablesのフォワーディングルールやデフォルトルートを変更すると、SSHセッションが切断されるリスクがあります。本番ノードを操作する前に、必ずローカル仮想マシン、ステージングサーバー、またはアウトオブバンドコンソールでこれらのコマンドをテストしてください。

今回のターゲットトポロジは以下の通りです:

  • 2つの分離されたネットワーク名前空間:ns-redとns-blue
  • Layer 2仮想スイッチとして機能するホスト上のソフトウェアブリッジ(br0)
  • 各名前空間をbr0に接続する2組のポイントツーポイントvethペア
  • ホストレベルのIPv4パケット転送、およびプライマリアップリンク(eth0など)を経由してアウトバウンドパケットをルーティングするiptables MASQUERADEルール

ステップ・バイ・ステップの実装手順

ステップ1:ネットワーク名前空間のプロビジョニング

まず、マシン上の既存の名前空間を確認し、2つの独立した環境を作成します:

# 現在の名前空間を確認
sudo ip netns list

# 2つの独立した名前空間を作成
sudo ip netns add ns-red
sudo ip netns add ns-blue

# 作成を確認
sudo ip netns list

新しく作成された名前空間のループバックインターフェースは非アクティブ状態になっています。ns-red内でこれを確認してみましょう:

sudo ip netns exec ns-red ip link show

loデバイスがDOWNと表示されています。ローカルプロセスがIPCやlocalhostバインディングを解決できるように、両方の名前空間でループバックを立ち上げます:

sudo ip netns exec ns-red ip link set dev lo up
sudo ip netns exec ns-blue ip link set dev lo up

ステップ2:仮想ブリッジの初期化

ホスト上にソフトウェアブリッジを作成します。このデバイスは物理的なLayer 2スイッチのように機能し、両方の名前空間のデフォルトゲートウェイ(192.168.50.1)としても機能します:

# ブリッジインターフェースを作成
sudo ip link add name br0 type bridge

# サブネット(192.168.50.0/24)のゲートウェイIPアドレスを割り当て
sudo ip addr add 192.168.50.1/24 dev br0

# ブリッジインターフェースを有効化
sudo ip link set dev br0 up

ステップ3:vethペアを使用した名前空間の接続

vethペアは、双方向の仮想パッチケーブルのようなものと考えてください。一方の端に入ったフレームは、即座にもう一方の端から出てきます。名前空間をブリッジに接続するために2組のペアを作成します:

# ns-red用のvethペアを作成
sudo ip link add veth-red type veth peer name veth-red-br

# ns-blue用のvethペアを作成
sudo ip link add veth-blue type veth peer name veth-blue-br

ホスト側のエンドポイントをbr0に接続し、ピア側をそれぞれの対象名前空間に移動します:

# ホスト側のエンドポイントをブリッジに接続
sudo ip link set veth-red-br master br0
sudo ip link set veth-blue-br master br0

# 両方のブリッジ側エンドポイントを有効化
sudo ip link set dev veth-red-br up
sudo ip link set dev veth-blue-br up

# コンテナ側のエンドポイントをそれぞれの名前空間に移動
sudo ip link set veth-red netns ns-red
sudo ip link set veth-blue netns ns-blue

ステップ4:IPアドレスの割り当てと名前空間間Pingの確認

エンドポイントが配置されたら、192.168.50.0/24ブロックから静的IPを割り当て、各名前空間内のリンクを起動します:

# ns-redにIP 192.168.50.10を設定
sudo ip netns exec ns-red ip addr add 192.168.50.10/24 dev veth-red
sudo ip netns exec ns-red ip link set dev veth-red up

# ns-blueにIP 192.168.50.20を設定
sudo ip netns exec ns-blue ip addr add 192.168.50.20/24 dev veth-blue
sudo ip netns exec ns-blue ip link set dev veth-blue up

ブリッジを経由して2つの名前空間間の接続性をテストします:

sudo ip netns exec ns-red ping -c 3 192.168.50.20

ICMPフレームはveth-redから送出され、ホスト上のbr0に到達し、MAC学習に従って転送され、ns-blue内のveth-blueに着信します。通常、レイテンシは0.05ミリ秒未満です。

ステップ5:デフォルトゲートウェイとNATルーティングの設定

これで名前空間同士の通信が可能になりましたが、外部サブネットやインターネットにはまだ到達できません。デフォルトルートをブリッジのIPアドレスに向けます:

sudo ip netns exec ns-red ip route add default via 192.168.50.1
sudo ip netns exec ns-blue ip route add default via 192.168.50.1

次に、ホストがインターフェース間でトラフィックをルーティングできるように、LinuxカーネルでIPv4パケット転送を有効にします:

sudo sysctl -w net.ipv4.ip_forward=1

デフォルトのアウトゴーイング物理インターフェースの名前を確認します:

ip route show default | awk '{print $5}'

プライマリアップリンクがeth0であると仮定し、エグレスパケットをマスカレードして双方向の転送を許可するiptablesルールを追加します:

# eth0から出ていくアウトバウンドトラフィックをマスカレード
sudo iptables -t nat -A POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE

# ブリッジとパブリックインターフェース間のパケット転送を許可
sudo iptables -A FORWARD -i br0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT

ステップ6:外部接続のテストとDNSの設定

ns-red内からパブリックIPアドレスにpingを送信し、ルーティングとNATが正常に機能していることを確認します:

sudo ip netns exec ns-red ping -c 3 1.1.1.1

ドメイン名解決を有効にするため、名前空間専用のresolv.confファイルを用意します:

sudo mkdir -p /etc/netns/ns-red
echo "nameserver 1.1.1.1" | sudo tee /etc/netns/ns-red/resolv.conf

ns-red内でコマンドを実行する際、ip netns execは自動的に/etc/netns/ns-red/resolv.confを/etc/resolv.confの上にバインドマウントします。curlを使用して完全なDNS名前解決をテストします:

sudo ip netns exec ns-red curl -I https://kernel.org

ステップ7:環境のクリーンアップ

テストが完了したら、わずか数秒でクリーンアップできます。名前空間を削除すると、所属するvethインターフェースとホスト側のピアが自動的に削除されます:

# 名前空間を削除(veth-redおよびveth-blueペアも自動削除されます)
sudo ip netns del ns-red
sudo ip netns del ns-blue

# ブリッジインターフェースを削除
sudo ip link set dev br0 down
sudo ip link del dev br0

# iptablesルールを削除
sudo iptables -t nat -D POSTROUTING -s 192.168.50.0/24 -o eth0 -j MASQUERADE
sudo iptables -D FORWARD -i br0 -o eth0 -j ACCEPT
sudo iptables -D FORWARD -i eth0 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT

これで現代のコンテナネットワーキングに関する実践的なメンタルモデルが身につきました。ここで行った一連のCLIコマンド操作こそが、コンテナ起動のたびにDockerやKubernetes CNIが舞台裏で自動化している処理そのものです。

Share: