Zenn
Closed4

KubernetesだけでHAを完結する「kube-vip」を試す

kun432kun432

GitHubレポジトリ

https://github.com/kube-vip/kube-vip

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
  • ハードウェアロードバランサー(ベンダーごとに機能が異なります)

ロードバランシング:

  • HAProxy
  • Nginx
  • ハードウェアロードバランサー(ベンダーごとに機能が異なります)

これらすべては、実装するために別個の設定が必要であり、いくつかのインフラストラクチャでは複数のチームが必要になる場合があります。また、ソフトウェアコンポーネントを考慮すると、コンテナにパッケージ化する必要があるか、事前にパッケージ化されている場合、セキュリティや透明性の問題が生じる可能性があります。最後に、エッジ環境では、ハードウェアのスペースが限られている場合(ハードウェアロードバランサーがない)、または適切なアーキテクチャのパッケージソリューションが存在しない場合(例:ARM)があります。幸いなことに、kube-vipはGOで書かれており、小さく(ish)、複数のアーキテクチャ向けに簡単にビルドでき、コンテナ内で必要な唯一のものとしてセキュリティ上の利点もあります。

公式ドキュメント

https://kube-vip.io/

kun432kun432

とりあえず、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をセットアップする場合のドキュメントは以下。

https://kube-vip.io/docs/usage/k3s/

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マニフェスト生成に必要な設定を確認していく。

https://kube-vip.io/docs/installation/daemonset/#generating-a-manifest

まずコントロールプレーンの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台目に紐づいている。

k3s-master-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)
k3s-master-2出力
(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)
k3s-master-3出力
(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が移動する。

k3s-master-2出力
(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が移動した。

k3s-master-3出力
(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を適用する。

https://kube-vip.io/docs/usage/cloud-provider/#install-the-kube-vip-cloud-provider

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なサービスをデプロイしてみる。

sample-nginx-service.yaml
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)
kun432kun432

まとめ

今回はK3Sを使用したが、K3S向けの設定みたいなところもあって少しハマったりしたのだが、Kubernetesのクラスタ内でHAがすべて完結できてとてもスッキリした。

このスクラップは2ヶ月前にクローズされました
ログインするとコメントできます