図解: Cluster APIを使ってKubernetesクラスタを作る  (on Docker)

# Cluster APIを使うと何ができるのか

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/134664f7-e596-7609-423b-a5172acc7f27.png width=200px>


[Cluster API](https://cluster-api.sigs.k8s.io/)とは、クラスタ管理を宣言的APIで実現するKubernetesプロジェクトです。
[Quickstart](https://cluster-api.sigs.k8s.io/user/quick-start.html)を実行することで、Cluster APIの実現機能が把握できます。

今回は、このQuickstartの流れを実機で試してみます。流れをまとめた次のような4コマ漫画を作りました。

| 1. 最初はManagement Clusterだけがあります。 |
|---|
|![first.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/863267d1-4db1-61bf-95de-f77190edaaac.png)|
| 2. `Cluster`を追加すると、クラスタが新規作成されますが、<br/>**中には何もありません**。 |
|![cluster.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/38a87bbf-b872-d069-3c67-1e4c3e565b2f.png)|
| 3. `Machine`を追加すると、マスターノードが追加されます。 |
|![machine.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/5e3d0fc2-36b0-940c-1858-a9cb9a5c202f.png) |
| 4. `MachineDeployment`を追加すると、ワーカノード追加が追加されます。 |
|![last.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/361173ba-6503-e1be-b147-0a7725af543f.png) |

以下ではこの流れに沿って、手順を実行してみます。

# 環境

今回は、Docker Desktop for mac v2.2.0.0 がインストールされたMacbook Proで実施しました。その他以下がインストールされています。

- `kind` 1.17

# Management Clusterの準備

まずは4コマ漫画の1コマ目です。Cluster APIを持つクラスタ、Management Clusterを用意します。

![first.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/863267d1-4db1-61bf-95de-f77190edaaac.png)

## 最小限のKubernetesの準備
Cluster APIをインストールするには、Kubernetesクラスタが必要です。このクラスタをManagement Clusterといいます。クラスタを作る前にクラスタが必要というのは、少し手間なように感じます。でもAPIが利用できればよいので、マスターノードだけでかまいません。

ここでは、`kind`というツールを使いマスターノード１台だけの最小限のクラスタを用意します。`control-plane`というのは、`kind`の設定でマスターノードと言う意味です。

```sh
cat > kind-cluster-with-extramounts.yaml <<EOF
kind: Cluster
apiVersion: kind.sigs.k8s.io/v1alpha3
nodes:
- role: control-plane
  extraMounts:
    - hostPath: /var/run/docker.sock
      containerPath: /var/run/docker.sock
EOF
kind create cluster --config ./kind-cluster-with-extramounts.yaml --name clusterapi
```

## Cluster API のインストール

Cluster APIは、設定ファイルに相当するCRD (CustomResourceDefinition)と、その設定にしたがってクラスタを作成するcontroller-managerからなります。これらを一括でインストールします。

```sh
kubectl create -f https://github.com/kubernetes-sigs/cluster-api/releases/download/v0.2.9/cluster-api-components.yaml

namespace/capi-system created
customresourcedefinition.apiextensions.k8s.io/clusters.cluster.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/machinedeployments.cluster.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/machines.cluster.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/machinesets.cluster.x-k8s.io created
role.rbac.authorization.k8s.io/capi-leader-election-role created
clusterrole.rbac.authorization.k8s.io/capi-manager-role created
rolebinding.rbac.authorization.k8s.io/capi-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/capi-manager-rolebinding created
deployment.apps/capi-controller-manager created
```

次に、実際の環境に合わせたブートストラップとプロバイダをインストールします。
今回は、Dockerだけの環境を使いますので、ブートストラップにKubeadmを、プロバイダにDocker用のものを使います。
以下の専用コンポーネントをインストールします。

```
kubectl create -f https://github.com/kubernetes-sigs/cluster-api-bootstrap-provider-kubeadm/releases/download/v0.1.5/bootstrap-components.yaml

namespace/cabpk-system created
customresourcedefinition.apiextensions.k8s.io/kubeadmconfigs.bootstrap.cluster.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/kubeadmconfigtemplates.bootstrap.cluster.x-k8s.io created
role.rbac.authorization.k8s.io/cabpk-leader-election-role created
clusterrole.rbac.authorization.k8s.io/cabpk-manager-role created
clusterrole.rbac.authorization.k8s.io/cabpk-proxy-role created
rolebinding.rbac.authorization.k8s.io/cabpk-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/cabpk-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/cabpk-proxy-rolebinding created
service/cabpk-controller-manager-metrics-service created
deployment.apps/cabpk-controller-manager created

kubectl get po -n cabpk-system   
NAME                                       READY   STATUS    RESTARTS   AGE
cabpk-controller-manager-c58d8596f-rqczh   2/2     Running   0          7m13s
```

```sh
kubectl create -f https://github.com/kubernetes-sigs/cluster-api-provider-docker/releases/download/v0.2.1/provider-components.yaml

namespace/capd-system created
customresourcedefinition.apiextensions.k8s.io/dockerclusters.infrastructure.cluster.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/dockermachines.infrastructure.cluster.x-k8s.io created
customresourcedefinition.apiextensions.k8s.io/dockermachinetemplates.infrastructure.cluster.x-k8s.io created
role.rbac.authorization.k8s.io/capd-leader-election-role created
clusterrole.rbac.authorization.k8s.io/capd-manager-role created
clusterrole.rbac.authorization.k8s.io/capd-proxy-role created
rolebinding.rbac.authorization.k8s.io/capd-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/capd-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/capd-proxy-rolebinding created
service/capd-controller-manager-metrics-service created
deployment.apps/capd-controller-manager created

kubectl get po -n capd-system 
NAME                                      READY   STATUS    RESTARTS   AGE
capd-controller-manager-7cd8c765c-84j7r   2/2     Running   0          5m22s
```

この時点で、`Docker`上には`clusterapi-control-plane`というコンテナ(Management cluster)が一つだけ動いています。

```sh
docker ps    
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                       NAMES
68d711500e26        kindest/node:v1.17.0   "/usr/local/bin/entr…"   11 minutes ago      Up 11 minutes       127.0.0.1:32775->6443/tcp   clusterapi-control-plane
```

# ClusterAPIを使う

Cluster APIの以下のCRDを順に作成していきます。

- `Cluster`
- `Machine`
- `MachineDeployment`

## `Cluster`新規作成

4コマ漫画の2コマ目になります。

![cluster.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/38a87bbf-b872-d069-3c67-1e4c3e565b2f.png)

Cluster APIで作るためのクラスタの設定ファイルを作成します。

```cluster.yaml
apiVersion: cluster.x-k8s.io/v1alpha2
kind: Cluster
metadata:
  name: capi-quickstart
spec:
  clusterNetwork:
    pods:
      cidrBlocks: ["192.168.0.0/16"]
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
    kind: DockerCluster
    name: capi-quickstart
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
kind: DockerCluster
metadata:
  name: capi-quickstart
```

```sh
kubectl create -f cluster.yaml
cluster.cluster.x-k8s.io/capi-quickstart created
dockercluster.infrastructure.cluster.x-k8s.io/capi-quickstart created
```

```sh
kubectl get dockercluster 
NAME              AGE
capi-quickstart   11s
```

`Docker`を確認してみますと、以下のように`capi-quickstart-lb`というコンテナが起動していることがわかります。

```sh
 docker ps 
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                        NAMES
38d5a9338e3d        nginx:1.15.12-alpine   "nginx -g 'daemon of…"   48 seconds ago      Up 47 seconds       80/tcp, 40319/tcp, 0.0.0.0:40319->6443/tcp   capi-quickstart-lb
68d711500e26        kindest/node:v1.17.0   "/usr/local/bin/entr…"   14 minutes ago      Up 14 minutes       127.0.0.1:32775->6443/tcp                    clusterapi-control-plane
```

### `Machine`を作る

4コマ漫画の3コマ目になります。`Machine`というCRDでマスターノードを作成します。

![machine.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/5e3d0fc2-36b0-940c-1858-a9cb9a5c202f.png)

```machine.yaml
apiVersion: cluster.x-k8s.io/v1alpha2
kind: Machine
metadata:
  name: capi-quickstart-controlplane-0
  labels:
    cluster.x-k8s.io/control-plane: "true"
    cluster.x-k8s.io/cluster-name: "capi-quickstart"
spec:
  version: v1.15.3
  bootstrap:
    configRef:
      apiVersion: bootstrap.cluster.x-k8s.io/v1alpha2
      kind: KubeadmConfig
      name: capi-quickstart-controlplane-0
  infrastructureRef:
    kind: DockerMachine
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
    name: capi-quickstart-controlplane-0
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
kind: DockerMachine
metadata:
  name: capi-quickstart-controlplane-0
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha2
kind: KubeadmConfig
metadata:
  name: capi-quickstart-controlplane-0
spec:
  initConfiguration:
    nodeRegistration:
      kubeletExtraArgs:
        eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
  clusterConfiguration:
    controllerManager:
      extraArgs:
        enable-hostpath-provisioner: "true"
```

```sh
kubectl create -f machine.yaml
machine.cluster.x-k8s.io/capi-quickstart-controlplane-0 created
dockermachine.infrastructure.cluster.x-k8s.io/capi-quickstart-controlplane-0 created
kubeadmconfig.bootstrap.cluster.x-k8s.io/capi-quickstart-controlplane-0 created
```

以下のようにmachineが起動されるまでにおおよそ10分かかりました。

```sh
kubectl get machines --selector cluster.x-k8s.io/control-plane

NAME                             PROVIDERID                                                  PHASE
capi-quickstart-controlplane-0   docker:////capi-quickstart-capi-quickstart-controlplane-0   running

```

`Docker`でも以下のように、`capi-quickstart-capi-quickstart-controlplane-0`というコンテナが起動したことがわかります。

```

docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS              PORTS                                        NAMES
f449784405e6        kindest/node:v1.15.3   "/usr/local/bin/entr…"   About a minute ago   Up About a minute   35547/tcp, 127.0.0.1:35547->6443/tcp         capi-quickstart-capi-quickstart-controlplane-0
38d5a9338e3d        nginx:1.15.12-alpine   "nginx -g 'daemon of…"   11 minutes ago       Up 11 minutes       80/tcp, 40319/tcp, 0.0.0.0:40319->6443/tcp   capi-quickstart-lb
68d711500e26        kindest/node:v1.17.0   "/usr/local/bin/entr…"   25 minutes ago       Up 25 minutes       127.0.0.1:32775->6443/tcp                    clusterapi-control-plane
```

## 作成したクラスタにアクセスする`kubeconfig`を作る

`kubectl`でクラスタ内に接続するために、`kubeconfig`ファイルを作成します。予め`cluster`作成時に以下のような`Secret`が作成されているので、そこから抜き出します。

```sh
kubectl --namespace=default get secret/capi-quickstart-kubeconfig -o json \
  | jq -r .data.value \
  | base64 --decode \
  > ./capi-quickstart.kubeconfig
```

今回はMacbook上で実行したために、以下のコマンドで一部だけ変更します(`cluster`の宛先と接続方法`insecure-skip-tls-verify`を変更します。)

```sh
sed -i -e "s/server:.*/server: https:\/\/$(docker port capi-quickstart-lb 6443/tcp | sed "s/0.0.0.0/127.0.0.1/")/g" ./capi-quickstart.kubeconfig

# Ignore the CA, because it is not signed for 127.0.0.1
sed -i -e "s/certificate-authority-data:.*/insecure-skip-tls-verify: true/g" ./capi-quickstart.kubeconfig
```

生成した`kubeconfig`はこうなっています。

```capi-quickstart.kubeconfig
apiVersion: v1
clusters:
- cluster:
    insecure-skip-tls-verify: true
    server: https://127.0.0.1:40319
  name: capi-quickstart
contexts:
- context:
    cluster: capi-quickstart
    user: kubernetes-admin
  name: kubernetes-admin@capi-quickstart
current-context: kubernetes-admin@capi-quickstart
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: LS0tLS1CR...
    client-key-data: LS0t...
```

この`kubeconfig`を使って`kubectl`を実行してみます。

```sh

kubectl --kubeconfig=./capi-quickstart.kubeconfig get nodes 
NAME                                             STATUS     ROLES    AGE     VERSION
capi-quickstart-capi-quickstart-controlplane-0   NotReady   master   8m35s   v1.15.3

```

まずはMasterノードが動いていることがわかります。ただし、状態はまだ`NotReady`です。クラスタ内ネットワークが構築されていないためです。

## クラスタ内ネットワークを構築する

次に、ネットワークを構築します。`calico`をインストールします。

![calico.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/9be1ff6c-37e9-eebe-3f6c-76a8f3e9b3c9.png)


```sh
kubectl --kubeconfig=./capi-quickstart.kubeconfig \
  apply -f https://docs.projectcalico.org/v3.8/manifests/calico.yaml

configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/felixconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamblocks.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/blockaffinities.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamhandles.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ipamconfigs.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgppeers.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/ippools.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/hostendpoints.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/clusterinformations.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/globalnetworksets.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networkpolicies.crd.projectcalico.org created
customresourcedefinition.apiextensions.k8s.io/networksets.crd.projectcalico.org created
clusterrole.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrolebinding.rbac.authorization.k8s.io/calico-kube-controllers created
clusterrole.rbac.authorization.k8s.io/calico-node created
clusterrolebinding.rbac.authorization.k8s.io/calico-node created
daemonset.apps/calico-node created
serviceaccount/calico-node created
deployment.apps/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
```

```sh
kubectl --kubeconfig=./capi-quickstart.kubeconfig \
  -n kube-system patch daemonset calico-node \
  --type=strategic --patch='
spec:
  template:
    spec:
      containers:
      - name: calico-node
        env:
        - name: FELIX_IGNORELOOSERPF
          value: "true"
'
daemonset.extensions/calico-node patched
```

これを実行することにより、マスターノードは`Ready`状態となります。

## Workerを追加する

4コマ漫画の最後のコマになります。ワーカーノードを`MachineDeployment`を使って追加します。

![last.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/361173ba-6503-e1be-b147-0a7725af543f.png)

以下の`worker.yaml`ファイルを作成し、Management Clusterに対し`kubectl create`コマンドを実行します。

```worker.yaml
apiVersion: cluster.x-k8s.io/v1alpha2
kind: MachineDeployment
metadata:
  name: capi-quickstart-worker
  labels:
    cluster.x-k8s.io/cluster-name: capi-quickstart
    # Labels beyond this point are for example purposes,
    # feel free to add more or change with something more meaningful.
    # Sync these values with spec.selector.matchLabels and spec.template.metadata.labels.
    nodepool: nodepool-0
spec:
  replicas: 1
  selector:
    matchLabels:
      cluster.x-k8s.io/cluster-name: capi-quickstart
      nodepool: nodepool-0
  template:
    metadata:
      labels:
        cluster.x-k8s.io/cluster-name: capi-quickstart
        nodepool: nodepool-0
    spec:
      version: v1.15.3
      bootstrap:
        configRef:
          name: capi-quickstart-worker
          apiVersion: bootstrap.cluster.x-k8s.io/v1alpha2
          kind: KubeadmConfigTemplate
      infrastructureRef:
        name: capi-quickstart-worker
        apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
        kind: DockerMachineTemplate
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
kind: DockerMachineTemplate
metadata:
  name: capi-quickstart-worker
spec:
  template:
    spec: {}
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha2
kind: KubeadmConfigTemplate
metadata:
  name: capi-quickstart-worker
spec:
  template:
    spec:
      # For more information about these values,
      # refer to the Kubeadm Bootstrap Provider documentation.
      joinConfiguration:
        nodeRegistration:
          kubeletExtraArgs:
            eviction-hard: nodefs.available<0%,nodefs.inodesFree<0%,imagefs.available<0%
      clusterConfiguration:
        controllerManager:
          extraArgs:
            enable-hostpath-provisioner: "true"
```

```sh
kubectl create -f worker.yaml 
machinedeployment.cluster.x-k8s.io/capi-quickstart-worker created
dockermachinetemplate.infrastructure.cluster.x-k8s.io/capi-quickstart-worker created
kubeadmconfigtemplate.bootstrap.cluster.x-k8s.io/capi-quickstart-worker created
```

```sh
docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED             STATUS              PORTS                                        NAMES
e6162d760dd7        kindest/node:v1.15.3   "/usr/local/bin/entr…"   2 minutes ago       Up 2 minutes                                                     capi-quickstart-capi-quickstart-worker-85cbf8fd8c-mhrlz
f449784405e6        kindest/node:v1.15.3   "/usr/local/bin/entr…"   16 minutes ago      Up 16 minutes       35547/tcp, 127.0.0.1:35547->6443/tcp         capi-quickstart-capi-quickstart-controlplane-0
38d5a9338e3d        nginx:1.15.12-alpine   "nginx -g 'daemon of…"   26 minutes ago      Up 26 minutes       80/tcp, 40319/tcp, 0.0.0.0:40319->6443/tcp   capi-quickstart-lb
68d711500e26        kindest/node:v1.17.0   "/usr/local/bin/entr…"   40 minutes ago      Up 40 minutes       127.0.0.1:32775->6443/tcp                    clusterapi-control-plane
```




```sh
kubectl --kubeconfig=./capi-quickstart.kubeconfig get nodes     ✔  1656  01:13:19

NAME                                                      STATUS   ROLES    AGE     VERSION
capi-quickstart-capi-quickstart-controlplane-0            Ready    master   20m     v1.15.3
capi-quickstart-capi-quickstart-worker-85cbf8fd8c-mhrlz   Ready    <none>   6m30s   v1.15.3
```

## Workerを増やす

オプションで、`MachineDeployment`を編集することで動的にワーカーノードを追加することができます。

以下では、1台だったワーカーノードを2台に増やします。

```sh
$ kubectl edit machinedeployment capi-quickstart-worker
```

以下の行を変更します。

```diff
- replica: 1
+ replica: 2
```

保存して終了します。`Machine`が以下のように増えています。

```sh
$ kubectl get machines
NAME                                      PROVIDERID                                                           PHASE
capi-quickstart-controlplane-0            docker:////capi-quickstart-capi-quickstart-controlplane-0            running
capi-quickstart-worker-85cbf8fd8c-mhrlz   docker:////capi-quickstart-capi-quickstart-worker-85cbf8fd8c-mhrlz   running
capi-quickstart-worker-85cbf8fd8c-wjk9h                                                                        provisioning
```

Dockerコンテナを見ても、以下のように追加されます。

```sh
$ docker ps
CONTAINER ID        IMAGE                  COMMAND                  CREATED              STATUS              PORTS                                        NAMES
e55a1192feeb        kindest/node:v1.15.3   "/usr/local/bin/entr…"   About a minute ago   Up About a minute                                                capi-quickstart-capi-quickstart-worker-85cbf8fd8c-wjk9h
e6162d760dd7        kindest/node:v1.15.3   "/usr/local/bin/entr…"   3 days ago           Up 3 days                                                        capi-quickstart-capi-quickstart-worker-85cbf8fd8c-mhrlz
f449784405e6        kindest/node:v1.15.3   "/usr/local/bin/entr…"   3 days ago           Up 3 days           35547/tcp, 127.0.0.1:35547->6443/tcp         capi-quickstart-capi-quickstart-controlplane-0
38d5a9338e3d        nginx:1.15.12-alpine   "nginx -g 'daemon of…"   3 days ago           Up 3 days           80/tcp, 40319/tcp, 0.0.0.0:40319->6443/tcp   capi-quickstart-lb
68d711500e26        kindest/node:v1.17.0   "/usr/local/bin/entr…"   3 days ago           Up 3 days           127.0.0.1:32775->6443/tcp                    clusterapi-control-plane
```

`kubectl get nodes`コマンドでも確認できます。

```sh
$ kubectl --kubeconfig=./capi-quickstart.kubeconfig get nodes

NAME                                                      STATUS   ROLES    AGE     VERSION
capi-quickstart-capi-quickstart-controlplane-0            Ready    master   3d22h   v1.15.3
capi-quickstart-capi-quickstart-worker-85cbf8fd8c-mhrlz   Ready    <none>   3d22h   v1.15.3
capi-quickstart-capi-quickstart-worker-85cbf8fd8c-wjk9h   Ready    <none>   3m8s    v1.15.3
```

上記のように、`capi-quickstart-capi-quickstart-worker-85cbf8fd8c-xxxxx`の名前のノードが2台に増えたことが確認できました。

# まとめ

Cluster APIを使ってManagement ClusterからKubernetesクラスタを作ってみました。
宣言的APIで動的にワーカーノードを追加することができ、`kind`よりも柔軟性が高いと感じました。
ただし、`Kubeadm`の設定があまり隠蔽されないため、YAMLファイルを書く行数は`kind`より多くなっていると思います。
注意点として、頻繁に`kubectl`を実行するのですが、今自分がManagement Clusterに`kubectl`を打っているのか、はたまた作成後のKubernetesクラスタに`kubectl`を打っているのか、混乱しないように進める必要があると思いました。