Якщо ви колись бачили, як цілком здоровий сервіс «випадково» відмовляє залежно від того, звідки прийшов запит, ви зустріли split‑horizon DNS у його природному середовищі: неправильно налаштований, недостатньо протестований і сприйманий як фокус.
Симптоми знайомі. В офісі api.example.com працює. Через VPN — таймаут. З пода в Kubernetes він резолвиться в щось, що ви клянетесь видалили. З інтернету він резолвиться «правильно», але ваше внутрішнє моніторинґ тепер кричить. Постмортем каже «DNS», і всі кивають, ніби це щось пояснює.
Що таке split‑horizon DNS насправді (і чого воно не є)
Split‑horizon DNS означає, що різні клієнти отримують різні відповіді DNS для одного й того самого імені, залежно від того, звідки вони приходять (source IP, інтерфейс, TSIG‑ключ, view, шлях резолвера або явна політика). Ось і все. Це не є апріорі зло, і це не є автоматично безпечно. Це політика маршрутизації для імен.
В підприємствах його зазвичай використовують для:
- Повернення приватних RFC1918 адрес для внутрішніх клієнтів і публічних адрес для зовнішніх.
- Повернення різних цілей для одного імені сервісу (внутрішній load balancer проти публічного CDN).
- Приховування внутрішніх імен від публічного інтернету (хоча «приховування» не дорівнює «захист»).
- Підтримки застарілих застосунків, які жорстко прописали одне ім’я, тоді як розгортання охоплюють кілька мереж.
Split‑horizon це не:
- Брандмауер. Якщо ви покладаєтесь на DNS, щоб блокувати доступ — ви будуєте фігові двері з зональних файлів.
- Механізм виявлення, що толерує дрейф. Якщо «внутрішні» і «зовнішні» відповіді не походять з одного джерела істини, вони розійдуться.
- Хороший спосіб робити мульти‑регіональний трафік‑інжиніринг якщо ви не ставитеся до DNS як до повільної, кешованої, ймовірнісної системи, якою вона є.
Складність не в тому, щоб змусити split‑horizon працювати. Складність — зробити його сірим. Сірий (нудний) DNS — надійний DNS.
Чому split‑horizon ламається в реальних компаніях
Split‑horizon відмовляє, коли ви втрачаєте контроль над трьома межами:
1) Авторитетність vs рекурсія плутаються
Ваші авторитетні сервери повинні відповідати для зон, якими ви володієте. Ваші рекурсивні резолвери повинні отримувати й кешувати відповіді для всіх інших. У зламаних середовищах «DNS‑сервери» роблять усе підряд, форвардять один одному в петлях, віддають застарілі дані і вгадують, який view застосувати.
2) Клієнти не питають те, що ви думаєте, що вони питають
Ноутбуки по Wi‑Fi, сервери в VPC, поди в Kubernetes і мобільні клієнти через VPN часто використовують різні резолвери, різні search domain і різну поведінку кешування. Ви можете мати «один дизайн DNS» на папері і п’ять різних на практиці.
3) Два джерела істини тихо перетворюються на двадцять
«Внутрішній DNS» у BIND. «Зовнішній DNS» у керованого провайдера. Conditional forwarder в Windows DNS. Приватна хостинг‑зона в хмарі. CoreDNS rewrite‑правило. Сайдкар‑кешуючий stub. Ручний запис у /etc/hosts, про який хтось забув. Ось як ви отримуєте одне ім’я, що резолвиться в три IP залежно від того, чи запитували ви через dig, nslookup або рантайм застосунку.
Split‑horizon не є лиходієм. Безконтрольна складність — ось лиходій.
Кілька фактів і історія, що пояснюють безлад
- DNS старший за більшість ваших «легасі» застосунків. Його стандартизували в 1980‑х, щоб замінити централізовану модель HOSTS.TXT.
- Існує негативне кешування. Якщо ім’я не існує (NXDOMAIN), це «ні» також може кешуватися, керовано параметрами SOA зони.
- Резолвери не зобов’язані чесно чергувати записи. Багато хто робить, багато — ні, а деякі «приклеюють» відповіді довше, ніж вам би хотілося.
- UDP‑truncation реальний. Великі DNS‑відповіді можуть вимагати TCP‑fallback; якщо TCP 53 заблокований, ви отримуєте «випадкові» помилки з DNSSEC або великими TXT‑записами.
- Термін «split‑horizon» став популярним в enterprise‑операціях. Він відображає ідею «split horizon» в маршрутизації: не рекламувати маршрути назад, звідки вони прийшли.
- CDN зробили нормою ідею, що відповіді DNS залежать від місця клієнта. Це змусило політико‑базовані відповіді відчуватись звичними, навіть коли ваша інфраструктура не може витримати експлуатаційні навантаження.
- Кешуючі резолвери можуть обрізати TTL. Ваш ретельно вибраний TTL у 30 секунд може перетворитися на 300 секунд у деяких резолверів, особливо у споживчих ISP.
- Search domains породжують «примарні запити». Запит для
apiможе статиapi.corp.example.com, потімapi.example.com, потім зновуapi— і резолвер може кешувати проміжні невдачі.
Якщо запам’ятаєте лиш одне: DNS — це розподілений кеш із власними вподобаннями.
Швидка діагностика (робіть це насамперед)
Це чекліст, який я використовую, коли хтось каже «в офісі працює, зовні — ні» або «VPN поламав DNS» і очікує, що ви читаєте думки.
Спочатку: з’ясуйте, яким шляхом резолвера користується проблемний клієнт
- На проблемному хості визначте сконфігуровані резолвери і search domain.
- Підтвердіть, чи є локальний stub (systemd-resolved, dnsmasq, nscd) і чи взагалі ваші запити доходять до мережі.
- Перевірте, чи клієнт за якимось ланцюгом пересилання (VPN DNS, VPC resolver, on‑prem forwarders).
По‑друге: порівняйте відповіді авторитетних джерел і рекурсивних кешів
- Запитайте рекурсивний резолвер, яким користується клієнт.
- Запитайте авторитетні сервери напряму.
- Порівняйте: IP‑адреси, CNAME‑ланцюг, TTL і чи відповідь — NXDOMAIN чи NODATA.
По‑третє: перевірте вибір view або логіку conditional forwarding
- Підтвердіть source IP, який бачить DNS‑сервер (NAT та проксі важливі).
- Переконайтесь, що BIND views відповідають підмережі клієнта в тому порядку, який ви очікуєте.
- Перевірте conditional forwarders: чи вони форвардять правильну зону, на правильний цільовий хост, з recursion увімкненим, де потрібно?
По‑четверте: підтвердіть, що транспорт здоровий (UDP/TCP 53)
- Переконайтесь, що UDP 53 працює.
- Переконайтесь, що TCP 53 працює.
- Якщо задіяні DNSSEC або великі відповіді, TCP 53 не є опційним.
По‑п’яте: зробіть видимим стан кешу
- Подивіться на TTL, які ви отримуєте.
- Оберіть деліберативне очищення кешів (локальний stub, рекурсивні резолвери) і протестуйте знову.
- Якщо очищення «вирішує» проблему, у вас проблема з інвалідизацією кешу. Вітаю: ви досягли найвищого рівня.
Практичні завдання: команди, результати, рішення (12+)
Це реальні завдання, які я виконую під час інцидентів. Кожне містить команду, що означає вивід і яке рішення прийняти далі. Запускайте їх з обох «працюючої» і «зламаної» точок зору: внутрішній LAN, VPN, cloud‑інстанс, pod у Kubernetes і публічний хост.
Завдання 1: Подивіться, якими резолверами та search domain хост фактично користується
cr0x@server:~$ resolvectl status
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
Current DNS Server: 10.10.0.53
DNS Servers: 10.10.0.53 10.10.0.54
DNS Domain: corp.example.com
Значення: Цей хост питає локальний stub (systemd-resolved), який форвардить на 10.10.0.53 і 10.10.0.54 і додає search domain corp.example.com.
Рішення: Якщо проблемний клієнт користується іншим DNS‑сервером, ніж ви очікували, припиніть сперечатися теорією і протестуйте той резолвер напряму наступним кроком.
Завдання 2: Підтвердіть, куди вказує /etc/resolv.conf (і чи це stub)
cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Jan 15 09:12 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
Значення: Запити йдуть до локального stub (зазвичай 127.0.0.53), а не напряму до корпоративних резолверів.
Рішення: У відлагодженні тестуйте обидва шляхи: stub (щоб виявити локальні проблеми кешування) і апстрім‑резолвер (щоб виявити split‑horizon і форвардинг‑проблеми).
Завдання 3: Запитайте ім’я тим же резолвером, яким користується клієнт
cr0x@server:~$ dig +time=2 +tries=1 api.example.com @10.10.0.53
; <<>> DiG 9.18.24 <<>> +time=2 +tries=1 api.example.com @10.10.0.53
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4242
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
api.example.com. 20 IN A 10.20.30.40
;; Query time: 12 msec
;; SERVER: 10.10.0.53#53(10.10.0.53) (UDP)
Значення: Внутрішній резолвер повертає приватний IP з малим TTL (20s). Він рекурсивний (ra) і відповідь зараз узгоджена.
Рішення: Якщо це відрізняється від того, що бачить «зовнішній» світ, активний split‑horizon. Далі підтвердіть авторитетні відповіді для кожного горизонту.
Завдання 4: Запитайте публічний резолвер і порівняйте
cr0x@server:~$ dig +time=2 +tries=1 api.example.com @1.1.1.1
; <<>> DiG 9.18.24 <<>> +time=2 +tries=1 api.example.com @1.1.1.1
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1234
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
api.example.com. 300 IN A 203.0.113.77
;; Query time: 18 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
Значення: Публічний резолвер повертає публічний IP з більшим TTL (300s). Це «зовнішня» відповідь.
Рішення: Вирішіть, чи це намірено. Якщо так — переконайтесь, що обидві відповіді ведуть до робочих цілей і що клієнти надійно класифікуються у відповідні горизонти.
Завдання 5: Визначте авторитетні name server для зони
cr0x@server:~$ dig +short NS example.com
ns1.dns-provider.net.
ns2.dns-provider.net.
Значення: Публічний інтернет вважає ns1/ns2 авторитетними для example.com.
Рішення: Якщо внутрішній DNS обслуговує інший authority (наприклад, внутрішній BIND master), ви запускаєте дві авторитетності. Це може бути прийнятно, але лише за дисциплінованого контролю змін і автоматизації.
Завдання 6: Запитайте авторитетні сервери напряму (обійшовши кеші)
cr0x@server:~$ dig api.example.com @ns1.dns-provider.net +norecurse
; <<>> DiG 9.18.24 <<>> api.example.com @ns1.dns-provider.net +norecurse
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9000
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
api.example.com. 300 IN A 203.0.113.77
Значення: aa означає авторитетну відповідь: у публічній зоні 203.0.113.77.
Рішення: Якщо ваш внутрішній резолвер має переважати цю відповідь, підтвердіть, де визначено це переваження (BIND view, приватна хостинг‑зона, conditional forwarder) і чи воно також авторитетне.
Завдання 7: Перевірте, чи CNAME‑ланцюг відрізняється між внутрішнім і зовнішнім
cr0x@server:~$ dig +noall +answer +authority +additional api.example.com @10.10.0.53
api.example.com. 20 IN CNAME api-internal.example.com.
api-internal.example.com. 20 IN A 10.20.30.40
Значення: Внутрішній горизонт використовує CNAME на внутрішнє ім’я.
Рішення: Переконайтесь, що ціль CNAME резольвиться для кожного клієнта, який може її бачити. Якщо VPN‑клієнти класифікуються як «зовнішні», але отримують внутрішній CNAME, вони впадуть у помилку, що виглядає як мережевий збій.
Завдання 8: Виявити наслідки search‑domain (питання «чому було такий запит?»)
cr0x@server:~$ dig +search api @10.10.0.53
; <<>> DiG 9.18.24 <<>> +search api @10.10.0.53
;; ANSWER SECTION:
api.corp.example.com. 60 IN A 10.9.8.7
Значення: Клієнт запитав api, і резолвер розширив це до api.corp.example.com. Це може бути не той сервіс, який ви мали на увазі.
Рішення: Якщо конфіги застосунків використовують короткі імена, виправте конфіг. Не «виправляйте» DNS додаванням неоднозначних записів. Неоднозначність масштабується швидше за кількість людей.
Завдання 9: Підтвердіть, чи працює TCP‑fallback (великі відповіді / DNSSEC / усікання)
cr0x@server:~$ dig example.com DNSKEY @1.1.1.1 +dnssec +tcp +time=2 +tries=1
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7777
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
Значення: TCP‑запит пройшов. Якщо UDP працює, але TCP не працює, ви отримаєте перервні помилки резолюції, коли відповіді занадто великі для UDP без EDNS0.
Рішення: Якщо TCP 53 десь між клієнтом і резолвером/авторитетом заблокований, виправте це перш за все. Перестаньте сперечатися з фаєрволами про «але DNS — це UDP». Це обидва протоколи.
Завдання 10: Спостерігайте реальний DNS‑трафік і source IP (вибір view залежить від нього)
cr0x@server:~$ sudo tcpdump -ni eth0 port 53 -vv
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
10:22:31.100001 IP 172.16.50.10.51123 > 10.10.0.53.53: 4242+ A? api.example.com. (33)
10:22:31.100220 IP 10.10.0.53.53 > 172.16.50.10.51123: 4242* 1/0/0 A 10.20.30.40 (49)
Значення: Резолвер бачить клієнта як 172.16.50.10. Якщо ви очікували іншу підмережу (наприклад, VPN pool vs NAT pool), ваше матчування view може бути неправильним.
Рішення: Налаштуйте ACLs view так, щоб вони відповідали адресам, які сервер фактично бачить. Не підганяйте під мережі, якими ви хотіли б їх бачити.
Завдання 11: Перевірте порядок view в BIND і прикріплення зони
cr0x@server:~$ sudo named-checkconf -p | sed -n '1,120p'
acl "internal-nets" { 10.0.0.0/8; 172.16.0.0/12; 192.168.0.0/16; };
view "internal" {
match-clients { "internal-nets"; };
recursion yes;
zone "example.com" { type master; file "/etc/bind/zones/db.example.com.internal"; };
};
view "external" {
match-clients { any; };
recursion yes;
zone "example.com" { type master; file "/etc/bind/zones/db.example.com.external"; };
};
Значення: Два різні zone‑файли для одного й того самого імені зони, вибираються за IP‑адресою клієнта. Порядок view має значення: перший збіг вирішує.
Рішення: Переконайтесь, що VPN/NAT підмережі включені до internal-nets, якщо вони повинні отримувати внутрішні відповіді. Якщо ні — ви знайшли своє «всередині/зовні» божевілля.
Завдання 12: Перевірте цілісність zone‑файлу перед тим, як звинуватити «рандомний DNS»
cr0x@server:~$ sudo named-checkzone example.com /etc/bind/zones/db.example.com.internal
zone example.com/IN: loaded serial 2026020401
OK
Значення: Зона розпарсилась і загрузилась; серіал видно. Якщо це не вдається, може бути, що ви обслуговуєте старішу версію або зона взагалі не завантажена.
Рішення: Якщо серіал не той, що очікуєте — знайдіть реальний master і шлях реплікації (AXFR/IXFR, git‑деплой, конфіг‑менеджмент).
Завдання 13: Перевірте, що Unbound має в кеші (застарілі відповіді — стиль життя)
cr0x@server:~$ sudo unbound-control lookup api.example.com
api.example.com. 20 IN A 10.20.30.40
Значення: Unbound зараз має кешований запис з залишковим TTL. Якщо ця відповідь неправильна — клієнти будуть її бачити, доки TTL не спливе (або доки ви не очистите кеш).
Рішення: Якщо ви щойно змінили зону, а кеш все ще має старі дані — скиньте кеш для конкретного імені і підтвердіть, що авторитетні дані коректні.
Завдання 14: Очистіть конкретне ім’я в кеші (хірургічно, не «перезавантаж DNS»)
cr0x@server:~$ sudo unbound-control flush api.example.com
ok
Значення: Запис у кеші видалено; наступний запит витягне свіжі дані.
Рішення: Якщо очищення фіксує проблему — вам потрібна TTL‑стратегія і процес змін, що враховує кешування, особливо для split‑horizon, де два кеші можуть суперечити один одному.
Завдання 15: Доведіть, чи застосунок використовує OS‑резолвер, чи щось інше
cr0x@server:~$ strace -f -e trace=network -s 128 -p $(pidof myapp) 2>&1 | head -n 20
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP) = 42
connect(42, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.53")}, 16) = 0
sendto(42, "\252\252\1\0\0\1\0\0\0\0\0\0\3api\7example\3com\0\0\1\0\1", 33, 0, NULL, 0) = 33
Значення: Аплікація звертається до 127.0.0.53, локального stub. Якщо ви очікували прямі запити до корпоративного резолвера, ваш «DNS‑фікс» може не зачепити реальний шлях.
Рішення: Розв’яжіть, чи виправляти на рівні stub, апстрім‑резолвера або аплікації (наприклад, JVM DNS cache). Звинувачувати «DNS», не знайшовши резолвер — мистецтво перформансу.
Завдання 16: Перевірте кешування JVM (бо Java пам’ятає образи)
cr0x@server:~$ jcmd $(pidof java) VM.system_properties | egrep 'networkaddress.cache|networkaddress.cache.negative'
networkaddress.cache.ttl=-1
networkaddress.cache.negative.ttl=10
Значення: TTL -1 означає кеш назавжди (якщо не перевизначено політикою security manager). Це руйнує DNS‑базований failover і може зберегти «неправильний горизонт» при переміщеннях мереж.
Рішення: Якщо ви покладаєтесь на DNS‑зміни для відновлення, задайте адекватні TTL у рантаймі або перенесіть discovery з DNS для швидкого failover.
Жарт №1: DNS — єдина система, де «це закешовано» одночасно і пояснення, і визнання провини.
Виправлення: дизайн split‑horizon, що залишається адекватним
Надійне виправлення — це не «додати ще один view» або «знизити TTL до 5 секунд». Виправлення — зробити split‑horizon продуктом з єдиним власником, єдиним джерелом істини і передбачуваним шляхом резолвера.
Крок 1: Визначте модель іменування: одне ім’я в зоні чи різні імена
У вас є два життєздатні підходи:
-
Те саме ім’я зони (класичний split‑horizon):
api.example.comіснує і в внутрішньому, і в зовнішньому view з різними відповідями.- Плюси: зручно для користувачів, одне ім’я всюди.
- Мінуси: кожна помилка класифікації стає інцидентом; відлагодження повільніше; кеші підсилюють помилки.
-
Різні імена зон (рекомендовано, коли можливо): внутрішні сервіси під
api.corp.example.com, публічні підapi.example.com.- Плюси: менше неоднозначних відповідей; «неправильний горизонт» часто все ще працює, бо це інше ім’я.
- Мінуси: потребує змін у застосунках/конфігах; люди мають вивчити, яке ім’я для чого.
Мій суб’єктивний рада: якщо можете дозволити собі, використовуйте різні імена. Якщо ні — використовуйте same‑name split‑horizon, але ставтеся до нього як до продакшн‑коду: з тестами, CI і перевіркою змін.
Крок 2: Виберіть один авторитетний workflow на кожен горизонт
Якщо ви запускаєте дві різні авторитетності (публічний DNS‑провайдер і внутрішній BIND), прийміть, що ви керуєте двома продуктами.
Ваше завдання — усунути «ручні правки» і дрейф:
- Зберігайте дані зон у version control (навіть якщо вони генеруються).
- Генеруйте внутрішні і зовнішні зони з спільного інвентарю з явними перевизначеннями.
- Вимагайте дисципліни серіалів (монотономні, автоматизовані) і валідовуйте перед розгортанням.
- Зробіть так, щоб питання «хто володіє цим записом?» відповідалися за хвилину.
Крок 3: Розділіть авторитетний DNS від рекурсивного
Саме тут багато середовищ гниють. Вони ставлять BIND на сервер, вмикають recursion, додають views і форвардинг — і той самий демон тепер:
авторитетний для внутрішніх зон, авторитетний для зовнішніх оверрайдів, рекурсивний для всього іншого і відкритий для мереж, де бути не повинен.
Чистий розподіл виглядає так:
- Authoritative layer: обслуговує внутрішні зони (і внутрішні view спільних зон) лише для рекурсивних резолверів, а не для клієнтів.
- Recursive layer: клієнти розмовляють з рекурсивними резолверами; резолвери питають авторитетні сервери та інтернет за потреби.
- Policy layer: логіка split‑horizon живе в невеликій кількості місць (BIND views на авторитетних, або conditional forwarding на рекурсорах), а не розкидана по ноутбуках і VPN‑клієнтах.
Крок 4: Зробіть вибір view детермінованим і задокументованим
Якщо ви використовуєте BIND views, матчте по IP‑ах, які DNS‑сервер бачить. Це означає, що ви повинні врахувати:
- VPN‑пули (часто інші підмережі, ніж LAN)
- NAT‑шлюзи (клієнти виглядають як NAT IP, а не їхня оригінальна підмережа)
- Проксуючі резолвери (IP форвардера може бути «клієнтом»)
- Поведінку dual‑stack (IPv6 клієнти можуть обійти IPv4‑припущення)
Найпоширеніша split‑horizon відмова — новий мережевий шлях (новий VPN, новий NAT, новий VPC), який не додали до ACL «internal». Раптом «внутрішні користувачі» стають «зовнішніми», і DNS‑відповіді несумісні з внутрішньою маршрутизацією.
Крок 5: По можливості тримайте внутрішні і зовнішні відповіді сумісними
Коли потрібно ділитися іменем, спроектуйте так, щоб «неправильна» відповідь падала безболісно:
- Надавайте маршрутизовані адреси з обох горизонтів, коли можливо (наприклад, внутрішні клієнти також можуть дістатися до публічного LB).
- Якщо внутрішній повертає приватні IP, переконайтесь, що зовнішні клієнти ніколи не побачать ці записи (немає витоків через неправильний view, форвардер або резолвер у невірній підмережі).
- Обережно використовуйте CNAME: вони розширюють радіус поширення помилки, бо ціль може не існувати в іншому горизонті.
- Будьте обережні з wildcard‑записами; вони можуть перетворити опечатки на «валідні» імена, що ведуть кудись дорого.
Крок 6: TTL‑стратегія: припиніть культово ставитись до «TTL = 5»
TTL — ваш важіль того, як довго живуть помилки. Це також важіль навантаження на інфраструктуру DNS під час нормальної роботи.
Практичні поради щодо TTL:
- Для стабільних записів використовуйте помірні TTL (5–30 хвилин). Низькі TTL збільшують кількість запитів і не гарантують швидку пропагацію через обмеження резолверів.
- Для міграційних вікон заздалегідь понижуйте TTL (години‑дні), потім змінюйте записи, і після стабільності підвищуйте TTL.
- Пам’ятайте про негативне кешування: NXDOMAIN може залишитися і зіпсувати ваш момент «я щойно створив запис».
Крок 7: Тестуйте split‑horizon так само, як розгорнення
Вам потрібні тести з кожної перспективи. Не «я один раз запустив dig з ноутбука». Тести, що працюють постійно і сповіщають про розбіжності:
- Внутрішній LAN: очікувані внутрішні відповіді.
- VPN: очікувані внутрішні відповіді або явно очікувана зовнішня поведінка.
- Cloud VPC: очікувані внутрішні відповіді, якщо це частина корпоративної мережі.
- Публічний інтернет: очікувані зовнішні відповіді.
- Безпосередньо з рекурсивних резолверів: очікувана авторитетна поведінка, правильний TTL, коректний CNAME‑ланцюг.
Ось операційна цитата, що має переслідувати ваші DNS‑рев’ю:
«Сподівання — це не стратегія.» — General Gordon R. Sullivan
DNS‑відмови часто починаються зі сподівання: сподівання, що кеші швидко спливуть, що VPN‑пули ніколи не зміняться, що ніхто не додасть «тимчасовий» forwarder. Не керуйте продакшном на сподіваннях.
Три короткі історії з корпоративного життя
Коротка історія 1: Інцидент, спричинений хибним припущенням
Середня компанія тримала split‑horizon на одній парі BIND‑серверів. Внутрішній view повертав приватні IP для git.example.com; зовнішній view — публічний reverse proxy.
Роки працювало. Тихо. Саме так починаються більшість інцидентів.
Вони замінили VPN‑концентратор. Новий вендор, нова «сучасна» архітектура: трафік клієнта пройшов через набір NAT‑шлюзів. Команда, що оновлювала VPN, зосередилась на аутентифікації і пропускній здатності. DNS «не змінився».
У понеділок вранці віддалені інженери могли аутентифікуватись в VPN і дістатися внутрішніх IP за адресою, але git.example.com резолвився до зовнішнього проксі. Зовнішній проксі вимагав SSO, який блокувався умовами доступу при зверненні з корпоративних IP‑адрес. Інженери були в VPN, але DNS класифікував їх як «зовнішніх». Найгірше поєднання.
Хибне припущення було просте: «VPN‑клієнти все ще будуть з VPN‑пулу підмереж». Вони не були. DNS‑сервер бачив лише IP NAT‑шлюзу. BIND views матчились на діапазон NAT як any, отже клієнти потрапляли у зовнішній view.
Виправлення було нудним: додати діапазони NAT‑шлюзів до internal ACL, створити моніторинговий probe, який виконує той самий DNS‑запит з‑за NAT і перевіряє внутрішню відповідь. Також задокументували, що «класифікація view залежить від source IP після NAT», що мало бути очевидним, але інциденти — це плата за навчання очевидному.
Коротка історія 2: Оптимізація, що від’єднала
Велике підприємство хотіло швидший failover для публічного API. Вони знизили TTL з 300 секунд до 5 секунд на зовнішньому A‑записі і налаштували автоматизацію для перемикання між двома load balancer. В демо виглядало чудово.
Потім прийшов продакшн. Їх флот рекурсивних резолверів (внутрішні й у великих ISP) піднімав низькі TTL вгору. Не послідовно; не передбачувано. Деякі клієнти перепитували швидко, інші використовували стару адресу ще хвилинами. Тим часом низький TTL збільшив кількість запитів. Їх авторитетний провайдер витримав, але внутрішні форвардери — ні. CPU підскочив. Коефіцієнт кеш‑хітів впав. Латентність зросла. Оптимізація підвищила ймовірність відмови саме в момент, коли DNS мав бути спокійним.
Гірше: внутрішні split‑horizon записи для того ж імені мали інші TTL і керувалися в іншій системі. Під час тесту failover внутрішній моніторинг усе ще резолвив стару внутрішню ціль довше, ніж зовнішні клієнти. Канали інцидентів наповнились «але дашборд каже, що досі впав», у той час як клієнти вже були назад.
Вони відкотили TTL у 5 секунд, перемістили failover на рівень load balancer, де стан поширюється швидше, ніж через DNS‑кеші, і тримали TTL DNS у «нормальному» діапазоні. Також вирівняли політики TTL для внутрішніх і зовнішніх спільних імен і почали відстежувати кількість запитів як перший клас SLO.
Жарт №2: Встановити TTL на 5 секунд — це DNS‑аналог їхати швидше, забравши гальма.
Коротка історія 3: Нудна, але правильна практика, що врятувала день
Інша організація мала чіткий поділ: авторитетні сервери для внутрішніх зон, рекурсивні резолвери для клієнтів і невеликий набір conditional forwarders для приватних зон у хмарі. Було письмове правило: «Жоден клієнт не вказує напряму на авторитетний DNS». Звучало педантично. Але це спрацювало.
Одного дня нове розгортання zone‑файлу внесло синтаксичну помилку у внутрішній view для спільної зони. Авторитетний демон відмовився завантажити цю зону. Рекурсивні резолвери продовжували віддавати кешовані відповіді, купуючи час. Їх моніторинг перевіряв не лише резолюцію; він перевіряв стан завантаження авторитетних зон і інкременти серіалів.
On‑call отримав оповіщення «authoritative zone not loaded» до того, як кеш вивівся. Вони відкотили зміну zone‑файлу за кілька хвилин. Клієнти навіть не помітили. Це той інцидент, що не підходить для конференційного оповідання, бо нічого не загорілося. Але саме це і була мета.
Практика, що врятувала їх: найнудніше в DNS — валідувати конфіг перед reload, моніторити авторитетне здоров’я і не дозволяти клієнтам прямого доступу до authority. Це перетворило потенційний інцидент на тихий відкат.
Типові помилки: симптом → корінь → виправлення
1) Симптом: «В офісі працює, на VPN — не працює»
Корінь проблеми: VPN‑клієнти NAT‑яться або отримують підмережу, не включену в internal view ACL; вони отримують зовнішні DNS‑відповіді.
Виправлення: Оновіть view ACL, щоб включити VPN/NAT‑діапазони, які бачить DNS‑сервер; додайте постійні та контрольні probe з виходу VPN.
2) Симптом: «dig працює, аплікація — ні»
Корінь проблеми: Аплікація використовує інший шлях резолвера (локальний stub, container DNS, кеш рантайму мови), ніж ваш тест.
Виправлення: Трасуйте DNS‑виклики аплікації (strace), перевірте кеш рантайму (JVM, Go netdns, .NET) і протестуйте саме той резолвер, яким користується аплікація.
3) Симптом: «nslookup показує один IP, dig — інший»
Корінь проблеми: Різні сервери за замовчуванням; інструмент питає різний резолвер, або розширення search domain змінює ім’я запиту.
Виправлення: Завжди вказуйте резолвер з @server; використовуйте повністю кваліфіковані імена з крапкою наприкінці, коли треба; перевіряйте search domain.
4) Симптом: «Після зміни DNS деякі клієнти все ще потрапляють на стару ціль»
Корінь проблеми: Кешування резолверів, TTL‑капи, кеш рантайму аплікації; негативне кешування для попереднього NXDOMAIN.
Виправлення: Плануйте зниження TTL заздалегідь перед міграціями; очищайте кеші там, де це доречно; не покладайтесь на DNS для субхвилинного failover.
5) Симптом: «Переривчасті відмови з DNSSEC або великими TXT»
Корінь проблеми: TCP 53 заблокований; фрагментація UDP або проблеми з EDNS0; усічені відповіді не перепитуються коректно.
Виправлення: Дозвольте TCP/UDP 53 наскрізь; перевірте підтримку EDNS0; тестуйте з dig +tcp.
6) Симптом: «Зовнішні користувачі іноді отримують приватні IP»
Корінь проблеми: Дані split‑horizon просочилися через неправильно застосовані view, форвардер, відкритий для інтернету, або публічний резолвер форвардить запити до внутрішнього.
Виправлення: Ніколи не робіть внутрішні рекурсивні резолвери публічними; обмежуйте recursion; аудитуйте ланцюги форвардингу; переконайтесь, що внутрішні зони не обслуговуються у публічних контекстах.
7) Симптом: «Поди Kubernetes резолвять інакше, ніж ноди»
Корінь проблеми: Поди використовують CoreDNS (або еквівалент), який форвардить по конфігу кластера. Ноди використовують системний резолвер; conditional forwarding відрізняється.
Виправлення: Узгодьте CoreDNS forwarders з вашою стратегією корпоративних рекурсивних резолверів; додайте probe з подів; уникайте rewrite‑хаків, якщо не можете їх протестувати.
8) Симптом: «Тільки деякі сайти/офіси відмовляють»
Корінь проблеми: Різні форвардери на сайтах; непослідовний conditional forwarding; застарілі zone transfer до локального резолвера на сайті.
Виправлення: Стандартизуйте конфіг резолверів; моніторте серіали зон по сайтах; віддавайте перевагу централізованій рекурсії з локальним кешуванням тільки за потреби.
Чеклісти / покроковий план
Покроково: перебудова split‑horizon, щоб воно перестало шкодити
-
Зробіть інвентар шляхів резолверів.
- Перелічіть корпоративні рекурсивні резолвери, хмарні резолвери, резолвери, які надає VPN, і будь‑які локальні stub‑и.
- Вирішіть: які клієнти повинні використовувати який резолвер і чому.
-
Накресліть межу авторитетності.
- Які сервери авторитетні для внутрішніх зон?
- Який провайдер авторитетний для публічних зон?
- Хто володіє змінами в кожному з них?
-
Виберіть механізм split‑horizon.
- BIND views на авторитетних? Conditional forwarding на рекурсивних? Хмарні приватні зони?
- Мінімізуйте кількість точок політики. Дві вже багато.
-
Визначте правила класифікації view.
- Перерахуйте внутрішні мережі так, як їх бачать DNS‑сервери (після NAT).
- Включіть VPN‑egress і cloud NAT‑діапазони навмисно.
-
Уніфікуйте генерацію записів.
- Генеруйте внутрішні/зовнішні варіанти з спільних даних з явними diff‑ами.
- Забороніть ручні правки в production DNS поза контрольованим аварійним процесом.
-
Встановіть політику TTL і дотримуйтеся її.
- Визначте стандартні TTL за типом запису і середовищем.
- Визначте playbook міграції для попереднього пониження TTL.
-
Побудуйте тести з кожного горизонту.
- Щонайменше: внутрішній LAN, VPN, cloud, публічний інтернет.
- Тестуйте ім’я, CNAME‑ланцюг і кінцеву доступність (HTTP/TCP health).
-
Розгортайте з валідаційними гейтами.
- Перевірка синтаксису зони, монотонності серіалу і canary‑перезавантаження резолвера перед повним rollout.
-
Моніторьте те, що має значення.
- Показники: NXDOMAIN rate, SERVFAIL rate, recursion failures, query latency і TCP fallback failures.
- Стан завантаження зон і серіал‑дрейф між secondaries.
Операційний чекліст: перед тим, як змінити split‑horizon запис
- Чи це ім’я існує в більше ніж одному горизонті? Перерахуйте.
- Чи зміна зачепить CNAME‑цілі, яких немає зовні?
- Чи TTL вже низький достатньо для вікна міграції? Якщо ні — понизьте заздалегідь.
- Чи ви змінюєте авторитетне джерело або лише рекурсивний оверрайд?
- Які probe моніторингу скажуть, що все спрацювало з кожного горизонту?
- Чи є у вас готовий (і протестований) rollback‑набір записів?
Операційний чекліст: під час інциденту
- Визначте шлях резолвера від проблемного клієнта.
- Порівняйте відповіді: резолвер клієнта vs авторитетний.
- Підтвердіть матчинґ view за допомогою захоплення пакетів або логів резолвера.
- Перевірте TCP 53.
- Розв’яжіть: виправити класифікацію, виправити дані запису чи очистити кеші (і де саме).
- Запишіть точне ім’я запиту (з крапкою в кінці, якщо потрібно) і IP резолвера. Невизначеність — причина затягування інцидентів.
Питання та відповіді
1) Чи варто взагалі використовувати split‑horizon DNS?
Використовуйте його, коли вам дійсно потрібно, щоб одне ім’я мапилось на різні цілі залежно від локації клієнта. Інакше надавайте перевагу різним іменам для внутрішніх і зовнішніх сервісів. Це зменшує неоднозначність і скорочує час інцидентів.
2) Чи те саме, що «private DNS» у хмарі?
За концептом — схожий результат: різні відповіді залежно від того, звідки ви питаєте. Механічно відрізняється: cloud private zones часто прив’язані до VPC і використовують провайдерські резолвери, тоді як класичний split‑horizon використовує views або політику на ваших DNS‑серверах.
3) Чому я бачу «неправильну» відповідь тільки з подів Kubernetes?
Поди зазвичай питають CoreDNS (або еквівалент), який форвардить згідно з конфігом кластера. Ноди можуть використовувати системний резолвер. Вирішіть, узгодивши CoreDNS forwarding/conditional правила з корпоративною стратегією рекурсивних резолверів і протестуйте зсередини пода.
4) Чи можу я покладатись на TTL для швидкого failover?
Ні для субхвилинної поведінки. Деякі резолвери збільшують TTL; аплікації кешують; connection pool‑и продовжують використовувати старі IP. Покладіть швидкий failover в load balancer або service mesh; використовуйте DNS для steady‑state discovery і повільних cutover‑ів.
5) Який найбільший операційний ризик з BIND views?
Помилкова класифікація. Якщо нова підмережа (VPN, NAT, cloud egress) не включена в правильний ACL, ці клієнти отримують неправильну зону. Тоді ви дебагуєте «мережу», коли насправді проблема — політика.
6) Як запобігти витоку внутрішніх записів в інтернет?
Не робіть внутрішні рекурсивні резолвери публічними. Вимикайте recursion на авторитетних серверах, що виходять до ненадійних мереж. Обмежуйте zone transfer. Аудитуйте ланцюги форвардингу, щоб жоден публічний резолвер не форвардив приватні зони до внутрішньої інфраструктури.
7) Чому іноді очищення кешу «фіксує» проблему, але потім вона повертається?
Бо ви очистили один кеш, а не весь ланцюг. Існують клієнтські stub‑кеші, рекурсивні кеші, кеші рантайму аплікацій і деколи проміжні форвардери. Якщо авторитетні дані неправильні, очищення просто купує короткочасну ілюзію компетентності.
8) Чи повинні внутрішні та зовнішні TTL збігатися?
Не обов’язково, але вони повинні керуватися однією політикою. Якщо те саме ім’я split‑horizon має дуже різні TTL, це може заплутати моніторинг, уповільнити підтвердження інцидентів і створити асиметричну поведінку під час міграцій.
9) Який безпечний патерн для «одного імені всюди» без split‑horizon?
Завершуйте трафік на єдиній публічній точці, до якої внутрішні мережі також мають доступ (публічний LB з внутрішніми allowlist‑ами або dual‑access VIP). Тоді DNS може повертати одну відповідь глобально. Це міняє складність DNS на мережеву політику, що зазвичай вигідно.
10) Як правильно моніторити split‑horizon?
Виконуйте той самий DNS‑запит з кожного горизонту і сповіщайте про непередбачувані розбіжності. Також моніторьте SERVFAIL/NXDOMAIN, latency резолвера і послідовність серіалів зон. Якщо ви моніторите лише «чи резолвиться» — ви пропустите «чи резолвиться в правильне».
Висновок: практичні наступні кроки
Split‑horizon DNS можна пережити, якщо перестати ставитись до нього як до хитрої фішки і почати ставитись як до інфраструктури з відмовами. Виправлення — не ще один запис. Це нова дисципліна: чіткі шляхи резолверів, детермінована класифікація і одна істина для даних зон.
Зробіть це наступне, по черзі
- Змапте шляхи резолверів для LAN, VPN, cloud, Kubernetes і публічного хоста. Запишіть їх.
- Виберіть точку політики: views на авторитетних або conditional forwarding на рекурсивних. Мінімізуйте кількість.
- Зробіть класифікацію явною: перераховуйте підмережі так, як їх бачать DNS‑сервери (включно з NAT/VPN egress).
- Уніфікуйте керування записами: генеруйте внутрішні/зовнішні варіанти з спільних даних; без ручних правок.
- Додайте horizon‑probe і сповіщайте про розбіжності у відповідях, а не лише про успішність резолюції.
- Прийміть playbook міграції TTL, щоб змінювати цілі без благачень кешам про пощаду.
Якщо хочете, щоб «всередині/зовні» божевілля припинилось, ваш DNS має стати нудним. Не мінімалістичним. Не хитрим. Нудним. Саме так виживає продакшн.