KubernetesだけでHAを完結する「kube-vip」を試す
おお、こんなのがある
LBを別に用意したくない・できない、MetalLBはなんか難しそう。これはシンプルにVIP付け替えてくれるだけのようなので、お手軽で良さそう、ってか自宅ならそれで十分かな。
GitHubレポジトリ
kube-vip
高可用性とロードバランシング
概要
KubernetesのコントロールプレーンおよびKubernetesサービス向けの仮想IPとロードバランサー
kube-vip
の背後にあるアイデアは、特に以下の環境向けに、小型で自己完結型の高可用性オプションを提供することです:
- ベアメタル
- エッジ(ARM / Raspberry PI)
- 仮想化
- その他ほぼすべての環境 :)
注: 使用方法やアーキテクチャに関するすべてのドキュメントは、現在https://kube-vip.ioで入手可能です。
特徴
Kube-Vipは元々、Kubernetesコントロールプレーンの高可用性ソリューションを提供するために作成されましたが、時間の経過とともに、その機能をKubernetesサービスのLoadBalancerタイプにも組み込むよう進化しました。
- VIPアドレスはIPv4およびIPv6の両方をサポート
- ARP(レイヤー2)またはBGP(レイヤー3)によるコントロールプレーン
- リーダー選出またはRaftを使用したコントロールプレーン
- kubeadm(スタティックPod)によるコントロールプレーンの高可用性
- K3sやその他(DaemonSet)によるコントロールプレーンの高可用性
- ARP(レイヤー2)用のリーダー選出を使用したサービスロードバランサー
- BGPを使用した複数ノードによるサービスロードバランサー
- 名前空間ごと、またはグローバルなサービスロードバランサーのアドレスプール
- (既存のネットワークDHCP)を介したサービスロードバランサーアドレス
- UPNPを介したゲートウェイへのサービスロードバランサーアドレスの公開
- Egress! Kube-vipは、PodのIngressおよびEgressの両方としてサービスロードバランサーを利用します。
- ... マニフェスト生成、ベンダーAPI統合など、他にも多数...
なぜ?
kube-vip
の目的は、高可用性のKubernetesクラスターの構築を簡素化することです。現在、この構築には管理が必要な複数のコンポーネントや設定が含まれる場合があります。これについては、thebsdboxが詳細にブログで取り上げています -> https://thebsdbox.co.uk/2020/01/02/Designing-Building-HA-bare-metal-Kubernetes-cluster/#Networking-load-balancing。代替の高可用性オプション
kube-vip
は、Kubernetesクラスターに仮想IPアドレスを提供するとともに、さまざまなコントロールプレーンレプリカへの受信トラフィックのロードバランシングも行います。現在、この機能を再現するには、最低でも2つのツールが必要です:VIP:
- Keepalived
- UCARP
- ハードウェアロードバランサー(ベンダーごとに機能が異なります)
ロードバランシング:
これらすべては、実装するために別個の設定が必要であり、いくつかのインフラストラクチャでは複数のチームが必要になる場合があります。また、ソフトウェアコンポーネントを考慮すると、コンテナにパッケージ化する必要があるか、事前にパッケージ化されている場合、セキュリティや透明性の問題が生じる可能性があります。最後に、エッジ環境では、ハードウェアのスペースが限られている場合(ハードウェアロードバランサーがない)、または適切なアーキテクチャのパッケージソリューションが存在しない場合(例:ARM)があります。幸いなことに、
kube-vip
はGOで書かれており、小さく(ish)、複数のアーキテクチャ向けに簡単にビルドでき、コンテナ内で必要な唯一のものとしてセキュリティ上の利点もあります。
公式ドキュメント
とりあえず、1台のProxmox上で複数のVMを立ち上げて、そこで試すこととし、Kubernetesクラスタの構築にはK3Sを使用する。
以下のVMを用意する。
ホスト名 | 役割 | CPUコア数 | メモリ | IPアドレス | 備考 |
---|---|---|---|---|---|
k3s-master-1 |
コントロールプレーン | 1 | 2GB | 192.168.69.201/22 | VIPは192.168.69.200 |
k3s-master-2 |
コントロールプレーン | 1 | 2GB | 192.168.69.202/22 | |
k3s-master-3 |
コントロールプレーン | 1 | 2GB | 192.168.69.203/22 | |
k3s-worker-1 |
ワーカーノード | 1 | 1GB | 192.168.69.211/22 | |
k3s-worker-2 |
ワーカーノード | 1 | 1GB | 192.168.69.212/22 | |
k3s-worker-2 |
ワーカーノード | 1 | 1GB | 192.168.69.213/22 |
K3S上でkube-vipをセットアップする場合のドキュメントは以下。
K3Sではあらかじめマニフェスト用のディレクトリにマニフェストを用意しておくと、K3Sインストール時に自動でapplyしてくれる機能があるので、先にkube-vipのマニフェストを用意してからK3Sのインストールを行うという流れになっている。なお、K3Sでクラスタセットアップしてからkube-vipをセットアップするというやり方でもやってみたのだけど、どうもうまくいかなかったので、手順通りにやったほうが良さそう。
まずコントロールプレーンから。
kube-vipのマニフェスト用ディレクトリを作成
sudo mkdir -p /var/lib/rancher/k3s/server/manifests/
kube-vipはDaemonSetでインストールするので、必要なRBACのマニフェストをダウンロード
curl https://kube-vip.io/manifests/rbac.yaml | sudo tee -a /var/lib/rancher/k3s/server/manifests/kube-vip-rbac.yaml
あとでkube-vipのDaemonSetのマニフェストを生成して上記のマニフェストに追加するので、デリミタを追加しておく。
echo "---" | sudo tee -a /var/lib/rancher/k3s/server/manifests/kube-vip-rbac.yaml
次に、kube-vipのDaemonSetマニフェスト生成に必要な設定を確認していく。
まずコントロールプレーンのVIP。コントロールプレーン・ワーカーノードで割り当てないIPアドレスを選択する。今回は192.168.69.200
とした・
export VIP=192.168.69.200
このVIPを割り当てるNICの名前を設定。自分の環境ではens18
だった。
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
(snip)
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
export INTERFACE=ens18
kube-vipの最新バージョンを取得。
KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases | jq -r ".[0].name")
echo $KVVERSION
v0.8.9
で、若干卵鶏になってしまうのだが、kube-vipのマニフェスト生成にはcontainerdかdockerが必要になるので、仮でインストールする。自分はcontainerdを使った。
sudo apt install -y containerd
kube-vipのイメージをpull
sudo ctr image pull ghcr.io/kube-vip/kube-vip:$KVVERSION
kube-vipのコンテナ経由でコマンド実行できるようにエイリアスを設定
alias kube-vip="sudo ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip /kube-vip"
サラッと叩いてみてUsageが返ってきたらOK
kube-vip
This is a server for providing a Virtual IP and load-balancer for the Kubernetes control-plane
Usage:
kube-vip [command]
Available Commands:
(snip)
ではkube-vipのDaemonSetのマニフェストを生成する。
kube-vip manifest daemonset \
--interface $INTERFACE \
--address $VIP \
--inCluster \
--taint \
--controlplane \
--services \
--arp \
--leaderElection
apiVersion: apps/v1
kind: DaemonSet
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/name: kube-vip-ds
app.kubernetes.io/version: v0.8.9
name: kube-vip-ds
namespace: kube-system
spec:
selector:
matchLabels:
app.kubernetes.io/name: kube-vip-ds
template:
metadata:
creationTimestamp: null
labels:
app.kubernetes.io/name: kube-vip-ds
app.kubernetes.io/version: v0.8.9
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/master
operator: Exists
- matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: Exists
containers:
- args:
- manager
env:
- name: vip_arp
value: "true"
- name: port
value: "6443"
- name: vip_nodename
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: vip_interface
value: ens18
- name: vip_cidr
value: "32"
- name: dns_mode
value: first
- name: cp_enable
value: "true"
- name: cp_namespace
value: kube-system
- name: svc_enable
value: "true"
- name: svc_leasename
value: plndr-svcs-lock
- name: vip_leaderelection
value: "true"
- name: vip_leasename
value: plndr-cp-lock
- name: vip_leaseduration
value: "5"
- name: vip_renewdeadline
value: "3"
- name: vip_retryperiod
value: "1"
- name: address
value: 192.168.69.200
- name: prometheus_server
value: :2112
image: ghcr.io/kube-vip/kube-vip:v0.8.9
imagePullPolicy: IfNotPresent
name: kube-vip
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
hostNetwork: true
serviceAccountName: kube-vip
tolerations:
- effect: NoSchedule
operator: Exists
- effect: NoExecute
operator: Exists
updateStrategy: {}
これを先ほどのRBACのマニフェストに追記する。
kube-vip manifest daemonset \
--interface $INTERFACE \
--address $VIP \
--inCluster \
--taint \
--controlplane \
--services \
--arp \
--leaderElection | sudo tee -a /var/lib/rancher/k3s/server/manifests/kube-vip-rbac.yaml
これでOK。containerdは不要なので削除しておく。
sudo apt remove -y containerd
sudo apt autoremove -y
sudo rm -rf /var/lib/containerd
ではコントロールプレーンにK3Sをインストールする。この時--tls-san
でVIPを指定する。あと、コントロールプレーンのVIPとServiceのロードバランシングもkube-vipに行わせるので、K3Sがデフォルトで持っているservicelbを無効化しておく。
curl -sfL https://get.k3s.io | sh -s - server \
--cluster-init \
--tls-san $VIP \
--disable servicelb
[INFO] Finding release for channel stable
[INFO] Using v1.31.5+k3s1 as release
[INFO] Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.31.5+k3s1/sha256sum-amd64.txt
[INFO] Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.31.5+k3s1/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Skipping installation of SELinux RPM
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Creating /usr/local/bin/ctr symlink to k3s
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s.service
[INFO] systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
[INFO] systemd: Starting k3s
kubectlで確認してみる。
sudo kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3s-master-1 Ready control-plane,etcd,master 18s v1.31.5+k3s1 192.168.69.201 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.23-k3s2
kube-vipのDaemonSetも動いている
sudo kubectl get ds -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-vip-ds 1 1 1 1 1 <none> 25s
VIPも割り当てられている。
ip a
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
inet 192.168.69.201/22 brd 192.168.71.255 scope global ens18
valid_lft forever preferred_lft forever
inet 192.168.69.200/32 scope global ens18
valid_lft forever preferred_lft forever
(snip)
ではコントロールプレーンの2台目・3台目。まずコントロールプレーン1台目でトークンを確認。
sudo cat /var/lib/rancher/k3s/server/node-token
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX::server:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2台目・3台目で以下を実行
export TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX::server:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export VIP=192.168.69.200
curl -sfL https://get.k3s.io | sh -s - server \
--server https://${VIP}:6443 \
--token ${TOKEN} \
--tls-san $VIP \
--disable servicelb
[INFO] Finding release for channel stable
[INFO] Using v1.31.5+k3s1 as release
[INFO] Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.31.5+k3s1/sha256sum-amd64.txt
[INFO] Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.31.5+k3s1/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Skipping installation of SELinux RPM
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Creating /usr/local/bin/ctr symlink to k3s
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s.service
[INFO] systemd: Enabling k3s unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s.service → /etc/systemd/system/k3s.service.
[INFO] systemd: Starting k3s
問題なく完了したらkubectlで確認してみる。
sudo kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3s-master-1 Ready control-plane,etcd,master 10m v1.31.5+k3s1 192.168.69.201 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.23-k3s2
k3s-master-2 Ready control-plane,etcd,master 66s v1.31.5+k3s1 192.168.69.202 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.23-k3s2
k3s-master-3 Ready control-plane,etcd,master 9s v1.31.5+k3s1 192.168.69.203 <none> Ubuntu 24.04.2 LTS 6.8.0-53-generic containerd://1.7.23-k3s2
で、この時点ではVIPは1台目に紐づいている。
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
inet 192.168.69.201/22 brd 192.168.71.255 scope global ens18
valid_lft forever preferred_lft forever
inet 192.168.69.200/32 scope global ens18
valid_lft forever preferred_lft forever
(snip)
(snip)
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
inet 192.168.69.202/22 brd 192.168.71.255 scope global ens18
valid_lft forever preferred_lft forever
(snip)
(snip)
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
inet 192.168.69.203/22 brd 192.168.71.255 scope global ens18
valid_lft forever preferred_lft forever
(snip)
1台目を再起動してみると、2台目にVIPが移動する。
(snip)
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
inet 192.168.69.202/22 brd 192.168.71.255 scope global ens18
valid_lft forever preferred_lft forever
inet 192.168.69.200/32 scope global ens18
valid_lft forever preferred_lft forever
(snip)
さらに2台目を再起動してみると、3台目にVIPが移動した。
(snip)
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
(snip)
inet 192.168.69.203/22 brd 192.168.71.255 scope global ens18
valid_lft forever preferred_lft forever
inet 192.168.69.200/32 scope global ens18
valid_lft forever preferred_lft forever
(snip)
コントロールプレーンはこれで完了。
次にワーカーノードをクラスタに追加する。コントロールプレーンで取得したトークンを使う。
export TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX::server:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
export VIP=192.168.69.200
curl -sfL https://get.k3s.io | K3S_URL=https://${VIP}:6443 K3S_TOKEN=$TOKEN sh -
[INFO] Finding release for channel stable
[INFO] Using v1.31.5+k3s1 as release
[INFO] Downloading hash https://github.com/k3s-io/k3s/releases/download/v1.31.5+k3s1/sha256sum-amd64.txt
[INFO] Downloading binary https://github.com/k3s-io/k3s/releases/download/v1.31.5+k3s1/k3s
[INFO] Verifying binary download
[INFO] Installing k3s to /usr/local/bin/k3s
[INFO] Skipping installation of SELinux RPM
[INFO] Creating /usr/local/bin/kubectl symlink to k3s
[INFO] Creating /usr/local/bin/crictl symlink to k3s
[INFO] Creating /usr/local/bin/ctr symlink to k3s
[INFO] Creating killall script /usr/local/bin/k3s-killall.sh
[INFO] Creating uninstall script /usr/local/bin/k3s-agent-uninstall.sh
[INFO] env: Creating environment file /etc/systemd/system/k3s-agent.service.env
[INFO] systemd: Creating service file /etc/systemd/system/k3s-agent.service
[INFO] systemd: Enabling k3s-agent unit
Created symlink /etc/systemd/system/multi-user.target.wants/k3s-agent.service → /etc/systemd/system/k3s-agent.service.
[INFO] systemd: Starting k3s-agent
全部追加するとこうなる
sudo kubectl get node
NAME STATUS ROLES AGE VERSION
k3s-master-1 Ready control-plane,etcd,master 42m v1.31.5+k3s1
k3s-master-2 Ready control-plane,etcd,master 32m v1.31.5+k3s1
k3s-master-3 Ready control-plane,etcd,master 31m v1.31.5+k3s1
k3s-worker-1 Ready <none> 78s v1.31.5+k3s1
k3s-worker-2 Ready <none> 2s v1.31.5+k3s1
k3s-worker-3 Ready <none> 3s v1.31.5+k3s1
kube-vipにService タイプ LoadBalancerを制御させるために、cloud controllerを適用する。
sudo kubectl apply -f https://raw.githubusercontent.com/kube-vip/kube-vip-cloud-provider/main/manifest/kube-vip-cloud-controller.yaml
serviceaccount/kube-vip-cloud-controller created
clusterrole.rbac.authorization.k8s.io/system:kube-vip-cloud-controller-role created
clusterrolebinding.rbac.authorization.k8s.io/system:kube-vip-cloud-controller-binding created
Warning: spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[1].preference.matchExpressions[0].key: node-role.kubernetes.io/master is use "node-role.kubernetes.io/control-plane" instead
deployment.apps/kube-vip-cloud-provider create
で、Service タイプ LoadBalancerの時に自動で割り当てるIPアドレスを指定する。指定方法はCIDRとレンジがあるが、今回はレンジで。
sudo kubectl create configmap -n kube-system kubevip --from-literal range-global=192.168.69.220-192.168.69.230
のだが、実はすでにConfigMapは存在しているので、上のコマンドはエラーになる。
sudo kubectl get configmap kubevip -n kube-system -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: "2025-02-12T01:03:34Z"
name: kubevip
namespace: kube-system
resourceVersion: "16871"
uid: 6a41c012-39c4-4eb4-8396-6f0dcc2ba7cb
ただしIPアドレスの指定はないので、patchで更新する。
sudo kubectl patch configmap kubevip -n kube-system -p '{"data":{"range-global": "192.168.69.220-192.168.69.230"}}'
configmap/kubevip patched
これでOK。
では試しに、nginxを使ったタイプLoadBalancerなサービスをデプロイしてみる。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
namespace: default
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
type: LoadBalancer
deployment.apps/nginx-deployment created
service/nginx-service created
serviceを確認してみる。
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 64m
nginx-service LoadBalancer 10.43.160.255 192.168.69.220 80:31334/TCP 39s
192.168.69.220
が自動で割り当てられているのがわかる。curlで外部からアクセスできることも確認できる。
curl http://192.168.69.220
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
(snip)
まとめ
今回はK3Sを使用したが、K3S向けの設定みたいなところもあって少しハマったりしたのだが、Kubernetesのクラスタ内でHAがすべて完結できてとてもスッキリした。