Laptop pokazuje zielony tunel, log klienta donosi, że trasy zostały wypchnięte, a jedyny odpowiadający adres to IP tunelu samego serwera VPN. Pingi dalej giną niezależnie od celu, czy to ClusterIP, adres innego poda, czy publiczna nazwa hosta. Ta kombinacja jest diagnostyczna, a nie mglista: dekapsulacja działa, a drugi skok nie.

Spotkałem to na węźle Debian z obrazem OpenVPN zainstalowanym przez chart, i tunel był łatwą częścią. Klient dostał dzierżawę 10.8.0.x, 10.8.0.1 odpowiedział, a 10.96.0.10, usługa DNS klastra, przekroczyła limit czasu. Nic w logu serwera nie wyglądało chorobliwie, i właśnie dlatego ludzie zaczynają przepisywać reguły iptables, których nigdy nie musieli dotykać.

Pod VPN jest routerem, i warto powiedzieć to wprost. Zaszyfrowane pakiety przychodzą, są rozpakowywane w przestrzeni nazw sieci samego poda, a potem muszą wyjść innym interfejsem. Linux odmawia tego drugiego kroku, dopóki przekazywanie nie jest włączone w tej samej przestrzeni nazw.

Do czego klient może dotrzeć, a do czego nie

Odpowiadający adres należy do punktu końcowego tunelu, a proces VPN posiada go lokalnie, więc przekazywanie w ogóle nie uczestniczy. Wszystko inne, w tym inny pod, VIP usługi czy internet, wymaga od jądra przeniesienia pakietu z tun0 do eth0. Gdy to przeniesienie zostaje odrzucone, objawem jest równomierna cisza, a nie odrzucenie, bo nic nie generuje błędu ICMP z powrotem w tunel. Dostajesz limity czasu, a nie komunikaty o nieosiągalności, i to jest wskazówka, że nikt w ogóle nie routuje.

Dwie minuty klasyfikacji zawężają warstwę. Jeśli nazwy DNS zawiodą, a surowe pingi do IP usługi przechodzą, problem leży w hydraulice DNS, nie w przekazywaniu. Jeśli każdy cel za adresem peera kończy się limitem czasu, także goły IP, zacznij od przestrzeni nazw.

Odczytaj ustawienie wewnątrz przestrzeni nazw

Każdy pod dostaje własną przestrzeń nazw sieci, a net.ipv4.ip_forward to sysctl na przestrzeń nazw. Zmiana go na węźle nie mówi nic o kontenerze i jest pewnym sposobem spędzenia popołudnia na niewłaściwej maszynie.

kubectl exec -it deploy/openvpn -- sysctl net.ipv4.ip_forward
# net.ipv4.ip_forward = 0

Zero potwierdza diagnozę. Wartość nie jest też dziedziczona z konfiguracji uruchomieniowej węzła: startuje od domyślnej obrazu za każdym razem, gdy tworzony jest kontener. Poprawka zastosowana ręcznie przez kubectl exec ... sysctl -w znika więc przy następnym restarcie i wraca jako usterka wyglądająca na okresową.

Włączyć to tak, aby manifest to zachował

Trwałą opcją jest kontener init, który działa w tym samym pod sandboxie i zapisuje wartość przed startem kontenera VPN. Kontenery w podzie dzielą przestrzeń nazw sieci, więc ustawienie przenosi się do kontenera głównego.

initContainers:
- name: enable-forwarding
  image: busybox:1.36
  command: ["sysctl", "-w", "net.ipv4.ip_forward=1"]
  securityContext:
    privileged: true

Właśnie privileged: true to linia, przeciw której protestują recenzenci, i mają rację. Zapis sysctl wymaga albo uprawnienia, albo wąsko ograniczonej zdolności, a klaster z restrykcyjnym Pod Security Standard może wprost odrzucić uprzywilejowany kontener init. Jeśli admission odrzuci poda, dowiesz się o tym przy tworzeniu, co przynajmniej jest uczciwe i łatwe do odczytania.

Czystsza alternatywa to lista securityContext.sysctls na poziomie poda, która pozwala kubeletowi stosować sysctls z przestrzenią nazw bez przyznawania uprawnień żadnemu kontenerowi. Obejmuje tylko nazwy z listy dozwolonych klastra. To, czy net.ipv4.ip_forward jest na tej liście, zależy od konfiguracji kubeleta, a pod żądający sysctl poza nią jest odrzucany przy admission albo utyka w CreateContainerConfigError, gdy sandbox jest ponawiany. Sprawdź listę dozwolonych, zanim uznasz, że pole jest zepsute.

Zanim zbudujesz ręcznie którekolwiek z podejść, sprawdź, czy chart już udostępnia przełącznik, często jako coś w rodzaju ipForwardInitContainer: true. Użycie wartości charta utrzymuje plik override mały i przetrwa następną aktualizację, czego ręcznie łatane zmiany w Deploymencie nie robią.

Druga połowa: droga powrotna i polityka

Włączone przekazywanie jest konieczne, nie wystarczające. Jeśli klienci używają prywatnej puli, do której sieć klastra nie ma trasy, odpowiedzi nie znajdują drogi z powrotem w tunel, i pod musi maskować ten ruch w stronę celu.

Polityka siedzi na tym. Obiekty NetworkPolicy i reguły zapory chmurowej są oceniane wobec adresu źródłowego, który pakiet niesie po tłumaczeniu, więc reguła napisana dla puli klientów może po cichu przestać pasować, gdy maskowanie zostanie włączone.

Gdy klienci potrzebują dotrzeć tylko do usług wewnątrz klastra, zakończenie VPN na VIP usługi jest mniejszym projektem niż routowanie do sieci podów. Trzyma też pulę klientów całkowicie poza tabelą routingu CNI.

Izolowanie zawodzącego skoku od klienta

Pracuj na zewnątrz i zatrzymaj się na pierwszym cichym kroku. Zacznij od peera VPN, potem spróbuj adresu jakiegoś poda, potem VIP usługi, a dopiero potem celu publicznego. Każdy sukces przesuwa granicę o jeden skok, a każda porażka nazywa warstwę wartą zbadania. Po każdym wdrożeniu wejdź przez exec i przeczytaj sysctl ponownie, bo podmieniony pod startuje od domyślnej obrazu, a poprawka, której nigdy nie było w manifeście, już zniknęła.

Dokumentacja Kubernetes o używaniu sysctl w klastrze wymienia, które nazwy można ustawić przez kontekst bezpieczeństwa poda, i to pierwsze miejsce, w które należy spojrzeć, gdy admission odrzuca zmianę.

Powiązane artykuły