Поди з сайдкаром OpenVPN зазнають збою у розв'язанні імен за впізнаваним взірцем: імена кластера висять, віддалені імена відповідають до перезапуску пода, а відредагований файл резолвера повертається. Ніщо в цій послідовності не є несправністю тунелю. Под має один файл резолвера і два світи імен, і файл може перелічити сервери по черзі, але не може виразити зони. Стійке рішення — резолвер, що працює всередині пода й володіє розподілом, тому файл залишається простим і жодна політика не має причини його переписувати.

Чому зроблена зміна постійно зникає

Файл належить kubelet, а не вам. За dnsPolicy: ClusterFirst kubelet створює його, щоб він вказував на DNS кластера, і перестворює його при кожному запуску контейнера. Усе, що ви змінюєте через kubectl exec, живе у записуваному шарі контейнера, тому перезапуск, витіснення або rollout це викидають. Специфікація пода досі несе ту саму політику, тому повертається той самий файл.

Встановлення dnsPolicy: None зупиняє перестворення і також прибирає розв'язання імен кластера. Ви отримуєте файл під своїм контролем і втрачаєте потрібні імена. Ця заміна і є усією проблемою: resolv.conf не знає поняття зон, лише сервери, що пробуються по черзі, тому не може надіслати cluster.local на одну адресу, а приватну зону на іншу.

Запустіть один резолвер у поді, на loopback

Спрямуйте под на єдиний сервер імен на loopback, потім дозвольте контейнеру CoreDNS, що розділяє мережевий простір імен пода, відповідати на порту 53. Задайте політику None, дайте поду цю єдину адресу і додайте контейнер DNS поруч із VPN та застосунком:

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

Усі три контейнери розділяють один мережевий простір імен, тому 127.0.0.1:53 із контейнера застосунку досягає CoreDNS без служби, без port-forward і без об'їзду через мережу кластера. Монтування Corefile з ConfigMap зберігає його як версіонований вхід, а не файл, що редагується на місці. Згенерований resolv.conf тоді містить один сервер імен і більше нічого, стан, про який можна міркувати.

Тут кусаються дві деталі. Офіційний образ прив'язує порт 53 як root, і деякі політики кластера вимагають написати це явно. Кожен контейнер у поді тепер залежить від роботи того єдиного контейнера, тому додайте readiness-пробу на нешкідливе ім'я і дозвольте застосунку терпіти збої запуску.

Дайте кожній приватній зоні власний upstream

Corefile — це список зон, кожна називає сервер, що може на неї відповісти. Один блок охоплює зону тунелю, а блок catch-all йде останнім і бере усе, на що не претендував жоден явний блок. Catch-all має цілити на справжній upstream, ніколи не повертатися до пода: .:53 { forward . 10.100.0.10 } використовує резолвер кластера повторно, бо він уже пересилає публічні імена резолверам самого вузла. Більше нічому в поді не потрібно знати, до якої мережі належить ім'я.

.:53 {
  errors
  cache 30
  forward . 10.100.0.10
}

svc.acme.internal:53 {
  errors
  cache 30
  forward . 192.168.240.1
}

Читайте адресу кластера з сервісу kube-dns у kube-system, а не копіюйте зі статті, бо CIDR сервісів різняться. Адреса тунелю надходить зі значення dhcp-option DNS, яке надсилає сервер OpenVPN, або від авторитетного сервера цієї зони. Якщо ваш резолвер кластера не відповідає на публічні імена, вмістіть адресу резолвера вузла у блоці catch-all, прочитану на вузлі, а не в поді.

Не спрямовуйте catch-all на /etc/resolv.conf, що є спокусливим однорядковим рішенням і пасткою саме в цьому налаштуванні. За dnsPolicy: None файл самого пода містить єдиний сервер імен 127.0.0.1, тому ця ціль — сам CoreDNS, і кожен незбіг повторюється, доки не спливе час. Блок з іменем svc.acme.internal збігається з цим іменем і кожним іменем під ним, що охоплює db.svc.acme.internal і виключає svc.acme.internal.example.net. Крапка в кінці косметична, бо імена зон нормалізуються до повної форми; межа суфікса вирішує, куди потрапить запит.

Вирішіть, чи належить пересилання резолверу кластера

Якщо кожному робочому навантаженню в просторі імен потрібна зона тунелю, блок пересилання в ConfigMap CoreDNS під kube-system дешевший за сайдкар на кожен под. Ця зміна на рівні кластера: потрібні права адміністратора і перезапуск розгортання, від якого залежать усі. Обмежте її цією однією зоною, ніколи коренем. Адреса тунелю одного навантаження тоді стає резолвером кожного пода для зони, і в день падіння тунелю увесь кластер втрачає ім'я.

Sidecar тримає радіус ураження всередині одного пода, що є кращим замовчанням, коли віддалена мережа потрібна лише кільком службам. Що б ви не обрали, запишіть, який резолвер володіє зоною, бо наступна людина, що відлагоджуватиме таймаут, запитає.

Імена, що розв'язуються, поки з'єднання ще падають

Розв'язання — одна з двох речей, які тунель має нести; друга — зворотний шлях, і мережа пода добре його приховує. Якщо сам сервер OpenVPN працює як под, правила, додані в тому контейнері, не керують трафіком, що виходить через вузол. Ця несправність узька: відповіді отримували лише поди на вузлі, який теж запускав сервер, а все інше вийшло за час.

Запуск сервера з hostNetwork: true повертає його в простір імен вузла, де діють правило FORWARD і трансляція джерела. Перекладіть пул клієнтів на виході, інакше жоден віддалений вузол не матиме маршруту назад до адреси тунелю як 10.8.0.6. Перевірте обидва напрямки, перш ніж знову торкатися Corefile. Тунель, що несе запит, але не відповідь, дає точно такий самий таймаут, як і резолвер, що ніколи не отримав запиту.

Перевіряйте з нового пода, а не з сеансу, який ви виправили

Запитайте обидві зони явно, потім повторіть запит після заміни пода. Конфігурація, що існує лише в працюючому контейнері, не є конфігурацією:

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

Якщо перша пара відповідає, а пара після заміни ні, Corefile гаразд, а специфікація пода ні. Якщо обидві відповідають, але застосунок усе ще не підключається, припиніть налагоджувати DNS і перевірте шлях TCP через nc -vz db.svc.acme.internal 5432 зсередини контейнера. Збережіть вивід dig у тикеті, бо це той артефакт, що відділяє проблему імен від проблеми маршрутизації.

Схожі статті