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