Виявлення сервісів у Docker не працює: DNS і мережеві псевдоніми — як правильно

Було корисно?

Ви деплоїте нову версію контейнера в п’ятницю. Нічого екзотичного. Та сама збірка, той самий файл compose, просто інший тег.
Потім ваш додаток починає логувати: «could not resolve db» або «Name or service not known.» Через п’ять хвилин це «само собою виправляється».
Або не виправляється. Всі погоджуються, що це «DNS», що по-корпоративному означає «ще ніхто точно не знає».

Сервісне виявлення в Docker просте, коли ви рухаєтеся по накатаній дорозі, і дивно веде себе, коли ви ступаєте бодай на півкроку вбік.
Хороша новина: більшість збоїв детерміновані. Погана новина: детермінізм ховається за значеннями за замовчуванням, як-от ndots,
розмежування мереж і вбудований DNS-сервер, про який ви ніколи не просили.

Модель мислення, яка відповідає продакшену

Ось модель, яку варто тримати в голові: сервісне виявлення в Docker — це DNS, обмежений мережею.
Імена резольвляться лише всередині тієї самої Docker-мережі, використовуючи вбудований DNS-сервер Docker (зазвичай 127.0.0.11 всередині контейнера),
а відповідність імен — IP-адресам будується на підставі кінцевих точок контейнерів і мережевих псевдонімів. Ось і все. Решта — наслідки.

Якщо запам’ятати одне речення, то нехай це буде таке: якщо два контейнери не знаходяться в одній користувацькій мережі, вони не “бачать” одне одного за іменем.
«Але вони на одному хості!» — це не мережна модель; це крик про допомогу.

Чого ви можете очікувати, що працюватиме

  • На користувацькій bridge-мережі, створеній Docker/Compose: імена сервісів і мережеві псевдоніми резольвляться в IP-адреси контейнерів.
  • У стандартній мережі Compose: кожне ім’я сервісу стає DNS-іменем (db, redis, api).
  • У Swarm: імена сервісів резольвляться або у VIP (балансовану IP), або в кілька IP задач (режим DNSRR).

Чого варто вважати зламаним, поки не доведено протилежне

  • Резольвінг імен через різні Docker-мережі без явних з’єднань.
  • Використання IP-адрес контейнерів як стабільної ідентичності (це «скотина», а не «улюбленець»).
  • Залежність від пошукових доменів і коротких імен, коли в гру вступають ndots і корпоративні DNS-політики.
  • Compose-файли «в мене працює на ноуті», що опираються на мережеві значення за замовчуванням і дружні налаштування.

Ще думка: Docker DNS — це не загальний DNS-сервер. Це реєстр імен для кінцевих точок контейнерів з шаром переадресації.
Ставтеся до нього як до елементу контрольної площини з залежністю від даних. Якщо ви використовуєте його як «просто DNS», він нагадає, хто тут керує.

Парафразована ідея (приписується): Вернер Фогельс часто підкреслює, що все ламається; проектуйте під збої, а не припускайте надійність.
Це правильна позиція і для сервісного виявлення Docker.

Цікаві факти та трохи історії

Це не святкова вікторина. Ці факти пояснюють, чому Docker поводиться саме так і чому ваше «просте DNS-питання» перетворюється на
двогодинний інцидент.

  1. Спочатку Docker сильно покладався на записи в /etc/hosts для пошуку імен контейнерів; пізніше, з еволюцією мереж, дозріло DNS-базоване виявлення сервісів.
  2. Вбудований DNS-сервер (127.0.0.11) — на рівні контейнера у тому сенсі, що це stub-слухач у мережевому просторі імен контейнера, підкріплений Docker-демоном.
  3. Користувацькі bridge-мережі принесли справжнє виявлення сервісів: історично дефолтна мережа bridge не надавала автоматичного резольвінгу імен так, як роблять це користувацькі мережі.
  4. Compose популяризував «ім’я сервісу = DNS-ім’я», що спростило розробку мікросервісів і водночас змусило людей забувати, що DNS обмежений мережею.
  5. Swarm ввів discovery на основі VIP: при резольвінгу ім’я сервісу часто повертає віртуальну IP, а не IP задачі, переклавши балансування на платформу.
  6. TTL у DNS — не гарантія у контейнеризованих середовищах; відповіді Docker DNS і клієнтські кеші можуть зробити зміни «липкими».
  7. ndots став тихим винуватцем, коли Kubernetes і рантайми контейнерів почали активно використовувати пошукові домени; Docker успадковує ту саму поведінку резолвера з libc.
  8. Корпоративні схеми split-horizon DNS конфліктують з контейнерами: те, що ваш хост резольвить через VPN, може бути недоступним або нерозв’язуваним у контейнері без додаткового налаштування.

Як насправді працює DNS у Docker (bridge, compose, swarm)

Вбудований DNS-сервер: чому ви постійно бачите 127.0.0.11

Всередині більшості контейнерів на користувацьких мережах /etc/resolv.conf вказує nameserver 127.0.0.11.
Ця адреса — не ваш корпоративний DNS. Це вбудований DNS-стаб Docker. Він виконує дві задачі:

  • Відповідає на запити щодо імен контейнерів і псевдонімів в межах тієї ж мережі.
  • Переадресовує інші запити до зовнішніх резолверів, сконфігурованих для демона Docker (часто скопійованих з хоста).

Це важливо, бо коли зовнішній DNS відмовляє, ви побачите збої навіть для внутрішніх імен, якщо бібліотека резолвера заплутається
(таймаути, повторні передачі, розширення пошукових доменів). Також це важливо, бо контейнер може мати інші upstream DNS-сервери, ніж хост.

Користувацькі bridge-мережі: розумний дефолт

Створіть мережу, приєднайте до неї контейнери — Docker зареєструє їхні імена в DNS-просторі імен цієї мережі.
Compose робить це за вас, створюючи мережу в межах проекту. Саме тому db резольвиться в Compose без додаткових хитрощів.

Дефолтна мережа bridge — це історична зручність. Вона покращилася, але все ще небезпечніша за користувацькі мережі.
Якщо вам важлива передбачувана робота виявлення сервісів, припиніть використовувати дефолтний bridge для багатоконтейнерних додатків.

Overlay-мережі (Swarm): зона «залежить від обставин»

У Swarm виявлення сервісів вбудоване в оркестратор. Зазвичай ви отримуєте один із двох режимів:

  • Режим VIP: ім’я сервісу резольвиться в віртуальну IP; swarm маршрутизує до задач. Добре для простоти; іноді заплутує при відлагодженні.
  • Режим DNSRR: ім’я сервісу повертає кілька записів A (IP задач). Добре для клієнтського балансування; легко використати неправильно.

Overlay-мережі додають рухомі частини: gossip/контрольну площину, ingress-меш для маршрутизації і обробку DNS на кожному вузлі. Коли тут зникає виявлення сервісів,
«DNS» може бути насправді проблемою контрольної площини overlay. Ваш підхід до діагностики має це враховувати.

Чого Docker DNS не є

  • Це не повноцінний авторитативний DNS-сервер для вашого корпоративного домену.
  • Це не service mesh.
  • Це не гарантія того, що поведінка резолвера вашого додатку є коректною.

Жарт №1: DNS означає «Did Not Sleep», і Docker подбає, щоб ви заслужили цю абревіатуру, якщо ігноруєте налаштування резолвера.

Імена сервісів, імена контейнерів, імена хостів і мережеві псевдоніми

Люди плутають ці терміни, ніби вони синоніми. Це не так, і різниця — це різниця між «працює» і «таємничо падає в продакшені».

Ім’я сервісу (Compose)

У Docker Compose ім’я сервісу (ключ під services:) стає DNS-іменем у мережі Compose.
Саме тому це працює:

  • api може підключитися до db:5432, якщо обидва на тій самій мережі Compose.

Compose також додає scoping по проекту за лаштунками, але DNS-імена зазвичай — це ім’я сервісу, а не повне ім’я контейнера.
Якщо ви не перевизначаєте цього. А люди інколи перевизначають.

Ім’я контейнера

Ім’я контейнера — те, що ви бачите в docker ps. Compose часто генерує його як project-service-1.
Ви можете задати container_name, але це зазвичай створює більше проблем, ніж вирішує:
це порушує масштабування і заохочує крихкі залежності від конкретної ідентичності.

Hostname

Hostname контейнера — те, що повертає команда hostname всередині контейнера. Воно може впливати на те, як деяке ПЗ ідентифікує себе,
але це не те саме, що DNS-запис. Встановлення hostname: у Compose не створює автоматично стабільного DNS-імені в різних мережах.

Мережевий псевдонім (інструмент, який вам справді потрібен)

Мережевий псевдонім — це DNS-ім’я, асоційоване з кінцевою точкою контейнера в конкретній мережі.
Псевдоніми обмежені мережею. Оце і є їхня суть. Ви можете дати одному контейнеру різні псевдоніми в різних мережах.

Використовуйте псевдоніми коли:

  • Потрібне стабільне «відоме» ім’я, наприклад db, під час заміни реалізації (postgres проти cockroach).
  • Потрібні кілька імен для того самого сервісу для міграції (db та postgres-primary).
  • Контейнер підключено до кількох мереж і ви хочете контролювати імена, видимі в кожній з них.

Уникайте псевдонімів коли:

  • Ви використовуєте їх, щоб заклеїти поганий мережевий дизайн («просто заалісуй, щоб резольвилось»).
  • Будуєте псевдо-глобальний простір імен. Docker-мережі призначені бути межами.

Слово про «localhost»

Всередині контейнера localhost — це сам контейнер. Не хост. Не інший контейнер. І не ваші почуття.
Якщо ваш додаток використовує localhost для звернення до залежності, він зламається, якщо ця залежність не знаходиться в тому ж контейнері.
Розділіть процеси — і вам треба змінити адресу.

Жарт №2: «Працювало, коли я використовував localhost» — це контейнерний еквівалент «чек у пошті».

Швидкий план діагностики

Коли виявлення сервісів зривається, у вас немає часу на експресивні танці. Потрібна послідовність, що швидко знаходить вузьке місце.
Цей план передбачає, що ви дебагуєте з Docker-хосту з доступом до CLI.

По-перше: підтвердіть, що це проблема імен, а не TCP

  • Якщо резольвінг імені падає — ви в зоні DNS.
  • Якщо резольвінг працює, але з’єднання не вдається — це маршрутизація/фаєрвол/слухаючий сервіс.

По-друге: перевірте, що обидва контейнери в тій самій користувацькій мережі

  • Якщо вони не в одній мережі, ніякі хитрощі з псевдонімами не допоможуть.
  • Якщо в одній — проінспектуйте мережу і кінцеві точки на предмет псевдонімів і IP.

По-третє: перевірте конфігурацію резолвера у клієнтському контейнері

  • Перегляньте /etc/resolv.conf на предмет 127.0.0.11, пошукових доменів і options ndots.
  • Перевірте, чи контейнер може дістатися до upstream DNS (якщо ім’я зовнішнє).

По-четверте: відтворіть lookup детермінованими інструментами

  • Використовуйте getent hosts, щоб відповідати поведінці libc.
  • Використовуйте dig/nslookup, щоб бачити сирі DNS-відповіді (якщо встановлені).

По-п’яте: перевірте стан демона Docker і об’єкти мережі

  • Шукайте дивності: застарілі кінцеві точки, сирітські мережі, перезапуски демона або «корисні» перезаписи DNS.

Практичні завдання: команди, виводи та рішення

Це ті польові завдання, які я виконую на практиці. Кожне містить пояснення, що означає вивід і яке рішення прийняти далі.
Виконуйте їх у порядку, коли у вас обмежений час.

Завдання 1: Підтвердити, в яких мережах знаходиться клієнтський контейнер

cr0x@server:~$ docker inspect -f '{{json .NetworkSettings.Networks}}' api | jq
{
  "app_net": {
    "IPAMConfig": null,
    "Links": null,
    "Aliases": [
      "api",
      "3d2c9a8c4c1a"
    ],
    "MacAddress": "02:42:ac:14:00:05",
    "DriverOpts": null,
    "NetworkID": "c5f2e4b8d0f1...",
    "EndpointID": "4c0f2a7c2e0b...",
    "Gateway": "172.20.0.1",
    "IPAddress": "172.20.0.5",
    "IPPrefixLen": 16,
    "IPv6Gateway": "",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "DNSNames": [
      "api",
      "3d2c9a8c4c1a"
    ]
  }
}

Значення: Контейнер api приєднаний до app_net і має DNS-імена api плюс його ID контейнера.

Рішення: Перевірте, чи цільовий контейнер теж у app_net. Якщо ні — виправляйте приєднання до мережі, а не налаштування DNS.

Завдання 2: Підтвердити, що цільовий контейнер у тій самій мережі

cr0x@server:~$ docker inspect -f '{{json .NetworkSettings.Networks}}' db | jq
{
  "app_net": {
    "IPAMConfig": null,
    "Links": null,
    "Aliases": [
      "db",
      "postgres",
      "a9b1c2d3e4f5"
    ],
    "NetworkID": "c5f2e4b8d0f1...",
    "EndpointID": "d1a6b9aa0f2c...",
    "Gateway": "172.20.0.1",
    "IPAddress": "172.20.0.10",
    "IPPrefixLen": 16
  }
}

Значення: Обидва контейнери ділять app_net. DNS повинен працювати для db і псевдоніма postgres.

Рішення: Перейдіть до перевірки резолвера всередині клієнтського контейнера.

Завдання 3: Перевірити конфіг резолвера всередині клієнтського контейнера

cr0x@server:~$ docker exec -it api cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0
search corp.example

Значення: Контейнер використовує вбудований DNS Docker. ndots:0 означає, що навіть односкладові імена, як db, спочатку обробляються як «абсолютні».

Рішення: Якщо ви бачите ndots:5 і довгий список пошукових доменів, очікуйте затримок і дивних зовнішніх пошуків; розгляньте налаштування або використання FQDN для зовнішніх імен.

Завдання 4: Відтворити lookup з поведінкою libc

cr0x@server:~$ docker exec -it api getent hosts db
172.20.0.10     db

Значення: Резольвінг імен libc вдається. Якщо ваш додаток все ще каже «cannot resolve», підозрійте кешування DNS на рівні додатку, іншу бібліотеку резолвера або невірне ім’я.

Рішення: Якщо getent не вдається, це реальна проблема шляху резолвера/DNS — продовжуйте з сирими DNS-інструментами.

Завдання 5: Запитати Docker DNS напряму (сирий запит)

cr0x@server:~$ docker exec -it api sh -lc 'apk add --no-cache bind-tools >/dev/null 2>&1; dig @127.0.0.11 db +short'
172.20.0.10

Значення: Docker DNS повертає очікуваний A-запис.

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

Завдання 6: Перевірити, чи застосунок не падає через преференцію IPv6

cr0x@server:~$ docker exec -it api sh -lc 'getent ahosts db | head -n 5'
172.20.0.10      STREAM db
172.20.0.10      DGRAM
172.20.0.10      RAW

Значення: Лише IPv4-відповіді. Якщо ваш додаток намагається спочатку використовувати IPv6, а DNS повертає AAAA-записи в інших місцях, ви отримаєте «зависання, потім відкат».

Рішення: Якщо ви бачите AAAA-записи, але немає маршрутизації IPv6, виправте IPv6-конфігурацію або змусьте клієнта використовувати IPv4.

Завдання 7: Підтвердити, що в мережі є правильні кінцеві точки і псевдоніми

cr0x@server:~$ docker network inspect app_net | jq '.[0] | {Name, Driver, Containers}'
{
  "Name": "app_net",
  "Driver": "bridge",
  "Containers": {
    "3d2c9a8c4c1a...": {
      "Name": "api",
      "IPv4Address": "172.20.0.5/16",
      "IPv6Address": ""
    },
    "a9b1c2d3e4f5...": {
      "Name": "db",
      "IPv4Address": "172.20.0.10/16",
      "IPv6Address": ""
    }
  }
}

Значення: Мережа бачить обидві кінцеві точки. Якщо цільовий контейнер не вказаний — DNS не може його резольвити в цій мережі.

Рішення: Приєднайте контейнер до мережі або виправте конфігурацію Compose.

Завдання 8: Перевірити, що Compose створив мережу, яку ви очікуєте

cr0x@server:~$ docker compose ls
NAME            STATUS
payments        running(6)

cr0x@server:~$ docker network ls | grep payments
c5f2e4b8d0f1   payments_default   bridge    local

Значення: Compose створив payments_default. Якщо ваші контейнери на іншій мережі, можливо ви запускаєте кілька проєктів або змішуєте ручні запуски і Compose.

Рішення: Стандартизуйтесь: запускайте все через Compose (або через ваш оркестратор), а не напівручним способом.

Завдання 9: Довести, що контейнер випадково опинився на дефолтному bridge

cr0x@server:~$ docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' legacy_worker
bridge 

Значення: Цей контейнер лише на дефолтній мережі bridge. Він не буде резольвити імена з вашої користувацької мережі Compose.

Рішення: Перемістіть його на користувацьку мережу; припиніть очікувати розпізнавання імен між мережами.

Завдання 10: Приєднати працюючий контейнер до правильної мережі (гаряче виправлення)

cr0x@server:~$ docker network connect payments_default legacy_worker
cr0x@server:~$ docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' legacy_worker
bridge payments_default 

Значення: Тепер контейнер ділить мережу Compose і має резольвитися за іменем у ній.

Рішення: Розглядайте це як тимчасове рішення. Виправте Compose-файл або розгортання, щоб контейнер стартував правильно наступного разу.

Завдання 11: Підтвердити, що ім’я, яке ви використовуєте, дійсно зареєстроване як псевдонім

cr0x@server:~$ docker inspect -f '{{json (index .NetworkSettings.Networks "payments_default").Aliases}}' db | jq
[
  "db",
  "postgres",
  "a9b1c2d3e4f5"
]

Значення: Псевдонім postgres реальний у цій мережі.

Рішення: Якщо ваш додаток підключається до postgresql і такого псевдоніма тут немає, або додайте псевдонім, або оновіть конфіг додатку. Не вгадуйте.

Завдання 12: Виявити розширення пошукового домену, що спричиняє повільні запити

cr0x@server:~$ docker exec -it api sh -lc 'cat /etc/resolv.conf; echo; time getent hosts db >/dev/null'
nameserver 127.0.0.11
options ndots:5
search corp.example svc.corp.example cloud.corp.example

real    0m2.013s
user    0m0.000s
sys     0m0.003s

Значення: Двохсекундний lookup для локального імені — поганий сигнал. З ndots:5 резолвер спочатку пробує додавати пошукові домени. Це може викликати таймаути перед тим, як запитати db «як є».

Рішення: Для внутрішніх імен Docker віддавайте перевагу односкладовим іменам з ndots:0 де доречно, або використовуйте явну точку в кінці (db.) у клієнтах, які це підтримують. Альтернативно, скоротіть список пошукових доменів для цього навантаження.

Завдання 13: Підтвердити DNS-налаштування демона (на хості)

cr0x@server:~$ docker info | sed -n '/DNS:/,/Registry Mirrors:/p'
DNS: 10.10.0.53
  10.10.0.54
Registry Mirrors:

Значення: Демон Docker переадресовує позаконтейнерні запити на ці upstream-сервери.

Рішення: Якщо зовнішні імена в контейнерах не резольвяться, але на хості працюють — порівняйте upstream-резолвери; можливо потрібно явно налаштувати DNS у /etc/docker/daemon.json.

Завдання 14: Перевірити з’єднання до цільового сервісу після успішного DNS

cr0x@server:~$ docker exec -it api sh -lc 'apk add --no-cache busybox-extras >/dev/null 2>&1; nc -vz db 5432'
db (172.20.0.10:5432) open

Значення: DNS і TCP-з’єднання обидва в порядку. Якщо додаток все ще видає помилку — це TLS, креденшіали, невідповідність протоколу або конфіг додатку.

Рішення: Припиніть звинувачувати DNS. Підіймайтесь вище по стеку.

Завдання 15: Перевірка здорового глузду в режимі Swarm (VIP vs DNSRR)

cr0x@server:~$ docker service inspect payments_api --format '{{json .Endpoint.Spec.Mode}}'
"vip"

Значення: У Swarm сервіс резольвиться у VIP. Ви побачите одну IP для імені сервісу, а не по IP кожної задачі.

Рішення: Якщо ваш клієнт очікує кілька записів A для балансування, перемкніться на DNSRR або змініть клієнт, щоб підключатися до VIP.

Три корпоративні історії з полів DNS

Історія 1: Інцидент через хибне припущення

Середня компанія запускала платежеве API на одному Docker-хості для застарілої лінії продуктів. Це було «тимчасово» вже рік,
що є стандартною одиницею часу в корпоративній архітектурі.
Використовували Compose з сервісом api і db, та одноразовим контейнером для міграцій, який запускали вручну під час деплою.

Одного дня job міграції почав падати з помилкою «could not translate host name ‘db’ to address».
Інженер on-call перевірив стек Compose: db був запущений, здоровий, логи в нормі.
Він перезапустив базу, бо так заведено. Помилка залишилась.

Хибне припущення було тонким: вони вірили, що «контейнери на одному хості можуть резольвити одне одного за іменем».
Контейнер міграції був запущений через docker run і опинився на дефолтному bridge.
Сервіси Compose були на project_default. Два різні всесвіти. Той самий хост, різні мережі, без DNS-зв’язку.

Виправлення було нудним: запускати міграції як сервіс Compose, приєднаний до тієї ж мережі, або явно приєднувати одноразовий контейнер до мережі Compose.
Після цього команда написала невеликий wrapper для деплою, що відмовлявся запускати ad-hoc контейнери без --network.
Це трохи дратувало розробників, що і є ознакою ефективного рішення.

Історія 2: Оптимізація, яка відпрацювала проти нас

Інша організація мала latency-чутливий внутрішній API. Хтось помітив випадкові 1–2 секундні затримки при старті, коли сервіси намагалися дістатися залежностей.
Доброзичливий інженер правильно визначив причину — повторні спроби DNS і розширення пошукових доменів.
Їхня «оптимізація» полягала в жорсткому прописуванні IP-адрес залежностей у змінних оточення.

Тиждень усе було добре. Старт проходив швидше, графіки заспокоїлися. Потім стався інцидент: рутинний редеплой перемішав IP-адреси контейнерів.
Один сервіс продовжував намагатися підключитися до старої IP-адреси, і через те, що IP тепер належала комусь іншому, помилка була не «connection refused».
Вона була «підключено до неправильного сервісу», за яким пішли помилки автентифікації, схожі на drift креденшіалів.

Аутейдж не був катастрофічним, але був неприємним: часткові помилки, заплутані логи і rollback, що не відновив порядок, бо «оптимізована» конфігурація жила в спільному CI-шаблоні.
Постмортем був ввічливим і глибоко незручним.

Справжнє вирішення полягало не в обході DNS: зменшити шум пошукових доменів, використовувати мережеві псевдоніми і реалізувати повторні спроби з jitter на рівні додатка.
IP знову стали тимчасовими, як і належить.

Історія 3: Нудна, але правильна практика, що врятувала ситуацію

Велике підприємство запускало кілька проєктів Compose на спільних хостах під час міграції.
Це було хаотично, але команда SRE ввела одне правило: кожен проєкт визначає явні мережі з конкретними іменами, а всі крос-проєктні залежності
використовують виділену «shared» мережу з контрольованими псевдонімами.

Вони також ввели конвенцію іменування: з’єднання між сервісами мають використовувати DNS-ім’я, яке або є ім’ям сервісу Compose
у локальній мережі, або мережевим псевдонімом у shared-мережі. Ніяких імен контейнерів. Ніяких IP. Ніякого «що завгодно працює».
Це здавалося педантичним і сповільнювало кілька швидких хаків.

Потім прийшов контейнер від постачальника з жорстко вбудованим очікуванням звертатися до license-server.
Якби не конвенція, команди б перейменували сервіси, додали випадкові extra_hosts або почали правити образи.
Натомість вони приєднали контейнер постачальника до shared-мережі і дали реальному сервісу ліцензій псевдонім license-server.

Інтеграція постачальника спрацювала з першої спроби у стенді й продакшені. Ніхто не святкував, бо все було нудно.
Це найвища похвала для операційної роботи.

Поширені помилки: симптом → корінна причина → виправлення

1) «Name or service not known» із одного контейнера, але працює з іншого

Симптом: api може резольвити db, а worker — ні.

Корінна причина: Контейнери на різних мережах (часто один на дефолтному bridge).

Виправлення: Приєднайте обидва до однієї користувацької мережі або підключіть worker до мережі Compose. Віддавайте перевагу явним мережам у Compose.

2) «Temporary failure in name resolution», що зникає після перезапуску

Симптом: Пуск не вдається спочатку; перезапуск «вирішує» проблему.

Корінна причина: Порядок старту плюс повторні спроби резолвера плюс залежність не готова; іноді також повільний DNS через пошукові домени.

Виправлення: Додайте повторні спроби з backoff у додаток; використайте healthchecks і шаблони wait-for; зменшіть шум пошукових доменів або налаштуйте ndots відповідно.

3) Ім’я сервісу резольвиться в IP, але з’єднання зависає або падає

Симптом: getent hosts db працює; nc -vz db 5432 не вдається.

Корінна причина: Сервіс не слухає, неправильний порт, правила фаєрволу або контейнер недоступний через політику маршрутизації/iptables конфлікти.

Виправлення: Перевірте слушачі сокетів (ss -lntp в цільовому), логи контейнера і правила iptables/nft на хості Docker. DNS уже не ваша проблема.

4) Зовнішні домени резольвляться на хості, але не в контейнерах

Симптом: Хост резольвить corp-internal; контейнер — ні.

Корінна причина: Демон Docker використовує інші upstream DNS-сервери, ніж поточний VPN-резолвер хоста; або VPN-маршрути недоступні контейнерам.

Виправлення: Налаштуйте DNS демона явно; забезпечте доступність VPN-DNS і маршрутів для контейнерів (іноді потрібно запускати VPN на хості таким чином, щоб контейнер міг ним користуватися).

5) «Працювало, поки ми не масштабували до 2 реплік»

Симптом: Після масштабування з’єднання йдуть до неправильного бекенду або стають непослідовними.

Корінна причина: Неправильно використане container_name або залежність від однієї ідентичності контейнера; у Swarm — помилка в розумінні VIP vs DNSRR.

Виправлення: Приберіть container_name у масштабованих сервісах; використовуйте імена сервісів; свідомо обирайте VIP або DNSRR.

6) Колізія імен між проектами

Симптом: db резольвиться, але до «іншої» БД.

Корінна причина: Спільна мережа з перекриваючимися псевдонімами або кілька стеків, приєднаних до тієї самої мережі з нестриманим іменуванням.

Виправлення: Використовуйте project-специфічні псевдоніми (payments-db) або ізолюйте мережі і діліться лише через виділену, кураторовану мережу.

7) Повільні запити для односкладових імен

Симптом: Кожен внутрішній lookup триває ~1–5 секунд.

Корінна причина: ndots забагато плюс довгий список пошукових доменів викликає кілька невдалих запитів перед спробою голого імені.

Виправлення: Зменшіть ndots для навантаження, скоротіть пошукові домени або використовуйте кінцеву крапку там, де це підтримується.

8) «Ми додали extra_hosts і тепер все дивно»

Симптом: Сервіс періодично підключається до старих кінцевих точок після редеплойу.

Корінна причина: extra_hosts прив’язує імена до фіксованих IP, обходячи оновлення Docker DNS.

Виправлення: Видаліть extra_hosts для внутрішніх сервісів; використовуйте мережеві псевдоніми і правильні мережі. extra_hosts прийнятний лише для дійсно статичних кінцевих точок, які ви готові підтримувати вічно.

Чеклисти / покроковий план

Чеклист: проектування сервісного виявлення Docker, яке не буде вас піднімати вночі

  1. Усюди використовуйте користувацькі мережі. У Compose визначайте їх явно; не покладайтеся на дефолтний bridge.
  2. Тримайте виявлення сервісів обмеженим мережею. Розглядайте мережі як межі довіри та домени відмов.
  3. Використовуйте мережеві псевдоніми для стабільних «інтерфейсних» імен. Особливо під час міграцій і інтеграцій з постачальниками.
  4. Не хардкодьте IP-адреси контейнерів. Якщо ви думаєте, що потрібно — вам насправді потрібне стабільне ім’я або інша архітектура.
  5. Уникайте container_name у масштабованих сервісах. Це ламає масштабування і заохочує крихкі залежності.
  6. Зробіть старт стійким. DNS-успіх не означає, що залежність готова. Реалізуйте повторні спроби з jitter і адекватні таймаути.
  7. Контролюйте поведінку резолвера. Слідкуйте за ndots і пошуковими доменами; узгодьте їх із вашою стратегією іменування.
  8. Розділіть внутрішні й зовнішні імена. Для внутрішніх сервісів — короткі імена/псевдоніми. Для зовнішніх залежностей — FQDN.
  9. Свідомо обирайте режим Swarm. VIP для простоти; DNSRR, якщо клієнти вміють працювати з кількома A-записами.
  10. Напишіть runbook з командами. Якщо кроки діагностики живуть лише в чиїйсь голові — їх немає.

Покроково: міграція від «випадкових імен» до адекватних псевдонімів

  1. Визначте канонічне ім’я залежності для кожного сервісу (db, cache, queue).
  2. Реалізуйте мережеві псевдоніми у внутрішній мережі додатка для цих імен.
  3. Оновіть додатки, щоб використовувати тільки ці імена (жодних імен контейнерів, жодних IP).
  4. Розгортайте по одному сервісу; тримайте тимчасові подвійні псевдоніми для сумісності.
  5. Після повного циклу деплою і вікна для відкату видаліть застарілі псевдоніми.

Покроково: дисциплінований потік дебагу під час інциденту

  1. Запустіть getent hosts всередині контейнера з помилкою для імені залежності.
  2. Якщо він падає — інспектуйте мережі контейнерів і спільні мережі.
  3. Перегляньте /etc/resolv.conf на предмет ndots і пошукових доменів.
  4. Використайте dig @127.0.0.11 (якщо можливо), щоб перевірити поведінку вбудованого DNS.
  5. Якщо DNS працює — перевірте TCP-з’єднання за допомогою nc -vz.
  6. Якщо TCP працює — зупиніться. Це не DNS. Переходьте до TLS/конфіг/автентифікації.

FAQ

1) Чому мої контейнери використовують nameserver 127.0.0.11?

Це вбудований DNS-стаб Docker у просторі імен контейнера. Він резольвить імена контейнерів/псевдоніми в Docker-мережах і переадресовує інші запити вгору за ланцюжком.

2) Чому виявлення сервісів працює в Compose, але не з docker run?

Compose приєднує сервіси до користувацької мережі проекту, де увімкнене DNS-базоване виявлення. bare docker run часто потрапляє на дефолтний bridge,
якщо ви не вказали --network. Різна мережа — різний простір DNS-імен.

3) Чи добре використовувати container_name для стабільних DNS-імен?

Ні. Це ускладнює масштабування і заохочує зв’язування. Використовуйте імена сервісів і мережеві псевдоніми; вони виражають намір без прив’язки ідентичності до одного контейнера.

4) Чим мережеві псевдоніми відрізняються від hostname?

Hostname — локальна ідентичність всередині контейнера. Мережевий псевдонім — це DNS-ім’я, зареєстроване в конкретній Docker-мережі для цієї кінцевої точки.
Псевдоніми — те, що інші контейнери можуть резольвити.

5) Чому іноді lookup займе секунди?

Поширена причина: ndots у парі з пошуковими доменами. Односкладове ім’я, як db, може пробуватися як db.corp.example,
db.svc.corp.example тощо з таймаутами, перш ніж буде спробована проста форма db.

6) Чи слід використовувати FQDN для внутрішніх Docker-сервісів?

Зазвичай ні. Використовуйте короткі імена сервісів і псевдоніми в межах мережі. Використовуйте FQDN для зовнішніх сервісів, особливо через VPN і корпоративні DNS.
Змішування підвищує складність резолвера і ризики збоїв.

7) У Swarm чому ім’я сервісу резольвиться в одну IP, навіть якщо багато реплік?

Ймовірно, ви в режимі VIP. Ім’я сервісу резольвиться в віртуальну IP; Swarm обробляє балансування. Якщо ви хочете кілька записів A, використайте DNSRR і переконайтеся, що клієнт вміє з цим працювати.

8) Чи безпечно використовувати extra_hosts, щоб «пофіксити DNS»?

Тільки якщо ви готові нести відповідальність за це відношення імена до IP довгостроково. extra_hosts прив’язує імена до IP і обходить динамічне виявлення. Підходить для справді статичних кінцевих точок; пастка для внутрішніх сервісів.

9) Чому мій додаток падає по DNS, а getent hosts працює?

Ваш додаток може використовувати іншу бібліотеку резолвера, агресивно кешувати, віддавати перевагу IPv6 або мати власний DNS-клієнт з іншими таймаутами.
Підтвердіть, який стек резолвера використовує рантайм (glibc vs musl vs кастомний) і тестуйте відповідними інструментами.

10) Чи можуть дві мережі безпечно мати той самий псевдонім?

Так, бо псевдоніми обмежені мережею. Проблеми виникають, коли ви приєднуєте контейнер до кількох мереж і припускаєте, що ім’я резольвиться однаково скрізь.
Будьте явними щодо того, в якій мережі клієнт знаходиться.

Висновок: наступні кроки, які можна відправити в реліз

Сервісне виявлення Docker ламається з передбачуваних причин: контейнери не в тій мережі, псевдоніми не такі, як ви думаєте,
або резолвер робить саме те, що ви налаштували (або успадкували), і ви просто цього не прочитали.
Виправлення — здебільшого про вибір моделі і її дотримання.

Практичні наступні кроки

  1. Аудит мереж: перелічіть контейнери, що досі використовують дефолтний bridge, і перемістіть їх на користувацькі мережі.
  2. Стандартизуйте імена: оберіть імена сервісів і додайте мережеві псевдоніми для стабільних ідентичностей залежностей.
  3. Припиніть використовувати IP: приберіть будь-які внутрішні IP-адреси залежностей і хаки з extra_hosts, якщо вони не справді статичні.
  4. Інструментуйте старт: додайте повторні спроби з jitter і обмежені таймаути; вважайте успішний DNS необхідною, але не достатньою умовою.
  5. Напишіть runbook: скопіюйте швидкий план діагностики і розділ завдань у ваші документи on-call та підтримуйте їх в актуальному стані.

Якщо ви зробите ці п’ять речей, більшість «інцидентів Docker DNS» перестануть бути інцидентами. Вони стануть п’ятихвилинною діагностикою й одномовним виправленням.
Саме так DNS заслуговує: на тиху компетентність, а не драму.

← Попередня
Пагінація проти нескінченного скролу: шаблони інтерфейсу, що не дратують користувачів
Наступна →
MariaDB vs PostgreSQL Container Storage: Bind Mount vs Volume Performance Truth

Залишити коментар