【備忘録】ProxmoxVE上にKeepalivedとHAProxyを用いた冗長なKubernetesクラスタを作成する

# はじめに
microk8sでは物足りなくなってきたので、ProxmoxVE上にKubernetesを構築してみることにしました。  
備忘録も兼ねて、手順を残しておきます。  

# 環境
ProxmoxVE 8.1.4  
VM作成等の手順は省略するが、以下のような構成で構築します。  
| hostname | ip | role |
| --- | --- | --- |
| k8s-proxy-1 | 172.16.11.1 | Proxy 1 |
| k8s-proxy-2 | 172.16.11.2 | Proxy 2 |
| k8s-control-1 | 172.16.11.10 | Control Plane 1 |
| k8s-control-2 | 172.16.11.11 | Control Plane 2 |
| k8s-control-3 | 172.16.11.12 | Control Plane 3 |
| k8s-worker-1 | 172.16.11.50 | Worker 1 |
| k8s-worker-2 | 172.16.11.51 | Worker 2 |

# 手順
一般ユーザーでは正常に完了できないものが含まれているため、これ以下の手順はすべてrootユーザーで実行してください。  
## Proxyの設定
k8s-proxy-1, k8s-proxy-2にhaproxyとkeepalivedをインストール・設定します。  
```bash
apt install haproxy keepalived -y
```
  
  
以下の作業は各のサーバーで行ってください。
  
### k8s-proxy-1
/etc/haproxy/haproxy.cfg  
```conf
frontend kube-api
    bind *:6443
    option tcplog
    mode tcp
    default_backend    api-6443
    
backend api-6443
    mode tcp
    balance    roundrobin
    server k8s-control-1 172.16.11.10:6443 
    server k8s-control-2 172.16.11.11:6443 
    server k8s-control-3 172.16.11.12:6443 
```
  
/etc/keepalived/keepalived.conf  
```conf
# k8s-proxy-1 keepalived config

global_defs {
}

vrrp_script check_haproxy { 
    script "killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance Instance1 {
    state MASTER
    interface eth0
    virtual_router_id 10
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass v4SevXnwLAxJ73qFx
    }
    unicast_src_ip 172.16.11.1 # k8s-proxy-1 Public IP
    unicast_peer {
       172.16.11.2   # k8s-proxy-2 Public IP
    }
    virtual_ipaddress {
       172.16.11.0   # VRRP IP
    }
    track_script {
      check_haproxy 
    }
}
```
  
### k8s-proxy-2
/etc/haproxy/haproxy.cfg  
```conf
frontend kube-api
    bind *:6443
    option tcplog
    mode tcp
    default_backend    api-6443
    
backend api-6443
    mode tcp
    balance    roundrobin
    server k8s-control-1 172.16.11.10:6443 
    server k8s-control-2 172.16.11.11:6443 
    server k8s-control-3 172.16.11.12:6443 
```
  
/etc/keepalived/keepalived.conf  
```conf
# k8s-proxy-2 keepalived config

global_defs {
}

vrrp_script check_haproxy { 
    script "killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance Instance1 {
    state BACKUP
    interface eth0
    virtual_router_id 10
    priority 99
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass v4SevXnwLAxJ73qFx
    }
    unicast_src_ip 172.16.11.2 # k8s-proxy-2 Public IP
    unicast_peer {
       172.16.11.1   # k8s-proxy-1 Public IP
    }
    virtual_ipaddress {
       172.16.11.0   # VRRP IP
    }
    track_script {
      check_haproxy
    }
}
```
  
  
  
  
###  k8s-proxy-1, k8s-proxy-2
以下のコマンドを各サーバーで実行してください。
```bash
systemctl enable haproxy
systemctl start haproxy
systemctl enable keepalived
systemctl start keepalived
```

## Kubernetesの構築
k8s-control-1, k8s-control-2, k8s-control-3, k8s-worker-1, k8s-worker-2にKubernetesを構築します。  

CRI-Oのインストール  
```bash
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 update -y && apt upgrade -y
apt install cri-o cri-o-runc cri-tools -y

systemctl enable crio.service
systemctl start crio.service
```
　　
カーネルパラメータを設定する。  
```bash
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
```
  
swap の無効化  
```bash
swapoff -a
```
GPG公開鍵の追加、リポジトリの追加、kubeadm / kubelet / kubectl のインストール
```bash
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | tee /etc/apt/sources.list.d/kubernetes.list

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

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

ここからの作業はcontrol nodeとworker nodeで異なるため、それぞれのサーバーで実行してください。

### control node
controlとして割り当てたノードのうち、1台のみ(ここではk8s-control-1)で初期化を実行していきます。  
#### k8s-control-1
実行時に、`control-plane-endpoint`でProxy Serverのフロントエンドを指定する必要があります。今回の場合、冒頭のProxy設定で`frontend kube-api`として指定されている、`VRRPのIP:6443`を指定します。  
また、`upload-certs`コマンドを指定して、ControlNode用の証明書を暗号化し、kubeadm-certsをSecretsリソースにアップロードします。  
```bash
kubeadm init --control-plane-endpoint "172.16.11.0:6443" --upload-certs
```
  
上記コマンドを実行した際に標準出力されるコマンドは、後述の手順にて必要となるため、どこかに控えておいてください。  
```
I0620 12:25:04.219703   19748 version.go:256] remote version is much newer: v1.30.2; falling back to: stable-1.28
[init] Using Kubernetes version: v1.28.11
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster

( -- 中略 -- )

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

  kubeadm join 172.16.11.0:6443 --token 5ogthw.cgv9o58h9y3yer45 \
        --discovery-token-ca-cert-hash sha256:3c42a10bf34c5162e266516d8eb27e32eaf492c9ddf458ccf6664250adb81d6c \        --control-plane --certificate-key 2f09d6a8c0aaebb9ee624c5621114a43e8cef88c999025cf2c11b48a90a8baed

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 172.16.11.0:6443 --token 5ogthw.cgv9o58h9y3yer45 \
        --discovery-token-ca-cert-hash sha256:3c42a10bf34c5162e266516d8eb27e32eaf492c9ddf458ccf6664250adb81d6c
```
  
#### k8s-control-2, k8s-control-3
controlとして割り当てたノードのうち、残りの2台(k8s-control-2, k8s-control-3)で初期化を実行します。  
ここで実行するコマンドは、k8s-control-1で実行したものと異なることに注意してください。  
```bash
kubeadm join 172.16.11.0:6443 --token 5ogthw.cgv9o58h9y3yer45 \
    --discovery-token-ca-cert-hash sha256:3c42a10bf34c5162e266516d8eb27e32eaf492c9ddf458ccf6664250adb81d6c \        --control-plane --certificate-key 2f09d6a8c0aaebb9ee624c5621114a43e8cef88c999025cf2c11b48a90a8baed
```

### worker node
worker node で初期化を実行します。  
```bash
kubeadm join 172.16.11.0:6443 --token 5ogthw.cgv9o58h9y3yer45 \
    --discovery-token-ca-cert-hash sha256:3c42a10bf34c5162e266516d8eb27e32eaf492c9ddf458ccf6664
```

## 接続確認
~/.kube/config に設定ファイルをコピーし、kubectlコマンドを実行できるようにします。  
```bash
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
```
  
control node で以下のコマンドを実行し、各ノードの状態を確認します。  
```bash
kubectl get nodes
```

以下のような結果が得られたら、Kubernetesの構築は成功しています！  
```
root@k8s-control-1:/home/chan-mai# kubectl get nodes
NAME            STATUS   ROLES           AGE     VERSION
k8s-control-1   Ready    control-plane   41m     v1.28.11
k8s-control-2   Ready    control-plane   21m     v1.28.11
k8s-control-3   Ready    control-plane   20m     v1.28.11
k8s-worker-1    Ready    <none>          9m11s   v1.28.11
k8s-worker-2    Ready    <none>          9m8s    v1.28.11
```

# まとめ
KeepalivedとHAProxyを組み合わせることで、冗長なKubernetesプロキシを構築し、高可用性なKubernetesクラスタを構築することができました。  
Controlノードを別々の場所に分散させると、いい感じの堅牢なクラスタができるかと思います。  
  
# 参考
- [HAProxy を利用したマルチクラスタ構成の Kubernetes Cluster 構築](https://qiita.com/yoshi_1010/items/6563fe70897a552e1d84)  
- [Keepalived User Guide](https://keepalived.readthedocs.io/en/latest/configuration_synopsis.html)  
- [Kubernetes Community Forums - Couldn’t get current server API group list: Get “http://localhost:8080/api?timeout=32s”: dial tcp 127.0.0.1:8080: connect: connection refused](https://discuss.kubernetes.io/t/couldnt-get-current-server-api-group-list-get-http-localhost-8080-api-timeout-32s-dial-tcp-127-0-0-1-connect-connection-refused/25471/1)