ぶていのログでぶログ

思い出したが吉日

Network Namespaceを使ったネットワークを簡単に構築できるnetnsplanを作った

Network Namespace(netns)を使ってネットワークを構築する必要がでてきた。 しかし、色々調べているとnetnsを使ってネットワークを構築するにはシェルスクリプトを使うしかなくてだる~っとなっていた。 さすがにそれは可搬性が低いので、他のツールはないか調査してみた。

  • netplan
    • 一番良さそうだったのだが、netplanが依存しているsystemd-networkdをnetns上でも動かさないと使えなく、大掛かりだったのでミスマッチ
  • systemd-networkd
    • 同上
  • mininet
    • netnsを使ったネットワークを環境するという目的ではマッチしていたのだが、Open vSwitchを使ったりしてミスマッチだった
  • NetworkManager
    • 未調査

結果として、私の要件に合うものがなかったのでnetnsplanというツールを自作した。

github.com

netnsplanはnetnsを使ったネットワークを簡単に構築するためのツールである。 名前からもわかるとおりnetplanに似せてつくっている。 これは単に私がnetplanに慣れているからという理由である。

使用例

netnsplanではデフォルトで /etc/netnsplan/*.yaml を見に行く。 複数ファイルがある場合は1つのYAMLにマージして、結果を適用する。

ここではサンプルとして /etc/netnsplan/example.yamlを用意してこれを適用してみる。

# /etc/netnsplan/example.yaml
netns:
  example:
    ethernets:
      eth1:
        addresses:
          - 192.168.20.1/24
        route:
          - to: default
            via: 192.168.20.254
    post-script: |
      sysctl --system
      iptables-restore /etc/iptables/rules.v4

この例ではexampleという名前でnetnsを作成し、そこにeth1を紐づけする。 eth1には192.168.20.1/24というIPを設定し、デフォルトゲートウェイとして192.168.20.254を設定する。 そして、post-scriptとしてsysctlとiptables-restoreを実行している。 post-scriptはnetnsplan特有の機能で、netnsの設定が終わったあとに任意のスクリプトを実行する。 実行されるスクリプトはnetns上で実行されるので、サンプルのようにsysctlやiptablesを設定したりするのに使える。

設定ファイルが作成できたら、netnsplan applyを実行する。

$ sudo netnsplan apply
INFO  create netns name="example"
INFO  link up name="lo" netns="example"
INFO  set netns name="eth1" netns="example"
INFO  link up name="eth1" netns="example"
INFO  add addresses name="eth1" address="192.168.20.1/24" netns="example"
INFO  run post script netns="example" script="sysctl --system\niptables -nvL\n"

applyが成功すると記述したとおりの設定が行われているはず。

$ ip netns
example (id: 1)
$ sudo ip netns exec example ip a show dev eth1
3: eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
    link/ether XX:XX:XX:XX:XX:XX brd ff:ff:ff:ff:ff:ff
    inet 192.168.20.1/24 scope global eth1
       valid_lft forever preferred_lft forever

applyは複数回実行でき、すでに設定されている項目はスキップされる。 ただし、post-scriptは実行されない。 post-scriptも実行したい場合は--always-run-post-script(-R)をつける必要がある。

$ sudo netnsplan apply
WARN  netns is already exists name="example"

# -Rをつけるとpost-scriptが実行される
$ sudo ./netnsplan apply -R
WARN  netns is already exists name="example"
INFO  run post script netns="example" script="sysctl --system\niptables -nvL\n"

destroyを実行するとnetnsの設定を破棄し元に戻る。

$ sudo ./netnsplan destroy
INFO  delete netns name="example"
$ ip netns
$

対応しているデバイスタイプ

使用例ではethernetsのみを紹介したが、netnsplanでは現在以下のタイプに対応している。

  • dummyデバイス(dummy-devices)
  • vethデバイス(veth-devices)

他のデバイスタイプにも対応したいので乞うご期待…

インストール方法

netnsplanは今のところamd64/arm64なLinux向けのバイナリを提供している。 netnsはLinuxでしか使えないので、他OSでのサポートはしていない。 また、debやrpmも用意しているのでDebian系やRedHat系のディストリであれば簡単にインストールできるはず。

これらのパッケージが簡単に作れるgoreleaserが便利すぎる…!!!!!!

Tips1: /etc配下のファイルをnetnsごとに切り替える

/etc/netns/<netns名> の配下にファイルを配置すると、ip netns execしたときに/etc配下のファイルを読み替えてくれる。 例えば、 /etc/netns/sample/hostsにファイルを置くとip netns exec cat /etc/hostsしたときにそのファイルに置き換えられる。

$ cat /etc/hosts
127.0.0.1       localhost
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

127.0.1.1       host-machine

$ cat /etc/netns/example/hosts
127.0.1.1 example-node
$ sudo ip netns exec example cat /etc/hosts
127.0.1.1 example-node

なお、この機能はip netns execしたときのみに有効になるので、例えばsystemdから起動した場合は読み替えがおこなれないので注意が必要だ(一敗)。

Tips2: systemd経由で起動する

netns上で特定のサービスを起動したいとなったときに、そのサービスが起動する前にnetnsplanを実行しないとならない。 その場合、netnsplanもsystemdから起動し、対象のサービスをnetns上で実行するように変更する必要がある。 その方法について解説する。

まずnetnsplanをsystemdのサービス化する。 以下のファイルを /etc/systemd/system/netnsplan.serviceとして保存する。

[Unit]
Description = Configure netns network
After = network.target

[Service]
Type = oneshot
ExecStart = /usr/sbin/netnsplan apply
ExecReload = /usr/sbin/netnsplan apply
ExecStop = /usr/sbin/netnsplan destroy
RemainAfterExit = yes

[Install]
WantedBy = multi-user.target

保存したらnetnsplanを起動する。

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now netnsplan

つぎにnetnsで動かしたいサービスの設定を変更する ここでは対象のサービス名をexample.servicceとする。

$ sudo systemctl edit example.service
# 以下の内容を書き込む
[Unit]
After =
After = netnsplan.service

[Service]
NetworkNamespacePath=/run/netns/vessel

Unit > Afterにnetnsplan.servieを設定することで、netnsplan起動後にexample.serviceを起動するように指定している。 また、Service > NetworkNamespacePathで使用するnetnsを指定することができる。 設定を変更したら、example.serviceを再起動するとnetns配下で起動するようになるはず。

$ sudo systemctl restart example.service

(この内容をREADMEに書き忘れていたのであとで追記しよう)

Tips3: netns配下の物理デバイス(ethernets)がdestroy後に消失する

apply/destroyを繰り返していると、稀に物理デバイス(ethernets)で設定したデバイスが消失することがある…。 これについてip-netnsのmanには以下の記載がある。

It is possible to lose the physical device when it was moved to netns and then this netns was deleted with a running process: 物理デバイスを失う可能性があります。それがnetnsに移動され、その後、実行中のプロセスでこのnetnsが削除された場合:

$ ip netns add net0 $ ip link set dev eth0 netns net0 $ ip netns exec net0 SOME_PROCESS_IN_BACKGROUND $ ip netns del net0

and eth0 will appear in the default netns only after SOME_PROCESS_IN_BACKGROUND will exit or will be killed. To prevent this the processes running in net0 should be killed before deleting the netns: そして、SOME_PROCESS_IN_BACKGROUNDが終了またはキルされた後にのみ、eth0はデフォルトのnetnsに表示されます。これを防ぐために、netnsを削除する前にnet0で実行中のプロセスをキルする必要があります:

netnsplan destroyではこの問題を回避するためにnetns上で動いているプロセスが存在した場合エラーになるようになっている。

$ sudo ./netnsplan destroy
INFO  delete netns name="example"
Error: netns example has running processes: 519572

しかし、プロセスをすべて終了してdestroyをしたが、一度だけ消失が発生したことがある…なんで… 消失が発生したらOSを再起動するしかない、つらい。 この問題の解決の方法を知っている人がいたら教えてください…