はじめに
Control Planeを冗長化しHAを実現するKubernetesクラスタを構築することを目的に、kube-vipを利用しました。その際に、kubesprayを用いて構築を行いましたが、その流れをまとめてみたいと思います。
kube-vipとは
概要
kube-vipは、HAなControl PlaneやService LoadBalancerを実現するためのOSSです。
HAProxyやKeepalivedなどの外部コンポーネントに依存せず、ARPやBGPを使ってシンプルに冗長構成を構築することのできる点が特徴です。
仕組み
ARPモード
ARPモードでは、kube-vipが Kubernetesのleader electionの仕組みを用いて複数ノードの中から1台のノードをleaderとして選出します。
leaderとなったノードにのみ、VIP(Virtual IP)がネットワークインターフェースに付与され、VIP宛の通信に対して唯一ARP応答を行います。
leaderノードに障害が発生した場合、leader electionが再実行され、新しいleaderが選出されます。同時に、VIPも新ノードに引き継がれます。
参考:
BGPモード
BGPモードでは、VIPを単一ノードに集約するためのleader electionは行われず、各ノードが外部ルーターに対してVIPをBGPで広告します。
ルーターはBGPの経路選択に基づいて通信先ノードを決定し、選択されたノード上のkube-apiserverがリクエストを処理します。
参考:
アーキテクチャについて
今回構築したKubernetesクラスタのControl Planeの構成を紹介します。
ARPモード
kubesprayの設定により、クライアントからのAPIリクエストはControl PlaneのVIPに送信されます。
VIPはkube-vipによってleaderノードのネットワークインターフェイスに付与され、ARPによりVIP宛通信はleaderノードのMACアドレスへ到達します。
その結果、leaderノード上のkube-apiserverがリクエストを処理します。
処理されたデータは etcdクラスタを通じて各Control Planeノードのetcdに分散して保存され、クラスタ全体で状態が共有されます。
leaderノードに障害が発生した場合はleader選出が行われ、leaderが交代します。
この仕組みにより、Control Planeの可用性が維持されます。

leaderノードに障害が発生した際の可用性やVIPの移動、及びL2によるブロードキャストについては後ほど実際に確認を行います。
BGPモード
BGPモードで利用する場合、各Master NodeがBGPルーターに対してVIPである192.168.80.10/32宛の通信を自Nodeに送るよう広告を行います。そして、BGPルーターは経路の学習を行い、経路選択で選んだMaster Nodeへ転送します。
設定
kubesprayによるクラスタ構築
kubesprayは、Ansibleをベースとした、Kubernetesクラスタ構築を行うツールです。
一度設定を作成すれば、クラスタの破棄や再構築、IPアドレスを変更した上での使い回しが容易であり、設定内容をソースコードとして管理できるため便利です。
all:
hosts:
k8s-master-1:
ansible_host: 192.168.0.11
ip: 192.168.0.11
access_ip: 192.168.0.11
k8s-master-2:
ansible_host: 192.168.0.12
ip: 192.168.0.12
access_ip: 192.168.0.12
k8s-master-3:
ansible_host: 192.168.0.13
ip: 192.168.0.13
access_ip: 192.168.0.13
k8s-worker-1:
ansible_host: 192.168.0.14
ip: 192.168.0.14
access_ip: 192.168.0.14
k8s-worker-2:
ansible_host: 192.168.0.15
ip: 192.168.0.15
access_ip: 192.168.0.15
k8s-worker-3:
ansible_host: 192.168.0.16
ip: 192.168.0.16
access_ip: 192.168.0.16
k8s-worker-4:
ansible_host: 192.168.0.17
ip: 192.168.0.17
access_ip: 192.168.0.17
vars:
ansible_user: ryu
children:
kube_control_plane:
hosts:
k8s-master-1:
k8s-master-2:
k8s-master-3:
etcd:
hosts:
k8s-master-1:
k8s-master-2:
k8s-master-3:
kube_node:
hosts:
k8s-worker-1:
k8s-worker-2:
k8s-worker-3:
k8s-worker-4:
k8s_cluster:
children:
kube_control_plane:
kube_node:
上記設定を加えた後、ansible-playbookを実行することでControl Planeが冗長化されたKubernetesクラスタが構築されます。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master-1 Ready control-plane 4h54m v1.34.2
k8s-master-2 Ready control-plane 4h53m v1.34.2
k8s-master-3 Ready control-plane 4m10s v1.34.2
k8s-worker-1 Ready <none> 3m18s v1.34.2
k8s-worker-2 Ready <none> 3m18s v1.34.2
k8s-worker-3 Ready <none> 3m18s v1.34.2
k8s-worker-4 Ready <none> 3m18s v1.34.2
ARPモードのkube-vip設定
kube_control_plane_vip を指定することで、clientがAPIサーバーへ接続するためのエンドポイントが設定されます。
また、ARPモードでkube-vipを利用する設定を明記しています。
kube_proxy_strict_arp: true
kube_control_plane_vip: "192.168.0.10"
kube_vip_enabled: true
kube_vip_controlplane_enabled: true
kube_vip_arp_enabled: true
kube_vip_interface: "ens18"
参考:
BGPモードのkube-vip設定
BGPモードを有効化した上で、自身のAS番号と接続先のBGPルーターの情報を定義し、ルート情報を広告する経路を確立しています。
kube_control_plane_vip: "192.168.0.10"
kube_vip_address: "192.168.0.10"
kube_vip_enabled: true
kube_vip_controlplane_enabled: true
kube_vip_interface: "ens18"
kube_vip_arp_enabled: false
kube_vip_bgp_enabled: true
kube_vip_local_as: 65000
kube_vip_bgp_peeras: 65001
kube_vip_bgppeers:
- "192.168.0.9:65001::false" # BGPルーターを指定
BGPルーター側のFRR設定は主に以下のようになります。
-
ルーターの設定
- ルーター自身のAS番号(65001)と、識別用のID(192.168.0.9)を設定します。
-
ピア登録
- 3台のMaster Node(AS 65000)を通信相手として登録し、ルート情報を受け取れるようにします。
-
ECMP有効化
- VIPに対して最大3つまでの経路を同時に使用することを許可し、複数のノードを等価に扱うようにしています。
frr version 8.4.4
frr defaults traditional
hostname bgp-route
log syslog informational
no ipv6 forwarding
service integrated-vtysh-config
!
router bgp 65001
bgp router-id 192.168.0.9
no bgp ebgp-requires-policy
no bgp hard-administrative-reset
no bgp graceful-restart notification
neighbor 192.168.0.11 remote-as 65000
neighbor 192.168.0.12 remote-as 65000
neighbor 192.168.0.13 remote-as 65000
!
address-family ipv4 unicast
maximum-paths 3
bgp bestpath as-path multipath-relax
exit-address-family
exit
ARPモードでの動作確認
冗長性について
現在、k8s-master-3がleaderとなっていることがわかります。
ryu@k8s-master-3:~$ ip addr show ens18
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:xx:xx:xx brd ff:ff:ff:ff:ff:ff
altname enp0s18
inet 192.168.0.13/24 metric 100 brd 192.168.0.255 scope global dynamic ens18
valid_lft 106063sec preferred_lft 106063sec
inet 192.168.0.10/32 scope global ens18
valid_lft forever preferred_lft forever
ここで、leaderであるk8s-master-3のVMをShutDownしてみます。この時、leaderが死んでいるものの、kubectlは継続することが確認できます。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-master-1 Ready control-plane 47h v1.34.2
k8s-master-2 Ready control-plane 47h v1.34.2
k8s-master-3 NotReady control-plane 42h v1.34.2
k8s-worker-1 Ready <none> 42h v1.34.2
k8s-worker-2 Ready <none> 42h v1.34.2
k8s-worker-3 Ready <none> 42h v1.34.2
k8s-worker-4 Ready <none> 42h v1.34.2
また、VIPがk8s-master-2に移動しleaderが交代したことがわかります。
ryu@k8s-master-2:~$ ip addr show ens18
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:xx:xx:xx brd ff:ff:ff:ff:ff:ff
altname enp0s18
inet 192.168.0.12/24 metric 100 brd 192.168.0.255 scope global dynamic ens18
valid_lft 208844sec preferred_lft 208844sec
inet 192.168.0.10/32 scope global ens18
valid_lft forever preferred_lft forever
また、k8s-master-1にはVIPが割り当てられていません。
ryu@k8s-master-1:~$ ip addr show ens18
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:xx:xx:xx brd ff:ff:ff:ff:ff:ff
altname enp0s18
inet 192.168.0.11/24 metric 100 brd 192.168.0.255 scope global dynamic ens18
valid_lft 258729sec preferred_lft 258729sec
このことから、Control Plane冗長化により、apiserverが1台死んでもleaderが交代し新たにVIPを割り当て直すことで動作を継続できていることが確認できました。
L2ブロードキャスト
leaderであるk8s-master-2についてVIPが付与されていることを確認します。
ryu@k8s-master-2:~$ ip addr show ens18
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:11:10:ab:cd brd ff:ff:ff:ff:ff:ff
altname enp0s18
inet 192.168.0.12/24 metric 100 brd 192.168.0.255 scope global dynamic ens18
valid_lft 207619sec preferred_lft 207619sec
inet 192.168.0.10/32 scope global ens18
valid_lft forever preferred_lft forever
leaderであるk8s-master-2のノードがVIP(192.168.0.10)を保持しており、そのMACアドレスであるbc:24:11:10:ab:cdであることを、自ノードから送信されているARP Request/ReplyによりL2でブロードキャストしていることがわかります。
これにより、同一L2ネットワークの各ノードが、VIP宛の通信をleaderのk8s-master-2のMACアドレスに流せば良いという判断ができるようになります。
ryu@k8s-master-1:~$ sudo tcpdump -i ens18 -nne arp
08:06:04.451726 bc:24:11:10:ab:cd > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 60: Request who-has 192.168.0.10 (ff:ff:ff:ff:ff:ff) tell 192.168.0.10, length 46
08:06:07.472682 bc:24:11:10:ab:cd > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 60: Reply 192.168.0.10 is-at bc:24:11:10:ab:cd, length 46
BGPモードでの確認
学習経路の確認
Master Nodeが落ちた際に動作を継続できる点についてはARPモードと同様なので、割愛します。ここではBGPルーターが各Master Nodeから広告された経路情報を学習できている点を確認したいと思います。
BGPルーターのルートテーブルを確認すると、経路の学習を行なっていることがわかります。nexthopが3つ並んでおり、通信が1台に偏らず、3台のMaster Nodeに分散されることが確認できます。1台のMaster Nodeが故障したとしても残りの2台で処理を継続することができ、高可用性が維持されます。
ryu@bgp-router:~$ ip route show 192.168.0.10
192.168.80.10 nhid 20 proto bgp metric 20
nexthop via 192.168.0.11 dev ens18 weight 1
nexthop via 192.168.0.12 dev ens18 weight 1
nexthop via 192.168.0.13 dev ens18 weight 1
また、全Master NodeとBGPルーター間でBGPセッションが確立され、VIPの広告が正常に受信できていることが確認できます。
ryu@bgp-router:~$ sudo vtysh -c "show ip bgp summary"
IPv4 Unicast Summary (VRF default):
BGP router identifier 192.168.80.16, local AS number 65001 vrf-id 0
BGP table version 2
RIB entries 1, using 192 bytes of memory
Peers 3, using 2172 KiB of memory
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd PfxSnt Desc
192.168.0.11 4 65000 34 35 0 0 0 00:05:18 1 1 N/A
192.168.0.12 4 65000 33 34 0 0 0 00:05:02 1 1 N/A
192.168.0.13 4 65000 32 32 0 0 0 00:04:53 1 1 N/A
まとめ
今回はkube-vipのARPモード及びBGPモードを用いたHA Control PlaneなKubernetesクラスタをkubesprayを用いて構築し、実際の動作について確認を行いました。
この記事が参考になれば幸いです。
