DIY Kubernetes / 家でk8sクラスターを構築する

![Screen Shot 2024-06-07 at 8.48.45 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/c669bbcd-ba77-d9d2-18fa-ce59fe2609ac.png)

今回のポストでは、家でWi-Fi設定とデスクトップを利用してKubernetes Clusterを構築する予定です。毎回トイプロジェクトを行う際にアプリケーションサーバーをどうするか悩んでいましたが、また、追加でRedis、ElasticSearch、MySQL、Jenkinsなどの複数のリソースを便利に使用するためにKubernetesを構築して管理およびデプロイする予定です。

私は次のように設定しました。

- 家に余っているデスクトップをCitrix Xen Hypervisorで仮想化
- VMを5つ作成した後にKubernetes Clusterを構築（Control Plane 1、Worker 3）

### ネットワーク設定
現在家ではLGU+ルーターを使用しており、DHCP範囲が自動で192.168.219.0/24に設定されています。ルーター設定ページでも確認しましたが、これを変更できないようです。とりあえずRFCのプライベートIPレンジを見て、大まかに次のように分けました。

#### 参考. RFC 1918 Private IPv4 address
- 24-bit block / 10.0.0.0–10.255.255.255 / 16777216
- 20-bit block / 172.16.0.0–172.31.255.255 / 1048576
- 16-bit block / 192.168.0.0–192.168.255.255 / 65536

設定したIP

- 既存の家で使用する必要があるIP（無線接続など）192.168.219.1(gateway) ~ 192.168.219.99
- Xen Server 192.168.219.100
- VM IP 192.168.219.150–192.168.219.199
- DNS & Util Server 192.168.219.200
- NFS 192.168.219.210
- Kubernetes IP Range 10.0.0.0/16

### SSH
SSH接続はSSHジャンプで簡単に行えます。一般的なSSHクライアントはこれをサポートしているため、DMZノードを通じて非常に簡単に内側のVMまでSSH接続を行うことができました。Mac用にはTerminusで設定しておきました。

### CentOS/RHEL Kubernetes Cluster構築

XenServerのVMで作成したCentOSにKubernetesクラスタを構築しました。Xen Centerのコンソール画面でも可能ですが不便なため、SSHクライアントでSSH接続を構成して使用する方が便利です。

おおよそ次のようなスペックで構築しました。

- Kubeadm
- CNI : Calico
- Ingress Controller : Nginx

ノードのスペックは次のように設定しました。vCPUの計算方法を検索すると、デスクトップのコアを通じてvCPUを計算できます。

- マスターノード : 2 vCPU, 6G RAM
- ワーカーノード : 4 vCPU, 6G RAM

### kubeadmでノードをジョインする
通常、マスターノードでkubeadm initを実行すると、最後にノードをジョインするコマンドが表示されますが、これを忘れてしまった場合は、以下のように再度実行できます。

```bash
# TOKENリストの確認
$ kubeadm token list

# ない場合はTOKENを生成
$ kubeadm token create

# HASHの確認
$ openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'

# kubeadmノードジョイン
$ kubeadm join {APISERVER}:{PORT} --token {TOKEN} --discovery-token-ca-cert-hash sha256:{HASH}
```


最終的に次のような状態になります。

```bash
$ kubectl get node
NAME          STATUS     ROLES    AGE   VERSION
k8s-master    Ready      master   25h   v1.18.8
k8s-node-01   Ready      <none>   25h   v1.18.8
k8s-node-02   NotReady   <none>   20s   v1.18.8
k8s-node-03   NotReady   <none>   7s    v1.18.8

$ kubectl get all -n kube-system
NAME                                          READY   STATUS    RESTARTS   AGE
pod/calico-kube-controllers-75d555c48-gzcsk   1/1     Running   1          15h
pod/calico-node-9c9fk                         1/1     Running   0          14h
pod/calico-node-mcnds                         1/1     Running   1          15h
pod/coredns-66bff467f8-c76mz                  1/1     Running   1          15h
pod/coredns-66bff467f8-nrdgl                  1/1     Running   1          15h
pod/etcd-k8s-master                           1/1     Running   1          15h
pod/kube-apiserver-k8s-master                 1/1     Running   1          15h
pod/kube-controller-manager-k8s-master        1/1     Running   1          15h
pod/kube-proxy-f9tz8                          1/1     Running   0          14h
pod/kube-proxy-ls2v2                          1/1     Running   1          15h
pod/kube-scheduler-k8s-master                 1/1     Running   1          15h

NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE
service/kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP,9153/TCP   15h

NAME                         DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                 AGE
daemonset.apps/calico-node   2         2         2       2            2           beta.kubernetes.io/os=linux   15h
daemonset.apps/kube-proxy    2         2         2       2            2           kubernetes.io/os=linux        15h

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/calico-kube-controllers   1/1     1            1           15h
deployment.apps/coredns                   2/2     2            2           15h

NAME                                                DESIRED   CURRENT   READY   AGE
replicaset.apps/calico-kube-controllers-75d555c48   1         1         1       15h
replicaset.apps/coredns-66bff467f8                  2         2         2       15h

$ kubectl get all -n ingress-nginx
NAME                                           READY   STATUS      RESTARTS   AGE
pod/ingress-nginx-admission-create-qx642       0/1     Completed   0          3h26m
pod/ingress-nginx-admission-patch-b8h8m        0/1     Completed   0          3h26m
pod/ingress-nginx-controller-9d6f6c9d7-dshnk   1/1     Running     0          3h26m

NAME                                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             NodePort    10.103.85.174   <none>        80:32360/TCP,443:31208/TCP   3h26m
service/ingress-nginx-controller-admission   ClusterIP   10.97.157.206   <none>        443/TCP                      3h26m

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           3h26m

NAME                                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-9d6f6c9d7   1         1         1       3h26m

NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           12s        3h26m
job.batch/ingress-nginx-admission-patch    1/1           15s        3h26m
```

### Jenkins構築
まず、Jenkinsのためにネームスペースを作成し、Deployment、Service、PV、PVCを設定しました。

```bash
$ k -n jenkins get all
NAME                                      READY   STATUS    RESTARTS   AGE
pod/jenkins-deployment-5754bd847d-v48z8   1/1     Running   0          113m

NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)              AGE
service/jenkins-service   ClusterIP   10.99.68.224   <none>        8080/TCP,50000/TCP   116m

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/jenkins-deployment   1/1     1            1           113m

NAME                                            DESIRED   CURRENT   READY   AGE
replicaset.apps/jenkins-deployment-5754bd847d   1         1         1       113m

$ k -n jenkins get pv
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                 STORAGECLASS   REASON   AGE
jenkins-volume   5Gi        RWX            Retain           Bound    jenkins/pvc-jenkins   manual                  117m

$ k -n jenkins get pvc
NAME          STATUS   VOLUME           CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-jenkins   Bound    jenkins-volume   5Gi        RWX            manual         117m

# 또한 jenkins를 Proxy 하기 위해서 Ingress를 만들고 서비스에 붙여 놓았습니다.

$ k -n jenkins get ing
NAME              CLASS    HOSTS               ADDRESS           PORTS   AGE
jenkins-ingress   <none>   jenkins.****.**     192.168.219.151   80      117m`
```

その後、Route53でIngress ControllerのノードへのIPにAレコードを設定して接続しました。

![Screen Shot 2024-06-07 at 8.55.35 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/91cedcc9-16f8-201f-1161-d25e61ddb4a9.png)

Jenkins接続成功

![Screen Shot 2024-06-07 at 8.55.55 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/841dcbba-4379-cc9d-287d-7ddb459bdd92.png)

イングレスにTLS設定（letsencrypt使用）
defaultにcertbot-autoを利用してTLSシークレットを作成し、各ネームスペースにコピー、更新するスクリプトを作成してcrontabに登録しました。

```bash#!/bin/bash

# シークレットスペック
SECRET=tls-secret
KEY_PATH=/etc/letsencrypt/archive/****.**/privkey1.pem
CERT_PATH=/etc/letsencrypt/archive/****.**/cert1.pem

# 更新する必要があるネームスペース
ARGOCD=argocd
JENKINS=jenkins

# 証明書再発行
certbot-auto renew

# 既存のシークレット削除
kubectl delete secret $SECRET
kubectl -n $ARGOCD delete secret $SECRET
kubectl -n $JENKINS delete secret $SECRET

# defaultにシークレット作成
kubectl create secret tls $SECRET --key $KEY_PATH --cert $CERT_PATH

# Argocdにシークレットコピー
kubectl get secret $SECRET --namespace=default -oyaml | grep -v namespace | kubectl apply --namespace=$ARGOCD -f -

# Jenkinsにシークレットコピー
kubectl get secret $SECRET --namespace=default -oyaml | grep -v namespace | kubectl apply --namespace=$JENKINS -f -
```


DNSサーバー構築と接続
dnsmasqを利用してDNSサーバーを構築しました。これにより、クラスタ内ではドメインを通じて内部通信が可能となりました。また、別途必要な場合に活用することもできると思います。

- 1次：DNSサーバーの /etc/hosts
- 2次：8.8.8.8（Google DNS）

```bash
### DNS
$ yum install -y dnsmasq

$ vi /etc/hosts
192.168.219.150 k8s-master
192.168.219.151 k8s-node-01 jenkins.****.** argocd.****.**
192.168.219.152 k8s-node-02
192.168.219.153 k8s-node-03

$ vi /etc/dnsmasq.conf
...
# 2次ネームサーバーとして使用するGoogle DNSを設定
resolv-file=/etc/resolv.dnsmasq

```

各ノードではネームサーバーを変更するだけで動作します。

```bash
$ vi /etc/resolve.conf
nameserver 192.168.219.200
```

### NFSサーバー構築と接続
ストレージ容量が大きいCentOS7のVMを1台作成し、nfs-serverを構築して/storageパスにすべてマウントしました。権限設定はno_all_squash、no_root_squashにしました。これにより、KubernetesのPersistenceVolumeをhostPathまたはnfsで使用できるようになりました。

### 結論
家庭用ルーターと余っているデスクトップ1台を仮想化してKubernetesクラスターを作成しました。シングルノードクラスターとは異なり、マルチノードクラスターで経験できることが確実にあるため、Kubernetesを学びたい方には考慮すべきオプションだと思います。また、EKS、GKEのようにマスターノードがベンダーネイティブな場合には試せないプラクティスも試すことができます。