terraform-provider-esxi を使って自宅 ESXi サーバに VM を立てよう

# はじめに

この記事は [terraform Advent Calendar 2020](https://qiita.com/advent-calendar/2020/terraform) 6日目です。

- Qiita に知見がないプロバイダの使ってみた系記事です
- 自宅サーバのようなオンプレかつ低コストに、かつ Terraform を使って ESXi の VM を管理したい人向けです
- この記事を書いている人は Terraform 素振り歴が累計1年あるかないかぐらいです
    - （初参加です、よろしくお願いします）

`terraform { required_version = ">= 0.13" }` にてお送りします。

# terraform-provider-esxi とは

[github.com/josenk/terraform-provider-esxi](https://github.com/josenk/terraform-provider-esxi) 

これは、Terraform から ESXi 仮想マシンを直接デプロイできるプロバイダです。

何をしてくれるのかと言うと:

1. VMware ESXi を導入したサーバに対し
2. このプロバイダを介して直接 SSH 接続し
3. `vim-cmd` などのコマンドを実行して仮想マシン諸々の管理をしてくれます

このプロバイダの利点は __vSphere を介さずに動作するため、ESXi 単体で（無償ライセンスの範疇で）利用できる__という点です。

プロバイダの性質的には `terraform-provider-libvirt` と似ていると思います。

ただ、気を付けなければいけない点として、ESXi サーバの SSH 接続が有効でなければ使用できないので、安全性を含めて使用には注意が必要だと思います。

<details><summary>もう少し詳細な解説</summary>
<p>

---

通常、無償ライセンスで ESXi を使うには少し苦労する点があります。

ESXi 自体は有償・無償のライセンスがあり、無償版では API による仮想マシンなどのリソースの新規作成などが制限されているため、
単体で仮想マシンを作成するには Web の GUI クライアントか、直接 SSH 接続したうえで `vim-cmd` や `esxcli` などを叩く必要があります[^1]。

[^1]: https://qiita.com/binzume/items/096ce77f73b6e853c342

なお、Terraform の公式プロバイダには [terraform-provider-vsphere](https://registry.terraform.io/providers/hashicorp/vsphere) が用意されていますが、API を介して ESXi を管理するため、無償版 ESXi はサポートされていません。

> This provider requires API write access and hence is not supported on a free ESXi license.
> ― [Docs overview | hashicorp/vsphere | Terraform Registry](https://registry.terraform.io/providers/hashicorp/vsphere/latest/docs)

今回使用する `terraform-provider-esxi` は、ESXi に備え付けられている `vim-cmd` や `esxcli` を SSH 接続して代わりに実行してくれるので、
API を介さずに ESXi 単体で仮想マシンやネットワークの構成を自動化出来るような仕組みになっています。

---

</p>
</details>

さらに詳細な使い方は [README](https://github.com/josenk/terraform-provider-esxi/blob/master/README.md) や [Wiki](https://github.com/josenk/terraform-provider-esxi-wiki) を一読しましょう。

というわけで、この記事では実際に `terraform-provider-esxi` を使って仮想マシンを作成してみます。

# 事前準備

ESXi がインストールされたサーバが必要になります（割愛します）。
そして、ESXi 管理コンソールにブラウザなどからアクセスし SSH を有効にしておきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124014/74c4f47d-5bbb-fb69-9604-91bcbec90910.png" width="1024">

# リソースの作成

Terraform で仮想マシンリソースを作成します。
今回は [Fedora CoreOS（FCOS）](https://getfedora.org/en/coreos?stream=stable)と言う、コンテナランタイム環境に特化したフットプリントの軽い OS をデプロイしてみます。

## VM の作成方法

このプロバイダで仮想マシンをデプロイする方法は 2種類あり、今回は FCOS の公式からダウンロードできる OVF テンプレートによるデプロイとなります。

そして、FCOS の Ignition という仕組みを使い、ユーザの作成、SSH サーバの設定、`open-vm-tools` のインストールをあらかじめ構成した仮想マシンをデプロイします。

ちなみに、もう一つのデプロイ方法として、すでに ESXi 上にある VM リソースをクローンすることが出来ます。

更にもう一つ、上記のどちらの方法も指定しなかった場合は、空の状態（デフォルトで PXE ブート）で仮想マシンを作成することも可能なようです。

## Fedora CoreOS VM イメージの取得

まず、Terraform を実行する PC 上に、`coreos-installer` で OVA フォーマットの VM イメージをあらかじめダウンロードしておきます。

```shell
$ docker run --rm -v $PWD:/data -w /data quay.io/coreos/coreos-installer:release download -s stable -p vmware -f ova
```

```shell
Read disk 11.1 MiB/791.0 MiB (1%)
Read disk 22.2 MiB/791.0 MiB (2%)
(snip)
Read disk 791.0 MiB/791.0 MiB (100%)
gpg: Signature made Mon Nov  2 23:13:28 2020 UTC
gpg:                using RSA key 97A1AE57C3A2372CCA3A4ABA6C13026D12C944D0
gpg: Good signature from "Fedora (32) <fedora-32-primary@fedoraproject.org>" [ultimate]
Read disk 791.0 MiB/791.0 MiB (100%)
./fedora-coreos-32.20201018.3.0-vmware.x86_64.ova
```

## 構成

Terraform ファイルを構成します。
ここでは Ignition を設定するため、 [terraform-provider-ct](https://registry.terraform.io/providers/poseidon/ct/latest) も依存関係に追加します。

```versions.tf
terraform {
  required_version = ">= 0.13"
  required_providers {
    esxi = {
      source  = "josenk/esxi"
      version = "1.8.0"
    }
    ct = {
      source  = "poseidon/ct"
      version = "0.6.1"
    }
  }
}
```

`esxi` プロバイダの接続情報には、Web クライアント上でリソースの作成権限があるユーザ情報を入力してください

```providers.tf
provider "esxi" {
  esxi_hostname = "esxi.example.com"
  esxi_username = "username"
  esxi_password = "password"
}

provider "ct" {}
```

### esxi_guest リソースの定義

`esxi_guest` で、仮想マシンリソースのプロパティを定義します。

- `ovf_source` に先程ダウンロードした VM イメージを指定します
- `esxi_guest` の `disk_store` や `network_interfaces.virtual_network` は環境ごとに異なります
- その他スペックについては環境に合わせて調整してください
    - `guestos` には、ESXi や Fusion 使用する OS の識別子を[この辺](https://gist.github.com/vmwarecode/2cb50ba1945ead45db77b65b1867a28c#file-snippet-sh-L25-L157)とかを参考に指定します<br>（FCOS の OVF テンプレートが RHEL 扱いとなっており、ESXi が警告を出すので上書きしてます）

```main.tf
locals {
  ovf_source       = "/path/to/fedora-coreos-32.20201004.3.0-vmware.x86_64.ova"
  ignition_content = "/path/to/ignition.yaml"
  guest_num_vcpu   = 2
  guest_memory_gb  = 4
  guest_disk_gb    = 30
}

data "ct_config" "ignition" {
  content      = local.ignition_content
  strict       = true
  pretty_print = false
}

resource "esxi_guest" "fcos_guest" {
  ovf_source = local.ovf_source
  guestos    = "fedora-64"

  guest_name = "fcos-guest"
  power      = "on"

  numvcpus       = local.guest_num_vcpu
  memsize        = max(local.guest_memory_gb * 1024, 512)
  disk_store     = "datastore1"
  boot_disk_size = max(local.guest_disk_gb, 20)
  network_interfaces {
    virtual_network = "VM Network"
  }

  guestinfo = {
    "ignition.config.data"          = base64gzip(data.ct_config.ignition.rendered)
    "ignition.config.data.encoding" = "gzip+base64"
  }
}
```

### Ignition テンプレートの実装

Ignition には `post-install.service` として `open-vm-tools` をインストールさせるように仕組みます[^2]。

[^2]: https://github.com/coreos/fedora-coreos-tracker/issues/307

<details><summary>ignition.yaml</summary>
<p>

```ignition.yaml
variant: fcos
version: 1.1.0
passwd:
  users:
    - name: test_user
      ssh_authorized_keys:
        - (snip)
      home_dir: /home/test_user
      shell: /bin/bash
      groups: [ docker, wheel ]
storage:
  files:
    - path: /etc/ssh/sshd_config.d/10-customize.conf
      contents:
        inline: |
          Port 40022
          PasswordAuthentication no
          PermitEmptyPasswords no
          PermitRootLogin no
      mode: 0644
systemd:
  units:
    - name: post-install.service
      enabled: true
      contents: |
        [Unit]
        Description=Post Installation
        ConditionFirstBoot=yes
        Wants=network-online.target
        After=network-online.target
        After=multi-user.target
        Before=boot-complete.target
        [Service]
        Type=oneshot
        TimeoutStartSec=0
        ExecCondition=/usr/bin/test ! -f /etc/package-installer.done
        ExecStart=/usr/bin/rpm-ostree install open-vm-tools
        ExecStartPost=/usr/bin/touch /etc/package-installer.done
        ExecStartPost=/usr/bin/systemctl --no-block reboot
        [Install]
        WantedBy=multi-user.target
        RequiredBy=boot-complete.target
```

</p>
</details>

### Output

```outputs.tf
output "ip_address" {
  value = esxi_guest.fcos_guest.ip_address
}
```

# 実行

`terraform apply` すると、 ESXi 上に OVF テンプレートがアップロードされ、仮想マシンがデプロイされます。
仮想マシンの起動後も Ignition によるセットアップが続行し、再起動後に Terraform 側もデプロイが完了します。

環境やタイミングにもよりますが、デプロイ完了直後は Output から IP アドレスは取得出来ないことが多いです。
これは、`open-vm-tools` がゲスト情報を取得するのにラグがあり、ESXi に反映されるまでの時間と Terraform のデプロイ完了とのタイミングを合わせられないためです。

```shell
$ terraform apply

(snip)

esxi_guest.fcos_guest: Creation complete after 3m22s [id=14]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

ip_address =
```

しばらくしてから `terraform refresh` する事で Output に反映させます。

```shell
$ terraform refresh
data.ct_config.ignition: Refreshing state... [id=2548703694]
esxi_guest.fcos_guest: Refreshing state... [id=14]

Outputs:

ip_address = xxx.xxx.xxx.xxx
```

ここから IP アドレスなどの情報を用いて SSH させる事が可能なので、Docker コンテナを配備したり、 [rke_cluster](https://registry.terraform.io/providers/rancher/rke/latest/docs/resources/cluster) で Kubernetes クラスタを構築するなど、他の Provisioner に繋げる事が可能です。

# まとめ（利点と欠点）

ESXi 自体は、ハードウェア要件さえ合えばどんなマシンにでもインストール出来て無償で利用できるので（最近 [Raspberry Pi 4 でも使える Arm 版](https://flings.vmware.com/esxi-arm-edition)が出ましたね）、これと Terraform を組み合わせてあらゆる環境の VM の IaC 化が捗るのはとてもおいしいなと思います。

欠点は、ESXi 1台につきプロバイダの設定が 1つ必要となってしまう事で、例えば Kubernetes クラスタ用に同じ構成の VM を複数の ESXi サーバにまとめてデプロイしようとすると、Module をひたすら複製して Proxy Configuration Block を濫用することになります。

理想は `for_each` を使って表現できれば良かったのですが、[Provider 周りは Terraform 実行中に動的に決定出来ない](https://github.com/hashicorp/terraform/issues/25244)というのが現状の動作のようです。

とは言え、Terraform を使ってオンプレ環境の VM を低コストに管理出来るようになって、個人的には満足しています。
Qiita にこのプロバイダに関する記事が無かったので、もし知らない方が居たら是非試してみてください。