とある DHCP サーバー機能を持つルーターに対して、大量のクライアントからリース要求が来た場合の動作を確認したい状況があった。
物理マシンや VM を何十何百と用意するのは現実的ではないため、 DHCP クライアントとして機能するコンテナを大量に用意する方法を検証してみる。
docker のデフォルト動作では、コンテナが NAT の下に入りブロードキャストドメインが区切られてしまうため、(IPv4 の場合) UDP のブロードキャストに依存する DHCP は機能しない。
この問題を解決するため、 macvlan ネットワークのブリッジモード を利用して、各コンテナに独立した MAC アドレスを割り当てる方法を採用する。
macvlan は Linux ホストでのみ動作するため、 Windows PC を使用する場合は、 VM 上で Linux と docker を実行し、それをホストマシンの NIC とブリッジする構成を取る。
構成
以下のような構成とする。
graph LR
routerA[ルーターA]
subgraph PC["Windows ホスト PC"]
NIC[NIC]
subgraph HyperV["Hyper-V"]
VSwitch["外部ネットワーク仮想スイッチ(プロミスキャスモード状態)"]
subgraph LinuxVM["Linux VM (Ubuntu 24.04)"]
eth["eth0<br>(promisc on)"]
subgraph Docker["docker"]
Macvlan["macvlanネットワーク"]
DC1[container]
DC2[container]
DC3["..."]
end
end
end
end
routerA --> NIC
NIC --> VSwitch
VSwitch --> eth
eth --> Macvlan
Macvlan --> DC1
Macvlan --> DC2
Macvlan --> DC3
ホストPCの NIC、 Linux VM のネットワークインターフェース、コンテナは、それぞれ独立した MAC アドレスを持つ。
ルーターとコンテナの間にある NIC と Linux VM は、自分自身以外の MAC アドレス宛ての通信を受信した場合、スイッチングハブのようにブリッジ先に転送する必要がある。
各 NIC は、デフォルトでは自身以外の MAC アドレス宛ての通信を無視するため、コンテナまでイーサネットフレームが到達しない。これを解決するには、全ての通信を受け入れる「プロミスキャスモード」相当の設定が必要となる。
順を追って設定していこう。
ホスト PC の NIC とブリッジする VM を作成
任意のハイパーバイザーで VM を作成する。なお、 Linux 物理マシンを利用可能な場合は、この章をスキップして次に進んで構わない。
Windows 上の Hyper-V を例に説明しよう。
まず、ホストPC のNIC と繋がる「外部ネットワーク」仮想スイッチを作成する。
ホスト OS が使用している NIC をコンテナから直接利用する場合は、「管理オペレーティング システムにこのネットワーク アダプターの共有を許可する」にチェックを入れる。別の USB LAN アダプタなどを使用する場合は、チェックを外してもよい(ただしホスト OS からその NIC が見えなくなる)。
次に VM を作成し、そのネットワークアダプターに前項の仮想スイッチを接続する。さらに、「高度な設定」で「MAC アドレスのスプーフィングを有効にする」にチェックを入れる。
これにより、ハイパーバイザー側で前述のプロミスキャスモード相当の設定が有効になる。
Linux で docker を立ち上げる
VM に Linux と docker をインストールする。
以下の例では、 Ubuntu 24.04 に snap で docker をインストールする手順を示す。
$ sudo addgroup --system docker
$ sudo adduser $USER docker
$ newgrp docker
$ sudo snap install docker
$ sudo chown root:docker /var/run/docker.sock
そして、 Linux 側のネットワークインターフェースにもプロミスキャスモードの設定を適用する。
$ sudo ip link set dev eth0 promisc on
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 192.168.1.26/24 metric 100 brd 192.168.1.255 scope global dynamic eth0
...
外部の NIC と接続するインターフェース(例: eth0)で PROMISC
と表示されていることと、 DHCP から IP アドレスがリースされていることを確認する。
なお、このプロミスキャスモードの設定は、ゲスト OS を再起動すると毎回リセットされるため、再設定が必要となることに注意。
コンテナを立ち上げる
この状態で、 $HOME
以下のディレクトリのいずれかに、以下のような compose.yaml
を作成する。
networks:
macvlan_net:
name: macvlan_net
driver: macvlan
driver_opts:
parent: eth0
ipam:
config:
- subnet: 169.254.0.0/16
gateway: 169.254.0.1
x-svc: &dockersvc
build:
context: .
dockerfile_inline: |
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y isc-dhcp-client iputils-ping net-tools && apt-get clean && rm -rf /var/lib/apt/lists/*
command: bash -c 'ip -4 a | awk '\''/169\.254\./ {print $$2, "dev", $$NF}'\'' | xargs -r ip a del && sleep $$((RANDOM % 8))s && dhclient eth0 -v && sleep 2s && ip a && sleep 1m'
cap_add:
- NET_ADMIN
networks:
- macvlan_net
services:
svc000: *dockersvc
svc001: *dockersvc
svc002: *dockersvc
svc003: *dockersvc
svc004: *dockersvc
svc005: *dockersvc
svc006: *dockersvc
svc007: *dockersvc
svc008: *dockersvc
svc009: *dockersvc
# ...
compose.yaml
が存在するフォルダをカレントディレクトリとして docker compose up
を実行すると、 services
で定義したコンテナの数だけ DHCP クライアントが起動し、それぞれが IP アドレスのリースを受ける。
docker イメージはキャッシュが効くため、イメージのビルドは1回で済み、大量の DHCP クライアントを高速に起動できる。
より多くのリースを行う場合は、以下のいずれかの方法を取ることができる:
services
のコンテナ数を増やして実行docker compose down
の後に再度up
を実行し、異なる MAC アドレスでリースを要求
なお、エスケープ処理のため command
部分が複雑になっているが、以下の3つの処理を行っている:
ip -4 a | awk '/169\.254\./ {print $2, "dev", $NF}' | xargs -r ip a del
で、 macvlan ネットワーク作成時に仮で登録したリンクローカルアドレスを(あれば)削除sleep $((RANDOM % 8))s
で、コンテナごとに 0~7 秒のランダムスリープdhclient eth0 -v
で DHCP から IP アドレスをリース
おわりに
本記事では、 DHCP サーバーの負荷テストを行う場合を念頭に、 docker コンテナに MAC アドレスを割り当て、 DHCP クライアントを大量に作成する方法を説明した。
プロミスキャスモードの設定など、利用に際して注意する点はいくつかあるが、手順自体は単純である。この方法を活用することで、物理マシンや VM を大量に用意することなく、効率的に物理的な DHCP サーバーの動作検証を行うことができる。