[OpenShift Virtualization] VMを追加のOverlay Networkに参加させ、静的IPを振ってLive Migrationしたりアクセス制御したりして遊ぶ

# はじめに
この記事では、前回の[記事](https://qiita.com/masaki-oomura/items/a1453307d005e4656479)の続きとして、仮想マシンのLive Migration前後で変わらないIPアドレス（静的IPアドレス）を付与します。また、その静的IPアドレスを用いてアクセス制御を試してみます。

# OpenShiftのOverlay Networkについて
この記事の内容を読み進める上では、OpenShift上でPod（インスタンス）達が相互に通信する為の仮想ネットワーク（Overlay Network)に関するイメージを持っておくことが重要です。KubernetesはCNI(Container Network Interface)プラグインを適用する事で、Cluster上に任意のOverlay Networkを展開することができますが、OpenShiftではver4.14から「OVN-Kubernetes CNI」をデフォルトに採用しています。

:::note info
ver 4.13までデフォルトだった「OpenShift SDN CNI」は、ver4.14以降は非推奨になりました。詳細は[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html-single/networking/index#about-networking)を参照ください。
:::

Overlay NetworkとPod達の関係を絵にすると以下のようになります。
パッと見難しそうなのですが、実はとても簡単です。順を追って解説します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/06e1750c-b2d7-43d1-e84a-7bf2ae8a5db6.png)

## 同一Node内に閉じたPod間通信
これは、各Pod同士がレイヤ2(L2)で通信可能です。「OVN-Kubernetes CNI」は仮想的なL2スイッチとしてOpen vSwitch(OVS)を採用し、Podに割り当てられた仮想NIC（名称は`eth0`）に振られたMACアドレスで通信します。つまり、同一ホスト内に閉じてL2レベルで通信しているということです。

## Nodeを跨いだPod間通信
既にOpenShift/Kubernetes上にコンテナやVMをDeployして通信させた事のある方は、特にコンテナやVMがどのNodeの上で起動しているのかを意識していないと思いますが、しかしよく考えると変です。Nodeを跨いだ通信は一体どうやって実現しているのか？と。更に、同じLANにつながっているNodeならいざ知らず、例えばNodeが異なるAvailability Zone(AZ)に跨っている場合など、いったいPod同士はどうやってお互いのMACアドレスを宛先に通信できているのでしょうか？

その実現手段が「L2 over L3」という技術です。あるいは「トンネリング」と呼びます。ここでは詳細は割愛します（ググってみてください）が、ざっくりいうとレイヤ3（L3)の世界、つまりIPの世界において仮想的なトンネルを張り、Pod同士を物理アドレス（MACアドレス）で通信させています。

:::note info
このトンネリングのプロトコルは様々なものがあります。例えば「VXLAN(仮想拡張可能LAN)」や「NVGRE(汎用ルーティングカプセル化を使用したネットワーク仮想化)」が有名です。特に、従来OpenShiftに採用されていたデフォルトのCNIプラグインである「OpenShift SDN CNI」はトンネリングプロトコルとして「VXLAN」を採用していました。「VXLAN」にはCPU使用率の上昇を招くという欠点があり（詳細は[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.7/html/scalability_and_performance/optimizing-networking)）、それも踏まえて現在の「OVN-Kubernetes CNI」では「Geneve」というプロトコルが採用されています。マニアックな話ですし、L2 over L3のトンネルはOpenShiftのインストール時に自動的に作成されます。よって特にクラスター管理者が意識するものでもありません。
:::

これによって、Node達が接続されている物理的なネットワーク（Underlay Network）を介して、Pod同士が通信されるわけですが、しかし当のPod達はそんなものは意識しません。自分たちはNode内に閉じた通信だろうが、Nodeを跨いだ通信だろうが、仮想的にPod達が参加するネットワーク、つまりOverlay Networkしか意識する事はありません。

なお、このデフォルトのOverlay Networkである「Pod Network（Cluster Networkともいう)」はPod間の通信のみならず、Kubernetes Clusterとの通信にも使われます。例えば、Podのヘルスチェックや、各種メトリクスの収集 等、いわゆる「U-Plane」のみならず「C-Plane」の通信も走っています。

Pod Networkに参加するPod達は、作成されたタイミングで自動的にCluster内でユニークなIPアドレスを振られます。ただし、Podが再作成されたり、Nodeを跨いだリスケジュールが行われる際には、IPアドレスは勝手に変わります。

これは、以前の[記事](https://qiita.com/masaki-oomura/items/a1453307d005e4656479)でVMをLive Migrationした際にも確認できました。

## Overlay Networkは追加できる
Podに複数の仮想NICを追加する事ができるCNIプラグインである「Multus CNI」を利用する事で、Podをデフォルトのネットワーク以外のOverlay Networkに参加させることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/69d5e2fa-16ba-7634-2def-5c7e59abb621.png)

また、今回は試すことはできませんが、Node（サーバ）が複数の物理NICを有しており、それぞれが異なるUnderlay Networkに接続している場合、Underlay Networkのレベルでも通信を分離することが可能です。

上の図でいう「Overlay Network 1」はデフォルトのPod Networkです。つまり、`eth0`にはPod Networkから自動的にIPアドレスが付与され、仮にVM（を内包するPod）がLive Migrationした場合は、勝手にIPアドレスが変わります。しかし、任意で追加する「Overlay Network 2」に接続している`eth1`は、静的IPアドレスを振ることが可能です。これにより、VMが再作成されたりLive Migrationしても変わらない、「そのVM固有のIPアドレス」を保持させることが可能です。

というわけで、早速やっていきます。

# VMを追加ネットワークに参加させよう
まずはOpenShift Cluster内に二つめのOverlay Networkを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/2c3d49c1-91e3-0743-ff67-4cbb5a3e588b.png)

とても簡単に作れます。

## `NetworkAttachmentDefinitions`の作成
追加のOverlay Networkの展開は、`NetworkAttachmentDefinitions`(NAD)と呼ばれるリソースを作成する事で可能です。

今回は`NameSpace: virtual-machine`に「OVN Kubernetes L2 overlay network」を作成します。
[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html/virtualization/virt-connecting-vm-to-ovn-secondary-network#virt-creating-layer2-nad-cli_virt-connecting-vm-to-ovn-secondary-network)のNADのテンプレを使って`oc apply -f`でサクッと作成できてしまいます。

:::note info
[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html/virtualization/virt-connecting-vm-to-ovn-secondary-network#virt-creating-layer2-nad-cli_virt-connecting-vm-to-ovn-secondary-network)のNADのテンプレではMTU=1300という設定が入っていますが、特にMTUの設定は不要です。MTUはLinux Kernelが自動的に設定（ただしデフォルト値が1300になっている）します。また、OVN-Kubernetesは1500byteを超えるイーサネットフレーム（ジャンボフレーム）にも[対応](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html/virtualization/virt-connecting-vm-to-default-pod-network)しています。
:::

```yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: l2-network
  namespace: virtual-machine
spec:
  config: |
    {
            "cniVersion": "0.3.1", 
            "name": "virtual-machine-l2-network", 
            "type": "ovn-k8s-cni-overlay", 
            "topology":"layer2", 
            "netAttachDefName": "virtual-machine/l2-network" 
    }
```

これでOverlay Networkの追加が完了しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b47f87c0-3bc3-4140-4032-d9a464db48df.png)

詳細は後述しますが、「OVN-Kubernetes CNI」は追加ネットワークに適用できる`MultiNetworkPolicy`に対応しており、そのおかげでアクセス制御も容易に実施できるのです。

## VMを作成する
VMの作成については、前回の[記事](https://qiita.com/masaki-oomura/items/a1453307d005e4656479)を参照頂き、
- `StorageClass`に`ocs-storagecluster-ceph-rbd-virtualization`を選択し、Live Migration可能な`RWX`の`PersistentVolume`(PV)をプロビジョニングできる様にする
- SSHするための公開鍵を登録する
を実施しますが、加えて以下二つの作業を行います。
- 追加のNetwork Interface`eth1`を設定する
- `eth1`に静的IPアドレスを設定する

つまり、これから作成するVM（名称は`fedora-source`としておきます）と2つのOverlay Networkの関係はこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/87a636e7-c992-09e3-98c3-2f6d1933dc87.png)

### 作成時に追加のNetwork Interfaceを設定する
VM作成のカスタマイズ画面にてタブ「Network interfaces」から「Add network interface」をクリックします。
インターフェース名はランダムに割り当てられますが、任意の好きなものを設定する事も可能です。
「Network」は、既に追加済みの`NAD`があれば選択されている状態です。複数の`NAD`があればプルダウンから選べます。
今回は必要な設定がされているので、このまま「Save」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/80957d56-0b89-b3a0-7df2-7cc687b91a05.png)


これでNetwork Interface`eth1`の追加が完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/767e0055-1723-c114-5bc8-c75e7c28b6d1.png)


### `eth1`に静的IPアドレスを設定する
VM作成画面のタブ「Scripts」に移動します。SSH公開鍵の登録は「Public SSH key」から行いましたが、今回は「Cloud-init」欄の「Edit」から、VM作成時に初回のみ実施する設定を行います。

「Add network data」にチェックを入れると、Network Interfaceの設定を入れられます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/d30c7695-e73f-34f0-2089-6a75d68605dc.png)

対象の「Ethernet name」と付与したい「IP addreses」を入力し、「Apply」をクリックします。
これでOKです。VMの名前を`fedora-source`として、「Create VirtualMachine」をクリックしてVMを作成します。

VMが起動すると、Pod Networkに接続している`eth0`には自動的にIPアドレスが振られますが、追加したOverlay Networkに接続している`eth1`には指定した静的IPアドレス`192.168.100.10`が振られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/ce2a39c5-6a58-4c23-09c4-6e9fc734d4fa.png)

### Live Migrationして`eth1`のIPアドレスが変わらないか確認する
Live Migration前の各種情報は以下です。
- Node: `ip-10-0-64-154.ap-northeast-3.compute.internal`
- IP address:
    -  default(=`eth0`): `10.129.8.114`
    -  nic-plum-dog-12(=`eth1`): `192.168.100.10`

なお、VMの詳細情報は`oc describe vmi`コマンドで確認することも可能です。`Interfaces`で`eth0`及び`eth1`の情報を確認できます。なお「OVN-Kubernetes」はIPv6/IPv4のデュアルスタックに対応しており、実は`eth1`にはIPv6アドレスも設定されますが今回は気にしません。
```sh
~ % oc describe vmi fedora-source
(中略)
  Interfaces:
    Info Source:     domain, guest-agent
    Interface Name:  eth0
    Ip Address:      10.129.8.114
    Ip Addresses:
      10.129.8.114
    Mac:             02:7b:6f:00:00:2c
    Name:            default
    Queue Count:     1
    Info Source:     domain, guest-agent, multus-status
    Interface Name:  eth1
    Ip Address:      192.168.100.10
    Ip Addresses:
      192.168.100.10
      fe80::e46:cadf:ece1:1dd0
```

では、Migrateしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/b68b01a7-ac5c-174f-4da1-1dda3d31ab78.png)

Live Migration後の情報を見ます。
- Node: `ip-10-0-49-4.ap-northeast-3.compute.internal`
- IP address:
    -  default(=`eth0`): `10.131.4.36`
    -  nic-plum-dog-12(=`eth1`): `192.168.100.10`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/17076a54-4ba2-7680-6b99-fea6cc0da073.png)

`eth1`のIPアドレスは変わっていません！これでVMに固有の静的IPアドレスを振れる事が確認できました。

### 同じ手順でもう一つのVMを作成する。
この後の`MultiNetworkPolicy`を試す為に、全く同様の手順でもう一つのVM(名前は`fedora-destination`、静的IPアドレスは`192.168.100.20`とする）を作成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/8e0e79e8-40eb-acba-146e-6e221b339151.png)

2つの仮想NICが付いた2つのVM、それぞれが2つのOverlay Networkに接続している状態です。

# 2nd Overlay Network内でVM同士の通信状況を確認する
2つのVMはそれぞれ仮想NIC`eth1`が2nd Overlay Networkに接続しています。ということは通信可能ということ。
`traceroute`コマンドで疎通状況を確認してみます。

前回の[記事](https://qiita.com/masaki-oomura/items/a1453307d005e4656479)を参考にして、`fedora-source`にSSH接続しておきます。`traceroute`をインストールしておいてください。
```sh
[fedora@fedora-source ~]$ sudo yum install traceroute -y
```
`fedora-destination`のIPアドレス`192.168.100.20`に`traceroute`してみます。
```sh
[fedora@fedora-source ~]$ traceroute 192.168.100.20
traceroute to 192.168.100.20 (192.168.100.20), 30 hops max, 60 byte packets
 1  192.168.100.20 (192.168.100.20)  3.698 ms *  3.656 ms
```
当然ながら1ホップで到達しました。次に、`fedora-source`を踏み台サーバにして`fedora-destination`にSSHしてみます。今からやることはこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/39db9b74-5050-a59e-fd60-1783c38721fb.png)

そのためには`fedora-source`に対して、`fedora-destination`に登録しておいた公開鍵のペアである秘密鍵を転送しておく必要があるので、SCPコマンドを使ってローカルから`id_rsa`を転送します。`oc login`済みのターミナルで`virtctl`コマンドの中にSCPコマンドを盛り込むことができます。`virtctl`コマンド、地味に便利だ...
```sh
~ % virtctl -n virtual-machine scp <path_to_sshkey> fedora@fedora-source:~/ --identity-file=<path_to_sshkey>
```
`<path_to_sshkey>`は`id_rsa`のパスです。

ファイルが転送されたっぽいですが、ちゃんと転送されたか、`fedora-source`にSSHして見てみます。
```sh
~ % virtctl -n virtual-machine ssh fedora@fedora-source:~/ --identity-file=<path_to_sshkey>
```
`ls`コマンドで確認。あと`id_rsa`がオーナーのみ参照・更新できる権限かも見ておきます。
```sh
[fedora@fedora-source ~]$ ls -la | grep id_rsa
-rw-------. 1 fedora fedora 2622 May 24 16:12 id_rsa
```
大丈夫そうです。では、このまま`fedora-destination`にSSHしてみます。
```sh
[fedora@fedora-source ~]$ ssh -i id_rsa 192.168.100.20
[fedora@fedora-destination ~]$ 
```
できました！

VMを踏み台サーバにして別のVMに入る。こうした作業がOpenShiftの上で行われている事が面白いですね。つまり、OpenShift VirtualizationはVMの為の基盤としてもとても自然に動作します。

この後、`MultiNetworkPolicy`を適用した状態でもう一回SSHしますので、一度`exit`しておいて、踏み台サーバに戻って来ておきます。
```sh
[fedora@fedora-source ~]$ ssh -i id_rsa 192.168.100.20
Last login: Sat May 25 03:29:46 2024 from 192.168.100.10

[fedora@fedora-destination ~]$ exit
logout
Connection to 192.168.100.20 closed.

[fedora@fedora-source ~]$
```

## `MultiNetworkPolicy`を設定し、2つのVM間のアクセス制御を行う
`MultiNetworkPolicy`は`NetworkPolicy`と名前が似ています。後者はPod Network内における、その名の通り「ネットワークポリシー」を設定する`Custom Resource`です。

:::note info
`NetworkPolicy`の詳細は[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html-single/networking/index#nw-networkpolicy-about_about-network-policy)を御覧ください。
:::

`MultiNetworkPolicy`は追加ネットワークに対するネットワークポリシーを設定できる`Custom Resource`です。`NetworkPolicy`自体のAPIが使えますので、記法は同じです。

なお、OpenShiftのデフォルトでは`MultiNetworkPolicy`のAPIが使えませんので、有効化しておきます。方法はOpenShiftの[ドキュメント](https://docs.openshift.com/container-platform/4.15/networking/multiple_networks/configuring-multi-network-policy.html)で公開されていますので、こちらを参考にして有効化しておきます。要は`network.operator.openshift.io`をいじるわけです。

ローカルでmultinetwork-enable-patch.yamlを作成して、
```yaml
apiVersion: operator.openshift.io/v1
kind: Network
metadata:
  name: cluster
spec:
  useMultiNetworkPolicy: true
```
`oc patch`してください。
```sh
oc patch network.operator.openshift.io cluster --type=merge --patch-file=multinetwork-enable-patch.yaml
```
これで`apiVersion: k8s.cni.cncf.io/v1beta1`が使えるようになりました。

いよいよ`MultiNetworkPolicy`(MNP)を作ります。今回MNPを適用する2nd Overlay Networkは`NameSpace: virtual-machine`にありますので、同じ`NameSpace`にMNPをapplyしてます。

リソース名は`mnp-ingress-vm.yaml`としました。

:::note info
OVN-KuberneteとMNPの互換性については[こちら](https://access.redhat.com/documentation/ja-jp/openshift_container_platform/4.15/html/virtualization/virt-connecting-vm-to-ovn-secondary-network)を参照ください。
:::

```yaml
apiVersion: k8s.cni.cncf.io/v1beta1
kind: MultiNetworkPolicy
metadata:
  name: mnp-ingress-vm
  namespace: virtual-machine
  annotations:
    k8s.v1.cni.cncf.io/policy-for: virtual-machine/l2-network
    # ここでMNPを適用する追加NWを指定します。
    # 以下の記法はNetworkPolicyと全く同じです。
spec:
  podSelector:
    matchLabels:
      vm.kubevirt.io/name: fedora-destination
      # MNPを適用するPodを特定するkeyを指定します。今回はPod名をそのまま指定します。
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 192.168.100.30
            except: []
      ports:
        - protocol: TCP
          port: 22
```

このポリシーは「`fedora-destination`に対して、IPアドレス`192.168.100.30`から22番ポートにのみInのアクセスを認める」というポリシーです。

さて、IPアドレス`192.168.100.30`は`fedora-source`のIPアドレス`192.168.100.10`とは異なります。ということは、この`MultiNetworkPolicy`を適用してしまうと、`fedora-source`から`fedora-dextination`にSSHできなくなってしまうはずです。

試してみましょう。上記の`mnp-ingress-vm.yaml`を`oc apply -f`してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/4ace6f19-8c36-bad2-c9bb-fc0758cfb025.png)

再度、`fedora-source`から`fedora-destination`にSSHを試みます。
```sh
[fedora@fedora-source ~]$ ssh -i id_rsa 192.168.100.20

```
あれれ、いつまでたっても接続できません。そりゃそうです。`ctrl + c`で中断します。
GUIでMNPのYAMLを修正してみます。`cidr`のIPアドレスを`fedora-source`のIPアドレス`192.168.100.10`に変更して「保存」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3627586/6e4caacc-43a6-7536-4a0e-9b7b8dcf1166.png)

もう一回SSHを試します。
```sh
[fedora@fedora-source ~]$ ssh -i id_rsa 192.168.100.20
Last login: Sat May 25 03:33:16 2024 from 192.168.100.10
[fedora@fedora-destination ~]$ 
```

今度はちゃんとSSHできました。

# おわりに
OpenShiftの追加ネットワークを設定し、OpenShift Virtualization上で建てたVM間の疎通を確認しました。またVMに静的IPアドレスを振ってネットワークポリシーを適用したりしました。
これまでのVMの運用のあり方をOpenShift上でも遜色なく適用する事が確認できました。