Jak rozdzielić DNS między Kubernetes a sidecarem OpenVPN
Pod z sidecarem potrzebuje dwóch światów nazw. Skieruj jeden resolver loopback do klastra i tunelu i przestań polegać na edytowanym resolv.conf.
Pody uruchamiające sidecar OpenVPN zawodzą w rozwiązywaniu nazw w rozpoznawalnym wzorcu: nazwy klastra wiszą, nazwy zdalne odpowiadają do restartu poda, a edytowany plik resolvera wraca do poprzedniego stanu. Nic w tej sekwencji nie jest awarią tunelu. Pod ma jeden plik resolvera i dwa światy nazw, a plik może wymienić serwery po kolei, ale nie potrafi wyrazić stref. Trwałym rozwiązaniem jest resolver działający w podzie i posiadający podział, więc plik pozostaje prosty i żadna polityka nie ma powodu go przepisywać.
Dlaczego wprowadzona zmiana ciągle znika
Plik należy do kubeleta, nie do ciebie. W ramach dnsPolicy: ClusterFirst kubelet generuje go, by wskazywał na DNS klastra, i generuje go ponownie przy każdym starcie kontenera. Wszystko, co zmienisz przez kubectl exec, żyje w zapisywalnej warstwie kontenera, więc restart, eksmisja lub rollout to wyrzucają. Specyfikacja poda nadal niesie tę samą politykę, dlatego wraca ten sam plik.
Ustawienie dnsPolicy: None zatrzymuje regenerację i usuwa też rozwiązywanie klastrowe. Zyskujesz plik, który kontrolujesz, i tracisz nazwy, których potrzebowałeś. Ta wymiana to cały problem: resolv.conf nie zna pojęcia stref, tylko serwery próbowane po kolei, więc nie może wysłać cluster.local na jeden adres, a strefy prywatnej na inny.
Uruchom jeden resolver w podzie, na loopbacku
Skieruj pod na jeden serwer nazw na loopbacku, a potem pozwól kontenerowi CoreDNS współdzielącemu przestrzeń nazw sieci poda odpowiadać na porcie 53. Ustaw politykę na None, daj podowi ten jeden adres i dodaj kontener DNS obok VPN i aplikacji:
spec:
dnsPolicy: None
dnsConfig:
nameservers:
- 127.0.0.1
options:
- name: ndots
value: "5"
containers:
- name: dns
image: coredns/coredns
args: ["-conf", "/etc/coredns/Corefile"]
volumeMounts:
- {name: corefile, mountPath: /etc/coredns, readOnly: true}
- name: vpn
image: your-openvpn-client-image
- name: app
image: your-app-image
volumes:
- name: corefile
configMap:
name: split-dns-corefileWszystkie trzy kontenery dzielą jedną przestrzeń nazw sieci, więc 127.0.0.1:53 z kontenera aplikacji dociera do CoreDNS bez usługi, bez port-forward i bez obejścia przez sieć klastra. Montowanie Corefile z ConfigMapy utrzymuje go jako wejście wersjonowane, a nie plik edytowany na miejscu. Wygenerowany resolv.conf zawiera wtedy jeden serwer nazw i nic więcej, stan, o którym można rozumować.
Dwa szczegóły tu gryzą. Oficjalny obraz wiąże port 53 jako root, a niektóre polityki klastra wymagają zapisania tego wprost. Każdy kontener w podzie zależy teraz od tego, czy ten jeden kontener działa, więc dodaj readiness probe na nieszkodliwą nazwę i pozwól aplikacji znosić błędy startu.
Daj każdej strefie prywatnej własny upstream
Corefile to lista stref, każda wskazuje serwer, który może na nią odpowiedzieć. Jeden blok obejmuje strefę tunelu, a blok catch-all przychodzi ostatni i bierze wszystko, czego nie przejął żaden jawny blok. Catch-all musi celować w prawdziwy upstream, nigdy z powrotem w pod: .:53 { forward . 10.100.0.10 } używa ponownie resolvera klastra, bo ten już przekazuje nazwy publiczne do resolverów samego węzła. Nic innego w podzie nie musi wiedzieć, do której sieci należy nazwa.
.:53 {
errors
cache 30
forward . 10.100.0.10
}
svc.acme.internal:53 {
errors
cache 30
forward . 192.168.240.1
}Czytaj adres klastra z usługi kube-dns w kube-system zamiast kopiować go z artykułu, bo CIDR-y usług się różnią. Adres tunelu pochodzi z wartości dhcp-option DNS wysyłanej przez serwer OpenVPN albo z serwera autorytatywnego dla tej strefy. Jeśli twój resolver klastra nie odpowiada na nazwy publiczne, umieść adres resolvera węzła w bloku catch-all, odczytany na węźle, a nie w podzie.
Nie kieruj catch-all na /etc/resolv.conf, to kusząca jedna linijka i pułapka w dokładnie tej konfiguracji. W ramach dnsPolicy: None plik samego poda zawiera jedyny serwer nazw 127.0.0.1, więc ten cel to sam CoreDNS, a każde niedopasowane zapytanie powtarza się aż do przekroczenia czasu. Blok o nazwie svc.acme.internal pasuje do tej nazwy i każdej nazwy pod nią, co obejmuje db.svc.acme.internal i wyklucza svc.acme.internal.example.net. Kropka na końcu jest kosmetyczna, bo nazwy stref są normalizowane do postaci w pełni kwalifikowanej; granica sufiksu decyduje, gdzie zapytanie trafia.
Zdecyduj, czy przekazywanie należy do resolvera klastra
Jeśli każde obciążenie w przestrzeni nazw potrzebuje strefy tunelu, blok przekazywania w ConfigMapie CoreDNS w kube-system jest tańszy niż sidecar na każdy pod. Ta zmiana jest na poziomie klastra: wymaga uprawnień administratora i restartu wdrożenia, od którego wszyscy zależą. Ogranicz ją do tej jednej strefy, nigdy do katalogu głównego. Adres tunelu jednego obciążenia staje się wtedy resolverem każdego poda dla tej strefy, a w dniu, gdy tunel padnie, cały klaster traci nazwę.
Sidecar trzyma promień rażenia w jednym podzie, co jest lepszą domyślną wartością, gdy tylko kilka usług potrzebuje sieci zdalnej. Cokolwiek wybierzesz, zapisz, który resolver posiada strefę, bo następna osoba debugująca limit czasu o to zapyta.
Nazwy, które rozwiązują się, gdy połączenia wciąż zawodzą
Rozwiązywanie to jedna z dwóch rzeczy, które tunel musi przenosić; druga to droga powrotna, a sieć poda dobrze ją ukrywa. Jeśli sam serwer OpenVPN działa jako pod, reguły dodane w tym kontenerze nie kontrolują ruchu wychodzącego przez węzeł. Ta awaria jest wąska: odpowiedzi dostawały tylko pody na węźle, który też uruchamiał serwer, a wszystko inne przekroczyło czas.
Uruchomienie serwera z hostNetwork: true przywraca go do przestrzeni nazw węzła, gdzie działa reguła FORWARD i translacja źródła. Przetłumacz pulę klientów na wyjściu, inaczej żaden zdalny węzeł nie ma trasy powrotnej do adresu tunelu, jak 10.8.0.6. Sprawdź oba kierunki, zanim znowu dotkniesz Corefile. Tunel, który przenosi zapytanie, ale nie odpowiedź, daje dokładnie ten sam limit czasu co resolver, który nigdy nie dostał zapytania.
Weryfikuj z nowego poda, a nie z sesji, którą naprawiłeś
Zapytaj obie strefy jawnie, a potem powtórz zapytanie po wymianie poda. Konfiguracja istniejąca tylko w działającym kontenerze nie jest konfiguracją:
kubectl exec -it deploy/app -- dig @127.0.0.1 kubernetes.default.svc.cluster.local +short
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short
kubectl delete pod -l app=your-app
kubectl wait --for=condition=Ready pod -l app=your-app --timeout=90s
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +shortJeśli pierwsza para odpowiada, a para po wymianie nie, Corefile jest w porządku, a specyfikacja poda nie. Jeśli obie odpowiadają, a aplikacja nadal nie może się połączyć, przestań debugować DNS i sprawdź ścieżkę TCP przez nc -vz db.svc.acme.internal 5432 wewnątrz kontenera. Zachowaj wynik dig w zgłoszeniu, bo to artefakt, który oddziela problem nazw od problemu routingu.