OVN-Kubernetes のノード再起動後にネットワーク断になる
Table of Contents
1. はじめに
みなさん、自宅 Kubernetes クラスタ運用してますか?
今回は、自宅サーバーで動かしている OVN-Kubernetes(Helm デプロイ)の環境で、ノードを再起動すると外部疎通が完全に断になるというなかなか厄介な問題に遭遇したので、原因の深掘りと解決までの記録をお話しします。
OVN-Kubernetes のセットアップについてはこちらの記事で紹介しているので、興味があればぜひ読んでみてください。
2. OVN-Kubernetes のブリッジ構成
NicToBridge の仕組み
OVN-Kubernetes は、ノードの物理 NIC(例: eth0)を OVS ブリッジ(breth0)に組み込んで使います。
この処理を行うのが NicToBridge() 関数です。
やっていることをざっくりまとめると:
- OVS ブリッジ
breth0を作成 eth0をbreth0のポートとして追加eth0にother-config:transient=trueを設定eth0の IP アドレスとルートをbreth0に移行
// go-controller/pkg/util/nicstobridge.go (NicToBridge)
stdout, stderr, err := RunOVSVsctl(
"--", "--may-exist", "add-br", bridge,
"--", "br-set-external-id", bridge, "bridge-id", bridge,
"--", "br-set-external-id", bridge, "bridge-uplink", iface,
"--", "set", "bridge", bridge, "fail-mode=standalone",
fmt.Sprintf("other_config:hwaddr=%s", ifaceLink.Attrs().HardwareAddr),
"--", "--may-exist", "add-port", bridge, iface,
"--", "set", "port", iface, "other-config:transient=true")
ここでポイントになるのが other-config:transient=true です。
これは「このポートは一時的なものなので、OVS 再起動時に削除してよい」というマーカーです。
正常な再起動フロー
正常に機能する場合、以下のような流れでノード再起動後もネットワークが復旧します。
sequenceDiagram
participant OVS as ovsdb-server
participant NIC as eth0
participant BR as breth0
participant OVN as ovnkube-node
Note over OVS: ノード再起動
OVS->>OVS: --delete-transient-ports で起動
OVS->>NIC: transient ポート(eth0)を削除
Note over NIC: eth0 がブリッジから外れる
NIC->>NIC: systemd-networkd が IP を設定
Note over NIC: eth0 経由で外部疎通が復旧
OVN->>OVN: API server に到達可能
OVN->>BR: NicToBridge() で eth0 を再接続
Note over BR: breth0 に IP を移行して通常運用に復帰
3. 発生した問題
さて、ここからが本題です。 実際にノードを再起動すると、外部疎通が完全に断になってしまいました。
ネットワーク初期化のデッドロック
もし --delete-transient-ports が OVS の起動時に指定されていない場合、transient ポートは削除されずにそのまま残ります。
OVS のデータベースは永続化されているため、再起動後も eth0 が breth0 の transient ポートとして残ったままになります。
一方、IP アドレスは systemd-networkd によって eth0 に設定されますが、eth0 はブリッジのポートなのでパケットは全て breth0 に吸い込まれてしまいます。
しかし breth0 には IP が設定されていないため、外部からの通信は一切できません。
ここで ovnkube-node が NicToBridge() を実行して IP を breth0 に移行すればいいのですが、
ovnkube-node が起動するには API server への到達が必要です。でもネットワークが壊れているので API server に到達できない…
sequenceDiagram
participant OVS as ovsdb-server
participant NIC as eth0
participant BR as breth0
participant OVN as ovnkube-node
participant API as API server
Note over OVS: ノード再起動
OVS->>OVS: transient ポートを削除しない!
Note over NIC: eth0 は breth0 のポートのまま
NIC->>NIC: systemd-networkd が IP を設定
Note over NIC: IP は eth0 にあるが<br/>パケットは breth0 に吸い込まれる
Note over BR: breth0 に IP がない → 外部疎通断
OVN->>API: API server に接続しようとする
API--xOVN: ネットワーク断で到達不可
Note over OVN: NicToBridge() を実行できない
Note over OVS,API: デッドロック!
このデッドロック自体は 2018 年の issue #274 で報告され、
/etc/default に --delete-transient-ports を書き込む仕組み(setupDefaultFile())で対処されていました。
しかし Helm デプロイ環境ではこの仕組みが機能しておらず、
issue #4731 で改めて報告され、2025 年 10 月にようやく修正されていたようです。
--delete-transient-ports が機能していなかった理由
other-config:transient=true が設定されているポートの削除は、 OVS の del_transient_ports() という関数によって行われます。
# utilities/ovs-ctl.in
del_transient_ports () {
for port in `ovs-vsctl --bare -- --columns=name find port other_config:transient=true`; do
ovs_vsctl -- del-port "$port"
done
}
この関数は ovs-ctl start に --delete-transient-ports オプションを渡すと呼ばれるものです。
問題は、このオプションが OVS の起動時に渡されていなかったことです。
NicToBridge() の中では、transient の設定後に setupDefaultFile() を呼んでいます。
この関数は /etc/default/openvswitch-switch に --delete-transient-ports を書き込む設計です。
// go-controller/pkg/util/nicstobridge.go
func setupDefaultFile() {
// ...
if platform == ubuntu {
defaultFile = ubuntuDefaultFile
text = "OVS_CTL_OPTS=\"$OVS_CTL_OPTS --delete-transient-ports\""
} else if platform == rhel {
defaultFile = rhelDefaultFile
text = "OPTIONS=--delete-transient-ports"
}
// defaultFile に text を追記...
}
この仕組みは systemd 経由で OVS を起動するホストネイティブなデプロイでは正しく動きます。 しかし、コンテナ化デプロイ(Helm)では以下の理由で完全に機能しません:
setupDefaultFile()は ovnkube-node コンテナ内の/etc/defaultに書くだけで、ホストには届かない- OVS を起動するのは 別コンテナ(ovs-node)
ovnkube.sh(OVS 起動スクリプト)は/etc/defaultを source しない
つまり setupDefaultFile() はコンテナ化環境では動作していなかったのです。
4. 解決
v1.2.0 での修正
この問題は OVN-Kubernetes v1.2.0 のコミット
fba0fef66
(“ovs-node: Delete transient ports on startup”)で修正されました。
修正内容はシンプルで、ovnkube.sh の OVS 起動部分に --delete-transient-ports を直接ハードコードしています。
# dist/images/ovnkube.sh
# OVN-K marks NIC port as transient on startup when plugging it into ovs
# bridge. This is done so that on ovsdb-server resrtart, the NIC interface is
# detached from the bridge, which is necessary to restore connectivity
# through the NIC and make the node healthy. Marking the port as transient
# works only when we also start ovsdb-server with --delete-transient-ports.
#
# Note: once ovnkube is started, it will rewire the NIC port back into the
# bridge, and move IP configuration as necessary.
ovs_options="${ovs_options} --delete-transient-ports"
/usr/share/openvswitch/scripts/ovs-ctl start --no-ovs-vswitchd \
--system-id=random ${ovs_options} ${USER_ARGS} "$@"
これにより、OVS(ovsdb-server)の起動時に transient ポートが自動で削除され、eth0 がブリッジから外れることでネットワークが復旧するようになります。
v1.1.0 → v1.2.0 へアップグレードすることで、この問題は解決しました。
5. 手動復旧メモ
もしデッドロックが発生してしまった場合、コンソールなどからVMを操作することができれば以下のコマンドで応急処置できます。
# breth0 に IP を付与して eth0 から削除
ip addr add <ADDR> dev breth0
ip addr del <ADDR> dev eth0
# デフォルトルートを breth0 経由に設定
ip route add default via <GW> dev breth0
ip route delete default via <GW> dev eth0
これで外部疎通が復旧するので、あとは ovnkube-node が API server に到達して通常の処理を再開できます。
6. まとめ
今回の問題は、OVN-Kubernetes の --delete-transient-ports の仕組みがコンテナ化デプロイに対応していなかったことが原因でした。
デッドロック自体は 2018 年に対処されていたものの、コンテナ環境では見落とされていたというのが面白いところです。 原因を追っていく中で、OVS の機能や OVN-Kubernetes の内部実装への理解が深まりました。 特に OVS の transient はこういう時に必要になるのかーと身に沁みて実感できましたね。
最後まで読んでいただき、ありがとうございました。