cloud-initでUbuntuServerのセットアップを自動化する on ESXi(vmware)

# 背景
Linux系OSのインストールはisoイメージを使って対話式に進めていくのがおなじみですが、最近はクラウドイメージと称してOSインストール済みのディスクイメージや、OVAイメージの配布をしているところが増えてきました。
自分が個人でよく使っているUbuntuServerもクラウドイメージを配布していたので使ってみましたが、設定に必要なcloud-init周りで色々ハマったのでメモします。

# 気づいたことまとめ
## クラウドイメージはそのままでは使えない
UbuntuServer 20.04 のクラウドイメージは以下にて公開されています。
https://cloud-images.ubuntu.com/focal/current/

vmware用のOVAイメージは、`focal-server-cloudimg-amd64.ova`として配置されています。
なるほど、このOVAをESXiからインポートして起動させれば使えるんだなと勝手に思っていました。

実際動かしてみるとOSは起動できるものの、ユーザ不在でログインもできない空っぽのサーバが生まれるだけでした。（それはそう）

## クラウドイメージの設定にはcloud-initを使う
一般的にクラウドイメージと呼ばれるものは、初回起動時にcloud-initを呼び出し、cloud-initはユーザから渡された設定情報(user-data)を読み込み初期セットアップを実施します。
AWSのEC2の場合、インスタンス作成時にcloud-initに渡すuser-dataを入力できるようになっています(下図)。
![スクリーンショット 2021-06-19 17.11.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261734/398aa7dc-770a-e0f2-88f0-82ab08a8f456.png)

## ESXi(VMware)の場合はどうするのか
ではESXiでVM起動する時はどうやって渡すのか。これは普通にcloud-initのドキュメントに書いてありました。
→ https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html
(NoCloudという見出しだったせいで見つけるのに苦労した)

まず、設定にあたって必要になるファイルは以下のとおりです。全てテキストファイルになります。

```
$ tree .
.
├── meta-data         # 必須
├── user-data         # 必須
├── network-config    # 必須ではない
└── vendor-data       # 必須ではない
```

全体の流れとしては、、、

まず`meta-data`, `user-data`ファイルに設定を記述します。この2つは必須です。
必要に応じて`network-config`, `vendor-data`ファイルも作成します。

これらのファイルの記法に関しては以下のドキュメントが参考になります。

* NoCloud — cloud-init 21.2 documentation
  * https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html
* Cloud config examples — cloud-init 21.2 documentation
  * https://cloudinit.readthedocs.io/en/latest/topics/examples.html 

次に、`genisoimage`コマンドでこれらのファイルを`cidata`という名前のボリュームでisoファイルにまとめます。
下記コマンド例では`network-config`, `vendor-data`を含めていますが、作成していなければ不要です。

```
$ genisoimage -output seed.iso \
  -volid "cidata" \
  -input-charset "utf-8" \
  -joliet -rock \
  user-data \
  meta-data \
  network-config \
  vendor-data
```

これで`seed.iso`という名前でISOファイルが作成されます。ボリューム名が`cidata`であれば良いのでisoファイルの名前は何でも良いです。
ESXiで実際に使用するときは、OVAファイルからVMをインポート後、VMの仮想光学ドライブにISOファイルを読み込ませて起動することでcloud-initが走ります。

## 作成してみたサンプル
以下の設定を行います。OSはUbuntu Server 20.04を想定しています。

* hostnameをmary01に設定
* ユーザ owner の作成(パスワード:owner, SSH公開鍵は適当)
* インターフェースens192のネットワーク設定
* Docker-CE,docker-composeのインストール

```yaml:meta-data
instance-id: iid-mary01   # <- ここは任意の文字列でok
local-hostname: mary01
```

```yaml:user-data
#cloud-config
users:
  - name: 'owner'
    plain_text_passwd: 'owner'
    ssh_authorized_keys:
      - 'ssh-ed25519 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    shell: '/bin/bash'
    sudo: 'ALL=(ALL) NOPASSWD:ALL'
    lock_passwd: false

runcmd:
  - 'echo Install Docker-CE'
  - 'curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg'
  - 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list'
  - 'apt-get update && apt-get install -y docker-ce'
  - 'gpasswd -a owner docker'

  - 'echo Install docker-compose'
  - 'curl -sL "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose'
  - 'chmod +x /usr/local/bin/docker-compose'
```

```yaml:network-config
version: 2
ethernets:
  ens192:
    mtu: 1454
    dhcp4: false
    addresses: ["192.168.1.2/24"]
    gateway4: "192.168.1.1"
    nameservers:
      addresses:
        - "1.1.1.1"
        - "8.8.8.8"
```

## 注意点,ハマりどころ
* `user-data`ファイルは先頭行に`#cloud-config`と記述しないとcloud-initから認識されません。必須です。
* `user-data`ファイルのフォーマットが正しいかどうかは `cloud-init schema -c ./user-data` コマンドでチェックできます。
  * ※cloud-init 22.2 で`cloud-init devel schema`は`cloud-init schema`に変更されました
* cloud-init実行時のログは`/var/log/cloud-init.log`, `/var/log/cloud-init-output.log`に出力されます。どこかうまく行っていない気がしたらここを見るべし。
* ESXiでOVAイメージからVMをインポートする場合、インポート完了後VMが自動起動するオプションがデフォルトでONになっています。光学ドライブにISOファイルをセットする必要があるので、OFFにするのを忘れずに。