🐷

dockerコマンドを使わずにコンテナを作る - 2

2023/03/06に公開

概要

前回はプロセス及びファイルシステムだけを切り離した非常にシンプルなコンテナを作成した.
今回は実際にdocker0ブリッジのような構成を作ってみる. 具体的にはlinux ブリッジネットワークを一つ作り, その上に2つのnetnsを繋げて2つのコンテナ間で疎通ができるまでを試す.

完成イメージ

下図のようなイメージのコンテナができる.
container1, container2はプロセス, ファイルシステムだけでなくnetnsも分離されている. 今回はcontainer1からdocker1というブリッジを経由してcontainer2とpingで疎通確認を行うことをゴールとする.

実践

1. 基点となるルートフォルダの作成

まずはターミナルを3つほど立ち上げ(以下ターミナル1, ターミナル2, ターミナル3), 2つのターミナルで前回の手順の1を行い, 基点となるルートフォルダを2つ作る.

2. netnsを作成して隔離されたnetns内でコンテナを作成する

containerが所属するnetnsを2つ作成し, 手順1で作ったルートフォルダを基点としてそれぞれのnetns内でコンテナを作成する.

sudo ip netns add container1
sudo ip netns add container2

# ファイルができていることを確認(netnsができていることの確認だけならip netnsコマンドを使う)
ls /var/run/netns
container1  container2

# ターミナル1にて
sudo cgexec -g cpu,memory:chemimotty /bin/bash -c "unshare -pfumr --net=/var/run/netns/container1 chroot $ROOTFS /usr/local/bin/bash"
mount -t proc proc /proc

# ターミナル2でも同様に
sudo cgexec -g cpu,memory:chemimotty /bin/bash -c "unshare -pfumr --net=/var/run/netns/container2 chroot $ROOTFS /usr/local/bin/bash"
mount -t proc proc /proc

3. docker1ブリッジ及びvethペアを作成してネットワークを繋ぐ

この手順はターミナル3で行う.
まずはdocker1ブリッジの作成. brctlはbridge-utilsを入れると良い.

sudo brctl addbr docker1

# 確認
ip link show docker1
25: docker1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 12:ac:24:15:5f:77 brd ff:ff:ff:ff:ff:ff

3つのvethペアを作成する. 本当はcontainer1-veth1, container2-veth1と命名したかったが何故かvalid nameでないみたい("name" not a valid ifnameが出た)のでcontainer-veth1, container-veth2と命名した.

sudo ip link add name docker1-veth2 type veth peer name container-veth1
sudo ip link add name docker1-veth3 type veth peer name container-veth2
sudo ip link add name veth1 type veth peer name docker1-veth1

# 確認
ip link show
...
25: docker1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 12:ac:24:15:5f:77 brd ff:ff:ff:ff:ff:ff
26: container-veth1@docker1-veth2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 92:55:3a:1c:9f:07 brd ff:ff:ff:ff:ff:ff
27: docker1-veth2@container-veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 3e:af:70:9c:dd:2b brd ff:ff:ff:ff:ff:ff
28: container-veth2@docker1-veth3: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether aa:da:9a:15:d1:e9 brd ff:ff:ff:ff:ff:ff
29: docker1-veth3@container-veth2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether de:c0:30:b4:d5:c0 brd ff:ff:ff:ff:ff:ff
30: docker1-veth1@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 02:f6:6b:8a:bc:0c brd ff:ff:ff:ff:ff:ff
31: veth1@docker-veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether f6:57:36:fc:42:fb brd ff:ff:ff:ff:ff:ff

ここでイメージ図の通りにcontainer-veth1およびcontainer-veth2をそれぞれcontainer1, container2に紐づける.

sudo ip link set container-veth1 netns container1
sudo ip link set container-veth2 netns container2

# ホストのnetnsを確認するとcontainer-veth1およびcontainer-veth2は消えている
ip link show
...
25: docker1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 12:ac:24:15:5f:77 brd ff:ff:ff:ff:ff:ff
27: docker1-veth2@if26: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 3e:af:70:9c:dd:2b brd ff:ff:ff:ff:ff:ff link-netns container1
29: docker1-veth3@if28: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether de:c0:30:b4:d5:c0 brd ff:ff:ff:ff:ff:ff link-netns container2
30: docker1-veth1@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 02:f6:6b:8a:bc:0c brd ff:ff:ff:ff:ff:ff
31: veth1@docker-veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether f6:57:36:fc:42:fb brd ff:ff:ff:ff:ff:ff
    
# container1, container2を確認するとこちらにcontainer-veth1, container-veth2が移っていることがわかる
sudo ip netns exec container1 ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
26: container-veth1@if27: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 92:55:3a:1c:9f:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0

sudo ip netns exec container2 ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
28: container-veth2@if29: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether aa:da:9a:15:d1:e9 brd ff:ff:ff:ff:ff:ff link-netnsid 0

docker1にもvethを紐づける.

sudo brctl addif docker1 docker1-veth1
sudo brctl addif docker1 docker1-veth2
sudo brctl addif docker1 docker1-veth3

# docker1にdocker1-veth1, docker1-veth2, docker-veth3が紐づいていることを確認
brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242c832abc5       no
docker1         8000.12ac24155f77       no              docker1-veth1
                                                        docker1-veth2
                                                        docker1-veth3

# bridgeコマンドでも調べられる
bridge link show
27: docker1-veth2@if26: <BROADCAST,MULTICAST> mtu 1500 master docker1 state disabled priority 32 cost 2 
29: docker1-veth3@if28: <BROADCAST,MULTICAST> mtu 1500 master docker1 state disabled priority 32 cost 2 
30: docker1-veth1@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 master docker1 state disabled priority 32 cost 2 

4. IPアドレスを付与

この手順もターミナル3で行う.

sudo ip addr add 192.168.10.100/24 dev veth1
ip addr show veth1
31: veth1@docker1-veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether f6:57:36:fc:42:fb brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.100/24 scope global veth1
       valid_lft forever preferred_lft forever
    inet6 fe80::f457:36ff:fefc:42fb/64 scope link 
       valid_lft forever preferred_lft forever

sudo ip netns exec container1 ip addr add 192.168.10.1/24 dev container-veth1
sudo ip netns exec container1 ip addr show container-veth1
26: container-veth1@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 92:55:3a:1c:9f:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.10.1/24 scope global container-veth1
       valid_lft forever preferred_lft forever
    inet6 fe80::9055:3aff:fe1c:9f07/64 scope link 
       valid_lft forever preferred_lft forever

sudo ip netns exec container2 ip addr add 192.168.10.2/24 dev container-veth2
sudo ip netns exec container2 ip addr show container-veth2                   
28: container-veth2@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether aa:da:9a:15:d1:e9 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.10.2/24 scope global container-veth2
       valid_lft forever preferred_lft forever
    inet6 fe80::a8da:9aff:fe15:d1e9/64 scope link 
       valid_lft forever preferred_lft forever

5. NICおよびbridgeをUPに変更

NICおよびbridgeは作成されたタイミングではDOWNになっており疎通できないためUPに変更する.
この手順もターミナル3で行う.

sudo ip link set dev docker1 up
sudo ip link set dev docker1-veth1 up
sudo ip link set dev docker1-veth2 up
sudo ip link set dev docker1-veth3 up
sudo ip link set dev veth1 up
sudo ip netns exec container1 ip link set dev container-veth1 up
sudo ip netns exec container2 ip link set dev container-veth2 up

# 今回は必要ないがついでにloopbackもupにしておく
sudo ip netns exec container1 ip link set dev lo up
sudo ip netns exec container2 ip link set dev lo up

# stateがUPになっていることを確認
ip link show
...
25: docker1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 12:ac:24:15:5f:77 brd ff:ff:ff:ff:ff:ff
27: docker1-veth2@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker1 state UP mode DEFAULT group default qlen 1000
    link/ether 3e:af:70:9c:dd:2b brd ff:ff:ff:ff:ff:ff link-netns container1
29: docker1-veth3@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker1 state UP mode DEFAULT group default qlen 1000
    link/ether de:c0:30:b4:d5:c0 brd ff:ff:ff:ff:ff:ff link-netns container2
30: docker1-veth1@veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker1 state UP mode DEFAULT group default qlen 1000
    link/ether 22:24:4e:da:81:66 brd ff:ff:ff:ff:ff:ff
31: veth1@docker1-veth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether f6:57:36:fc:42:fb brd ff:ff:ff:ff:ff:ff

sudo ip netns exec container1 ip link show
...
26: container-veth1@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 92:55:3a:1c:9f:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0

sudo ip netns exec container2 ip link show
...
28: container-veth2@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether aa:da:9a:15:d1:e9 brd ff:ff:ff:ff:ff:ff link-netnsid 0

6. 動作確認

疎通は完了しているはずなので動作確認. ただしLinuxブリッジは(少なくとも自分の確認した環境では)デフォルトでbr_netfilterによりブリッジ経由のパケットはフィルタされてしまう. そのためiptablesの設定を適切に行わなければならないが, 簡単のため今回はbr_netfilterを無効にする.

# 下記コマンドは再起動するとまた1に戻るためそれを避けるのであれば/etc/sysctl.confに設定を入れる
sudo sysctl net.bridge.bridge-nf-call-iptables=0

これでブリッジ経由のパケットはフィルタされなくなるので動作確認をしてみる. ここではターミナル1(container1)で確認する.

# container1からcontainer2(192.168.10.2)への疎通確認
ping -c 1 192.168.10.2
PING 192.168.10.2 (192.168.10.2): 56 data bytes
64 bytes from 192.168.10.2: seq=0 ttl=64 time=0.066 ms

--- 192.168.10.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.066/0.066/0.066 ms

もちろんターミナル3からも確認はできる.

sudo ip netns exec container1 ping -c 1 192.168.10.2
PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data.
64 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=0.026 ms

--- 192.168.10.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.026/0.026/0.026/0.000 ms

所感

今回はnetnsでネットワークも分離する方法を行った. 次はこれに加えてoverlayfsを使ってやってみる. 次が恐らくdockerコマンドを使わずにコンテナを作るの最後のまとめになると思う.

参照

Network NamespaceにBridge経由でアクセスする
Linux Bridgeを介した通信ができない

Discussion