はじめに
業務に邁進する中でネットワークの知見が足りていないことに気づき、ネットワークの基礎固めに精進することにしました。最終的にはDockerおよびKubernetesで利用されるネットワーク技術を解説できるようになること、を目標とし、シリーズ記事として記載する予定です。
参考にする書籍
Docker・Kubernetesで利用されるネットワーク技術に関する本
ネットワークの基礎を理解するための本
本記事で理解すること
- Network Namespaceとは
- DockerとNetwork Namespaceの関係性
シリーズ(予定)
- (基礎)TCP/IP4階層モデル
- VXLAN(Virtual eXtensible Local Area Network)
- Network Namespce ←今回はこれ
- iptables
Network Namespace(netns)とは
Linuxカーネルの機能で、ネットワークリソースを仮想化して独立した環境を作成するための技術。1台のサーバの中にあたかも独立した複数のネットワークがあるかのように振舞わせることができます。
Docker技術との関連
コンテナごとに独立したIPアドレスが割り振られ、他のコンテナと干渉せずに通信できるのは、この netns が裏側で動いているからです。
Network Namespaceを実現する技術
veth
Virtual Ethernet Device。必ずペアで生成され、片方を作成されたnetnsに、もう片方をホストに置くことで、作成されたnetnsとホスト間で通信が可能となる。
仮想ブリッジ
仮想的なL2スイッチのこと。作成したveth同士を繋げて、異なるnetns同士が通信可能となる。
Network Namespaceでネットワークを隔離する
以下の図を用いてNetwork Namespaceによるネットワークの隔離について説明しようと思います。
1. Network Namespaceの作成
ホストのネットワークと隔離されたネットワークを空間(netns1...4)を、Network Namespaceを作成します。以下のようなコマンドで作成できます。
sudo ip nents add nents1
2. hostネットワークとの通信をvethを通じて行う
次に、ホストとのネットワーク経路を確保するため、vethのペアを作成し、一方をnetnsに、もう一方をホストネットワークに配置します。以下のようなコマンドで作成できます。
# vethのペアの作成
sudo ip link add name veth_netns1 type veth peer name veth_host2
# vethの片方(veth_netns1)を作成したnetns(netns1)に移動させる
sudo ip link set dev veth_netns1 netns netns1
3. 仮想bridgeの作成
次に、異なるnetns間で通信を行うため、仮想ブリッジを作成し、vethと接続します。
# bridgeの作成
sudo ip link add name bridge1 type bridge
# bridgeとvethを接続する
sudo ip link set dev veth_host1 master bridge1
sudo ip link set dev veth_host2 master bridge1
ここからIPアドレスの設定とデバイスの起動を行うことで、通信が可能となります。
ネットワーク通信の動き
※Container1からContainer2にパケットを飛ばす場合
- Container1でパケットを生成
- veth_netns1 → veth_host1を通り、hostネットワークへ
- bridge1がパケットを受信し、MACアドレスを見てveth_host2へ転送
- veth_host2 → veth_netns2を通り、目的のContainer2へ到着
Network NamespaceとDockerの関係
まずは検証してみよう
Dockerコンテナがどのように隔離され、ホストと繋がっているのか。その「配線」をLinuxコマンドで一本ずつ確認してみました。
1. 検証用コンテナの起動
まず、ネットワークを保持し続けるための軽量なコンテナを起動します。
docker run -d --name test-container alpine sleep 3600
2. Dockerのnetnsを見れるようにする
Dockerは標準では ip netns コマンドから隠されているため、プロセスのネットワーク空間をマウントして見える化します。
# コンテナのプロセスID(PID)を取得
PID=$(docker inspect -f '{{.State.Pid}}' test-container)
# ip netns が参照するディレクトリを作成
sudo mkdir -p /var/run/netns
# プロセスのネットワーク空間をファイルとしてマウント(紐付け)
sudo touch /var/run/netns/test-container
sudo mount --bind /proc/$PID/ns/net /var/run/netns/test-container
# 確認:リストに現れる
ip netns list
# 結果 -> test-container
3. netns内のvethを確認する
作成した名前(test-container)を使って、vethの詳細を見ます。
sudo ip netns exec test-container ip addr
実行結果(抜粋)
2: eth0@if6: <...>
link/ether c2:63:2e:1c:cb:18 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 ...
- コンテナ内の eth0 は インデックス番号 2。
- @if6 と書いてあります。これは「対向相手のインデックス番号は 6 だよ」というサインです。
4. ホスト側のbridgeとvethを確認する
今度はホストOS(コンテナの外)側で、インデックス番号 6 を探します。
ip link show
実行結果(抜粋)
6: veth267fedd@if2: <...> master docker0 state UP ...
link/ether 5e:4e:93:76:62:82 <...> link-netns test-container
- ホスト側の インデックス番号 6 の名前は veth267fedd。
- @if2 と書いてあります。これは「対向相手は 2(コンテナ内の eth0)」を指しています。
- master docker0 の表記により、このケーブルが仮想ブリッジ(ハブ)に刺さっていることがわかります
まとめ
以上のように、docker runを実行すると、Linuxでは手動で行う以下のステップを、Docker は裏側で一瞬でこなしてくれます。
- netnsを用意する
- vethを生成し、両端を差し込む
- ケーブルの片方をハブ(docker0)に接続する
- 個室にIP アドレスを割り当てる
