はじめに
ふだん仮想マシンを使うとき、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できなかったら、もっとかかっていたかもしれません。