Terraform で KVM 上に Minikube on CoreOS を構築

## 概要
Terraform Libvirt Provider を用いて、KVM 上に CoreOS + Minikube が導入された1台の仮想マシンをプロビジョニングする。

## Prerequisite
- Terraform
- Container engine (docker, podman, or nerdctl, ...)
- KVM 関連パッケージ
    - qemu-kvm
    - libvirt-clients
    - libvirt-daemon
    - bridge-utils
    - virt-manager
- (OS: Ubuntu 22.04)


## サンプルコード
こちらの GitHub repository を参照。

https://github.com/sawa2d2/coreos-on-kvm/tree/main/minikube


## 手順
### CoreOS image の入手

[CoreOS のダウンロードページ](https://getfedora.org/ja/coreos/download?tab=metal_virtualized&stream=testing&arch=x86_64)から、QEMU用のイメージをダウンロードする。本記事では、
```
https://builds.coreos.fedoraproject.org/prod/streams/testing/builds/37.20230303.2.0/x86_64/fedora-coreos-37.20230303.2.0-qemu.x86_64.qcow2.xz
```
を使用する。

ダウンロードしたイメージを解凍し、`fedora-coreos-37.20230303.2.0-qemu.x86_64.qcow2` を `/var/lib/libvirt/images/` に移す ([*補足](#補足))。

```
$ unxz -k fedora-coreos-37.20230303.2.0-qemu.x86_64.qcow2.xz
$ sudo mv fedora-coreos-37.20230303.2.0-qemu.x86_64.qcow2 /var/lib/libvirt/images/
```


### Ignition の作成
[Ignition](https://coreos.github.io/ignition/configuration-v3_5_experimental/) は、CoreOS （をはじめとするいくつかの Linux 系 distribution） でサポートされている、OS のセットアップ自動化のための仕組みである。
設定は JSON 形式のファイルで記述される。ただ、人間にとってわかりやすい YAML で記述した後、 [Butane](https://coreos.github.io/butane/) (前身：Fedora CoreOS Config Transpiler, FCCT) を用いて JSON に変換するのが一般的である。
（ファイルの拡張子は、慣習的に変換前の YAML を `.bu` 、変換先の JSON を `.ign` にする）。

次のような `.bu`ファイルを作成する。

```yaml:ignition.bu
variant: fcos
version: 1.1.0
passwd:
  users:
    - name: core
      groups:
      ssh_authorized_keys:
        - "<YOUR_SSH_KEY>"

# Docs of ignition storage.files: https://coreos.github.io/butane/examples/#storage-and-files
storage:
  files:
    # minikube: https://minikube.sigs.k8s.io/docs/start/
    - path: /usr/local/bin/minikube
      overwrite: true
      mode: 0755
      contents:
        source: https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
    # kubectl: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
    - path: /usr/local/bin/kubectl
      overwrite: true
      mode: 0755
      contents:
        source: https://dl.k8s.io/release/v1.26.2/bin/linux/amd64/kubectl
```

- `passwd` ブロック で `core` ユーザの作成
- `storage.files` ブロックで `minikube` および `kubectl` のインストール

を指定する（上記の`ssh_authorized_keys` は自身の SSH 公開鍵に書き換える）。

上記の `ignition.bu` を、Butane を用いて `ignition.ign` に変換する。今回は quay.io で配布されている Butane コンテナを利用する。
```
$ docker run -i --rm quay.io/coreos/butane:release --pretty --strict < ignition.bu > ignition.ign
```

### Terraform libvirt-provider による VM のプロビジョニング

Terraform で libvirt 仮想化環境を管理するために、[dmacvicar/libvirt](https://registry.terraform.io/providers/dmacvicar/libvirt/latest/docs) provider を用いる。

#### 事前準備
Libvirt は、デフォルトでは外部のアプリケーションが関連するリソースにアクセスすることを制限している。
そこで、Terraform の libvirt-provider を実行する際に Permission Error にならないよう、権限を与えておく。
`/etc/apparmor.d/libvirt/TEMPLATE.qemu` を次のように編集し、関連するリソースに権限を付与する[^1]。
```qemu:/etc/apparmor.d/libvirt/TEMPLATE.qemu
# This profile is for the domain whose UUID matches this file.
#

#include <tunables/global>

profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
  #include <abstractions/libvirt-qemu>
   /var/lib/libvirt/images/** rwk,
   /tmp/** rwk,
}
```

libvirt を再起動する。

```
$ sudo systemctl restart libvirtd
```


#### プロビジョニングの実行

下記ような `main.tf` を作成し、1台の仮想マシンを作成する。（スペックは、[公式ドキュメントに記載されている必要最小の構成](https://minikube.sigs.k8s.io/docs/start/#what-youll-need)にした）：

- 2 vCPUs
- 2GB RAM
- 20GB Volume

```terraform:main.tf
terraform {
  required_providers {
    libvirt = {
      source = "dmacvicar/libvirt"
      version = "0.7.1"
    }
  }
}

provider "libvirt" {
  uri = "qemu:///system"
}

variable coreos_image_uri {
    #source: "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/37.20230205.3.0/x86_64/fedora-coreos-37.20230205.3.0-qemu.x86_64.qcow2.xz"
    default = "/var/lib/libvirt/images/fedora-coreos-37.20230205.3.0-qemu.x86_64.qcow2"
}

resource "libvirt_ignition" "ignition" {
  name = "ignition.ign"
  content = "${path.module}/ignition.ign"
}

resource "libvirt_domain" "minikube" {
  name = "minikube"
  memory = 2000  # in MiB
  vcpu = 2
  disk {
    volume_id = libvirt_volume.coreos_minikube_volume.id
  }
  coreos_ignition = libvirt_ignition.ignition.id

  network_interface {
    network_name = "default"
    wait_for_lease = true
  }
}

resource "libvirt_volume" "coreos_minikube_volume" {
  name = "coreos_minikube.qcow2"
  pool = "default" 
  format = "qcow2"
  base_volume_id = var.coreos_image_uri
  size   = 20 * 1024 * 1024 * 1024  # 20 GB
}
```

Terraform を実行し、仮想マシンをプロビジョニングする。

```
$ terraform init
$ terraform apply -auto-approve
```

仮想マシンが作成されたことを確認する。

```
$ virsh list --all
   Id    Name       State
---------------------------
 418   minikube   running
```

### 仮想マシンへの SSH login

作成した仮想マシンの IP アドレスを調べる。
```
$ virsh domifaddr minikube
  Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet512    52:54:00:8e:8f:dd    ipv4         192.168.122.204/24
```

`core` ユーザで SSH ログインできることを確認する。
```
$ ssh core@192.168.122.204
```

Minikube を起動する。
```
[core@localhost ~]$ minikube start
😄  minikube v1.29.0 on Fedora 37 (kvm/amd64)
...
```

kubectl が Kubernetes API サーバに接続されていることを確認する。
```
[core@localhost ~]$ kubectl cluster-info
kubectl cluster-info
Kubernetes control plane is running at https://192.168.49.2:8443
CoreDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
```


-----

## 補足
`/var/lib/libvirt/images/` は libvirt のデフォルトプールで、Terraform Libvirt Provider は通常ここからベースイメージとなる qcow2 ファイルを探す。


>base_volume_name - (Optional) The name of the backing volume (CoW) to use for this volume. Note well: when base_volume_pool is not specified the volume is going to be searched inside of pool.
>    -- <cite>[libvirt_volume | Resources | dmacvicar/libvirt | Terraform Registry](https://registry.terraform.io/providers/dmacvicar/libvirt/latest/docs/resources/volume#base_volume_name)</cite>

もしここ以外のプールを指定する場合（たとえば `/hoge` 下に qcow2 を置く場合）、`main.tf` を次のように書き換える：
```tf:main.tf

# `/hoge` 直下の qcow2 ファイルの URI を指定する
variable "coreos_image_uri" {
  default = "/hoge/fedora-coreos-37.20230205.3.0-qemu.x86_64.qcow2"
}

# (2) 次の "libvirt_pool" ブロックを追加し、`/hoge` プール を作成する。
resource "libvirt_pool" "image_pool" {
  name = "image_pool"
  type = "dir"
  path = "/hoge"
}

# ...

resource "libvirt_volume" "coreos_minikube_volume" {
  name = "coreos_minikube.qcow2"
  #(3) base_volume_pool に新たに作成したプールを指定する
  base_volume_pool = libvirt_pool.image_pool.id
  format = "qcow2"
  base_volume_id = var.coreos_image_uri
  size   = 20 * 1024 * 1024 * 1024  # 20 GB
}

```


[^1]: [How to fix Terraform libvirt permission denied on /var/lib/libvirt/images | Christian Nygaard Blog](https://cnygaard.com/2022/02/26/how-to-fix-terraform-libvirt-permission-denied-on-var-lib-libvirt-images.html)