Cluster APIをつかってvSphere環境にKubernetesクラスターを作成する


[Kubernetes Cluster API Provider vSphere](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere)で、vSphere上にKubernetesクラスターを作成します。[Getting Starged](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/blob/master/docs/getting_started.md)を参考にしています。

## Cluster API

Cluster APIを使うとKubernetesのAPIを使って宣言的にKubernetesクラスターを作成することができます。クラスターはCRD(Custom Resource Definition)で定義され、マニフェストの編集するだけでKubernetesクラスターに対するノードの追加や削除も行われます。Cluster APIはいくつかのProviderによって、[様々なクラウドやオンプレミスの環境](https://github.com/kubernetes-sigs/cluster-api/#provider-implementations)にKubernetesクラスターを作成することができます。

## 準備

### 必要なもの

- Linuxホスト : Bootstrapingに必要なKuberntes環境をDockerでホストします。
- Docker : kindによりDocker上でBootstrap用のKubernetesを起動します。各種マニフェストファイルの作成にも利用します。
- [kind(Kubernetes IN Docker)](https://github.com/kubernetes-sigs/kind) : Docker上にKubernetesを構築するのに利用します。
- clusterctl : Cluster APIを利用してManagementクラスターを作成するために利用します。
- kubectl
- DHCP : 今回は作成されるKubernetes環境のMaster/NodeはすべてDHCPでIPアドレスを構成しています。

### クラスター作成の概要

vSphere上にKubernetes Workload  Clusterを作成することがゴールです。

1. kindでLinuxホストのDocker上にKubernetesを作成し、kindで作成したLinuxマシン上のKuberntesのCluster APIからvSphere上にManagement用のKubernetes Clusterを作成します。
2. Management Clusterを利用して、vSphere上にWorkload Kubernetes Clusterを作成します。

### clusterctl

clusterctlコマンドをダウンロードします。今回はv0.4.1を利用しています。(バージョン毎にオプションが変わっているので注意が必要です。)

```bash
# wget https://github.com/kubernetes-sigs/cluster-api-provider-vsphere/releases/download/v0.4.1/clusterctl.linux_amd64
# chmod +x clusterctl.linux_amd64
# mv clusterctl.linux_amd64 $PWD/bin/clusterctl
```

### kind

kindコマンドをダウンロードします。今回はv0.5.1を利用しています。

```bash
# curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.5.1/kind-$(uname)-amd64
# chmod +x ./kind
# mv kind $PWD/bin/kind
```

### envファイルの作成

Kubernetesクラスターを作成するターゲットのvSphereに関するパラメーターや、作成する仮想マシンのパラメータを```envvars.txt```で指定します。```SSH_AUTHORIZED_KEY```は作成されるノードにSSHするための公開鍵を指定します。(以下の例では省略しています。)

```bash
# vCenter config/credentials
export VSPHERE_SERVER='10.0.0.1'                # (required) The vCenter server IP or FQDN
export VSPHERE_USERNAME='viadmin@vmware.local'  # (required) The username used to access the remote vSphere endpoint
export VSPHERE_PASSWORD='some-secure-password'  # (required) The password used to access the remote vSphere endpoint

# vSphere deployment configs
export VSPHERE_DATACENTER='Datacenter'              # (required) The vSphere datacenter to deploy the management cluster on
export VSPHERE_DATASTORE='vsanDatastore'            # (required) The vSphere datastore to deploy the management cluster on
export VSPHERE_NETWORK='vm-network-1'               # (required) The VM network to deploy the management cluster on
export VSPHERE_RESOURCE_POOL='Resources'            # (required) The vSphere resource pool for your VMs
export VSPHERE_FOLDER='vm'                          # (optional) The VM folder for your VMs, defaults to the root vSphere folder if not set.
export VSPHERE_TEMPLATE='ubuntu-1804-kube-v1.15.3'  # (required) The VM template to use for your management cluster.
export VSPHERE_DISK_GIB='50'                        # (optional) The VM Disk size in GB, defaults to 20 if not set
export VSPHERE_NUM_CPUS='2'                         # (optional) The # of CPUs for control plane nodes in your management cluster, defaults to 2 if not set
export VSPHERE_MEM_MIB='2048'                       # (optional) The memory (in MiB) for control plane nodes in your management cluster, defaults to 2048 if not set
export SSH_AUTHORIZED_KEY='ssh-rsa AAAAB3N...'      # (optional) The public ssh authorized key on all machines in this cluster

# Kubernetes configs
export KUBERNETES_VERSION='1.15.3'        # (optional) The Kubernetes version to use, defaults to 1.13.6
export SERVICE_CIDR='100.64.0.0/13'       # (optional) The service CIDR of the management cluster, defaults to "100.64.0.0/13"
export CLUSTER_CIDR='100.96.0.0/11'       # (optional) The cluster CIDR of the management cluster, defaults to "100.96.0.0/11"
```

### テンプレート用OVAファイルのデプロイ

クラスター作成時に利用する仮想マシンテンプレートを作成しておく必要があります。[ここ](https://github.com/kubernetes-sigs/cluster-api-provider-vsphere)からインストールするKuberntesバージョン(今回は1.15.3)のOVAファイルをダウンロードして、vSphere上にデプロイします。OVAはCentOS 7ベースのものと、Ubuntu 18.04ベースのものが提供されています。今回はUbuntu 18.04を利用しました。VM名は```envvars.txt```の```VSPHERE_TEMPLATE```に指定した名前にしておきます。また、テンプレートのVMDKのタイプをThickにしておくと作成されるKubernetesクラスターがThickを継承するので必要がなければ、Thinでデプロイすることをお勧めします。

## Managemt Clusterの作成

### Management Cluster用マニフェストの作成

作成したenvファイルをもとにDockerコンテナを利用してマニフェストファイルを作成します。```$PWD/out/management-cluster/```配下にマニフェストファイルが作成されます。

```bash
# docker run --rm \
  -v "$(pwd)":/out \
  -v "$(pwd)/envvars.txt":/envvars.txt:ro \
  gcr.io/cluster-api-provider-vsphere/release/manifests:v0.4.1 \
  -c management-cluster

# ls -l out/management-cluster/
total 132
-rw-r--r-- 1 root root  19828 Sep  4 19:18 addons.yaml
-rw-r--r-- 1 root root    879 Sep  4 19:18 cluster.yaml
-rw-r--r-- 1 root root   1087 Sep  4 19:18 machineset.yaml
-rw-r--r-- 1 root root    774 Sep  4 19:18 machines.yaml
-rw-r--r-- 1 root root 101245 Sep  4 19:18 provider-components.yaml
```

### clusterctlによるMnagement Clusterの作成

clusterctlコマンドを使って作成したマニフェストファイルから、Management Clusterを作成します。

```bash
# clusterctl create cluster \
  --provider vsphere \
  --bootstrap-type kind \
  --cluster ./out/management-cluster/cluster.yaml \
  --machines ./out/management-cluster/machines.yaml \
  --provider-components ./out/management-cluster/provider-components.yaml \
  --addon-components ./out/management-cluster/addons.yaml \
  --kubeconfig-out ./out/management-cluster/kubeconfig
I0904 19:47:47.988375   23481 createbootstrapcluster.go:27] Preparing bootstrap cluster
I0904 19:48:28.796179   23481 clusterdeployer.go:78] Applying Cluster API stack to bootstrap cluster
I0904 19:48:28.796201   23481 applyclusterapicomponents.go:26] Applying Cluster API Provider Components
I0904 19:48:29.491992   23481 clusterdeployer.go:83] Provisioning target cluster via bootstrap cluster
I0904 19:48:29.497501   23481 applycluster.go:36] Creating cluster object management-cluster in namespace "default"
I0904 19:48:29.505449   23481 clusterdeployer.go:92] Creating control plane machine in namespace "default"
I0904 19:48:29.510339   23481 applymachines.go:36] Creating machines in namespace "default"
I0904 19:51:39.585337   23481 clusterdeployer.go:97] Updating bootstrap cluster object for cluster management-cluster in namespace "default" with control plane endpoint running on machine
I0904 19:51:39.676422   23481 clusterdeployer.go:102] Creating target cluster
I0904 19:51:39.954416   23481 applyaddons.go:25] Applying Addons
I0904 19:51:41.198014   23481 clusterdeployer.go:120] Pivoting Cluster API stack to target cluster
I0904 19:51:41.198058   23481 pivot.go:67] Applying Cluster API Provider Components to Target Cluster
I0904 19:51:42.138461   23481 pivot.go:72] Pivoting Cluster API objects from bootstrap to target cluster.
I0904 19:51:56.363443   23481 clusterdeployer.go:125] Saving provider components to the target cluster
I0904 19:51:56.386356   23481 clusterdeployer.go:133] Updating target cluster object with control plane endpoint running on machine
I0904 19:51:56.416666   23481 clusterdeployer.go:149] Creating node machines in target cluster.
I0904 19:51:56.422778   23481 applymachines.go:36] Creating machines in namespace "default"
I0904 19:51:56.422794   23481 clusterdeployer.go:154] Done provisioning cluster. You can now access your cluster with kubectl --kubeconfig ./out/management-cluster/kubeconfig
I0904 19:51:56.422904   23481 createbootstrapcluster.go:36] Cleaning up bootstrap cluster.
```

最初にkindがDockerイメージをダウンロードして、Docker上にKubernetesを作成します。しばらくすると、vSphere上に```management-cluster-controlplane-1```という名前のVMが作成されます。ブートストラップ中は、kindがブートストラップ用のKubernetesを起動しているのが確認できますが、ブートストラップが終了するとDockerコンテナは自動的に削除されます。

```bash
# docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS              PORTS                                  NAMES
5de63a2bc705        kindest/node:v1.15.3   "/usr/local/bin/entr…"   About a minute ago   Up About a minute   40323/tcp, 127.0.0.1:40323->6443/tcp   clusterapi-control-plane
```

kubectlの接続先をManagement Clusterに変更し、Management Clusterが正常なことを確認します。

```bash
# export KUBECONFIG=$PWD/out/management-cluster/kubeconfig
# kubectl get nodes
NAME                                STATUS   ROLES    AGE     VERSION
management-cluster-controlplane-1   Ready    master   6m50s   v1.15.3
```

CRDを確認すると、ClusterAPIに関連するものが存在することが確認できます。

```bash
# kubectl get crd | grep cluster
clusterinformations.crd.projectcalico.org               2019-09-04T10:51:40Z
clusters.cluster.k8s.io                                 2019-09-04T10:51:41Z
machineclasses.cluster.k8s.io                           2019-09-04T10:51:41Z
machinedeployments.cluster.k8s.io                       2019-09-04T10:51:41Z
machines.cluster.k8s.io                                 2019-09-04T10:51:42Z
machinesets.cluster.k8s.io                              2019-09-04T10:51:42Z
vsphereclusterproviderspecs.vsphere.cluster.k8s.io      2019-09-04T10:51:41Z
vsphereclusterproviderstatuses.vsphere.cluster.k8s.io   2019-09-04T10:51:41Z
vspheremachineproviderspecs.vsphere.cluster.k8s.io      2019-09-04T10:51:41Z
vspheremachineproviderstatuses.vsphere.cluster.k8s.io   2019-09-04T10:51:41Z
```

Management Clsuterにはcluster-api-systemとvsphere-provider-systemネームスペースが追加されています。

```bash
# kubectl get namespace
NAME                      STATUS   AGE
cluster-api-system        Active   7m12s
default                   Active   7m24s
kube-node-lease           Active   7m28s
kube-public               Active   7m28s
kube-system               Active   7m28s
vsphere-provider-system   Active   7m12s
```

cluster-api-systemにはStatefulSetが作成され、cluster-api-controller-manager Podが作成されています。

```bash
# kubectl get statefulset,pod -n cluster-api-system
NAME                                              READY   AGE
statefulset.apps/cluster-api-controller-manager   1/1     7m30s

NAME                                   READY   STATUS    RESTARTS   AGE
pod/cluster-api-controller-manager-0   1/1     Running   0          7m25s
```

vsphere-provider-systemにもStatefulSetが作成され、vsphere-provider-controller-managerが作成されています。

```bash
# kubectl get statefulset,pod -n vsphere-provider-system
NAME                                                   READY   AGE
statefulset.apps/vsphere-provider-controller-manager   1/1     7m50s

NAME                                        READY   STATUS    RESTARTS   AGE
pod/vsphere-provider-controller-manager-0   1/1     Running   0          7m44s
```

## Workload Clusterの作成

### Workload Cluster用マニフェストの作成

```bash
# docker run --rm ¥
  -v "$(pwd)":/out ¥
  -v "$(pwd)/envvars.txt":/envvars.txt:ro ¥
  gcr.io/cluster-api-provider-vsphere/release/manifests:v0.4.1 ¥
  -c workload-cluster-1
# ls -l /root/out/workload-cluster-1/
total 132
-rw-r--r-- 1 root root  19828 Sep  4 20:00 addons.yaml
-rw-r--r-- 1 root root    879 Sep  4 20:00 cluster.yaml
-rw-r--r-- 1 root root   1087 Sep  4 20:00 machineset.yaml
-rw-r--r-- 1 root root    774 Sep  4 20:00 machines.yaml
-rw-r--r-- 1 root root 101245 Sep  4 20:00 provider-components.yaml
```

### Workload Cluster用マニフェストの適用

Management Clusterに対してマニフェストを適用して、Workload Clusterを作成します。

```bash
kubectl apply -f ./out/workload-cluster-1/cluster.yaml
kubectl apply -f ./out/workload-cluster-1/machines.yaml
kubectl apply -f ./out/workload-cluster-1/machineset.yaml
```

しばらくすると、vSphere上に```workload-cluster-1-controlplane-1```が作成され、WorkloadクラスターのMasterとして構成され、それを追って```workload-cluster-1-machineset-1-xxxx```が2台作成されます。

```bash
# kubectl get cluster
NAME                 AGE
management-cluster   11m
workload-cluster-1   2m10s
# kubectl get machineset
NAME                              AGE
workload-cluster-1-machineset-1   2m20s
# kubectl get machines
NAME                                    PROVIDERID                                       PHASE
management-cluster-controlplane-1       vsphere://422ae07d-1d4c-f94d-569d-fcd8a99c6e84
workload-cluster-1-controlplane-1       vsphere://422a4622-6591-7807-2f31-1f009f008951
workload-cluster-1-machineset-1-8kh2q
workload-cluster-1-machineset-1-sbv7x
```

しばらくすると、Machinesリソースが以下のようにすべて```PROVIDED```に値が入ります。

```bash
# kubectl get machines
NAME                                    PROVIDERID                                       PHASE
management-cluster-controlplane-1       vsphere://422ae07d-1d4c-f94d-569d-fcd8a99c6e84
workload-cluster-1-controlplane-1       vsphere://422a4622-6591-7807-2f31-1f009f008951
workload-cluster-1-machineset-1-8kh2q   vsphere://422a6d58-1871-1e54-35e7-47baf346c149
workload-cluster-1-machineset-1-sbv7x   vsphere://422a8747-6a1e-96b4-596c-8592edf6cc9f
```

### Workload Cluster用設定ファイルの取得

Workloadクラスターに接続するための設定ファイルはManagemetクラスターのSecretとして格納されているので取り出して利用します。

```bash
# kubectl get secret
NAME                            TYPE                                  DATA   AGE
default-token-xprdb             kubernetes.io/service-account-token   3      15m
management-cluster-kubeconfig   Opaque                                1      15m
workload-cluster-1-kubeconfig   Opaque                                1      4m21s
# kubectl get secret workload-cluster-1-kubeconfig -o jsonpath='{.data.value}' | base64 -d > ~/.kube/config
```

接続先をWorkloadクラスターに変更して状態を確認すると、すべて```NotReady```となっています。これはCNIが構成されていないためです。マニフェストファイルの```addons.yaml```を利用するとCNIとして[Calico](https://www.projectcalico.org/)を構成することができます。

```bash
# export KUBECONFIG=$HOME/.kube/config
# kubectl get nodes
NAME                                    STATUS     ROLES    AGE     VERSION
workload-cluster-1-controlplane-1       NotReady   master   5m26s   v1.15.3
workload-cluster-1-machineset-1-8kh2q   NotReady   node     3m48s   v1.15.3
workload-cluster-1-machineset-1-sbv7x   NotReady   node     3m47s   v1.15.3

# kubectl apply -f out/workload-cluster-1/addons.yaml

# kubectl get nodes
NAME                                    STATUS   ROLES    AGE     VERSION
workload-cluster-1-controlplane-1       Ready    master   6m33s   v1.15.3
workload-cluster-1-machineset-1-8kh2q   Ready    node     4m55s   v1.15.3
workload-cluster-1-machineset-1-sbv7x   Ready    node     4m54s   v1.15.3
```

## Workload Clusterに対するノード追加

Workloadクラスターのノード数は、Machinesetによって定義されているため、Management ClusterのMachinsetの```spec.replicas```を変更することで簡単にノードを追加・削除することが可能です。

```bash
# export KUBECONFIG=/root/out/management-cluster/kubeconfig
# kubectl edit machineset workload-cluster-1-machineset-1
```

MachineSetマニフェストのレプリカ数を2から6に変更します。

```yaml
apiVersion: cluster.k8s.io/v1alpha1
kind: MachineSet
metadata:
...
spec:
  replicas: 6
```

しばらくするとvSphere上で仮想マシンが４台追加され、Management Cluster上のMachinesリソースが4つ増えていることを確認できます。

```bash
# kubectl get machines
NAME                                    PROVIDERID                                       PHASE
management-cluster-controlplane-1       vsphere://422ae07d-1d4c-f94d-569d-fcd8a99c6e84
workload-cluster-1-controlplane-1       vsphere://422a4622-6591-7807-2f31-1f009f008951
workload-cluster-1-machineset-1-5br4h   vsphere://422a4ad5-763c-02a6-8278-1092a357b649
workload-cluster-1-machineset-1-8kh2q   vsphere://422a6d58-1871-1e54-35e7-47baf346c149
workload-cluster-1-machineset-1-cwxrx   vsphere://422ac22f-f47c-f411-8825-8a4cac60076c
workload-cluster-1-machineset-1-mjqck   vsphere://422a1446-9374-bda1-6826-3f844d2879da
workload-cluster-1-machineset-1-pgb7l   vsphere://422ae547-dafc-7177-a3ec-ef6d152005f0
workload-cluster-1-machineset-1-sbv7x   vsphere://422a8747-6a1e-96b4-596c-8592edf6cc9f
```

Workloadクラスターに接続してノードを確認すると指定した6台にノード数が増えていることが確認できました。

```bash
# export KUBECONFIG=$HOME/.kube/config
# kubectl get nodes
NAME                                    STATUS   ROLES    AGE   VERSION
workload-cluster-1-controlplane-1       Ready    master   15m   v1.15.3
workload-cluster-1-machineset-1-5br4h   Ready    node     43s   v1.15.3
workload-cluster-1-machineset-1-8kh2q   Ready    node     13m   v1.15.3
workload-cluster-1-machineset-1-cwxrx   Ready    node     45s   v1.15.3
workload-cluster-1-machineset-1-mjqck   Ready    node     45s   v1.15.3
workload-cluster-1-machineset-1-pgb7l   Ready    node     44s   v1.15.3
workload-cluster-1-machineset-1-sbv7x   Ready    node     13m   v1.15.3
```

## まとめ

Cluster APIを利用することでvSphere環境にとても簡単にKubernetesクラスターを作成することができました。また、マニフェストを更新することでクラスターのスケールアウト/インが簡単に行うことが可能です。