Artykuł pochodzi z wydania: Wrzesień 2022
Głównym elementem Kubernetesa jest sieć. To za jej pomocą odbywa się komunikacja z uruchomionymi serwisami i podami. Poprawnie funkcjonująca jest kluczowa do działania klastra, więc poznanie jej architektury pomoże rozwiązać ewentualne problemy.
Podstawowe rozwiązania sieciowe, które wykorzystuje i implementuje Kubernetes, to przestrzenie nazw, wirtualne karty sieciowe oraz bridge. Dodatkowo platforma korzysta z takich narzędzi jak Netfilter i iptables. Do zobrazowania współdziałania tych elementów oraz zrozumienia ich funkcjonowania musimy utworzyć własną sieć, którą oprzemy na tych samych elementach. Prezentację przedstawimy na Linuksie Ubuntu, na którym nie został zainstalowany jeszcze Kubernetes.
Izolacja sieciowa
Pierwszy element sieci to przestrzeń nazwa. Jej zadaniem jest oddzielenie urządzeń jednej przestrzeni od drugiej. Izolujemy adresy IP, routing, zaporę sieciową, stos sieciowy. W czystej instalacji systemu operacyjnego mamy dostępną tylko jedną przestrzeń – domyślną (default namespace). Możemy ją zobaczyć, wykonując polecenie lsns–type net. W niej znajdują się domyślnie fizyczne (lub uznawane za fizyczne w zwizualizowanym środowisku) urządzenia sieciowe podłączone do naszej maszyny – karty sieciowe.
Do przeprowadzania izolacji sieciowej musimy sami utworzyć nową przestrzeń, wykorzystując polecenia systemu operacyjnego ip netns. W terminalu Linuksa jako root lub z wykorzystaniem sudo wykonujemy komendę sudo ip netns add private_ns1, która utworzy dla nas nową przestrzeń nazw – private_ns1. Aby wyświetlić ich listę, należy posłużyć się komendą ip netns list. Jeżeli natomiast chcemy wykonać jakiekolwiek polecenie w kontekście innej przestrzeni niż domyślna, należy posłużyć się komendą ip netns exec, która przyjmuje nazwę docelowej przestrzeni, a następnie wprowadzić polecenie do wykonania. Listę interfejsów znajdujących się w private_ns1 możemy otrzymać poleceniem ip netns exec private_ns1 ip a. Na razie zobaczymy tylko jeden interfejs lo – loopback. Naszym następnym krokiem będzie dodanie kolejnego interfejsu do nowo utworzonej przestrzeni.
Veth niczym tunel
Kolejny element sieciowych rozważań to wirtualne karty ethernet – veth. Jest to jeden z typów interfejsów dostępnych w Linuksie. Veth działa jak tunel, w którym ruch przekazywany jest z jednego końca na drugi. Cokolwiek pojawi się z jednej strony, zostanie wysłane na drugi koniec. Karty veth składają się zawsze z dwóch urządzeń, które tworzą parę, a jednocześnie dwie strony tunelu. Ich głównym zdaniem jest przekazywanie pakietów z jednej strony na drugą.
Aby dodać veth do systemu, wykonujemy polecenie sudo ip link add veth1 type veth peer name veth1_1ns. Następnie listujemy interfejsy sieciowe, wykonując ip a. Nowe interfejsy zostaną oznaczone veth1@veth1_1ns oraz veth1_1ns@ veth1, gdzie po znaku @ znajduje się drugi koniec (peer). Końcówkę oznaczoną jako veth1_1ns przeniesiemy do utworzonej wcześniej przestrzeni nazw private_ns1, wykonując komendę sudo ip link set dev veth1_1ns netns private_ns1. Listując wszystkie interfejsy, zobaczymy, że veth1_1ns nie jest już dostępny. Ostatnie polecenie listujące interfejsy wykonaliśmy w domyślnej przestrzeni nazw, dlatego nie zobaczymy go w wyniku. Aby wykonać komendy sieciowe w kontekście innej przestrzeni, należy posługiwać się jedną ze składni. ip netns NAZWA_PRZESTRZENI exec POLECENIE wykona ostatni parametr POLECENIE – w kontekście danej przestrzeni może to być dowolny program, ip -n NAZWA_PRZESTRZENI OPCJE_POLECENIA_IP wykona polecenie ip we wskazanej przestrzeni. Korzystając z opcji exec, możemy jako POLECENIE podać nawę powłoki (bash), wtedy zostanie otwarta sesja terminala, która wykona kolejne komendy we wskazanej przestrzeni nazw. Aby z niej wyjść, należy posłużyć się kombinacją Ctrl+D. Aby wylistować interfejsy w przestrzeni private_ns1, wykonujemy polecenie sudo ip netns private_ns1 ip link show. Zobaczymy interfejs veth1_1ns oraz loopback, które mają status DOWN (wyłączone).
Budujemy most
W kolejnym kroku przypiszemy adres IP jednemu z interfejsów wirtualnych, który znajduje się w przestrzeni private_ns1 – sudo ip addr add 192.168.10.1/24 dev veth1_1ns. Na koniec uruchamiamy wszystkie interfejsy komendami up – sudo ip netns private_ns1 ip link set veth1_1ns up, sudo ip netns private_ns1 ip link set lo up. W wyniku powyższej procedury utworzyliśmy sieciową przestrzeń nazw private_ns1, w której mamy interfejs sieciowy veth1_1ns o typie veth, którego drugi koniec znajduje się w domyślnej przestrzeni nazw. Ten drugi koniec podłączymy do kolejnego elementu
rozwiązań sieciowych.
Do bridge’a (mostu) możemy podłączać inne urządzenia sieciowe. Jest podobny do switcha, jego zadaniem jest grupowanie portów i umożliwienie komunikacji w drugiej warstwie modelu OSI. Linux pozwala na utworzenie wirtualnego bridge’a i zarządzanie nim. Nowy most o nazwie br0 dodajemy w domyślniej przestrzeni nazw poleceniem sudo ip link add br0 type bridge. Na koniec ustawiamy adres IP dla bridge’a, który będzie w tej samej podsieci co przestrzeń nazw private_ns1 sudo ip addr add 192.168.10.100/24 dev br0 i uruchamiamy most sudo ip link set dev br0 up. Jego właściwości możemy wyświetlić poleceniem ip -d link show type bridge. Opcja –d sprawi, że dostaniemy wszystkie szczegóły urządzenia. Po nadaniu adresu IP zobaczymy, że do routingu (ip route) został dodany nowy wpis, który kieruje ruch do sieci 192.168.10.0/24 na urządzenie br0. Aby sprawdzić most, wysyłamy ping na jego adres.
Do zarządzania i monitorowania mostami Linux udostępnia polecenie bridge. Znajdziemy w nim narzędzia do administrowania portami, monitorowania, zarządzania tablicą FDB, czyli mapowania portów na adresy MAC, a także do zarządzania VLAN. Nasz bridge znajduje się w domyślnej przestrzeni, w której mamy także dostępny jeden interfejs z pary veth – veth1. Aby podłączyć go do mostu, wykonujemy polecenie ip link set dev veth1 master br0. Urządzenie veth1 może zostać po tej zmianie wyłączone, więc trzeba będzie ponownie zmienić jego stan na up. Podłączone interfejsy możemy przeglądać przez bridge link show br0. Drugi koniec – veth1_1ns, który znajduje się w innej przestrzeni nazw, należy skonfigurować tak, aby jego ruch sieciowy był kierowany na adres mostu. Aby to zrobić, należy dodać domyślną trasę routingu, wskazując adres bridge br0 jako domyślną bramę. Polecenie nie dotyczy domyślnej przestrzeni, więc powinniśmy dostać się do niej, np. wcześniej uruchomić w niej proces terminala z wykorzystaniem opcji exec, a następnie wykonać sudo ip r add default via 192.168.10.100 dev veth1_1ns. Po tej zmianie możemy przetestować kanał veth, aby sprawdzić, czy jest widoczna komunikacja po obu jego stronach. W przestrzeni private_ns1 możemy uruchomić narzędzie ping i wysłać pakiet ICMP do wybranego celu. Po uruchomieniu tcpdump na interfejsie veth1 w domyślnej przestrzeni nazw powinniśmy zobaczyć ruch z interfejsu veth1_1ns.
Następny etap to dodanie kolejnej przestrzeni nazw – private_ns2, która będzie w tej samej sieci co private_ns1 oraz umożliwienie komunikacji między nimi. Nową przestrzeń tworzymy komendą sudo ip netns add private_ns2. Poleceniem sudo ip link add veth2 type veth peer name veth2_2ns tworzymy interfejsy, z których jeden przenosimy do nowej przestrzeni sudo ip link set dev veth2_2ns netns private_ns2, a drugi podłączamy do mostu sudo ip link set dev veth2 master br0. Dodajemy adres IP dla interfejsu, który znajduje się w private_ns2 sudo ip netns exec private_ns2 ip addr a 192.168.10.2/24 dev i dodajemy wpis do routingu sudo ip r add default via 192.168.10.100 dev veth2_2ns. Na koniec uruchamiamy interfejsy lo i veth sudo ip link set dev veth2 up. Po tych zmianach możemy wysyłać ping z każdej z przestrzeni nazw na adres mostu, ponadto przestrzenie będą mogły komunikować się między sobą. Tym sposobem dzięki prostym konstrukcjom dostępnym w systemie Linux symulowaliśmy działające kontenery lub pody za pomocą dwóch przestrzeni nazw. Kontenery te mogą się ze sobą komunikować bez żadnych problemów oraz bez wykorzystania NAT.
Komunikacja do podstawa
Kubernetes używany jest często na więcej niż jednym hoście, więc pody muszą mieć możliwość komunikowania się ze sobą bez ograniczeń. Wprowadzenie takiej komunikacji w naszym środowisku testowym wymaga ponownego wykonania wszystkich powyższych kroków w celu utworzenia przestrzeni nazw, interfejsów i mostów na kolejnych maszynach. Na koniec na każdej z nich należy dodać wpis do tablicy routingu, który będzie kierował ruch do sieci utworzonej dla mostu i interfejsów do niego podłączonych. Aby wykonać routing do pierwszej maszyny, którą już skonfigurowaliśmy, posługujemy się komendą sudo ip route add 192.168.10.0/24 via 172.16.0.150 dev eth1. Na każdej z maszyn musimy też włączyć opcję forward – sudo sysctl -w net. ipv4.ip_forward=1.
Przy rosnącej liczbie urządzeń, które będą tworzyły klaster kubernetesowy, możemy napotkać kilka problemów. Należą do nich m.in. zwiększony ruch i fizyczne oddzielenie od siebie tych urządzeń, które mogą w konsekwencji znajdować się w różnych sieciach IP. Jedną z zasad działania klastra jest brak NAT na poziomie sieci podów. Do rozwiązania tych trudności użyto kolejnych technologii, m.in. wykorzystano sieci underlay i overlay. Pierwszą możemy skojarzyć z istniejącą siecią fizyczną, do której dodajemy kolejną warstwę logiczną (wirtualną), zazwyczaj za pomocą aplikacji lub konfiguracji urządzeń, czyli overlay. Przykładem jest sieć VPN i tunelowanie. Tę technikę wykorzystuje się podczas komunikacji znajdujących się na różnych węzłach w klastrze K8s podów. Żaden z elementów biorących udział w rozwiązaniu – od poda do sieci – nie wie o pozostałych, dzięki czemu nie są od siebie zależne i można je wymieniać oraz konfigurować bez wpływu na inne. Komunikujące się pody wysyłają pakiety, a cała reszta odbywa się w kolejnych warstwach sieciowych. Overlay network ma za zadanie dodawanie (enkapsulację) lub usuwanie (dekapsulację) nadmiarowych danych z pakietów.
Podstawowe rozwiązania wykorzystywane w Kubernetesie to VXLAN oraz IP-IN-IP. VXLAN (Virtual eXtensible Local Area Network) to rozwinięcie protokołu VLAN, które działa w warstwie drugiego modelu OSI, przenosząc ramki z pomocą warstwy trzeciej. Powstał on, by rozszerzyć liczbę dostępnych identyfikatorów. Dodatkowo VXLAN pozwala na komunikację członków podsieci nawet oddzielonych od siebie siecią internet. Dzięki temu połączone urządzenia mogą zachowywać się, jakby były w tej samej domenie rozgłoszeniowej i komunikować za pomocą warstwy drugiej. Realizacja VXLAN polega na utworzeniu dodatkowego interfejsu, który jest w tej samej podsieci co pody. Interfejsy te nazywane są VTEP (VXLAN Tunnel End Point) i w K8s tworzy się je na każdym węźle. Ich zadaniem jest połączyć ze sobą drugą i trzecią warstwę sieci, wykonując enkapsulację i dekapsulację danych. Każdy z VTEP połączony jest tunelem, który po prostu transportuje dane z jednego końca na drugi za pomocą protokołu UDP na wskazanym porcie (domyślnie 4789). Podczas wysyłania danych z podów między węzłami, pakiet przechodzi z jednego końca przez VTEP, który wstawia wysyłaną ramkę przez pod jako dane w polu UDP. VTEP po drugiej stronie odbiera dane i dokonuje dekapsulacji. Wewnętrzną ramkę przekazuje do docelowego poda.
[…]
Paweł Ziółkowski
Autor jest absolwentem Uniwersytetu Ekonomicznego we Wrocławiu, pracuje jako informatyk w Urzędzie Gminy w Miękini.