Skip to content
Published on

Live Migration 3:migration proxy、ポート、TLS、ソケットはなぜ必要か

Authors

はじめに

ライブマイグレーションを考えると「ソース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インターフェースを見ると役割が明確である。

ターゲット側

  • StartTargetListener
  • GetTargetListenerPorts
  • StopTargetListener

つまりターゲットノードは外部から入るTCP接続を受け取るリスナーを開く。

ソース側

  • StartSourceListener
  • GetSourceListenerFiles
  • StopSourceListener

つまりソースノードはローカル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スケジューリングとゲストハードウェアモデルでどう解釈されるかを見ていく。