はじめに
ふだん仮想マシンを使うとき、MACアドレスについて気にすることはあまりないと思います。筆者もそうだったのですが、SONiCの仮想マシンを使っていてMACアドレスを気にせざるを得ない事象に遭遇してしまいました。原因と対応方法まで調べましたので共有したいと思います。
なお、物理マシン向けSONiCには上記のような事象はありませんので念のため。
きっかけ
SONiCはホワイトボックススイッチ用のOSで、仮想マシン版としてSONiC-VS (Virtual Switch)というものがあります。ホワイトボックススイッチはネットワーク機器なので単体で動かす意味はなく、仮想マシンであってもそれは同様です。
そんなわけで、最小構成としてSONiC-VSが動作する仮想マシンを2台用意して、仮想マシンの(2つめの)NIC同士を接続します。
トポロジーは下記のようになります。s0
とs1
がVMです。default
はホストマシンとつながるネットワークになります(DHCPでアドレスが降ってきます)。
(default) (default)
eth0| |eth0
---+--- ---+---
| s0 | | s1 |
---+--- ---+---
|10.0.0.0/31 10.0.0.1/31|
|Ethernet0 Ethernet0|
---------------c1---------------
Ethernet0
には手でIPアドレスを割り当てます。
$ sudo config interface ip add Ethernet0 10.0.0.0/31
$ sudo config interface ip add Ethernet0 10.0.0.1/31
インタフェースもupしてるし、ping
くらい大丈夫だろう、と送ってみると……応答が返ってきません。tcpdump
で観測すると、arp
を受けた相手がreply
を送ってるのが見えました。それなのにping
がとおらない? そんなことあるんだ……
状況確認
なぜping
が通らないのか
あちこち調べたのですが、結論としてはs1
のEthernet0
のMACアドレスと、s2
のEthernet0
のMACアドレスが同一だったため通信できませんでした。具体的には、どちらも52:54:00:12:34:56
でした。
なぜMACアドレスが同一なのか
まず、QEMU/KVMで起動したVMでは、なにも指定がなければNICにつけられるMACアドレスが**52:54:00:12:34:56
**固定になります。このこと自体は有名なようで、このMACアドレスで検索するとこの動作について言及しているWebページが山のように出てきます。
SONiCの設定ファイルである/etc/sonic/config_db.json
には"mac"
という設定項目があり、起動したSONiC-VSの中を覗き込むと、2つのVMでどちらも52:54:00:12:34:56
となっているのがわかります。ip link
で確認できる、フロントパネルポートに割り当たっているMACアドレスもこの値です。
{
"DEVICE_METADATA": {
"localhost": {
"hwsku": "Force10-S6000",
"platform": "x86_64-kvm_x86_64-r0",
"mac": "52:54:00:12:34:56",
"hostname": "sonic",
"type": "LeafRouter",
"bgp_asn": "65100"
}
/etc/sonic/config_db.json
の"mac"
の値を書き換えて再起動すると、ip link
で見えるMACアドレスが変更された状態となりました。ping
も通りましたので、この設定項目が直接原因で間違いないでしょう。
config_db.json
はSONiCの初回起動時に生成されるので、起動時にどこかに付与されているMACアドレスを拾ってるんだろうなあ、という想像はつきます。なんとなくeth0
なんだろうなあと思いつつ、追跡して間違いないか確認し、VM起動時に別の値を与えることで症状が改善するか確認してみます。
調査: 初期起動時のconfig_db.json
生成の流れ
"mac"
の値を取得するまでの流れは下記でした。
-
/etc/rc.local
起動 -
firsttime
ファイルがあればtouch /tmp/pending_config_initialization
-
config-setup.service
開始 -
ExecStart=
で指定されているconfig-setup boot
が起動 -
boot_config()
で/tmp/pending_config_initialization
の有無がチェックされる - 結果、
do_config_initialization()
が呼ばれる(戻ってきたら/tmp/...
やfirsttime
ファイルを削除) -
generate_config factory
が呼ばれる -
sonic-cfggen
コマンドが呼ばれる -
device_info.get_system_mac()
でMACアドレスを得ていた -
cat /sys/class/net/eth0/address
がMACアドレス!
つまり**eth0
のMACアドレスが52:54:00:12:34:56
であることが原因**で間違いないようです。
対策: MACアドレスを指定してみた
MACアドレスはVM起動時に-macaddr=
で指定できますので、指定してみることにします。もちろん初回起動時じゃないとconfig_db.json
は生成されませんので、新しいイメージを用意して。(terraform-provider-libvirtを使うときはnetwork_interface
ブロックにmac=
を追加すれば指定できます)
起動したのでconfig_db.json
を見てみます。
{
"DEVICE_METADATA": {
"localhost": {
"hwsku": "Force10-S6000",
"platform": "x86_64-kvm_x86_64-r0",
"mac": "52:54:00:12:34:56",
"hostname": "sonic",
"type": "LeafRouter",
"bgp_asn": "65100"
}
……あれ?
指定を間違えたのかと思ってcat /sys/class/net/eth0/address
してみます。
admin@sonic:~$ cat /sys/class/net/eth0/address
52:54:00:12:00:00
指定した値になっているのに、config_db.json
の"mac"
は52:54:00:12:34:56
のままです。これはいったいどういうこと?
調査: なぜ指定が無視されるのか
当初は、外部で指定していてもいったん52:54:00:12:34:56
でeth0
が生えて、その後どこかのタイミングで値が変わるのか? と思い、sonic-cfggen
に手を入れて/sys/class/net/eth0/address
の中身を呼ばれるたび吐き出すようにしてみたのですが、ここにはまったく52:64:00:12:34:56
の痕跡なし。eth0
は最初から指定した値になっていました。
じゃあもしかして自分の知らないルートでconfig_db.json
が生成されている? と思い、あちこち調べてみると、結果として下記のことがわかりました。
-
/etc/rc.local
が呼ばれたときにはすでにconfig_db.json
は存在している - ビルドした
sonic-vs.img
の中にはすでに**firsttime
ファイルがない**
firsttime
ファイルがない? へ? そんなことってあるの?
firsttime
ファイルはSONiC起動時に参照され、初期化が必要かの目印として使われます。初期化が終わると削除されるため、ふつうに使っていればその存在を意識することはありません。が、config_db.json
の生成は初期化処理です。これは追跡する必要がありそうです。
firsttime
ファイルはいつ消える?
ベースとなるファイルシステムの中身はbuild_debian.sh
によって作られます。中を見るとfirsttime
ファイルは無条件に作成されているように見えます。
mkdir -p $FILESYSTEM_ROOT
mkdir -p $FILESYSTEM_ROOT/$PLATFORM_DIR
mkdir -p $FILESYSTEM_ROOT/$PLATFORM_DIR/x86_64-grub
touch $FILESYSTEM_ROOT/$PLATFORM_DIR/firsttime
しかし、下記手順でqcow2イメージをmount
しsonic-vs.img
の中身を起動前に調べてみると、たしかにありませんでした。
$ sudo modprobe nbd max_part=63
$ sudo qemu-nbd -c /dev/nbd0 sonic-vs.img
$ sudo mount /dev/nbd0p3 /mnt
$ ls /mnt/image-*/platform
x86_64-grub
$ sudo umount /mnt
$ sudo qemu-nbd -d /dev/nbd0
だとすると、どこかで誰かが消している? しかしソースコードのどこを見てもビルド時にfirsttime
ファイルを削除するという記述はありません。
ん? まさか、ビルド時に一度起動している? ということは、まさか……ビルドしたsonic-vs.img
の中にはすでに**config_db.json
がある? ……あった!!** orz
調査: どこで起動している?
ここまで簡単にまとめます。
- SONiC-VSの仮想マシンイメージは、どうやらビルド中に一度起動している模様
- そのためビルド済みの仮想マシンイメージにはすでに
config_db.json
が存在する - 初期化処理を示す
firsttime
ファイルもビルド中の起動により削除されている
もしビルド中に起動しているのであれば、そのときのコマンドラインに-macaddr
指定などありませんので、当然eth0
のMACアドレスは52:54:00:12:34:56
になります。これっぽいなー。ということで、どこで起動しているのか、そもそもなぜ起動しているのか、ビルド中の起動は本当に必要なのかを調べます。
仮想マシンイメージ作成フロー
ビルドしたばかりのイメージですでに起動済みということは、仮想マシンイメージを作成する手順のどこかで起動しているはず。調べてみると、scripts/build_kvm_imagee.sh
の中で起動しているっぽいのがわかりました。
/usr/bin/kvm -m $MEM \
-name "onie" \
-boot "order=cd,once=d" -cdrom "$ONIE_RECOVERY_ISO" \
-device e1000,netdev=onienet \
-netdev user,id=onienet,hostfwd=:0.0.0.0:3041-:22 \
-vnc 0.0.0.0:0 \
-vga std \
-drive file=$DISK,media=disk,if=virtio,index=0 \
-drive file=$INSTALLER_DISK,if=virtio,index=1 \
-serial telnet:127.0.0.1:$KVM_PORT,server > $kvm_log 2>&1 &
これはなんのためにやっているのだろう? 必要なの? と思ったのですが、これ自体は必要でした。具体的には下記のようなフローでSONiC-VSの仮想マシンイメージを作成していました。よって、ここを削ると正しくイメージを作れないということになります。
-
qemu-img create
で空のディスクイメージファイルを作る - FATのディスクイメージにSONiCのインストーラーを書き込む
- 2つのディスクイメージとONIEリカバリーISOをつけた状態でKVM起動
check_install.py
によりコンソール制御開始-
GRUB
メニューが出たらEnterを送ってONIE(リカバリモード)起動 - リカバリモードの自動処理によってSONiCをインストール
フローがここまでなら問題ないのですが、そのあとcheck_install.py
は下記も実行していました。
- 自動リブートのあとSONiCが起動するまで待つ
- SONiCにログインする
- インストールできているかコマンド実行で確認(プロンプトが返ってこないとビルドエラー扱い)
ここか!!
本命の対策
考えられる対策は2つあります。
- ビルド時にSONiCの起動をやめる。ONIEによるインストールだけにする
-
check_isntall.py
の中で、firsttime
とconfig_db.json
を起動前の状態に戻す
前者はブートメディアの指定(order=cd
をorder=d
に変更)でおそらくなんとかなります。
rc.local
内の処理をざっと見た限り、初期起動シーケンスがもう一度動いても問題ないように見えましたので、今回は後者でやってみます。
ビルド時に生成したconfig_db.json
の無効化
config_db.json
を削除して、firsttime
ファイルを用意します。
diff --git a/check_install.py b/check_install.py
index 08759db12..86950e73b 100755
--- a/check_install.py
+++ b/check_install.py
@@ -62,6 +62,10 @@ def main():
p.expect([cmd_prompt])
p.sendline('show ip bgp sum')
p.expect([cmd_prompt])
+ p.sendline('sudo rm /etc/sonic/config_db.json')
+ p.expect([cmd_prompt])
+ p.sendline('cd /host/image-*/platform; sudo touch firsttime; cd -')
+ p.expect([cmd_prompt])
p.sendline('sync')
p.expect([cmd_prompt])
これで大丈夫なはずです。
対策の確認
SONiC-VSをビルドして(わりと時間がかかります)、できあがったsonic-vs.img
を使ってVMを起動してみます。もちろんMACアドレス指定を忘れずに。
そしてconfig_db.json
を確認。
{
"DEVICE_METADATA": {
"localhost": {
"hwsku": "Force10-S6000",
"platform": "x86_64-kvm_x86_64-r0",
"mac": "52:54:00:12:00:00",
"hostname": "sonic",
"type": "LeafRouter",
"bgp_asn": "65100"
ping
も無事通りました。めでたしめでたし。
おわりに
わかってみればなんてことはないのですが、調べるのに半日以上かかってしまいました。qcow2
の中身をmount
できなかったら、もっとかかっていたかもしれません。