Klaster K8s na Raspberry Pi, czyli K3s na nowo

Trzy lata temu pisałem o K3s i Raspberry Pi 4.

Setup u mnie ewaluował do postaci 5-węzłowego klastra i śmigało to nawet świetnie. Klaster stał sobie kilka tygodni nieruszany i naszła mnie ochota na sprawdzenie czegoś w piątek wieczór. Klątwa piątku po 17tej zaatakowała: 2 karty SD się skończyły (zużyły), więc przy okazji sobotniej naprawy tego klastra postanowiłem spisać co i jak. 😅

Mój domowy klaster K8s na Raspberry Pi

Główne założenia dla tej zabawki to:

Jeżeli ktoś chce powielić ten zestaw, lista zakupów wygląda następująco:

Architektura

Architektura klastra

Przygotowanie malinek

Na start nudna robota, czyli przygotowanie kart z OS i aktualizacje.

Polecam do tego wykorzystać Raspberry Pi Imager. I ISO sciągnie, i wstępnie system ustawi.

OS jaki wybieramy to Raspberry Pi OS Lite (64-bit).

Raspberry Pi Imager

W ustawieniach warto sobie zaznaczyć resztę konfiguracji, czyli:

Wyjdzie coś takiego jak poniżej:

UWAGA! Pamiętamy o zmianie hostname przy zmianie karty 😅

Raspberry Pi Imager ustawienia

Po odpaleniu na każdej malinie przygotowujemy /etc/hosts:

cat << EOF | sudo tee -a /etc/hosts

192.168.1.11 k3s-1 k3s-1.local
192.168.1.12 k3s-2 k3s-2.local
192.168.1.13 k3s-3 k3s-3.local
192.168.1.14 k3s-4 k3s-4.local
192.168.1.15 k3s-5 k3s-5.local
EOF

Do tego jeszcze robimy instalację aktualizacji i uruchamiamy cgroups:

sudo apt update
sudo apt upgrade -y
echo -n " cgroup_memory=1 cgroup_enable=memory" | sudo tee -a /boot/cmdline.txt
sudo reboot

Voilà! Maliny są gotowe na instalację klastra K3s.

Pierwszy control plane

Tutaj lecimy z instalacją zgodnie z dokumentacją. Ważny jest parametr --cluster-init odpowiadający za inicjację klastra oraz parametryzację sieci.

curl -sfL https://get.k3s.io | K3S_TOKEN=MY_CLUSTER sh -s - server --cluster-init --disable-helm-controller --flannel-backend=host-gw --disable servicelb

Czekamy ok. minutę i sprawdzamy, czy działa.

sudo k3s kubectl get nodes

Drugi i trzeci control plane

Instalacja drugiego i trzeciego noda pod control plane na tej samej zasadzie.

curl -sfL https://get.k3s.io | K3S_TOKEN=MY_CLUSTER sh -s - server --server https://k3s-1:6443 --disable-helm-controller --flannel-backend=host-gw --disable servicelb

Pora na test wysokiej dostępności, czyli łączymy się do każdego cp równolegle, zapuszczamy watch kubectl get nodes i losowo odłączamy od węzłów zasilanie i sieć.

Tutaj pokazuję jak to wygląda u mnie (w przyspieszonym tempie).

Workery aka agenty

Zaczynamy od instalacji lokalnie nginxa na każdym z workerów. Posłuży on do zapewnienia wysokodostępnego połączenia do control plane.

sudo apt install nginx -y
sudo mkdir -p /etc/nginx/tcpconf.d
cat << EOF | sudo tee /etc/nginx/tcpconf.d/kubernetes.conf
stream {
  upstream kubernetes {
      server 192.168.1.11:6443;
      server 192.168.1.12:6443;
      server 192.168.1.13:6443;
  }
  server {
      listen 6443;
      listen 443;
      proxy_pass kubernetes;
   }
}
EOF
echo 'include /etc/nginx/tcpconf.d/*;' | sudo tee -a /etc/nginx/nginx.conf
sudo systemctl restart nginx

Dopisujemy sobie nazwę naszego control plane do hosts. Ładnie podajemy nazwę k3s jako adres control plane w agencie, a ruch lokalnie wpadnie na nginx, który zrobi load balancing/failover, jeżeli któryś z węzłów nie będzie dostępny.

cat << EOF | sudo tee -a /etc/hosts
127.0.0.1 k3s k3s.local
EOF

Architektura nginx

Dołaczając nowy worker do klastra trzeba się zautoryzować tokenem. Token dostępowy znajdziemy na pierwszym cp w pliku /var/lib/rancher/k3s/server/node-token.

Teraz instalacja agenta K3s zgodnie z dokumentacją:

curl -sfL https://get.k3s.io | K3S_URL=https://k3s:6443 K3S_TOKEN={token z pliku} sh -

Około minuty i w liscie nodów powinień być działający worker 🙃

root@k3s-1:/home/pi# kubectl get nodes
NAME    STATUS   ROLES                       AGE     VERSION
k3s-1   Ready    control-plane,etcd,master   75m     v1.26.3+k3s1
k3s-2   Ready    control-plane,etcd,master   72m     v1.26.3+k3s1
k3s-3   Ready    control-plane,etcd,master   59m     v1.26.3+k3s1
k3s-4   Ready    <none>                      16m     v1.26.3+k3s1

Żeby lepiej wyglądał, worker możemy oznaczyć poprzez label:

kubectl label node k3s-4 node-role.kubernetes.io/worker=worker

Od razu lepiej 😉

root@k3s-1:/home/pi# kubectl get nodes
NAME    STATUS   ROLES                       AGE     VERSION
k3s-1   Ready    control-plane,etcd,master   75m     v1.26.3+k3s1
k3s-2   Ready    control-plane,etcd,master   72m     v1.26.3+k3s1
k3s-3   Ready    control-plane,etcd,master   59m     v1.26.3+k3s1
k3s-4   Ready    worker                      16m     v1.26.3+k3s1

Load Balancer

Pora na instalację MetalLB. Tutaj też zgodnie z dokumentacją:

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.9/config/manifests/metallb-native.yaml

Teraz należy dodać konfigurację naszej sieci, a dokładniej: puli adresów IP, z jakich ma korzystać nasz Kubernetes na RPi. MetalLB konfigurujemy za pomocą obiektu IPAddressPool w K8s. Poniżej przykład mojej (192.168.1.80-192.168.1.99 to zakres adresów z mojej fizycznej sieci, do której wpięte są maliny):

cat << EOF | sudo kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: svc-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.1.80-192.168.1.99
EOF

Mając pulę adresów IP trzeba jeszcze skonfigurować MetalLB, aby rozgłaszało je po L2. Służy do tego obiekt L2Advertisement:

cat << EOF | sudo kubectl apply -f -
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2advertisement
  namespace: metallb-system
EOF

Pozostaje szybki test czy działa, czyli pora wdrożyć nieśmiertelny Deployment z nginx 😏 i opublikować na świat.

sudo kubectl create deployment nginx --image=nginx
sudo kubectl expose deployment nginx --type=LoadBalancer --name=nginx --port=80

Po kilku chwilach będzie można połączyć się z usługą na wystawionym adresie IP.