Kubernetes Cluster API Provider BYOHを試してみる

[Kubernetes Cluster API Provider Bring Your Own Host (BYOH)](https://github.com/vmware-tanzu/cluster-api-provider-bringyourownhost)は、インストール済みのLinuxノードに対してClustere APIを利用してKubernetesクラスターを構成することが可能です。ベアメタル環境のLinuxマシン等、Infrastructure Providerが存在しない環境でCluster APIを利用してKubernetesを構成することができます。現在の最新バージョンは[v0.2.0](https://github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/releases/tag/v0.2.0)です。[Getting Startedドキュメントに従って](https://github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/blob/main/docs/getting_started.md)kindでinfrastructure byohをホストして、LinxマシンでKubernetesクラスターを作成してみました。

## Management Clusterの作成

Cluster APIをホストするManagement Clusterを作成します。kindで作ります。外部にKubernetes APIを公開するため、以下のようなkind.configファイルを作成してkindでKubernetesクラスターを作成します。

```yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  apiServerAddress: "192.168.200.107"
  apiServerPort: 6443
```

kind.configファイルを指定してクラスターを作成します。

```bash
kind create cluster --config=kind.config
```

`clusterctl`CLIをダウンロードしてインストールします。

```bash
wget https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.1.3/clusterctl-linux-amd64
sudo install clusterctl-linux-amd64 /usr/local/bin/clusterctl
```

`clusterctl`CLIでManagement ClusterにCluster APIとinfrastructure-byohをインストールします。

```bash
$ clusterctl init --infrastructure byoh
Fetching providers
Installing cert-manager Version="v1.5.3"
Waiting for cert-manager to be available...
Installing Provider="cluster-api" Version="v1.1.3" TargetNamespace="capi-system"
Installing Provider="bootstrap-kubeadm" Version="v1.1.3" TargetNamespace="capi-kubeadm-bootstrap-system"
Installing Provider="control-plane-kubeadm" Version="v1.1.3" TargetNamespace="capi-kubeadm-control-plane-system"
Installing Provider="infrastructure-byoh" Version="v0.1.1" TargetNamespace="byoh-system"

Your management cluster has been initialized successfully!

You can now create your first workload cluster by running the following:

  clusterctl generate cluster [name] --kubernetes-version [version] | kubectl apply -f -
```

Cluster APIとBYOH Infrastructure ProviderのCRDが追加されます。

```bash
$ kubectl get crd | grep cluster.x-k8s.io
byoclusters.infrastructure.cluster.x-k8s.io                  2022-05-07T06:44:00Z
byohosts.infrastructure.cluster.x-k8s.io                     2022-05-07T06:44:00Z
byomachines.infrastructure.cluster.x-k8s.io                  2022-05-07T06:44:00Z
byomachinetemplates.infrastructure.cluster.x-k8s.io          2022-05-07T06:44:00Z
clusterclasses.cluster.x-k8s.io                              2022-05-07T06:43:57Z
clusterresourcesetbindings.addons.cluster.x-k8s.io           2022-05-07T06:43:57Z
clusterresourcesets.addons.cluster.x-k8s.io                  2022-05-07T06:43:57Z
clusters.cluster.x-k8s.io                                    2022-05-07T06:43:58Z
kubeadmconfigs.bootstrap.cluster.x-k8s.io                    2022-05-07T06:43:58Z
kubeadmconfigtemplates.bootstrap.cluster.x-k8s.io            2022-05-07T06:43:58Z
kubeadmcontrolplanes.controlplane.cluster.x-k8s.io           2022-05-07T06:43:59Z
kubeadmcontrolplanetemplates.controlplane.cluster.x-k8s.io   2022-05-07T06:43:59Z
machinedeployments.cluster.x-k8s.io                          2022-05-07T06:43:58Z
machinehealthchecks.cluster.x-k8s.io                         2022-05-07T06:43:58Z
machinepools.cluster.x-k8s.io                                2022-05-07T06:43:58Z
machines.cluster.x-k8s.io                                    2022-05-07T06:43:58Z
machinesets.cluster.x-k8s.io                                 2022-05-07T06:43:58Z
providers.clusterctl.cluster.x-k8s.io                        2022-05-07T06:43:31Z
```

cert-managerが追加され、capi-controllerとbyoh-controller等がpodとして起動します。

```bash
$ kubectl get po -A
NAMESPACE                           NAME                                                             READY   STATUS    RESTARTS   AGE
byoh-system                         byoh-controller-manager-5886bfb5df-v2rmw                         2/2     Running   0          2d15h
capi-kubeadm-bootstrap-system       capi-kubeadm-bootstrap-controller-manager-7694977987-9lr2d       1/1     Running   0          2d15h
capi-kubeadm-control-plane-system   capi-kubeadm-control-plane-controller-manager-5c887bc7f9-6wh5t   1/1     Running   0          2d15h
capi-system                         capi-controller-manager-756b9b6576-xxmc8                         1/1     Running   0          2d15h
cert-manager                        cert-manager-6888d6b69b-btbdj                                    1/1     Running   0          2d15h
cert-manager                        cert-manager-cainjector-76f7798c9-fw6n6                          1/1     Running   0          2d15h
cert-manager                        cert-manager-webhook-7d4b5d8484-n5jsf                            1/1     Running   0          2d15h
kube-system                         coredns-64897985d-jqc7q                                          1/1     Running   0          2d15h
kube-system                         coredns-64897985d-mg82d                                          1/1     Running   0          2d15h
kube-system                         etcd-kind-control-plane                                          1/1     Running   0          2d15h
kube-system                         kindnet-fr79p                                                    1/1     Running   0          2d15h
kube-system                         kube-apiserver-kind-control-plane                                1/1     Running   0          2d15h
kube-system                         kube-controller-manager-kind-control-plane                       1/1     Running   0          2d15h
kube-system                         kube-proxy-rb5xr                                                 1/1     Running   0          2d15h
kube-system                         kube-scheduler-kind-control-plane                                1/1     Running   0          2d15h
local-path-storage                  local-path-provisioner-5ddd94ff66-286hb                          1/1     Running   0          2d15h
```

byoshostで利用するため、Management Clusterに対するアクセス情報をファイルに出力しておきます。

```bash
kind get kubeconfig > management-cluster.conf
```

## byohostの作成

KubernetesクラスターのノードとなるLinuxマシンを用意します。現在Cluster API Providere BYOHが対応しているのはUbuntu 20.04以上となっているため今回はUbuntu 20.04の仮想マシン(2vCPU/8GB RAM)を6台用意しました。

```bash
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.4 LTS
Release:        20.04
Codename:       focal
```

byohostsリソースを確認すると、マシンは存在しません。

```bash
$ kubectl get byohosts.infrastructure.cluster.x-k8s.io
No resources found in default namespace.
```

[リリースページ](https://github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/releases/tag/v0.2.0)からbyoh-hostagent-linux-amd64をダウンロードして各マシンに配布します。

```bash
wget https://github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/releases/download/v0.2.0/byoh-hostagent-linux-amd64
```

Management Clusterに対するアクセス情報(management-cluster.conf)もあわせて配布し、各マシンでbyoh-hostagentを起動します。

```bash
for i in byoh-master1 byoh-master2 byoh-master3 byoh-node1 byoh-node2 byoh-node3
do
scp management-cluster.conf root@$i:management-cluster.conf
scp byoh-hostagent-linux-amd64 root@$i:byoh-hostagent-linux-amd64
ssh root@$i chmod +x byoh-hostagent-linux-amd64
ssh root@$i './byoh-hostagent-linux-amd64 --kubeconfig management-cluster.conf > byoh-agent.log 2>&1 &'
ssh root@$i ps ax | grep byoh
done
```

各マシンでbyoh-hostagentが起動して、Management Clusterに登録されたのでbyohostsリソースとして確認できます。

```bash
$ kubectl get byohosts.infrastructure.cluster.x-k8s.io
NAME           AGE
byoh-master1   9s
byoh-master2   8s
byoh-master3   7s
byoh-node1     6s
byoh-node2     5s
byoh-node3     4s
```

## Kubernetesクラスターの作成

クラスターを作成するためのマニフェストを作成します。`CONTROL_PLANE_ENDPOINT_IP`はkube-vipによりKubernetes APIのVIPとなります。`--control-plane-machine-count`と`--worker-machine-count `でMasterとNodeの台数を指定します。

```bash
BUNDLE_LOOKUP_TAG=v1.23.5 CONTROL_PLANE_ENDPOINT_IP=192.168.200.110 clusterctl generate cluster byoh-cluster \
  --infrastructure byoh \
  --kubernetes-version v1.23.5 \
  --control-plane-machine-count 3 \
  --worker-machine-count 3 > cluster.yaml
```

cluster.yamlは以下のような内容で作成されます。

```yaml
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: KubeadmConfigTemplate
metadata:
  name: byoh-cluster-md-0
  namespace: default
spec:
  template:
    spec: {}
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  labels:
    cni: byoh-cluster-crs-0
    crs: "true"
  name: byoh-cluster
  namespace: default
spec:
  clusterNetwork:
    pods:
      cidrBlocks:
      - 192.168.0.0/16
    serviceDomain: cluster.local
    services:
      cidrBlocks:
      - 10.128.0.0/12
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1beta1
    kind: KubeadmControlPlane
    name: byoh-cluster-control-plane
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
    kind: ByoCluster
    name: byoh-cluster
---
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
  name: byoh-cluster-md-0
  namespace: default
spec:
  clusterName: byoh-cluster
  replicas: 3
  selector:
    matchLabels: null
  template:
    metadata:
      labels:
        nodepool: pool1
    spec:
      bootstrap:
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
          kind: KubeadmConfigTemplate
          name: byoh-cluster-md-0
      clusterName: byoh-cluster
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
        kind: ByoMachineTemplate
        name: byoh-cluster-md-0
      version: v1.23.5
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: KubeadmControlPlane
metadata:
  labels:
    nodepool: pool0
  name: byoh-cluster-control-plane
  namespace: default
spec:
  kubeadmConfigSpec:
    clusterConfiguration:
      apiServer:
        certSANs:
        - localhost
        - 127.0.0.1
        - 0.0.0.0
        - host.docker.internal
      controllerManager:
        extraArgs:
          enable-hostpath-provisioner: "true"
    files:
    - content: |
        apiVersion: v1
        kind: Pod
        metadata:
          creationTimestamp: null
          name: kube-vip
          namespace: kube-system
        spec:
          containers:
          - args:
            - start
            env:
            - name: vip_arp
              value: "true"
            - name: vip_leaderelection
              value: "true"
            - name: vip_address
              value: 192.168.200.110
            - name: vip_interface
              value: {{ .DefaultNetworkInterfaceName }}
            - name: vip_leaseduration
              value: "15"
            - name: vip_renewdeadline
              value: "10"
            - name: vip_retryperiod
              value: "2"
            image: ghcr.io/kube-vip/kube-vip:v0.3.5
            imagePullPolicy: IfNotPresent
            name: kube-vip
            resources: {}
            securityContext:
              capabilities:
                add:
                - NET_ADMIN
                - SYS_TIME
            volumeMounts:
            - mountPath: /etc/kubernetes/admin.conf
              name: kubeconfig
          hostNetwork: true
          volumes:
          - hostPath:
              path: /etc/kubernetes/admin.conf
              type: FileOrCreate
            name: kubeconfig
        status: {}
      owner: root:root
      path: /etc/kubernetes/manifests/kube-vip.yaml
    initConfiguration:
      nodeRegistration:
        criSocket: /var/run/containerd/containerd.sock
        ignorePreflightErrors:
        - Swap
        - DirAvailable--etc-kubernetes-manifests
        - FileAvailable--etc-kubernetes-kubelet.conf
    joinConfiguration:
      nodeRegistration:
        criSocket: /var/run/containerd/containerd.sock
        ignorePreflightErrors:
        - Swap
        - DirAvailable--etc-kubernetes-manifests
        - FileAvailable--etc-kubernetes-kubelet.conf
  machineTemplate:
    infrastructureRef:
      apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
      kind: ByoMachineTemplate
      name: byoh-cluster-control-plane
      namespace: default
  replicas: 3
  version: v1.23.5
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: ByoCluster
metadata:
  name: byoh-cluster
  namespace: default
spec:
  bundleLookupBaseRegistry: projects.registry.vmware.com/cluster_api_provider_bringyourownhost
  bundleLookupTag: v1.23.5
  controlPlaneEndpoint:
    host: 192.168.200.110
    port: 6443
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: ByoMachineTemplate
metadata:
  name: byoh-cluster-md-0
  namespace: default
spec:
  template:
    spec: {}
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: ByoMachineTemplate
metadata:
  name: byoh-cluster-control-plane
  namespace: default
spec:
  template:
    spec: {}
```

このままクラスターを作成すると、6台のマシンがMasterとNodeに適当に割り当てらるので、マシンにラベルをつけてMaster用とNode用のマシンを区別します。cluster.yamlのControlPlaneとWorkerのByoMachineTempalteを以下のように修正します。 (参考 [Ability to select a specific host/set of hosts for control plane / worker nodes #276](https://github.com/vmware-tanzu/cluster-api-provider-bringyourownhost/issues/276))

```yaml
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: ByoMachineTemplate
metadata:
  name: byoh-cluster-md-0
  namespace: default
spec:
  template:
    spec:
      selector:
        matchLabels:
          "type": "worker"
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: ByoMachineTemplate
metadata:
  name: byoh-cluster-control-plane
  namespace: default
spec:
  template:
    spec:
      selector:
        matchLabels:
          "type": "controlplane"
```

byohostsにMaster用とNode用のラベルをつけます。

```bash
kubectl label byohosts.infrastructure.cluster.x-k8s.io byoh-master1 type=controlplane
kubectl label byohosts.infrastructure.cluster.x-k8s.io byoh-master2 type=controlplane
kubectl label byohosts.infrastructure.cluster.x-k8s.io byoh-master3 type=controlplane
kubectl label byohosts.infrastructure.cluster.x-k8s.io byoh-node1 type=worker
kubectl label byohosts.infrastructure.cluster.x-k8s.io byoh-node2 type=worker
kubectl label byohosts.infrastructure.cluster.x-k8s.io byoh-node3 type=worker
```

ラベルが付いたことを確認します。

```bash
$ kubectl get byohosts.infrastructure.cluster.x-k8s.io -L type
NAME           AGE     TYPE
byoh-master1   7m2s    controlplane
byoh-master2   7m1s    controlplane
byoh-master3   7m      controlplane
byoh-node1     6m59s   worker
byoh-node2     6m58s   worker
byoh-node3     6m57s   worker
```

クラスター作成用のマニフェスト(cluster.yaml)を利用してクラスターを作成します。

```bash
$ kubectl apply -f cluster.yaml
kubeadmconfigtemplate.bootstrap.cluster.x-k8s.io/byoh-cluster-md-0 created
cluster.cluster.x-k8s.io/byoh-cluster created
machinedeployment.cluster.x-k8s.io/byoh-cluster-md-0 created
kubeadmcontrolplane.controlplane.cluster.x-k8s.io/byoh-cluster-control-plane created
byocluster.infrastructure.cluster.x-k8s.io/byoh-cluster created
byomachinetemplate.infrastructure.cluster.x-k8s.io/byoh-cluster-md-0 created
byomachinetemplate.infrastructure.cluster.x-k8s.io/byoh-cluster-control-plane created
```

しばらくすると、byohostsがMachineリソースとして登録され、Cluster APIによりクラスターが構成されます。

```bash
$ kubectl get cluster
NAME           PHASE         AGE     VERSION
byoh-cluster   Provisioned   4m56s
$ kubectl get byoclusters.infrastructure.cluster.x-k8s.io
NAME           AGE
byoh-cluster   4m59s
$ kubectl get kubeadmcontrolplane
NAME                         CLUSTER        INITIALIZED   API SERVER AVAILABLE   REPLICAS   READY   UPDATED   UNAVAILABLE   AGE    VERSION
byoh-cluster-control-plane   byoh-cluster   true                                 3                  3         3             5m5s   v1.23.5
$ kubectl get machinesets.cluster.x-k8s.io
NAME                           CLUSTER        REPLICAS   READY   AVAILABLE   AGE     VERSION
byoh-cluster-md-0-866c95bdcd   byoh-cluster   3                              5m13s   v1.23.5
$ kubectl get machine
NAME                                 CLUSTER        NODENAME       PROVIDERID                   PHASE     AGE     VERSION
byoh-cluster-control-plane-d9nm6     byoh-cluster   byoh-master1   byoh://byoh-master1/i4szip   Running   5m16s   v1.23.5
byoh-cluster-control-plane-gbhld     byoh-cluster   byoh-master3   byoh://byoh-master3/6deh0f   Running   113s    v1.23.5
byoh-cluster-control-plane-qg6vf     byoh-cluster   byoh-master2   byoh://byoh-master2/0gbuc5   Running   3m51s   v1.23.5
byoh-cluster-md-0-866c95bdcd-p7b7v   byoh-cluster   byoh-node3     byoh://byoh-node3/oymt6s     Running   5m18s   v1.23.5
byoh-cluster-md-0-866c95bdcd-sqcbz   byoh-cluster   byoh-node2     byoh://byoh-node2/cdyrn6     Running   5m18s   v1.23.5
byoh-cluster-md-0-866c95bdcd-wf4jg   byoh-cluster   byoh-node1     byoh://byoh-node1/4qth8r     Running   5m18s   v1.23.5
```

作成したクラスターにアクセスするためのkubeconfigをsecretから取得します。

```bash
kubectl get secret/byoh-cluster-kubeconfig -o json | jq -r .data.value | base64 --decode > ./byoh-cluster.kubeconfig
```

作成したkubeconfigを指定してAPIにアクセスすると、6台のマシンでクラスターが構成されています。CNIが構成されないため`NotReady`状態となっています。

```
$ kubectl --kubeconfig=byoh-cluster.kubeconfig get node -o wide
NAME           STATUS     ROLES                  AGE     VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
byoh-master1   NotReady   control-plane,master   6m3s    v1.23.5   192.168.200.41   <none>        Ubuntu 20.04.4 LTS   5.4.0-109-generic   containerd://1.6.0
byoh-master2   NotReady   control-plane,master   4m40s   v1.23.5   192.168.200.85   <none>        Ubuntu 20.04.4 LTS   5.4.0-109-generic   containerd://1.6.0
byoh-master3   NotReady   control-plane,master   2m45s   v1.23.5   192.168.200.43   <none>        Ubuntu 20.04.4 LTS   5.4.0-109-generic   containerd://1.6.0
byoh-node1     NotReady   <none>                 4m42s   v1.23.5   192.168.200.82   <none>        Ubuntu 20.04.4 LTS   5.4.0-109-generic   containerd://1.6.0
byoh-node2     NotReady   <none>                 4m57s   v1.23.5   192.168.200.83   <none>        Ubuntu 20.04.4 LTS   5.4.0-109-generic   containerd://1.6.0
byoh-node3     NotReady   <none>                 4m56s   v1.23.5   192.168.200.21   <none>        Ubuntu 20.04.4 LTS   5.4.0-109-generic   containerd://1.6.0
```

CNIとしてCalicoをインストールします。

```bash
kubectl --kubeconfig=byoh-cluster.kubeconfig apply -f https://docs.projectcalico.org/v3.20/manifests/calico.yaml
```

暫く待つと`Ready`になりました。

```bash
$ kubectl --kubeconfig=byoh-cluster.kubeconfig get node
NAME           STATUS   ROLES                  AGE     VERSION
byoh-master1   Ready    control-plane,master   11m     v1.23.5
byoh-master2   Ready    control-plane,master   9m49s   v1.23.5
byoh-master3   Ready    control-plane,master   7m54s   v1.23.5
byoh-node1     Ready    <none>                 9m51s   v1.23.5
byoh-node2     Ready    <none>                 10m     v1.23.5
byoh-node3     Ready    <none>                 10m     v1.23.5
```