
Niedawno spotkałem się z dość nietypowym zadaniem skonfigurowania routingu dla MetalLB. Zazwyczaj nie wymaga to dodatkowych działań, ale w naszym przypadku mamy do czynienia z dość dużym klastrem i prostą konfiguracją sieci.
W tym artykule opowiem, jak skonfigurować routing oparty na źródle i polityce dla zewnętrznej sieci waszego klastra.
Nie będę szczegółowo omawiać instalacji i konfiguracji MetalLB, ponieważ zakładam, że macie już pewne doświadczenie. Proponuję przejść od razu do rzeczy, a dokładniej do konfiguracji routingu. Mamy cztery przypadki:
Przypadek 1: Gdy konfiguracja nie jest wymagana
Rozważmy prosty przypadek.

Dodatkowa konfiguracja routingu nie jest wymagana, gdy adresy przydzielane przez MetalLB znajdują się w tej samej podsieci co adresy waszych węzłów.
Na przykład macie podsieć 192.168.1.0/24, w niej znajduje się router 192.168.1.1, a wasze węzły otrzymują adresy: 192.168.1.10-30, w takim razie dla MetalLB możecie skonfigurować zakres 192.168.1.100-120 i być pewnym, że będą działać bez jakiejkolwiek dodatkowej konfiguracji.
Dlaczego tak? Ponieważ wasze węzły mają już skonfigurowane trasy:
# ip route
default via 192.168.1.1 dev eth0 onlink
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10A adresy z tego samego zakresu będą je wykorzystywać bez jakichkolwiek dodatkowych działań.
Przypadek 2: Gdy wymagana jest dodatkowa konfiguracja

Powinniście skonfigurować dodatkowe trasy za każdym razem, gdy wasze węzły nie mają skonfigurowanego adresy IP lub trasy do podsieci, dla której MetalLB przydziela adresy.
Wyjaśnię to trochę dokładniej. Za każdym razem, gdy MetalLB przydzieli adres, można to porównać do prostego przypisania jak w poniższym przykładzie:
ip addr add 10.9.8.7/32 dev loZwróćcie uwagę na:
- a) Adres jest przydzielany z prefiksem
/32to znaczy, że trasa do podsieci dla niego nie zostanie automatycznie dodana (to po prostu adres) - b) Adres jest przypisywany do dowolnego interfejsu węzła (na przykład loopback). Warto wspomnieć o specyfice stosu sieciowego w systemie Linux. Nie ma znaczenia, do którego interfejsu dodacie adres, jądro zawsze będzie przetwarzać zapytania ARP i wysyłać odpowiedzi ARP na każdy z nich, to zachowanie uważa się za prawidłowe i jest szeroko stosowane w tak dynamicznym środowisku, jak Kubernetes.
To zachowanie można konfigurować, na przykład włączając strict arp:
echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announceW takim przypadku odpowiedzi ARP będą wysyłane tylko wtedy, gdy interfejs wyraźnie zawiera konkretny adres IP. Ta konfiguracja jest obowiązkowa, jeśli planujesz używać MetalLB i twój kube-proxy działa w trybie IPVS.
Jednakże MetalLB nie używa jądra do obsługi zapytań ARP, a robi to samodzielnie w przestrzeni użytkownika, dlatego ta opcja nie wpłynie na działanie MetalLB.
Wróćmy do naszego zadania. Jeśli nie ma trasy dla przydzielanych adresów na twoich węzłach, dodaj ją wcześniej na wszystkich węzłach:
ip route add 10.9.8.0/24 dev eth1Przypadek 3: Kiedy potrzebne będzie routing oparte na źródle
Routing oparty na źródle musisz skonfigurować, gdy otrzymujesz pakiety przez oddzielną bramę, a nie tę, która jest ustawiona domyślnie, odpowiednio pakiety zwrotne również muszą być wysyłane przez tę samą bramę.
Na przykład, masz tę samą podsieć 192.168.1.0/24 przydzieloną dla twoich węzłów, ale chcesz przydzielać zewnętrzne adresy za pomocą MetalLB. Załóżmy, że masz kilka adresów z podsieci 1.2.3.0/24 znajdujących się w VLAN 100 i chcesz użyć ich do uzyskania dostępu do usług Kubernetes z zewnątrz.

Podczas uzyskiwania dostępu do 1.2.3.4 będziesz wysyłać zapytania z innej podsieci niż 1.2.3.0/24 i oczekiwać odpowiedzi. Węzeł, który obecnie jest mistrzem dla przydzielonego adresu MetalLB 1.2.3.4, otrzyma pakiet od routera 1.2.3.1, ale odpowiedź musi wrócić tą samą trasą, przez 1.2.3.1.
Ponieważ nasz węzeł już ma skonfigurowaną domyślną bramę 192.168.1.1, domyślnie odpowiedź pójdzie do niej, a nie do 1.2.3.1, przez którą otrzymaliśmy pakiet.
Jak poradzić sobie w tej sytuacji?
W takim przypadku musisz przygotować wszystkie swoje węzły, aby mogły obsługiwać zewnętrzne adresy bez dodatkowej konfiguracji. To znaczy, w powyższym przykładzie musisz wcześniej utworzyć interfejs VLAN na węźle:
ip link add link eth0 name eth0.100 type vlan id 100
ip link set eth0.100 upA następnie dodać trasy:
ip route add 1.2.3.0/24 dev eth0.100 table 100
ip route add default via 1.2.3.1 table 100Zauważ, że trasy dodajemy do osobnej tabeli routingu 100 będzie ona zawierać tylko dwie trasy potrzebne do wysłania pakietu zwrotnego przez bramę 1.2.3.1, znajdującą się za interfejsem eth0.100.
Teraz musimy dodać prostą regułę:
ip rule add from 1.2.3.0/24 lookup 100która wyraźnie mówi: jeśli adres źródłowy pakietu znajduje się w 1.2.3.0/24, należy użyć tabeli routingu 100. Opisuje to trasę, która wyśle go przez 1.2.3.1
Przypadek 4: Gdy potrzebne jest routowanie oparte na politykach
Topologia sieci jest taka sama jak w poprzednim przykładzie, ale załóżmy, że chcesz również mieć możliwość łączenia się z zewnętrznymi adresami puli 1.2.3.0/24 z twoich podów:

Specyfika polega na tym, że przy próbie połączenia z dowolnym adresem w 1.2.3.0/24, pakiet odpowiedzi docierając na węzeł i mając adres źródłowy w zakresie 1.2.3.0/24 zostanie grzecznie wysłany do eth0.100, ale chcemy, żeby Kubernetes przekierował go do naszego pierwszego podu, który wygenerował początkowe żądanie.
Rozwiązanie tego problemu okazało się trudne, ale stało się możliwe dzięki routowaniu opartemu na politykach:
Aby lepiej zrozumieć proces, przedstawię schemat blokowy netfilter:

Na początku, jak w poprzednim przykładzie, stworzymy dodatkową tabelę routingu:
ip route add 1.2.3.0/24 dev eth0.100 table 100
ip route add default via 1.2.3.1 table 100Teraz dodamy kilka reguł do iptables:
iptables -t mangle -A PREROUTING -i eth0.100 -j CONNMARK --set-mark 0x100
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j RETURN
iptables -t mangle -A POSTROUTING -j CONNMARK --save-markTe reguły będą oznaczać przychodzące połączenia na interfejsie eth0.100, oznaczając wszystkie pakiety etykietą 0x100, ta sama etykieta będzie przypisana także do odpowiedzi w ramach jednego połączenia.
Teraz możemy dodać regułę routingu:
ip rule add from 1.2.3.0/24 fwmark 0x100 lookup 100To znaczy, że wszystkie pakiety z adresem źródłowym 1.2.3.0/24 i etykietą 0x100 powinny być trasowane za pomocą tabeli 100.
W ten sposób inne pakiety, otrzymane na innym interfejsie, nie będą objęte tą regułą, co pozwoli na ich trasowanie standardowymi środkami Kubernetes.
Jest jeszcze jeden problem, w systemie Linux istnieje tzw. filtr odwrotnej ścieżki, który psuje całą zabawę, przeprowadzając prostą kontrolę: dla wszystkich przychodzących pakietów zmienia adres źródłowy pakietu na adres nadawcy i sprawdza, czy pakiet może zostać wysłany przez ten sam interfejs, przez który został odebrany, jeśli nie, to go filtruje.
Problem w tym, że w naszym przypadku będzie działał niepoprawnie, ale możemy go wyłączyć:
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/eth0.100/rp_filterZauważ, że pierwsza komenda kontroluje globalne zachowanie rp_filter, jeśli go nie wyłączysz, to druga komenda nie będzie miała żadnego efektu. Mimo to inne interfejsy pozostaną z włączonym rp_filter.
Aby nie ograniczać pracy filtra całkowicie, możemy skorzystać z realizacji rp_filter dla netfilter. Używając rpfilter jako modułu iptables, można skonfigurować dość elastyczne zasady, na przykład:
iptables -t raw -A PREROUTING -i eth0.100 -d 1.2.3.0/24 -j RETURN
iptables -t raw -A PREROUTING -i eth0.100 -m rpfilter --invert -j DROPwłącz rp_filter na interfejsie eth0.100 dla wszystkich adresów oprócz 1.2.3.0/24.
Źródło: habr.com
