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-corefile

Wszystkie 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 +short

Jeś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.

Powiązane artykuły