[OpenShift Virtualization] Node跨ぎのLive Migrationで本当にVMは停止しないのか検証してみた

# はじめに
OpenShift Container Platformで仮想マシン (VM) を起動・管理できる標準機能である「OpenShift Virtualization (以下、OCP-V) 」では、Kubernetes Clusterを構成するBare Metal Nodeを横断して、Live Migrationが可能です。その実験をします。またついでに、OCP-VでVMを操作するコマンドラインツールである`virtctl`も試しに触ってみましょう。

先に、OCP-VにおけるLive Migrationの仕組みを絵で示します。

すごく絵が雑で恐縮なのですが、VM (がContainer内でプロセスとして実行されているPod) がNode AにDeployされていたとしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4536199b-4aba-75b6-774f-ee6d13f3fa4d.png)

このVMをNode BにLive Migrationさせることを考えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4bd30dbe-795e-6e11-658c-784b8ce493dd.png)

Live Migrationを行うと、Node B上に新しいVM (インスタンスとしてはPodです) が新規作成されてきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/19d29621-c49c-2414-820a-8ac341bab2fd.png)

そして新Podが作成され、新旧Podが僅かな時間の間だけ同じPVを読み書きします。この時、VMのステータスは`Migrating`となります。また、インスタンスとしては異なる2つのPodが同時に読み書きできるPV、つまり`ReadWriteMany`でProvisioningされたPVが必要です。「Live MigrationするにはRWXのPVが要る」理由とはここにあります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ac1bf079-bc32-b840-2ed3-af039af4728f.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/1d05bc30-e395-1406-72e7-70c963c54529.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/6e33552f-cc4d-ef44-3764-f1caf7dae956.png)

という感じでLive Migrationが完了します。とはいえ「本当かよ？」という疑問も湧いてきます。「本当にVMは途中で止まってないの！？」とか。

なので軽く検証してみました。

# 環境構成
なお、OCP-Vの環境については[こちらの記事](https://qiita.com/masaki-oomura/items/62ae108115d6164f9e42)で作成した環境に準じて実施しております。
※記事内に記載の通り、こちらはRed Hatの動作保証外の環境ですので、そこだけご了承ください。
- ROSA (Red Hat OpenShift Service on AWS) v4.15.0
- OpenShift Data Foundation (ODF) にて`BlockDevice`の`StorageClass`を使える様にしている
 
なお、OpenShift Virtualization Operatorインストール済みのOpenShift ClusterでODFにてStorage Clusterを作成すると、VM作成時にデフォルトで選択してくれる`StorageClass`である`ocs-storagecluster-ceph-rbd-virtualization`も作成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/17e1ab05-254b-c5da-7ac0-e50585901031.png)

# SSH公開鍵を登録してVMを作成
今回はサクッと起動できるFedora VMを利用します。なお、VM作成時にターミナルからSSH接続する為の公開鍵を登録した上でVMを作成します。公開鍵登録はとても簡単にできます。

やっていきましょう。

適当な`NameSpace`（私は`virtual-machine`としました）で「+追加」の下の方にある「Virtual Machines」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/376ac90e-840b-0d62-1740-ab0a35dec9c3.png)

VMのテンプレート画面に遷移するので、「Fedora VM」をクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/7771f1b5-f8ba-4d7c-4da3-888cfe411243.png)

VM作成画面にいくのですが、今回はSSH公開鍵を登録したいので、「Customize VirtualMachine」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/efde64d1-b2a3-309f-3cea-963f2ec27147.png)

タブ「Scripts」から、「Public SSH key」の欄で「Edit」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/db67079b-eaab-d3f4-26c9-0a10d973ac57.png)

今回は既に`NameSpace: virtual-machine`に`Secret`として公開鍵をapply済みなので、それを選びます。「Save」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/e62d36fb-5427-f266-43e8-773e6dc0d619.png)

なお、新たに公開鍵を登録したい場合は「Add new」から可能です。PCで作成したSSH keyペアのうち、公開鍵の中身をコピペすればOKです。`Secret`名もここで指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/fdb03426-ddb0-d352-1a9e-9fde2b0173ae.png)

VMの名称はお好きなものに変更して（私は`fedora-live-migration`としました）、「Create VirtualMachine」をクリックしてVMが起動するのを待ちます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8634dd98-0254-b209-bb91-ff922b2a9b74.png)

起動したら、「Console」タブに遷移し、事前に指定されたUser/Password（ここもScriptsで変更できますが今回は特に不要）でログインしておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d4e6df81-43b8-6719-cda8-9b74e42aaed9.png)

これでVMの用意は完了です。

# 標準出力とログファイルに毎秒UTC時刻を吐くアプリを用意
次にVMがLive Migration中に止まらないか証明する為のアプリケーションをFedora VM上にDeployします。今回は`Node.js`のアプリを用意しました。
```index.js
const fs = require('fs');

// 時刻をファイルに書き込む関数
function logTime() {
    const now = new Date().toISOString();  // UTC時刻を取得
    console.log(now);  // 標準出力に時刻を出力
    fs.appendFile('timeLog.txt', now + '\n', (err) => {
        if (err) {
            console.error('ファイルへの書き込みエラー:', err);
        }
    });
}

// 1秒ごとにlogTime関数を実行
setInterval(logTime, 1000);
```
これはChatGPTに「毎秒UTCの時刻を標準出力とホストのディレクトリに出力し続けるNode.jsのアプリケーションのソースコードを教えて下さい」って聞いたら作ってくれました。ChatGPTくん、ありがとうございます。

このアプリをDeployしたいのですが、いかんせんOpenShiftのGUIのコンソールからやるのが結構操作性に難ありな為、SSHで接続してターミナルからやっていきます。その為のSSH公開鍵登録なのでした。。。

# ターミナルからVMにSSH接続する
ターミナルからVMにSSHで接続するには`virtctl`を使います。これはGUIでやっている事をコマンドで操作する為のコマンドラインツールです。インストール方法を含む詳細は[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html/virtualization/virt-using-the-cli-tools)を参照ください。

とりあえず`virtctl`がインストールできてPATHも通したというところから以下説明します。
まずは`oc login`でOpenShiftにログインしましょう。ご自身の`Token`でログインしてください。
```
oc login --token=sha256hogehogefugafuga
```
つづいて`virtctl`コマンドを使い、先程建てたVM`fedora-live-migration`にSSH接続したいのですが、なんとOpenShiftのコンソール画面でVMの詳細画面からプルダウン「Action」内にある「Copy SSH command」というボタンがあり、ここをクリックするだけでSSHコマンドを取得できます。クリックしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/fe3020c8-f707-0d1d-52b9-6c0e14979d7d.png)

コマンドがコピーできたので、`oc login`済みのターミナルにペーストします。
```
virtctl -n virtual-machine ssh fedora@fedora-live-migration --identity-file=<path_to_sshkey>
```
ここで、`<path_to_sshkey>`は、秘密鍵のPATHを指定してください。
```
[fedora@fedora-live-migration ~]$ 
```
VMにターミナルからログインできました。

# Node.jsアプリをFedora VMにDeployする
ここでNode.jsのアプリを作ってしまいます。ここからは特にOCP-V固有の内容ではなく、単にRHEL系LinuxにてNode.jsが使える様にする手順となります。
```sh
$ sudo yum install nodejs npm -y
$ npm init -y
$ ls 
package.json
```
`npm init`して`package.json`が作られました。続いて`index.js`をカレントディレクトリに`vi`で作っちゃいます。
```sh
$ vi index.js
$ ls
index.js  package.json
$ cat index.js
const fs = require('fs');

// 時刻をファイルに書き込む関数
function logTime() {
    const now = new Date().toISOString();  // UTC時刻を取得
    console.log(now);  // 標準出力に時刻を出力
    fs.appendFile('timeLog.txt', now + '\n', (err) => {
        if (err) {
            console.error('ファイルへの書き込みエラー:', err);
        }
    });
}

// 1秒ごとにlogTime関数を実行
setInterval(logTime, 1000);
```
これで準備完了です。一度Node.jsアプリの動作確認をしておきます。起動コマンドはカレントディレクトリにて`node index.js`です。
```sh
$ node index.js
2024-05-14T00:12:06.509Z
2024-05-14T00:12:07.512Z
2024-05-14T00:12:08.513Z
2024-05-14T00:12:09.514Z
2024-05-14T00:12:10.516Z
2024-05-14T00:12:11.519Z
2024-05-14T00:12:12.521Z
2024-05-14T00:12:13.522Z
2024-05-14T00:12:14.523Z
```
標準出力に1秒ごとにUTC時刻を吐き出しています。`ctrl+c`で一度アプリを終了します。カレントディレクトリにて`ls`すると`timeLog.txt`というログファイルが生成されています。これは標準出力に出力されたものと全く同じ内容が出力されたテキストファイルです。`cat`すれば同じ内容であることが確認できますので、見てみてください。

さて、アプリの方も問題無く動きました。さっそくLive Migrationを試します。

# OpenShiftのコンソール画面からFedora VMでアプリを起動
既にFedoraにログイン済みかと思うので、このままNode.jsアプリを起動します。
```sh
$ node index.js
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/03c86f7d-f0f7-17f5-620f-e02eb7eb99d7.png)

同じ様に標準出力に毎秒時刻が出力されてきます。タブ「Overview」に遷移します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4c255c1e-a3c2-e9b0-c939-0eeae1d2aaed.png)

中央の「VMC Console」からも小さくVMのコンソールが見えています。この小さい画面でも毎時ログが出力されている動きが見えるはずです。

Live Migration前の稼働中の`Node`や`IP Address`は以下です。
- `Node: ip-10-0-64-154.ap-northeast-3.compute.internal`
- `IP Address: 10.129.8.160`

右上のプルダウン「Action」から「Migrate」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/452429ce-5b7a-1cf7-d870-397e16f174d9.png)

Live Migratationが始まります。「Status」が`Running`となっているところが一瞬だけ`Migrating`になるので、見逃さないようにしてください。本当に一瞬です。頑張ってスクショを撮りました。
![スクリーンショット 2024-05-14 10.39.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/f71fb941-9294-4368-49d2-d0f9c5846868.png)

再びVMのConsoleを見てください。変わらずNode.jsアプリはログを吐き続けています。

今回は`start`コマンドを用いていません（`node`コマンドからフォアグラウンドで実行した）ので、仮にVMが停止していた場合はNode.jsアプリも止まるはずです。
- ご参考： [【Node.js入門】foreverの使い方とデーモン化による永続化・自動起動まとめ！](https://www.sejuku.net/blog/81363)

アプリは止まっていない。ということはVMも無停止でLive Migrationされたということです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/60febf5f-821a-b118-1344-8aa50a640453.png)

ただし`Node`や`IP Address`は変わっています。
- `Node: ip-10-0-49-4.ap-northeast-3.compute.internal`
- `IP Address: 10.131.4.174`

VMインスタンス自体に吐き出したログファイルが失われていないかも確認しておきます。
Macのターミナルの方で確認しておきます。もしMacのターミナルで明示的にFedoraからログアウトしていない場合、MacのターミナルからのSSH接続はLive Migration前後で接続解除されていないことにお気づきでしょうか？
```sh
$ ls
index.js  package.json  timeLog.txt
$ cat timeLog.txt
```
無事、ログファイルは失われていません。

# おわりに
OpenShift VirtualizationにおけるVMのLive Migrationの挙動を見てきました。
Live Migration前後でVMが停止したり再起動したりせず、VM上にDeployされたアプリも動作し続ける事が確認できました。

次回（いつになるか不明...）は、Live Migration前後で`IP Address`が固定できるのか試してみる予定です。

:::note info
続編を書きました。IPアドレス固定を含む本記事の続きは[こちら](https://qiita.com/masaki-oomura/items/dc71dc780eabe0910161)をご参照ください。
:::

:::note info
UTCではなくJSTで時刻出力したい場合は以下のコードをおつかいください。
```
const fs = require('fs');

// 時刻をファイルに書き込む関数
function logTime() {
    const now = new Date();
    now.setHours(now.getHours() + 9);  // UTCからJSTへの変換（9時間進める）
    const jstTime = now.toISOString().replace('T', ' ').substring(0, 19);  // 時刻フォーマットの調整
    console.log(jstTime);  // 標準出力に時刻を出力
    fs.appendFile('timeLog.txt', jstTime + '\n', (err) => {
        if (err) {
            console.error('ファイルへの書き込みエラー:', err);
        }
    });
}

// 1秒ごとにlogTime関数を実行
setInterval(logTime, 1000);
```
:::