さくらのクラウドで無理矢理MACVLANを生やしてみる話

さくらのクラウド上のサーバでMACVLANを用い、1つのスイッチに対して2本のNetwork interfaceを繋ぐ。

background

さくらのクラウドには「スイッチ」(ルータ+スイッチ)というファンクションが存在する。

文字通り仮想的なL2スイッチにサーバを接続して通信できるのだが、このスイッチへのトラフィックは サーバに割り当てられているMACアドレス or VRRP仮想MACアドレス(VRID 1-4)を送信元としたパケットしか許可されない という制約がある。

で、ごく稀に1台のサーバから1つのルータ+スイッチに対して複数の足を伸ばしたくなる時がある 。しかしさくらのクラウドでは、あるスイッチに対してサーバごとにNIC1つしか接続できない & 前述のMAC制限があるので、容易ではない。

例えばサーバ内のLXCコンテナから直接L2の足を出したいケースなど。こうした場合では、単なるセカンダリIPアドレスではなく、IF自体がもう一つ欲しいのだ。 といってもさくらのクラウドは(専用ホストを利用しない限り) Nested KVM が使えないので、こうしたケースは多くない。

しかしVRRP用の仮想MACアドレス(VMAC)は4つぶん許可されている。VMACだろうがなんだろうがMACアドレスはMACアドレスである。ということでこのVMACを流用して、ルータ+スイッチに2本目の足を伸ばしてみる。

method

PoCということで、ルータ+スイッチに接続されているNIC (eth0)に対し、MACVLANを用いて追加のIFを生やしてみる。

MACVLANを扱う方法は以下のリンク先が分かりやすい。

https://qiita.com/albatross/items/8c32615b5154acf712f2
macvlan の使い方と挙動のメモ @albatross

root:~# sysctl -w net.ipv4.conf.all.arp_ignore=1
root:~# sysctl -w net.ipv4.conf.all.rp_filter=0

      # MACVLAN インターフェイスとして vmac0 を追加
root:~# ip link add link eth0 name vmac0 type macvlan mode private
      # vmac0のMACアドレスに(本来はVRRP用の)仮想MACを設定
root:~# ip link set vmac0 address 00:00:5e:00:01:01
      # IPアドレスをアサイン
root:~# ip addr add dev vmac0 192.0.2.85/28
root:~# ip link set vmac0 up
root:~# ip addr
          :
2: eth0:  mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 9c:a3:ba:32:1e:3a brd ff:ff:ff:ff:ff:ff
    altname enp0s3
    altname ens3
    inet 192.0.2.84/28 brd 192.0.2.95 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::9ea3:baff:fe32:1e3a/64 scope link
       valid_lft forever preferred_lft forever
          :
7: vmac0@eth0:  mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:00:5e:00:01:01 brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.85/28 scope global vmac0
       valid_lft forever preferred_lft forever
    inet6 fe80::200:5eff:fe00:101/64 scope link
       valid_lft forever preferred_lft forever

#
# eth0 と vmac0 からデフォゲ宛にIPv4 Ping疎通テスト (OK)
#
root:~# ping -I eth0 192.0.2.1
PING 192.0.2.1 (192.0.2.1) from 192.0.2.84 eth0: 56(84) bytes of data.
64 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=1.10 ms
64 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.14 ms
^C
--- 192.0.2.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.098/1.120/1.142/0.022 ms

root:~# ping -I vmac0 192.0.2.1
PING 192.0.2.1 (192.0.2.1) from 192.0.2.85 vmac0: 56(84) bytes of data.
64 bytes from 192.0.2.1: icmp_seq=1 ttl=64 time=15.1 ms
64 bytes from 192.0.2.1: icmp_seq=2 ttl=64 time=1.11 ms
^C
--- 192.0.2.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.105/8.121/15.137/7.016 ms

#
# 外部への疎通テスト (OK)
#
root:~# ip route add 0.0.0.0/0 via 192.0.2.1 dev eth0 src 192.0.2.84
root:~# ip route add 0.0.0.0/0 via 192.0.2.1 dev vmac0 src 192.0.2.85 
RTNETLINK answers: File exists   # src違くても同じCIDRはダメらしい
root:~# ip route add 0.0.0.0/1 via 192.0.2.1 dev vmac0 src 192.0.2.85

root:~# curl --interface eth0 api.ipify.org
192.0.2.84
root:~# curl --interface vmac0 api.ipify.org
192.0.2.85

#
# eth0 と vmac0 からデフォゲ宛にIPv6 Ping疎通テスト (NG)
# なぜか vmac0 だけ通ってない、ちゃんと原因追ってないけど
#
root:~# ping6 fe80::1%vmac0
PING fe80::1%vmac0(fe80::1%vmac0) 56 data bytes
^C
--- fe80::1%vmac0 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2051ms

root:~# ping6 fe80::1%eth0
PING fe80::1%eth0(fe80::1%eth0) 56 data bytes
64 bytes from fe80::1%eth0: icmp_seq=1 ttl=64 time=1.32 ms
^C
--- fe80::1%eth0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.316/1.316/1.316/0.000 ms

#
# お互いのIFに上位スイッチ経由でのPingは通らない
# (※普通L2スイッチで同じポートに対するヘアピンはできないと思う)
#
root:~# ping -I vmac0 192.0.2.84
PING 192.0.2.84 (192.0.2.84) from 192.0.2.85 vmac0: 56(84) bytes of data.
^C
--- 192.0.2.84 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3053ms
pipe 3
root:~# ping -I eth0 192.0.2.85
PING 192.0.2.85 (192.0.2.85) from 192.0.2.84 eth0: 56(84) bytes of data.
^C
--- 192.0.2.85 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1001ms

root:~# ip neigh
192.0.2.85 dev eth0  FAILED
192.0.2.84 dev vmac0  FAILED
192.0.2.82 dev eth0 lladdr 40:de:ad:3b:51:3a STALE
192.0.2.1 dev eth0 lladdr 00:00:5e:00:01:ff REACHABLE
192.0.2.1 dev vmac0 lladdr 00:00:5e:00:01:ff STALE
fe80::42de:ad06:ca3b:513a dev eth0 lladdr 40:de:ad:3b:51:3a router STALE
fe80::1 dev eth0 lladdr 00:00:5e:00:02:ff router STALE
2001:e42:407:102b::2 dev eth0 lladdr 40:de:ad:3b:51:3a router STALE
fe80::1 dev vmac0  FAILED

Appendix

もしさくらのクラウドでDocker コンテナなどから直接スイッチに足を出したい場合、 L2でのリーチャビリティに拘らなければ ipvlan を使うのがより良い方法だと思う。
https://docs.docker.com/network/drivers/ipvlan/

$ docker network create -d ipvlan \
    --subnet=192.168.1.0/24 \
    --gateway=192.168.1.1 \
    -o ipvlan_mode=l2 \
    -o parent=eth0 hoge_net

$ docker run --net=hoge_net -it --rm alpine /bin/sh