Open6

HA構成のKubernetesクラスタ構築手順

ふろんふろん

コントロールプレーン
ハード:Raspberry Pi 4 8GB
OS:Ubuntu Server 24.0.1 LTS
コンテナランタイム:containerd
CNI:Calico
Kubernetes:v1.32

ふろんふろん

基本的にrootで実行

sudo su

IPアドレスの設定

vim /etc/systemd/network/10-eth0.network

# 以下を書く 環境に応じて変更
[Match]
Name=eth0
[Network]
Address=192.168.10.10[ホスト番号]
Gateway=192.168.10.1
DNS=127.0.0.53
systemctl enable systemd-networkd
systemctl start systemd-networkd
systemctl restart systemd-networkd
systemctl status systemd-networkd

# 設定できたことを確認
ip addr show eth0
# 疎通確認
ping -c 3 google.com

containerdのインストール、CNIプラグインのインストール

apt update
apt install -y containerd

containerdでcgroup driverをsystemdに設定

mkdir /etc/containerd
touch /etc/containerd/config.toml
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
cat /etc/containerd/config.toml | grep Systemd

containerd serviceの再起動

systemctl restart containerd

CNIのインストール

mkdir -p /opt/cni/bin
wget https://github.com/containernetworking/plugins/releases/download/v1.6.2/cni-plugins-linux-arm64-v1.6.2.tgz
tar Cxzvf /opt/cni/bin cni-plugins-linux-arm64-v1.6.2.tgz

IPv4フォワーディングを有効化し、iptablesからブリッジされたトラフィックを見えるようにする

cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

sudo sysctl --system

下記コマンドで出力されればOK

lsmod | grep br_netfilter
lsmod | grep overlay

各カーネルパラメータが1になってることを確認。

sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward

kubeadm, kubectl, kubeletのインストール

apt update
apt install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt update
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
ふろんふろん

必要な変数の定義(下記は一例)
cf: https://github.com/kubernetes/kubeadm/blob/main/docs/ha-considerations.md#keepalived-and-haproxy
keepalivedのマスター用

# コントロールプレーンのVIPに使用するIPアドレス
CONTROL_PLANE_VIP=192.168.10.100
# keepalivedのマスターにするノードではMASTER、他ではBACKUP
STATE=MASTER
INTERFACE=eth0
ROUTER_ID=51
# keepalivedのマスターにするノードでは101, 他では100
PRIORITY=101
AUTH_PASS=42
APISERVER_VIP="$CONTROL_PLANE_VIP"
APISERVER_DEST_PORT=16443
APISERVER_SRC_PORT=6443
HOST1_ID=controlplane1
HOST1_ADDRESS=192.168.10.101
HOST2_ID=controlplane2
HOST2_ADDRESS=192.168.10.102
HOST3_ID=controlplane3
HOST3_ADDRESS=192.168.10.103

keepalivedのスレーブ用

# コントロールプレーンのVIPに使用するIPアドレス
CONTROL_PLANE_VIP=192.168.10.100
# keepalivedのマスターにするノードではMASTER、他ではBACKUP
STATE=BACKUP
INTERFACE=eth0
ROUTER_ID=51
# keepalivedのマスターにするノードでは101, 他では100
PRIORITY=100
AUTH_PASS=42
APISERVER_VIP="$CONTROL_PLANE_VIP"
APISERVER_DEST_PORT=16443
APISERVER_SRC_PORT=6443
HOST1_ID=controlplane1
HOST1_ADDRESS=192.168.10.101
HOST2_ID=controlplane2
HOST2_ADDRESS=192.168.10.102
HOST3_ID=controlplane3
HOST3_ADDRESS=192.168.10.103

/etc/hostsにコントロールプレーン用VIPのIPを記載

cat <<EOF | tee -a /etc/hosts
$CONTROL_PLANE_VIP control-plane-vip
$HOST1_ADDRESS $HOST1_ID
$HOST2_ADDRESS $HOST2_ID
$HOST3_ADDRESS $HOST3_ID
EOF

keepalivedの設定ファイル、マニフェスト作成

mkdir /etc/keepalived
cat <<EOF | tee /etc/keepalived/keepalived.conf
! /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
    router_id LVS_DEVEL
}
vrrp_script check_apiserver {
  script "/etc/keepalived/check_apiserver.sh"
  interval 3
  weight -2
  fall 10
  rise 2
}

vrrp_instance VI_1 {
    state ${STATE}
    interface ${INTERFACE}
    virtual_router_id ${ROUTER_ID}
    priority ${PRIORITY}
    authentication {
        auth_type PASS
        auth_pass ${AUTH_PASS}
    }
    virtual_ipaddress {
        ${CONTROL_PLANE_VIP}
    }
    track_script {
        check_apiserver
    }
}
EOF

cat <<EOF | tee /etc/keepalived/check_apiserver.sh
#!/bin/sh

errorExit() {
    echo "*** $*" 1>&2
    exit 1
}

curl -sfk --max-time 2 https://localhost:${APISERVER_DEST_PORT}/healthz -o /dev/null || errorExit "Error GET https://localhost:${APISERVER_DEST_PORT}/healthz"
EOF



mkdir -p /etc/kubernetes/manifests
cat <<EOF | tee /etc/kubernetes/manifests/keepalived.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  name: keepalived
  namespace: kube-system
spec:
  containers:
  - image: linkvt/osixia_keepalived:stable
    name: keepalived
    resources: {}
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
        - NET_BROADCAST
        - NET_RAW
    volumeMounts:
    - mountPath: /usr/local/etc/keepalived/keepalived.conf
      name: config
    - mountPath: /etc/keepalived/check_apiserver.sh
      name: check
  hostNetwork: true
  volumes:
  - hostPath:
      path: /etc/keepalived/keepalived.conf
    name: config
  - hostPath:
      path: /etc/keepalived/check_apiserver.sh
    name: check
status: {}
EOF

HAProxyの設定ファイル、マニフェスト作成

mkdir /etc/haproxy
cat <<EOF | tee /etc/haproxy/haproxy.cfg
# /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    log stdout format raw local0
    daemon

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 1
    timeout http-request    10s
    timeout queue           20s
    timeout connect         5s
    timeout client          35s
    timeout server          35s
    timeout http-keep-alive 10s
    timeout check           10s

#---------------------------------------------------------------------
# apiserver frontend which proxys to the control plane nodes
#---------------------------------------------------------------------
frontend apiserver
    bind *:${APISERVER_DEST_PORT}
    mode tcp
    option tcplog
    default_backend apiserverbackend

#---------------------------------------------------------------------
# round robin balancing for apiserver
#---------------------------------------------------------------------
backend apiserverbackend
    option httpchk

    http-check connect ssl
    http-check send meth GET uri /healthz
    http-check expect status 200

    mode tcp
    balance     roundrobin
    
    server ${HOST1_ID} ${HOST1_ADDRESS}:${APISERVER_SRC_PORT} check verify none
    server ${HOST2_ID} ${HOST2_ADDRESS}:${APISERVER_SRC_PORT} check verify none
    server ${HOST3_ID} ${HOST3_ADDRESS}:${APISERVER_SRC_PORT} check verify none
EOF


cat <<EOF | tee /etc/kubernetes/manifests/haproxy.yaml
apiVersion: v1
kind: Pod
metadata:
  name: haproxy
  namespace: kube-system
spec:
  containers:
  - image: haproxy:2.8
    name: haproxy
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: localhost
        path: /healthz
        port: ${APISERVER_DEST_PORT}
        scheme: HTTPS
    volumeMounts:
    - mountPath: /usr/local/etc/haproxy/haproxy.cfg
      name: haproxyconf
      readOnly: true
  hostNetwork: true
  volumes:
  - hostPath:
      path: /etc/haproxy/haproxy.cfg
      type: FileOrCreate
    name: haproxyconf
status: {}
EOF
ふろんふろん

コントロールプレーン1台目
kubeadm init

kubeadm init --control-plane-endpoint "control-plane-vip:16443" --upload-certs --pod-network-cidr '10.244.0.0/16'

kubeconfig生成

exit # rootから抜ける
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Calico Operatorのインストール

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/tigera-operator.yaml

必要なリソースの作成

kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/custom-resources.yaml

見守る

watch kubectl get pods -n calico-system

taintの削除

kubectl taint nodes --all node-role.kubernetes.io/control-plane-
ふろんふろん

2台目は、クラスタに参加する前にはkeepalivedで作ったVIPが使えないので、まずVIPではなくノードのIPを使ってクラスタに参加したあと、/etc/hostsを編集してVIPで通信するように変更する。
keepalivedのスレーブ用

# コントロールプレーンのVIPに使用するIPアドレス
CONTROL_PLANE_VIP=192.168.10.100
# keepalivedのマスターにするノードではMASTER、他ではBACKUP
STATE=BACKUP
INTERFACE=eth0
ROUTER_ID=51
# keepalivedのマスターにするノードでは101, 他では100
PRIORITY=100
AUTH_PASS=42
APISERVER_VIP="$CONTROL_PLANE_VIP"
APISERVER_DEST_PORT=16443
APISERVER_SRC_PORT=6443
HOST1_ID=controlplane1
HOST1_ADDRESS=controlplane1
HOST2_ID=controlplane2
HOST2_ADDRESS=controlplane2
HOST3_ID=controlplane3
HOST3_ADDRESS=controlplane3

control-plane-vipがkeepalivedのマスターノード(最初に立ち上げたノード)のIPアドレスに名前解決されるように/etc/hostsを編集する。

# control-plane-vipの行を編集
192.168.10.101 control-plane-vip

keepalivedのマスターノードで[ノードのIP]:16443へのリクエストをAPI Serverに飛ばすようにする。

iptables -t nat -A PREROUTING -p tcp --dport 16443 -j REDIRECT --to-port 6443

クラスタに参加する

kubeadm join control-plane-vip:16443 # kubeadm initで出力されたtokenなど

クラスタに参加できたらkeepalivedが立ち上がるので、ルールを削除して/etc/hostsを編集することでVIPに話しかけるようにする。

iptables -t nat -L PREROUTING --line-numbers
iptables -t nat -D PREROUTING [上で追加したルールの番号]
# control-plane-vipの解決先をVIPに書き換える
vi /etc/hosts

疎通確認にはcurlを使う。

curl https://<master-ip>/version --insecure
ふろんふろん

3台目の時点ではもうkeepalivedによるVIPが設定されているので、/etc/hostsを編集する必要はない。

ワーカーノード
control-plane-vipへの接続確認 with curl
apt update
apt install -y containerd
mkdir /etc/containerd
touch /etc/containerd/config.toml
containerd config default > /etc/containerd/config.toml
sed
swapon --show
swapoff -a
rm /swap.img
swapon --show
cat <<EOF | tee /etc/

ちゃんと全controlplaneのipが書いてある
cur