ご家庭に(コントロールプレーンだけでも)HAなkubernetesクラスタを作成する
クラスタ作成に使用する機材
- コントロールプレーン: RaspberryPi4 * 3台
- ワーカーノード: ヤフオクで落としたx86なサーバー機 * 2
な8sクラスタを作成する
kubedam
kubeadm
というk8s構築ツールがあり、これを使用してk8sクラスタの構築が行える。
- 最初の1台で(多分コントロールプレーンにするやつで)
kubeadm init
する - 2台目以降は、コントロールプレーン・ワーカーノードに関わらず、
kubeadm join
する - 諸々の設定はコマンドのオプションで行う
らしい
マスターノード・コントロールプレーンと表記ゆれしまくりそうなんだけどそこは許してほしい
できるだけコントロールプレーンで表記を統一するように気をつけます・・・
HAのためのk8sの概念の復習
コントロールプレーン・ワーカーノードとありますが、
- コントロールプレーンは、k8sクラスタ全体のあるべき姿を定義する
- ワーカーノードは、自分のあるべき姿をコントロールプレーンに問い合わせ、あるべき姿に合うようにPod等をデプロイしたりダウンさせたりする
という役割の分担があります。
これをより具体的に見ていきます。
k8sを構成するコンポーネントのうち、コントロールプレーンで動作する主なものが下記の4つです
- kube-apiserver
- kube-controller-manager
- kube-scheduler
- etcd
https://kubernetes.io/docs/concepts/architecture/ より
etcdがk8sのあるべき姿を保存するkey-valueストア(≒DB)で、kube-apiserverがこのラッパみたいな感じに動作してetcd内のデータの読み書きはkube-apiserverを介してすべて行われます。
例えばkubectl
コマンドでmanifestのapplyを行うときは、kube-apiserverを介してetcdの値を書き換える・・・ということを行っているわけです。
kube-schedulerとかkube-controller-managerはユーザーが登録したmanifestにかかれていないが、実際にNodeでPodを起動するには必要な設定をetcd上へ追加します。具体的にはPodをどのNodeで動かすかの指定とか(kube-scheduler)、ServiceAccountの作成とか(kube-controller-manager)をやります。
で書き込まれたetcdのデータを(kube-apiserver経由で)ワーカーノードで動作するkubeletが読み出し、自分のNodeで動作するとされているPodを実際に起動することで、manifestの内容がk8sクラスタへ反映される・・・ということになります。
参考:
kubernetes完全ガイド 19.1(p634) kubernetesのアーキテクチャ概略 も合わせて参考にしましたコントロールプレーンのHA
コントロールプレーンのHA構成のTopologyは下記の2種類があります
- etcdとそのたk8sのコンポーネントを1台のノードに同居させる
Stacked etcd topology
- etcdのクラスタとk8sのコンポーネントを別々で動作させる
External etcd topology
今回は前者のStacked etcd topology
を使用します。
下記のような仕組みでノードダウン時の可用性は担保されています
- etcdは他のノードのetcdとclusterを構築しこのクラスタ内で冗長性が確保される
- 1台ノードが死んでも、クラスタとしてデータは保持される
- etcdは同じノードのkube-apiserverとのみ通信を行う。kube-scheduler/kube-controller-managerも同様に同じノードのkube-apiserverとのみ通信を行う。
- kube-scheduler/kube-controller-managerはleader-selectionの仕組みがある
- こいつらの具体的なHAの仕組みについては資料が見つからず・・・詳しい方いれば教えてください・・・
- leader-electoのオプションがリファレンスにあるのは確認しました・・・
つまるところ、
- etcdはetcdの仕組みとして冗長化されていて、基本的にどのノードのetcdに対して書き込み・読み込みを行っても同一のデータは読み出せ、kube-apiserverは多分データを右から左に流すだけなステートレスな存在
- kube-scheduler/kube-controller-managerもいい感じに冗長化されており、どこかのノードのどれかは動いていて、ノードダウン時は別のノードのkube-scheduler/kube-controller-managerが動き出す
- どこのノードのkube-apiserverに対して読み書きを行っても、同じようにetcdのデータは読み書きできるので、どのノードがkube-scheduler/kube-controller-managerになってもよい?
ので、まぁなんか良い感じにHigh Availabilityな状態が保たれます。
参考:
kube-apiserverのエンドポイントの冗長化
コントロールプレーンの冗長化の仕組みはざっくり理解することができました。
しかし、下記のk8sのHA構成トポロジの図を見ると、コントロールプレーンのkube-apiserverの前段にload balancerがあることに気が付きます
ワーカーノードはHTTPSプロトコルでコントロールプレーンのkube-apiserverとやり取りをするのですが、このときのIPアドレスもしくはDNS名は1つしか指定できません(control-plane-endpointといいます(多分))。
そのため、コントロールプレーンノードのうち1つのノードのIPアドレスをこのcontrol-plane-endpointにしてしまうと、このIPアドレスを持つノードがダウンしたときに別のコントロールプレーンノードが処理を継続していたとしても、ワーカーノードはコントロールプレーンと通信ができなくなってしまいます。
(どうして・・・コントロールプレーンノードのエンドポイントのリストのどれかにアクセスする・・・みたいな仕様ではないんだ・・・
なんか理由はあるんだろうけど)
そこでcontrol-plane-endpointにはLB等のVIPを指定して、コントロールプレーンノードのうち生きているノードへLBがルーティングするようにすることで、kube-apiserverのエンドポイントを冗長化することができます。
必ずしもLBを用意する必要があるわけではありませんが、コントロールプレーンをHAする場合は何らかの方法でcontrol-plane-endpointで指定するIPアドレスもしくはDNS名を冗長化して上げる必要があります。
参考:
kube-vipでコントロールプレーンノードのIPアドレスを冗長化?する
kube-vipを使用して複数のホストにまたがってVIP(仮想IPアドレス)を設定し、アクティブなホストのうちどれかがそのVIPに応答するようにすることができます。
もちろんダウンすると別のホストがVIPに応答するようになるので、control-plane-endpointへこのVIPを指定することで、kube-apiserverのエンドポイントを冗長化することができます。
kube-vipはStatic Pods
とDaemonSet
の2つのインストール方法があるようです。
kubeadmで主に使われるのがStatic Pods
のようなので(というかDaemonSet
はk8s構築後に使えるようになるが、kubedamでのクラスター構築前にVIPを用意する必要があるのでStatic Pods
しかつかえない)、static pods
でkube-vipをインストールします。
kube-vipはstatic pods
で動作させても各コントロールプレーンノードのkubelet配下のコンテナエンジン上で動作するようですが、あくまでkubernetesからステータス等を確認できるようになるだけで、k8sのリソース(config mapとか)を使用することはできないようです。
kube-vipのインストール方法のメモ
kube-vip v0.6.4をインストールします
下記のgithubのreleaseページからkube-vipの最新バージョンは確認することができます。
また、コンテナランタイムとしてcontainerdを使用します。
sudo su -
//設定値を環境変数へ設定します
export VIP=<設定したいVIP>
export INTERFACE=eth0
export KVVERSION=v0.6.4
//kube-vipコマンドを、manifestを生成するコンテナイメージを実行するコマンドのaliasとして設定します
alias kube-vip="ctr image pull ghcr.io/kube-vip/kube-vip:$KVVERSION; ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip /kube-vip"
//先程設定したkube-vipコマンドを使って、kube-vipを動作させるマニフェストを生成します
kube-vip manifest pod \
--interface $INTERFACE \
--address $VIP \
--controlplane \
--services \
--arp \
--leaderElection | tee /etc/kubernetes/manifests/kube-vip.yaml
exit
CNIの選定
各ワーカーノードへデプロイされたコンテナ同士で通信を行うためには、 CNI(the Container Network Interface)を備えたプラグインをインストールする必要があります。
よく名前を見るものとしては下記の物があるようです
- flannel
- Calico
- Cilium
eBPFというLinuxの新しいカーネル技術を使っているというのがなんかかっこいいのと、新しめの記事でちょくちょく見かける流行りものっぽいので、今回はCilium
を使用しようと思います。
ubuntuの設定
IPアドレスの設定
netplanの設定ファイルを作成・適用する
cd /etc/netplan
sudo touch 99-config.yaml
sudo vim 99-config.yaml
network:
ethernets:
eth0:
optional: true
dhcp4: no
dhcp6: no
addresses:
- 172.16.23.21/24
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
routes:
- to: default
via: 172.16.23.1
wlan0:
optional: true
version: 2
sudo chmod 600 *
sudo netplan apply
swapの無効化
k8s(の少なくとも1.28では)、swap領域を無効にする必要があるようです(ベータ版の機能を使えば有効にもできるらしいですが・・・)
ただ、raspberry pi imagerからインストールしたubuntu server 22.04ではswap領域は0になっているようでした。
cappuccino@raspberrypi4-01:~$ free
total used free shared buff/cache available
Mem: 7995476 192188 6940528 3172 862760 7539288
Swap: 0 0 0
swap領域が設定されている場合は、下記手順でswapを無効化します
sudo swapoff -a //一時的のswapを無効にする
sudo vim /etc/fstab // swap領域をマウントする設定をコメントアウトする
コンテナランタイムのインストール
IPv4のフォワーディングとiptabelsへbridgeしたトラフィックを確認できるようにする
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# sysctl params required by setup, params persist across reboots
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
# Apply sysctl params without reboot
sudo sysctl --system
containerdのインストール
sudo apt install -y containerd
containerdが使用するcgroupをsystemdが管理するものを使用するように設定します。
# containerdのconfigの初期化
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
設定ファイルを下記のように変更します
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
#SystemdCgroup = false //変更前
SystemdCgroup = true
sudo systemctl restart containerd
kubeadmとかを入れる
リポジトリのアップデート
# apt-transport-https may be a dummy package; if so, you can skip that package
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
k8sのリポジトリの鍵の追加
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
k8sのリポジトリの追加
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
kubeadm/kubelet/kubectlをインストールしバージョンを固定
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
このあとkube-vipのインストールを実施する
kubeadmでクラスタの構築を行う
sudo kubeadm init \
--control-plane-endpoint 172.16.23.20 \
--pod-network-cidr=10.1.0.0/16 \
--upload-certs
- --control-plane-endpointフラグは、ロードバランサーのIPアドレスまたはDNS名と、ポートが設定される必要があります。
- --upload-certsフラグは全てのコントロールプレーンノードで共有する必要がある証明書をクラスターにアップロードするために使用されます。代わりに、コントロールプレーンノード間で手動あるいは自動化ツールを使用して証明書をコピーしたい場合は、このフラグを削除し、以下の証明書の手動配布のセクションを参照してください。
- --upload-certsフラグをkubeadm initで使用すると、プライマリコントロールプレーンの証明書が暗号化されて、kubeadm-certs Secretにアップロードされます。
- デフォルトのポートが6443
kubectlを使うための設定(多分)
一般ユーザー権限でk8sクラスタを設定するために下記コマンドを実行する
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
その代替としてrootユーザーで行う場合は、下記環境変数の設定を行う
export KUBECONFIG=/etc/kubernetes/admin.conf
ciliumのインストール
helmのインストール
curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm
Ubuntu 22.04 on Raspberry Pi で動作させるときに必要な追加パッケージをインストール
sudo apt install linux-modules-extra-raspi
helmを使用したciliumのデプロイ
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --version 1.14.4 --namespace kube-system
2台目のコントローラー作成
初期設定を行う
kubeadmとかを入れる
kube-vipの導入を行う
kubeadm joinする
sudo kubeadm join <コントロールプレーン向けのコマンド>
kubectlを使う設定を入れる
helmはいれる
コントロールプレーンノードの追加
ubuntuの設定を行う
kubeadmとかいれる
kubeadm joinする
sudo kubeadm join <ワーカーノード向けのコマンド>
ただ、joinコマンド内に含まれるトークンには有効期限があるため、期限が切れている場合はコントロールおプレーンノードにてトークンの再発行を行う
kubeadm token create --print-join-command