- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- proxyがなぜ必要か
- ソースとターゲットでそれぞれがやること
- ポートはどう使われるか
- ターゲットリスナーがランダムポートを使えるのはなぜか
- ソケットファイルがなぜ必要か
- TLSはどこに付くか
- マイグレーションネットワークをなぜ別にできるか
- ブロックマイグレーションとダイレクトマイグレーションがなぜ異なるか
- 運用者がproxyを見るべき瞬間
- よくある誤解
- まとめ
はじめに
ライブマイグレーションを考えると「ソースQEMUがターゲットQEMUにメモリをコピーする」で終わりがちである。しかし実際のKubernetes環境ではその間にはるかに複雑なtransportの問題がある。
- launcher内部のlibvirtは主にUnixソケットとローカルリソースを使う
- target Podは動的に生成される
- ソースとターゲットは異なるノードにある
- 追加のTLSレイヤーが必要
- ブロックマイグレーション時はポート要件が異なる
この問題を解決するためにKubeVirtはpkg/virt-handler/migration-proxy/migration-proxy.goのmigration proxyレイヤーを設けている。
proxyがなぜ必要か
核心的な理由はローカルlibvirt制御ソケットとクラスタネットワーク経路を直接1:1で結びつけにくいからである。
launcher内部ではlibvirtとQEMUがUnixソケット、ローカルファイル、Pod namespaceベースで動作する。しかしマイグレーションにはソースノードとターゲットノード間のネットワーク接続が必要である。そのためKubeVirtは間にproxyを置いて:
- ターゲット側ではTCPリスナーを開き
- ソース側ではローカルUnixソケットを提供し
- 両側をTLSまたはプレーンtransportで接続
する方式でtransportを整理する。
つまりmigration proxyはソースとターゲット間のアドレス翻訳器兼transport shimである。
ソースとターゲットでそれぞれがやること
ProxyManagerインターフェースを見ると役割が明確である。
ターゲット側
StartTargetListenerGetTargetListenerPortsStopTargetListener
つまりターゲットノードは外部から入るTCP接続を受け取るリスナーを開く。
ソース側
StartSourceListenerGetSourceListenerFilesStopSourceListener
つまりソースノードはローカルUnixソケットファイルを作り、それをターゲットアドレスとポートにフォワーディングする。
この構造のおかげでlauncher側からは馴染みのあるローカルエンドポイントに見えるが、実際のtransportはネットワークを経由してターゲットまで到達する。
ポートはどう使われるか
コードはデフォルトのマイグレーションポートを次のように定義している。
- ダイレクトマイグレーションポート:
49152 - ブロックマイグレーションポート:
49153
GetMigrationPortsListはブロックマイグレーションの有無に応じて必要なポートセットを決定する。つまりディスクを追加で移す必要があればポート要件が増える。
運用者がここで知るべき核心は、ライブマイグレーションが「見えない内部通信」ではなく、明確なポートとリスナーを持つネットワーク作業だということである。
ターゲットリスナーがランダムポートを使えるのはなぜか
StartTargetListenerはターゲットUnixファイルに対してproxyを作り、TCPバインドポートを0で渡してランダムポートを使用できる。その後GetTargetListenerPortsが実際にバインドされたポートをソース側に通知する。
この方式の利点はポート衝突を減らし、PodとノードのダイナミックE環境でより柔軟にリスナーを開けることである。
つまり固定論理ポートと実際のバインドポートを分離した構造である。
ソケットファイルがなぜ必要か
ソース側のSourceUnixFileを見ると、migration proxyはベースディレクトリの下にソースソケットファイルを作る。理由はlauncher内部のlibvirtが依然としてローカルUnixエンドポイントを基準に動作するフローとうまく合うからである。
これはmigration proxyがネットワークのために既存のローカルランタイムモデルを捨てるのではなく、Unixソケットモデルをネットワーク可能な形にラッピングしていることを意味する。
TLSはどこに付くか
migration-proxy.goはサーバーTLS config、クライアントTLS config、マイグレーションTLS configを別々に管理する。またクラスタマイグレーション設定にDisableTLSオプションがあればターゲットリスナーで追加TLSレイヤーをオフにできる。
しかしコードとAPI説明の両方が共通して示唆するのは明確である。DisableTLSは通常悪い考えである。
なぜならマイグレーショントラフィックはゲストメモリ状態と実行状態を含むため、ネットワーク経路の保護が重要だからである。
つまりmigration proxyは単なるフォワーダーではなくセキュリティ境界でもある。
マイグレーションネットワークをなぜ別にできるか
KubeVirt設定にはマイグレーショントラフィックをデフォルトPodネットワークではなく別のネットワーク経由にするオプションがある。これはマイグレーショントラフィックがアプリケーションPodネットワークと競合したり、帯域幅とセキュリティの面で分離が必要な場合があるからである。
この設定がある理由をproxy観点で見ると理解しやすい。proxyはどのみちソースとターゲット間のtransport shimなので、そのtransportをどのネットワークに送るか変更しやすい構造になっている。
ブロックマイグレーションとダイレクトマイグレーションがなぜ異なるか
ダイレクトマイグレーションは主にメモリと実行状態中心だが、ブロックマイグレーションはディスク関連データパスも含む可能性がある。そのためポートもより多く必要で、全体の帯域幅とタイムアウト計算もより敏感になる。
proxyレイヤーがこの二つを区別してポートマップを管理する理由がここにある。
運用者がproxyを見るべき瞬間
次のような状況ではmigration proxyをまず疑うべきである。
- target Podは生きているがマイグレーション接続が始まらない
- ソースとターゲット間のTLSハンドシェイク問題がある
- ブロックマイグレーションで特定ポートだけ失敗する
- マイグレーションネットワーク分離後に接続問題が発生する
このときは単にlibvirtエラーだけ見るのでは不十分で、リスナーが開いているか、ソースソケットが作られたか、ターゲットポートマッピングが正しいかを一緒に見る必要がある。
よくある誤解
誤解1:ソースとターゲットのQEMUがそのまま直接接続する
Kubernetes環境では中間のtransport shimが必要である。その役割をmigration proxyが担う。
誤解2:マイグレーションポートは常に固定
論理ポートは決まっていても実際のバインドポートとマッピングは流動的な場合がある。
誤解3:TLSは選択事項なのでオフにしても大きな問題はない
マイグレーション状態はセンシティブである。通常は追加TLSレイヤーを維持するのが正しい。
まとめ
KubeVirtのmigration proxyはライブマイグレーションtransportをKubernetes環境に合わせて調整する核心レイヤーである。ターゲット側はTCPリスナーを開き、ソース側はUnixソケットを提供し、その間をTLSとポートマッピングで接続する。この構造のおかげでlauncher内部のローカルlibvirtモデルとクラスタ間ネットワーク移動モデルがきれいにつながる。
次の記事では、マイグレーションと直接つながるリソースの問題、すなわちCPU、メモリ、NUMA、hugepagesがPodスケジューリングとゲストハードウェアモデルでどう解釈されるかを見ていく。