結論
こちら。
そもそもの始め
VirtualBoxでクラスタのテスト環境を作りたかった。
あくまでテスト環境で、いずれ実マシンに移行することを想定し、ネットワークアダプターも実マシンに装備するものに限定したい。
つまり、テスト環境のノードにはVirtualBoxの定番である「通信用NAT+接続用HostOnly」を追加したくない。
そこで、以下の構成にした。
ノードVM-AとノードVM-Bが、実マシン相当のネットワークアダプタを持つテスト機。
ルーターVMで通信を内部ネットワークに中継する。
ホストPCから各ノードにアクセスするには、ルーターを踏み台サーバーとして2段階でログインする。
しかし、それもまた面倒。というかブラウザでhttpアクセスとかできない。
そこで、ホストPC上でルーティングテーブルを設定する。
Windowsならこう。
route -p add 192.168.5.0 mask 255.255.255.0 192.168.56.10
これでとりあえずうまくいく。
ホストPCからノードAに直接sshもできるし、ブラウザでhttpアクセスもできる。
ただ、そもそもの話WindowsだとネステッドVT-xが使えない。
なのでクラスタ上での仮想マシンが激遅、というかえげつなくリソース喰う。
ということで、Ubuntuにそのまま持ってくことにした。
Ubuntuでの静的ルーティング
もちろんUbuntu、というかLinuxでも静的ルーティングは可能。
このコマンドを使う。
ip route add 192.168.5.0/24 via 192.168.56.10
ただ、これだとあくまで一時的な設定追加で、再起動すると消える。
Windowsみたいに -p オプション付ければOKというわけでもなく、別のところで設定してあげる必要がある。
NetPlan
Ubuntuで静的ルーティングをするならNetPlanの設定が基本っぽい。
network:
version: 2
ethernets:
eth0: # インターフェース名に合わせて変更
...
routes:
- to: 192.168.5.0/24
via: 192.168.56.10
この「インターフェース名に合わせて変更」のところ、どうすんだという話。
ルート設定はデバイスごとに行うっぽい。
192.168.56.10 につながってるのは VirtualBox のホストオンリーネットワークで、デバイス名は vboxnet0。
それは動的に追加されるやつで、netplanには設定が載っていない。
詰んだ /(^o^)\
NetworkManager
この辺で俺のググラビリティの限界を迎え(早!?)、以降 Gemini さんと相談しながら二人三脚で進める。
Gemini:
NetworkManager ディスパッチャスクリプト(推奨)
インターフェースが「up」になった瞬間にルートを追加するスクリプトを作成します。これが最も確実で、デバイスの動的な出現に対応できます。
Ubuntu Desktop では、netplanのバックエンドとして NetworkManager が利用されているのだが、そのdispatcher を利用する方法を教えてくれた。
早速やってみる。
ダメでした。
そもそもディスパッチャスクリプトが呼ばれてなさげ。
俺:
/etc/NetworkManager/dispatcher.d/99-vboxnet-routes の中で
echo "INTERFACE=\$INTERFACE" >> /home/myname/nm.txt
echo "ACTION=\$ACTION" >>/home/myname/nm.txt
としてみたのですが、もともとついているネットワークアダプタについては出力されますが、vboxnet0 については出力されませんでした。
動的に追加されたアダプタについてスクリプトが呼ばれていないように思われます。
Gemini:
詳細な検証結果をありがとうございます。非常に鋭いご指摘です。
原因は、VirtualBox のホストオンリーアダプタ(vboxnet0)が NetworkManager の管理対象外(unmanaged)になっていることにあります。
褒められたw
けど「まあ知ってたけどね」感がちょっとイラっとするww
確かに、nmcli device コマンドで unmanaged と表示される。
NetworkManagerのディスパッチャは管理対象のインターフェイスに対してのみ実行され、知らんデバイスが勝手に生えても関知しないらしい。
もちろんGeminiさんは解決策も教えてくれる。
解決策:udev ルールを使用する
NetworkManager に頼らず、Linux カーネルが「vboxnet0 というデバイスが作成された」ことを検知した瞬間にスクリプトを動かす udev(ユーデブ) を使うのが最も確実です。
確実なんだね!やったぜ!
やってみる。
cat <<'EOF' >/etc/udev/rules.d/99-vboxnet.rules
KERNEL=="vboxnet0", ACTION=="add", RUN+="/usr/local/bin/vboxnet-route-setup.sh vboxnet0"
EOF
cat <<'EOF' >/usr/local/bin/vboxnet-route-setup.sh
#!/bin/bash
# vboxnet0 が作成されたときにルートを追加する
if [ "$1" = "vboxnet0" ]; then
# 少し待機(インターフェースが完全に初期化されるのを待つ)
sleep 2
# ここに設定したいルートを記述
/usr/sbin/ip route add 192.168.100.0/24 dev vboxnet0 || true
fi
EOF
chmod +x /usr/local/bin/vboxnet-route-setup.sh
udevadm control --reload-rules
ダメでした。
俺:
udev の方法では、vboxnet0 が作成されたときに ip route add を実行しますが、この時点ではインターフェイスが DOWN しているためルートの追加が失敗します。
このインターフェイスを使用するVMが起動下地店でインターフェイスがUPするようですが、このタイミングとデバイスが作成されるタイミンは異なるため、UPされたタイミングでip route add を実行する方法が必要です。
Gemini:
ご指摘の通り、vboxnet0 は作成(add)された直後はまだ DOWN 状態にあり、VirtualBox の仮想マシン(VM)が起動してそのインターフェースがアクティブになった瞬間に初めて UP 状態になります。
add イベントのタイミングで ip route を実行しても失敗するため、インターフェースの状態変化(UP/DOWN)を検知して実行する 仕組みにする必要があります。
これを解決するには、udev の ACTION=="change" イベントを利用し、スクリプト内で状態を確認する方法が最もスマートです。
「起動下地店」を華麗にスルーするGeminiさんの優しさが染みる。俺ならツッコんじゃう。
ともあれスマートな方法があるんだね!やったぜ!
やってみる。
cat <<'EOF' >/etc/udev/rules.d/99-vboxnet.rules
KERNEL=="vboxnet0", ACTION=="add|change", RUN+="/usr/local/bin/vboxnet-route-setup.sh"
EOF
cat <<'EOF' >/usr/local/bin/vboxnet-route-setup.sh
#!/bin/bash
# インターフェース名(udevから渡されない場合を考慮して固定 or 引数)
TARGET_IF="vboxnet0"
# 設定したいルート
TARGET_ROUTE="192.168.100.0/24"
# インターフェースの状態を確認 (UP かどうか)
# operstate が 'up' または 'unknown' (VBoxの場合はunknownでも通信可能な場合がある) をチェック
STATE=$(cat /sys/class/net/$TARGET_IF/operstate 2>/dev/null)
if [ "$STATE" = "up" ] || [ "$STATE" = "unknown" ]; then
# すでにルートがあるか確認し、なければ追加
if ! /usr/sbin/ip route show dev $TARGET_IF | grep -q "$TARGET_ROUTE"; then
/usr/sbin/ip route add $TARGET_ROUTE dev $TARGET_IF
echo "$(date): Added route to $TARGET_IF because it is $STATE" >> /tmp/vboxnet_udev.log
fi
fi
EOF
udevadm control --reload-rules
ダメでした。
俺:
ACTION=="add|change" に変更して sudo udevadm control --reload-rules を実行しましたが、STATE=down の状態で一度呼ばれるだけで、UPになったタイミングでは呼ばれません。
Gemini:
VirtualBox の vboxnet0 は、カーネルの標準的なネットワークスタックを介さず VirtualBox 自身のドライバ(vboxnetadp)で制御されているため、通常のインターフェースのように「リンクが Up した」という change イベントが udev まで綺麗に通知されないことが多々あります。
この「デバイスは存在するが、通信可能(Up)になるまでラグがある」という状況を確実に解決するには、udev からスクリプトを呼び出し、そのスクリプト内で Up になるまでループ待機(ポーリング)させる手法が最も確実です。
多々あるんかーい。
何度ダメでも毎回自信満々でおすすめしてくる鋼マインドがイラっとするようなうらやましいような。
しかし、この方法は試してない。
sleep 1 を挟みながら最大60回 /sys/class/net/$TARGET_IF/operstate をチェックするロジックを提案されたんだけど、デバイスが作成されるのは VirtualBox Manager を立ち上げたときで、vboxnet0 がUPになるのは最初の仮想マシンを立ち上げたとき。
この間を60秒以内でやらなきゃいけない、という制約は設けたくない。
あと、ポーリングというけど、/sys/class/net/$TARGET_IF/operstate ファイルの変更イベント監視でいいのでは?と思って inotifywait を試してみた。
sudo apt install inotify-tools
inotifywait /sys/class/net/vboxnet0/operstate
でもダメ。イベントこねー。
まあうっすら思ってた。/runとか/sysとかの実ファイルじゃない奴は無理目かなーと。
それでは何か vboxnet0 の状態変化に反応してるやつはいないのかと探してみると、 dmesg がいた (゚∀゚)
dmesg -wk | grep 'vboxnet0:'
ip link set vboxnet0 up/down に反応して1行ずつ出力してくれる。
ではこれを通常ファイルに出力して、inotifywait で監視すればいいのでは!
$ LANG=C sudo dmesg -wk --time-format=iso | grep 'vboxnet0:'
2025-12-29T19:43:22,434455+09:00 vboxnet0: entered promiscuous mode
2025-12-29T19:44:12,584256+09:00 vboxnet0: left promiscuous mode
別ターミナルで sudo ip link set vboxnet0 up/down やったらちゃんと取れた (゚∀゚)
なので、これをファイルに出力して監視してみる。
# dmesg -wk | grep 'vboxnet0:' --line-buffered >/var/local/log/test1.log
$ inotifywait /var/local/log/test1.log -m -e modify
Setting up watches.
Watches established.
で、別端末でリンクをup/downすると、、、
$ sudo ip link set vboxnet0 up
$ sudo ip link set vboxnet0 down
端末2で出力出た!
$ inotifywait /var/local/log/test1.log -m -e modify
Setting up watches.
Watches established.
/var/local/log/test1.log MODIFY
/var/local/log/test1.log MODIFY
これをバックグラウンドで常駐させればよさそう。
最終稿
Geminiさん提案のポーリング処理はudevルールスクリプトからバックグラウンドで呼び出していたが、自分のやり方では60秒限定でなくずっと監視しててほしいので、常駐タスクとしてサービス化する。
cat <<'EOF' >/usr/local/bin/vboxnet-watch.sh
#!/bin/sh
TARGET_IF=vboxnet0
LOG_FILE=/var/local/log/vboxnet-watch/$TARGET_IF.log
mkdir -p "$(dirname "$LOG_FILE")"
if [ -e $LOG_FILE ]; then
rm $LOG_FILE
fi
/usr/bin/dmesg -Wk --time-format=iso | /usr/bin/grep "$TARGET_IF:" --line-buffered | tee -a $LOG_FILE
EOF
chmod +x /usr/local/bin/vboxnet-watch.sh
cat <<'EOF' >/usr/local/bin/vboxnet-route-setup.sh
#!/bin/sh
TARGET_IF=vboxnet0
TARGET_ROUTE=192.168.5.0/24
TARGET_GATEWAY=192.168.56.10
WATCH_FILE=/var/local/log/vboxnet-watch/$TARGET_IF.log
RETRY_MAX=10
add_route() {
local COMMAND="/usr/sbin/ip route add $TARGET_ROUTE via $TARGET_GATEWAY dev $TARGET_IF"
local i=1
echo "$COMMAND"
$COMMAND
local ret=$?
while [ "$ret" != 0 ] && [ $i -lt $RETRY_MAX ]; do
echo "exit code: $ret"
local i=$(expr "$i" + 1)
sleep 1
echo "$i: $COMMAND"
$COMMAND
local ret=$?
done
if [ "$ret" != 0 ]; then
echo "exit code: $ret"
fi
}
del_route() {
local COMMAND="/usr/sbin/ip route del $TARGET_ROUTE"
echo "$COMMAND"
$COMMAND
}
has_route() {
/usr/sbin/ip route show dev $TARGET_IF | grep -q "$TARGET_ROUTE"
}
while true; do
STATE=$(cat /sys/class/net/$TARGET_IF/operstate 2>/dev/null)
echo "STATE=$STATE"
if [ "$STATE" = "up" ] || [ "$STATE" = "unknown" ]; then
if ! has_route; then
add_route
fi
elif [ "$STATE" = "down" ]; then
if has_route; then
del_route
fi
fi
# vboxnet0.logが変化するまで待機
inotifywait -e modify $WATCH_FILE
sleep 0.1
done
EOF
chmod +x /usr/local/bin/vboxnet-route-setup.sh
cat <<'EOF' >/etc/systemd/system/vboxnet-watch.service
[Unit]
Description=Watch vboxnet0 network device
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/vboxnet-watch.sh
Restart=always
[Install]
WantedBy=multi-user.target
EOF
cat <<'EOF' >/etc/systemd/system/vboxnet-route-setup.service
[Unit]
Description=Control custom route for vboxnet0 network device
After=vboxnet-watch.service
[Service]
Type=simple
ExecStart=/usr/local/bin/vboxnet-route-setup.sh
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable vboxnet-watch.service
systemctl enable vboxnet-route-setup.service
これで、いい感じに vboxnet0 のUPと同時に希望のルートが追加されるようになった。
DOWNしたらルートも消える。
でもログを見たら、DOWNした時点で勝手にルートも消えていて、このスクリプトの DOWNならdel_route ルートは空振りしてそう。
ともあれ、ようやく希望の動作が達成できた。
にしても、だ。
Windowsなら route -p add 192.168.5.0 mask 255.255.255.0 192.168.56.10 で済むところ、Linuxアホほど大変すぎん?
udevとかNetworkManagerとかそれぞれ別に作られたソフトウェアをがっちゃんした「ディストリビューション」ってこういうとこめんどくせーってとても思ったわ。
システムの奥まで部品単位で差し替えられる自由度の裏表だと思うけど。
あと今回、Geminiさんのこともちょっとわかった。
以前見た記事で、いくつかのAIで音声認識させて自信度を出させてみたらGeminiがいつも自信満々でハルシネーション怖いっての見たけど、こういうとこねと納得したw