Image Builder を使って Cluster API 用の OVA を作成する

# Image Builder とは？

[Image Builder](https://github.com/kubernetes-sigs/image-builder) とは、Kubernetes があらかじめインストールされた仮想マシンイメージを作成するためのツールです。AWS や Azure、vSphere など複数の環境に対応しています。ベースとなるゲスト OS も RHEL や Ubuntu などさまざまなものに対応しています。

今回は、vSphere 用の仮想マシンイメージを作成するために、Ubuntu ベースの OVA を作っていきたいと思います。

# Image Builder を使って OVA を作成する

Image Builder の使い方ですが、以下のリンクに作成方法が記載されています。記述が古いのか、実際の動作と異なる部分もありますが、大まかな手順は変わらないと思うので、参考にしながら進めていきます。

- [The Image Builder Book - Quick Start](https://image-builder.sigs.k8s.io/capi/quickstart.html)
- [The Image Builder Book - Building Images for vSphere](https://image-builder.sigs.k8s.io/capi/providers/vsphere.html)

ちなみに、以下のコマンドはすべて Ubuntu 22.04 上で実行しています。

## 必要なパッケージのインストール

まずは、Image Builder のリポジトリをクローンします。

```
$ git clone https://github.com/kubernetes-sigs/image-builder.git
```

次に、`image/capi` ディレクトリに移動して、`make deps` コマンドを実行して、必要なパッケージをインストールします。

```
$ cd image-builder/images/capi/
$ make deps
hack/image-grok-latest-flatcar-version.sh: line 9: jq: command not found
hack/ensure-ansible.sh
Collecting pip
  Downloading pip-22.3.1-py3-none-any.whl (2.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 15.5 MB/s eta 0:00:00
Collecting wheel
  Downloading wheel-0.38.4-py3-none-any.whl (36 kB)
Installing collected packages: wheel, pip
  WARNING: The script wheel is installed in '/home/ubuntu/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The scripts pip, pip3 and pip3.10 are installed in '/home/ubuntu/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed pip-22.3.1 wheel-0.38.4
User's Python3 binary directory must be in $PATH
Location of package is:
hack/utils.sh: line 78: pip3: command not found
make: *** [Makefile:56: deps-ami] Error 1
```

`jq` コマンドと `pip3` が見つからないと怒られたので、インストールします。

```
$ sudo apt isntall jq python3-pip
```

`make deps` を再実行します。

```
$ make deps
hack/ensure-ansible.sh
Collecting ansible-core==2.11.5
~~ snip ~~
Successfully installed ansible-core-2.11.5 packaging-22.0 resolvelib-0.5.4
User's Python3 binary directory must be in $PATH
Location of package is:
WARNING: Package(s) not found: ansible
make: *** [Makefile:56: deps-ami] Error 1
```

`ansible` のインストールに成功したはずなのに、ansible パッケージが見つからないとエラーが出ました。パスが通っていない可能性があるので、`ansible` コマンドの場所を探して、パスを通します。

```
$ sudo find / -name "ansible"
/home/ubuntu/.local/bin/ansible
/home/ubuntu/.local/lib/python3.10/site-packages/ansible
/home/ubuntu/.local/lib/python3.10/site-packages/ansible_test/_data/injector/ansible
/home/ubuntu/workspace/image-builder/images/capi/ansible

$ export PATH="/home/ubuntu/.local/bin/:$PATH"
```

気を取り直して、`make deps` を再実行したところ、今度は `unzip` コマンドがないとのことなので、`unzip` をインストールします。

```
$ make deps
hack/ensure-ansible.sh
Starting galaxy collection install process
Process install dependency map
Starting collection install process
~~ snip ~~
Required-by: 
hack/ensure-packer.sh
packer_1.8.3_linux_amd64.zip: OK
hack/ensure-packer.sh: line 52: unzip: command not found
make: *** [Makefile:58: deps-ami] Error 127

$ sudo apt install unzip -y
```

今度こそはと思い、`make deps` を再実行したところ、無事成功したようです。

```
$ make deps
~~ snip ~~
Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`.
hack/ensure-packer.sh
hack/ensure-goss.sh
Right version of binary present
```

## Packer を使って vSphere 上で仮想マシンを作成する

ここからは、vSphere 向けの作成手順になります。 [vsphere.json を埋めろ](https://image-builder.sigs.k8s.io/capi/providers/vsphere.html#prerequisites-for-vsphere-builder)、ということなので、必要な項目を入力しておきます。

```
$ cat packer/ova/vsphere.json 
{
    "vcenter_server":"FQDN of vcenter",
    "username":"vcenter_username",
    "password":"vcenter_password",
    "datastore":"template_datastore",
    "folder": "template_folder_on_vcenter",
    "cluster": "esxi_cluster_used_for_template_creation",
    "network": "network_attached_to_template",
    "insecure_connection": "false",
    "template": "base_template_used_by_clone_builder",
    "create_snbapshot": "creates a snaphot on base OVA after building",
    "linked_clone": "Uses link cloning in vsphere-clone builder: true, by default"
}
```

次に、`make deps-ova` を実行して、OVA 作成に必要なパッケージがインストールされているか確認します。

```
$ make deps-ova
hack/ensure-ansible.sh
Starting galaxy collection install process
Nothing to do. All requested collections are already installed. If you want to reinstall them, consider using `--force`.
hack/ensure-ansible-windows.sh
hack/ensure-packer.sh
hack/ensure-goss.sh
Right version of binary present
hack/ensure-ovftool.sh
```

ここまでの手順で準備ができたわけですが、オプションとして、`ovftool` を使うことで OVA を作成できるということなので、ovftool もインストールしておきます。

```
$ sudo ./VMware-ovftool-4.4.3-18663434-lin.x86_64.bundle 
```

また、`packer` に対するパスが通っていないことに気づいたので、`./image-builder/images/capi.local/bin/packer` に対して、パスを通します。

それでは、以下のコマンドを実行して、OVA の作成を行います。

```
$ IB_OVFTOOL=1 IB_OVFTOOL_ARGS="--allowExtraConfig" make build-node-ova-vsphere-ubuntu-2004
```

順調に作成が進んでいるなあと思ったところ、エラーが発生しました...

```
vsphere: output will be in this color.

==> vsphere: File /home/ubuntu/.cache/packer/48e4ec4daa32571605576c5566f486133ecc271f.iso already uploaded; continuing
==> vsphere: File [iscsi01] packer_cache//48e4ec4daa32571605576c5566f486133ecc271f.iso already exists; skipping upload.
==> vsphere: Creating VM...
~~ snip ~~
==> vsphere: Waiting for SSH to become available...
==> vsphere: Connected to SSH!
==> vsphere: Provisioning with shell script: ./packer/files/flatcar/scripts/bootstrap-flatcar.sh
==> vsphere: Provisioning with Ansible...
    vsphere: Setting up proxy adapter for Ansible....
==> vsphere: Executing Ansible: ansible-playbook -e packer_build_name="vsphere" -e packer_*****_type=vsphere-iso -e packer_http_addr=192.168.10.151:8394 --ssh-extra-args '-o IdentitiesOnly=yes' --extra-vars containerd_url=https://github.com/containerd/containerd/releases/download/v1.6.2/cri-containerd-cni-1.6.2-linux-amd64.tar.gz containerd_sha256=91f1087d556ecfb1f148743c8ee78213cd19e07c22787dae07fe6b9314bec121 pause_image=k8s.gcr.io/pause:3.6 containerd_additional_settings= containerd_cri_socket=/var/run/containerd/containerd.sock containerd_version=1.6.2 crictl_url=https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.23.0/crictl-v1.23.0-linux-amd64.tar.gz crictl_sha256=https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.23.0/crictl-v1.23.0-linux-amd64.tar.gz.sha256 crictl_source_type=pkg custom_role_names="" firstboot_custom_roles_pre="" firstboot_custom_roles_post="" node_custom_roles_pre="" node_custom_roles_post="" disable_public_repos=false extra_debs="" extra_repos="" extra_rpms="" http_proxy= https_proxy= kubeadm_template=etc/kubeadm.yml kubernetes_cni_http_source=https://github.com/containernetworking/plugins/releases/download kubernetes_cni_http_checksum=sha256:https://storage.googleapis.com/k8s-artifacts-cni/release/v1.1.1/cni-plugins-linux-amd64-v1.1.1.tgz.sha256 kubernetes_http_source=https://dl.k8s.io/release kubernetes_container_registry=registry.k8s.io kubernetes_rpm_repo=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64 kubernetes_rpm_gpg_key="https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg" kubernetes_rpm_gpg_check=True kubernetes_deb_repo="https://apt.kubernetes.io/ kubernetes-xenial" kubernetes_deb_gpg_key=https://packages.cloud.google.com/apt/doc/apt-key.gpg kubernetes_cni_deb_version=1.1.1-00 kubernetes_cni_rpm_version=1.1.1-0 kubernetes_cni_semver=v1.1.1 kubernetes_cni_source_type=pkg kubernetes_semver=v1.23.10 kubernetes_source_type=pkg kubernetes_load_additional_imgs=false kubernetes_deb_version=1.23.10-00 kubernetes_rpm_version=1.23.10-0 no_proxy= pip_conf_file= python_path= redhat_epel_rpm=https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm epel_rpm_gpg_key= reenable_public_repos=true remove_extra_repos=false systemd_prefix=/usr/lib/systemd sysusr_prefix=/usr sysusrlocal_prefix=/usr/local load_additional_components=false additional_registry_images=false additional_registry_images_list= additional_url_images=false additional_url_images_list= additional_executables=false additional_executables_list= additional_executables_destination_path= build_target=virt amazon_ssm_agent_rpm= --extra-vars guestinfo_datasource_slug=https://raw.githubusercontent.com/vmware/cloud-init-vmware-guestinfo guestinfo_datasource_ref=v1.4.1 guestinfo_datasource_script=https://raw.githubusercontent.com/vmware/cloud-init-vmware-guestinfo/v1.4.1/install.sh --extra-vars  -e ansible_ssh_private_key_file=/tmp/ansible-key2998066837 -i /tmp/packer-provisioner-ansible3438111689 /home/ubuntu/workspace/image-*****/images/capi/ansible/firstboot.yml
    vsphere:
    vsphere: PLAY [all] *********************************************************************
==> vsphere: failed to handshake
    vsphere:
    vsphere: TASK [Gathering Facts] *********************************************************
    vsphere: fatal: [default]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Unable to negotiate with 127.0.0.1 port 41355: no matching host key type found. Their offer: ssh-rsa", "unreachable": true}
~~ snip ~~
```

どうも、localhost への ssh が失敗しているようです。`no matching host key type found` で調べると、`.ssh/config` に[以下の3行を追加すれば良いという記事](https://pyopyopyo.hatenablog.com/entry/2022/11/02/180000)が見つかったので、追加します。

```
Host *
   HostKeyAlgorithms=+ssh-rsa
   PubkeyAcceptedAlgorithms=+ssh-rsa
```

あらためて、OVA 作成のコマンドを実行すると、ansible による処理がいろいろと行われ、20分くらい経過した後、OVA が作成できました。

```
$ IB_OVFTOOL=1 IB_OVFTOOL_ARGS="--allowExtraConfig" make build-node-ova-vsphere-ubuntu-2004
~~ snip ~~
==> Wait completed after 20 minutes 17 seconds

==> Builds finished. The artifacts of successful builds are:
--> vsphere: ubuntu-2004-kube-v1.23.10
--> vsphere: ubuntu-2004-kube-v1.23.10
--> vsphere: ubuntu-2004-kube-v1.23.10
--> vsphere: ubuntu-2004-kube-v1.23.10
```

# 終わりに

途中、想定外のエラーがいくつか発生しましたが、思ったよりも簡単に Cluster API 用の OVA を作成することができました。これまでは、公式で提供されているイメージを使用していましたが、これからは、Kubernetes のバージョンやゲスト OS のバージョンを変えて、自分で検証用の OVA を作成したいと思います。