Homelab #12: Domowy klaster Kubernetes (k3s)

— 6 min read

PL
Table of Contents

Domowy klaster Kubernetes to na pewno nie jest coś, co chciałbym utrzymywać w domowej serwerowni. Jednak w pewnym momencie postanowiłem coś zmienić.

Kubernetes Cluster

Koszt utrzymania maszyn w chmurze zaczął rosnąć, a dodatkowo nie miałem żadnego publicznie hostowanego projektu, który potrzebowałby uptime na poziomie 99,99999%. Nie za bardzo lubię Docker Swarm Mode, więc jedynym „produkcyjnym” rozwiązaniem jest dla mnie k8s (Kubernetes). Może i jest trochę skomplikowany, ale daje mnóstwo rzeczy od razu z pudełka. Ogólnie lubię uruchamiać serwisy za pomocą Docker Compose, ale dla bardziej „produkcyjnego” środowiska wybieram klaster z kilku maszyn i kilkoma kopiami serwisu. A nie znam lepszego i prostszego rozwiązania na to niż k8s.

k3s

Jeżeli chodzi o Kubernetes, to do lokalnego uruchamiania wybieram k3s. Jest to lekka, uproszczona dystrybucja Kubernetes, zaprojektowana głównie do klastrów w środowiskach o ograniczonych zasobach. Jest łatwiejsza w instalacji niż pełnowymiarowy k8s i ma minimalne wymagania sprzętowe. K3s usuwa lub zastępuje cięższe elementy Kubernetes lżejszymi odpowiednikami, zachowując pełną kompatybilność z API k8s. Dlatego do lokalnego klastra wybieram dystrybucję k3s.

Węzły klastra

Na początku myślałem o uruchomieniu trzech maszyn wirtualnych w Proxmox i postawieniu na nich klastra. Jednak szybko zrozumiałem, że to bardzo zły pomysł — Proxmox służy mi jako główny teren testowy do różnych eksperymentów. Od czasu do czasu (chociaż ostatnio coraz rzadziej) główny serwer homelab potrafił się całkowicie wysypać lub coś się w nim psuło. Więc ten pomysł odpadł.

Miałem kilka Raspberry Pi, więc kolejnym pomysłem było zbudowanie klastra jako 1 maszyna wirtualna + 2 Raspberry Pi. Te drugie służyłyby jako backup w przypadku awarii Proxmoxa. Dwa Raspberry Pi z 4 GB RAM prawie na pewno wystarczyłyby, aby uruchomić wszystko to, co wcześniej hostowałem w chmurze. Jednak bardzo nie lubię Raspberry Pi z ich kartami pamięci (wiem, że do wersji 5 można podłączyć SSD, ale mam tylko wersję 4).

Kolejnym pomysłem było znalezienie tanich komputerów typu micro — Intel NUC i podobnych. Od czasu do czasu przeglądałem OLX i jednego dnia trafiłem na ofertę Dell Optiplex 3050 Micro z procesorem i3-7100T. Sprzedawca miał ich więcej, więc kupiłem cztery. Trzy miały trafić do klastra, a jeden miał być zapasowy na wypadek awarii. Niestety wszystkie były bez RAM i dysków, ale udało mi się bardzo tanio kupić 8 GB RAM i 128 GB SSD. Po wszystkim jeden komputer kosztował około 200 zł.

Dell 3050

Instalacja systemu operacyjnego

Zawsze miałem w szufladzie kilka pendrive’ów z różnymi systemami i trochę mnie to denerwowało — nigdy nie wiedziałem, jaki system i w jakiej wersji znajduje się na którym. Od dawna chciałem przetestować sieciową instalację systemów operacyjnych, ale ciągle to odkładałem. Teraz stałem się posiadaczem czterech komputerów, na których musiałem zainstalować system. Pierwszy szybki Google i trafiłem na FOG. Po wielu godzinach prób średnio mi to poszło i się poddałem. Kolejny Google — netboot.xyz. I to był strzał w dziesiątkę. Prawie od razu udało mi się zainstalować system na pierwszym komputerze. To świetne rozwiązanie, jeśli ktoś instaluje różne systemy na wielu urządzeniach. Wystarczy uruchomić netboot.xyz w Docker Compose, wpisać konfigurację Network Boot w routerze, zazwyczaj podać IP hosta z netboot.xyz i wskazać plik PXE netboot.xyz.efi. W BIOS urządzenia ustawić bootowanie sieciowe jako pierwsze. Wybór systemu padł na Debian GNU/Linux 13. Instalacja wszystkich czterech maszyn zajęła mniej niż godzinę. Dodatkowo teraz instalacja systemu operacyjnego w sieci domowej nie wymaga biegania i szukania odpowiedniego pendrive’a.

Konfiguracja węzłów

Maszyny wybrane, system zainstalowany — pozostało zainstalować i zainicjalizować k3s. Chociaż robię to pewnie tylko raz (i mam nadzieję, że długo znowu nie będę musiał), to chciałem ten proces zautomatyzować na przyszłość. Dlatego zamiast ręcznej instalacji użyłem Ansible, z którym mam największe doświadczenie.

Plan był prosty:

  • Zainstalować k3s na masterze (kube-1).
  • Zainicjalizować klaster k3s na masterze.
  • Pobierać token niezbędny do dołączenia węzłów.
  • Zainstalować k3s na pozostałych węzłach (kube-2, kube-3).
  • Dołączyć kube-2 oraz kube-3 do klastra za pomocą tokena. Te kroki pozwoliły uruchomić klaster z 3 węzłów: kube-1, kube-2 i kube-3. W lokalnym DNS przypisałem im nazwy jako aliasy, aby w razie zmiany adresu IP wystarczyła edycja w jednym miejscu i ponowne uruchomienie playbooka.

Dodatkowo w sieci domowej zrobiłem osobną sieć VLAN o nazwie Cloud, która izoluje cały klaster od reszty urządzeń.

Przestrzeń dyskowa

Kolejnym problemem były dyski. W Kubernetes serwisy mogą być migrowane pomiędzy węzłami (jeśli nie są przypięte do konkretnego). Jeśli aplikacja korzysta z lokalnego storage (np. SQLite), to w przypadku migracji pozostawiałaby dane na poprzednim węźle. Potrzebowałem rozwiązania, które zamieni lokalne dyski w replikowany, wysoko dostępny storage. Wiedziałem o Longhorn, więc zacząłem czytać. Okazało się, że jest to dokładnie to, czego potrzebowałem. Longhorn umożliwia tworzenie replikowanych wolumenów blokowych odpornych na awarie węzłów. Działa w przestrzeni użytkownika, nie wymaga specjalnego sprzętu ani zewnętrznych systemów storage. Dodatkowo ma wygodne UI, snapshoty i backupy.

Dostęp zewnętrzny

Początkowo planowałem połączenie VPN pomiędzy klastrem k3s a maszyną w chmurze pełniącą rolę reverse proxy. Powinno być prosto — zapytania trafiają do load balancera, a dalej do aplikacji. Zacząłem od Tailscale, którego używam w wielu projektach. Nawet ma operatora dla k8s, więc konfiguracja była łatwa. Niestety pojawił się dziwny bug: zapytania z małym payloadem przechodziły szybko, ale większe (powyżej ~100 KB) trwały… kilka minut. Aplikacja odpowiadała w milisekundach, więc problem był po stronie sieci. Po wielu testach wyszło, że problem prawdopodobnie leży w segmentacji pakietów (MTU mismatch). Próbowałem różnych ustawień, ale poddałem się. Nie chciałem hostować własnego VPN, więc zacząłem szukać innego rozwiązania.

Z pomocą przyszedł Cloudflare, a dokładnie Cloudflare Tunnels. O ile dobrze rozumiem, tworzy się tunel pomiędzy Cloudflare a lokalnym endpointem, a po stronie Cloudflare działa reverse proxy. Wszystko konfiguruje się wygodnie przez UI — zero utrzymania, zero dodatkowego hostowania. Jestem na tym od kilku miesięcy i działa perfekcyjnie.

Cloudflare Tunner

Podsumowanie

Finalnie po kilku dniach miałem działający klaster Kubernetes, który pozwala mi lokalnie hostować serwisy dostępne publicznie bez otwierania portów, na oddzielnej sieci i z pełną izolacją. Przerzuciłem wszystkie serwisy z chmury do k3s, dzięki czemu mogłem zrezygnować z kilku maszyn w chmurze, które kosztowały mnie ~100 USD miesięcznie. To w pewnym momencie było po prostu za dużo jak na coś, co miało tylko koszty i 0 zysku. Cały proces był bardzo ciekawy (hostowanie k3s okazało się mniej skomplikowane, niż wszyscy straszą). Może miałbym problem z Kubernetesem, gdybym go dopiero zaczynał poznawać, ale że mam wiele lat doświadczenia z podami, deploymentami, serwisami itp. — poszło szybko.

Na moment pisania artykułu klaster działa już około 4 miesięcy. Tylko raz miałem downtime — przez awarię Orange w okolicy, co uświadomiło mnie, że przyda mi się zapasowy internet. Przerwy w dostawie prądu rozwiązuje UPS w szafie rack. Same węzły też idealnie się w niej zmieściły.

PS: co do opłacalności — klaster już mi się zwrócił. Koszty to 600 zł, zużycie prądu ~30 W na cały klaster, więc za pierwsze 3 miesiące wyszło 600 + (25 × 3) = 675 zł. W tym samym czasie w chmurze zapłaciłbym ~1200 zł. W mojej specyficznej sytuacji więc — jak najbardziej się opłaciło.