HAProxy を利用したマルチクラスタ構成の Kubernetes Cluster 構築

# 目的

Kubernetes の Master Node に冗長性を担保させるため、マルチクラスタ構成で Kubernetes Cluster を作成する。
今回は複数台の Master Node にアクセスする際にプロキシサーバを用いる。

プロキシサーバをインストールし、別マシンから Kubernetes Cluster に接続するまでをまとめる。

# 構成

Master Node 3 台、Worker Node 2 台利用する。

- Proxy Server: 192.168.90.1, 192.168.122.1
- master01: 192.168.122.232
- master02: 192.168.122.116
- master03: 192.168.122.18
- worker01: 192.168.122.114
- worker02: 192.168.122.79

各マシンのスペックは下記の通りとする。

- Disk: 32GB
- Cores: 8 core
- Memory: 16GB
- OS: Ubuntu 22.04

# 手順

## HAProxy 

Proxy Server で実行する。

- 実行対象
  - Proxy Server

HAProxy をインストールする。
HAProxy は多機能なプロキシサーバであり、ソフトウェアロードバランサの 1 つである。

```
apt update
apt -y upgrade
apt -y install haproxy
```

デフォルト設定ファイルのバックアップを取得する。

```
cp -ai /etc/haproxy/haproxy.cfg{,.default}
```

設定ファイルを作成する。
今回は Master Node に対して接続する際、Proxy Server でアクセスを待ち受ける。
そして、受け取ったアクセスをバックエンドの Master Node に振り分ける。

```
cat << _EOF_ >> /etc/haproxy/haproxy.cfg

frontend kube-api
    bind *:6443
    option tcplog
    mode tcp
    default_backend    api-6443
    
backend api-6443
    mode tcp
    balance    roundrobin
    server master1 192.168.122.232:6443 
    server master2 192.168.122.116:6443 
    server master3 192.168.122.18:6443 
_EOF_
```

設定した内容を確認する。

```
$ diff /etc/haproxy/haproxy.cfg{.default,}
34a35,47
>
> frontend kube-api
>     bind *:6443
>     option tcplog
>     mode tcp
>     default_backend    api-6443
>
> backend api-6443
>     mode tcp
>     balance    roundrobin
>     server master1 192.168.122.232:6443
>     server master2 192.168.122.116:6443
>     server master3 192.168.122.18:6443
```

HAProxy を再起動する。

```
systemctl restart haproxy.service
systemctl enable haproxy.service
```

## Kubernetes

以下の手順は Kubernetes Cluster を構成するマシン上で実行する。

- 実行対象
  - master01, master02, master03, worker01, worker02

インストールを開始する前に 1 度環境をアップデートする。

```
apt update
apt upgrade
```

### CRI-O

CRI-O をインストールする。

CRI-O とは Kubernetes とコンテナランタイムが通信するために規定されている CRI (Container Runtime Interface) である。 

export する変数は自身の環境に沿うものに変更する。

```
export OS=xUbuntu_22.04
export VERSION=1.24

echo "deb [signed-by=/usr/share/keyrings/libcontainers-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
echo "deb [signed-by=/usr/share/keyrings/libcontainers-crio-archive-keyring.gpg] https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/ /" > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable:cri-o:$VERSION.list

mkdir -p /usr/share/keyrings
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/$OS/Release.key | gpg --dearmor -o /usr/share/keyrings/libcontainers-archive-keyring.gpg
curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/$VERSION/$OS/Release.key | gpg --dearmor -o /usr/share/keyrings/libcontainers-crio-archive-keyring.gpg

apt-get update
apt-get install cri-o cri-o-runc cri-tools
```

CRI-O を起動する。

```
systemctl enable crio.service
systemctl start crio.service
```

### Kernel Parameter 

カーネルパラメータを設定する。

```
cat << _EOF_ > /etc/modules-load.d/crio.conf
overlay
br_netfilter
_EOF_

modprobe overlay
modprobe br_netfilter

cat << _EOF_ > /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
_EOF_
```

設定を反映する。

```
sysctl --system
```

### kubeadm / kubelet / kubectl のインストール

Kubernetes をインストール・操作するために必要なソフトウェアをインストールする。

GPGキーの追加する。

```
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
```

リポジトリの追加する。

```
apt-add-repository "deb http://apt.kubernetes.io/ kubernetes-xenial main"
```

kubeadm / kubelet / kubectl をインストールする。

```
apt update
apt install -y kubeadm kubelet kubectl
```

### swap の無効化

swap が有効である場合は無効にする。
既に無効化されている場合は必要のない手順である。 

```
# 一時的な swap の無効化 (再起動で有効化)
swapoff -a

# 永続的な swap の無効化
cp -p /etc/fstab{,.default}
vim /etc/fstab
## swap の設定をコメントアウトする
```

### Kubernetes Cluster の構築

ここからの作業は Master Node と Worker Node で実行内容が異なる。

#### Master Node

Master Node を初期化する。
この初期化は Master Node のうちの 1 台で実行する。

- 実行対象
  - master01

実行時、`control-plane-endpoint` で Proxy Server のフロントエンドを指定する。
今回の場合は [HAProxy] の `frontend kube-api` で指定した通り、Proxy Server の IP アドレス:6443 を指定する。
また、`upload-certs` により Master Node 用の証明書を暗号化し、kubeadm-certs を Secrets リソースにアップロードする。

出力結果は後述する残りの Master Node 追加時と Worker Node 追加時に利用する。

```
$ kubeadm init --control-plane-endpoint "192.168.90.1:6443" --upload-certs
... (省略) ...
[bootstrap-token] Configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token
[bootstrap-token] Configured RBAC rules to allow certificate rotation for all node client certificates in the cluster
[bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace
[kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of the control-plane node running the following command on each as root:

  kubeadm join 192.168.90.1:6443 --token d9tr90.jpif6a1hboo2gxk2 \
        --discovery-token-ca-cert-hash sha256:c0afb2695d3b18590e9acefa7e28a9163f25b7532bf38dbd8e5c4494ad56f1d7 \
        --control-plane --certificate-key 5bf2e194f4a4b49ad1457e28aa338a6cf67554eb59ccb39bdb26a4a458516b51

Please note that the certificate-key gives access to cluster sensitive data, keep it secret!
As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use
"kubeadm init phase upload-certs --upload-certs" to reload certs afterward.

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.90.1:6443 --token d9tr90.jpif6a1hboo2gxk2 \
        --discovery-token-ca-cert-hash sha256:c0afb2695d3b18590e9acefa7e28a9163f25b7532bf38dbd8e5c4494ad56f1d7
```

構築した 1 台目の Master Node に残りのマシンも追加する。

- 実行対象
  - master02, master03

実行する下記のコマンドは 1 台目の Master Node 構築時に出力されたものと同等である。

```
kubeadm join 192.168.90.1:6443 --token d9tr90.jpif6a1hboo2gxk2 \
        --discovery-token-ca-cert-hash sha256:c0afb2695d3b18590e9acefa7e28a9163f25b7532bf38dbd8e5c4494ad56f1d7 \
        --control-plane --certificate-key 5bf2e194f4a4b49ad1457e28aa338a6cf67554eb59ccb39bdb26a4a458516b51
```

#### Worker Node

Worker Node を初期化する。

- 実行対象
  - worker01, worker02

実行する下記のコマンドは 1 台目の Master Node 構築時に出力されたものと同等である。
Master Node 追加時とコマンドが異なることに注意する。

```
kubeadm join 192.168.90.1:6443 --token d9tr90.jpif6a1hboo2gxk2 \
        --discovery-token-ca-cert-hash sha256:c0afb2695d3b18590e9acefa7e28a9163f25b7532bf38dbd8e5c4494ad56f1d7
```

## 接続確認

Kubernetes Cluster の kubeconfig を確認すると、下記のように `server` が control-plane-endpoint として指定したものとなっている。

```
$ cat .kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: xxx
    server: https://192.168.90.1:6443
  name: kubernetes
...
```

上記の config ファイルを手元に移動させ、接続確認をする。
接続確認時、Proxy Server でログファイルを tail すると、接続先の Master Node が振り分けられていることを確認できる。

```
# ローカル環境で実行
$ kubectl --kubeconfig config get node
NAME       STATUS   ROLES           AGE     VERSION
master01   Ready    control-plane   20m     v1.27.2
master02   Ready    control-plane   9m36s   v1.27.2
master03   Ready    control-plane   7m11s   v1.27.2
worker01   Ready    <none>          6m44s   v1.27.2
worker02   Ready    <none>          6m31s   v1.27.2

# Proxy Server で実行
ubuntu@wg-r640-01:~$ tail -f /var/log/haproxy.log
... (省略) ...
xxx kube-api api-6443/master02 1/0/47 31650 -- 11/11/10/3/0 0/0
xxx kube-api api-6443/master03 1/0/23 16348 -- 11/11/10/3/0 0/0
xxx kube-api api-6443/master01 1/0/21 4934 -- 11/11/10/4/0 0/0
xxx kube-api api-6443/master02 1/0/18 4933 -- 11/11/10/3/0 0/0
```

# まとめ

今回はプロキシサーバである HAProxy を用いることで Kubernetes のマルチ Master Node の Kubernetes Cluster を構築した。
マルチ Master Node 構成とすることで、冗長性担保以外にも Master Node を複数拠点に配置するような地理的な分散も考慮できるようになる。
次は異なるネットワークに Master Node が存在するような Kubernetes Cluster の構築をしてみたいと考えている。

今回、HAProxy を用いることで容易に Master Node を拡張し冗長性が確保できたが、HAProxy 自体に冗長性はない状態である。
この状態の場合、HAProxy 自体が障害点となりえるため、HAProxy 自体を冗長化させることが推奨される。
当該冗長化には Keepalived を用いることができる。
Keepalived はサービスの稼働状態を監視し冗長化機能を提供するオープンソースソフトウェアであるため、ロードバランサ自体の監視に基づき、障害時に自動的にバックアップに切り替えることができる。
このように、Keepalived を導入しHAProxy 自体が障害点とならないようにするのが望ましい。