Open15

ご家庭に(コントロールプレーンだけでも)HAなkubernetesクラスタを作成する

yuuriyuuri

クラスタ作成に使用する機材

  • コントロールプレーン: RaspberryPi4 * 3台
  • ワーカーノード: ヤフオクで落としたx86なサーバー機 * 2
    な8sクラスタを作成する

kubedam

kubeadmというk8s構築ツールがあり、これを使用してk8sクラスタの構築が行える。

  • 最初の1台で(多分コントロールプレーンにするやつで) kubeadm init する
  • 2台目以降は、コントロールプレーン・ワーカーノードに関わらず、kubeadm joinする
  • 諸々の設定はコマンドのオプションで行う

らしい

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/

yuuriyuuri

マスターノード・コントロールプレーンと表記ゆれしまくりそうなんだけどそこは許してほしい
できるだけコントロールプレーンで表記を統一するように気をつけます・・・

yuuriyuuri

HAのためのk8sの概念の復習

コントロールプレーン・ワーカーノードとありますが、

  • コントロールプレーンは、k8sクラスタ全体のあるべき姿を定義する
  • ワーカーノードは、自分のあるべき姿をコントロールプレーンに問い合わせ、あるべき姿に合うようにPod等をデプロイしたりダウンさせたりする
    という役割の分担があります。

これをより具体的に見ていきます。
k8sを構成するコンポーネントのうち、コントロールプレーンで動作する主なものが下記の4つです

  • kube-apiserver
  • kube-controller-manager
  • kube-scheduler
  • etcd

https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/#stacked-etcd-topology より


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クラスタへ反映される・・・ということになります。

参考:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/
https://kubernetes.io/ja/docs/concepts/overview/components/#kube-controller-manager
kubernetes完全ガイド 19.1(p634) kubernetesのアーキテクチャ概略 も合わせて参考にしました

yuuriyuuri

コントロールプレーンの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な状態が保たれます。

参考:
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/#stacked-etcd-topology
https://kubernetes.io/docs/reference/command-line-tools-reference/kube-scheduler/
https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/

yuuriyuuri

kube-apiserverのエンドポイントの冗長化

コントロールプレーンの冗長化の仕組みはざっくり理解することができました。
しかし、下記のk8sのHA構成トポロジの図を見ると、コントロールプレーンのkube-apiserverの前段にload balancerがあることに気が付きます


https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/ha-topology/#stacked-etcd-topology より

ワーカーノードは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名を冗長化して上げる必要があります。

参考:
https://kubernetes.io/docs/concepts/architecture/control-plane-node-communication/

yuuriyuuri

kube-vipでコントロールプレーンノードのIPアドレスを冗長化?する

kube-vipを使用して複数のホストにまたがってVIP(仮想IPアドレス)を設定し、アクティブなホストのうちどれかがそのVIPに応答するようにすることができます。
もちろんダウンすると別のホストがVIPに応答するようになるので、control-plane-endpointへこのVIPを指定することで、kube-apiserverのエンドポイントを冗長化することができます。

https://zenn.dev/tochiman/articles/0cf100f428e81a
https://speakerdeck.com/inductor/say-good-bye-to-haproxy-and-keepalived-with-kube-vip-on-your-k8s?slide=18
https://kube-vip.io/docs/installation/static/
https://github.com/kube-vip/kube-vip

kube-vipはStatic PodsDaemonSetの2つのインストール方法があるようです。
kubeadmで主に使われるのがStatic Podsのようなので(というかDaemonSetはk8s構築後に使えるようになるが、kubedamでのクラスター構築前にVIPを用意する必要があるのでStatic Podsしかつかえない)、static podsでkube-vipをインストールします。

kube-vipはstatic podsで動作させても各コントロールプレーンノードのkubelet配下のコンテナエンジン上で動作するようですが、あくまでkubernetesからステータス等を確認できるようになるだけで、k8sのリソース(config mapとか)を使用することはできないようです。

yuuriyuuri

kube-vipのインストール方法のメモ

kube-vip v0.6.4をインストールします
下記のgithubのreleaseページからkube-vipの最新バージョンは確認することができます。
https://github.com/kube-vip/kube-vip/releases

また、コンテナランタイムとして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

yuuriyuuri

CNIの選定

各ワーカーノードへデプロイされたコンテナ同士で通信を行うためには、 CNI(the Container Network Interface)を備えたプラグインをインストールする必要があります。

よく名前を見るものとしては下記の物があるようです

  • flannel
  • Calico
  • Cilium

eBPFというLinuxの新しいカーネル技術を使っているというのがなんかかっこいいのと、新しめの記事でちょくちょく見かける流行りものっぽいので、今回はCiliumを使用しようと思います。

https://cilium.io/

yuuriyuuri

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領域をマウントする設定をコメントアウトする

コンテナランタイムのインストール

https://kubernetes.io/docs/setup/production-environment/container-runtimes/

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

https://www.notr.app/posts/2023/07/17/#containerdの設定-今回のポイント
https://kubernetes.io/docs/setup/production-environment/container-runtimes/#containerd

sudo systemctl restart containerd

yuuriyuuri

kubeadmとかを入れる

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-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のインストールを実施する

yuuriyuuri

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

https://kubernetes.io/ja/docs/setup/production-environment/tools/kubeadm/high-availability/#最初のコントロールプレーンノードの手順
https://zenn.dev/tochiman/articles/0cf100f428e81a

yuuriyuuri

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
yuuriyuuri

ciliumのインストール

helmのインストール

https://helm.sh/ja/docs/intro/install/

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 で動作させるときに必要な追加パッケージをインストール

https://docs.cilium.io/en/stable/operations/system_requirements/#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

https://docs.cilium.io/en/stable/installation/k8s-install-kubeadm/#deploy-cilium

yuuriyuuri

コントロールプレーンノードの追加

ubuntuの設定を行う
https://zenn.dev/link/comments/5638a6c6f46a84

kubeadmとかいれる
https://zenn.dev/link/comments/2f0a45822b559e

kubeadm joinする

sudo kubeadm join <ワーカーノード向けのコマンド>

ただ、joinコマンド内に含まれるトークンには有効期限があるため、期限が切れている場合はコントロールおプレーンノードにてトークンの再発行を行う

 kubeadm token create --print-join-command

https://stackoverflow.com/questions/63936268/how-to-generate-kubeadm-token-for-secondary-control-plane-nodes
https://zaki-hmkc.hatenablog.com/entry/2020/04/05/103651