Ubuntu 24.04: systemd-resolved ламає Docker DNS — виправлення без відключення всього

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

Ви запускаєте контейнер, він стартує нормально, healthcheck зелений… а потім не може розв’язати ім’я хоста.
apt update зависає. У логах додатка видно “Temporary failure in name resolution.”
Хост сам при цьому ідеально розв’язує імена, що особливо принизливо.

На Ubuntu 24.04 це часто спрацьовує на одній й тій самій біді: systemd-resolved виконує свою роботу,
Docker виконує свою, а їхній перетин створює DNS-ситуацію, яка виглядає як чаклунство. Насправді — ні.
Це — мережеве підкапання. І виправлення не вимагає відключати пів-мережевого стека зопалу.

Що саме ламається: stub-резолвери, неймспейси та DNS у Docker

Назвемо рухомі частини, тому що збій виглядає містично тільки якщо розглядати DNS як одну коробку.
На Ubuntu 24.04 хост зазвичай використовує systemd-resolved, щоб надати локальний «stub-резолвер»,
прив’язаний до 127.0.0.53. Файл /etc/resolv.conf хоста часто є симлінком на файл,
який вказує на цей stub.

Docker, між тим, зазвичай підсовує резолвер у контейнери, копіюючи або генеруючи
/etc/resolv.conf всередині мережевого неймспейсу контейнера. Якщо Docker бачить nameserver у файлі
resolv.conf хоста, він спробує використати його. Ось де підступ: 127.0.0.53 в контейнері — не хост.
Це сам контейнер. Отже коли контейнер намагається запитати 127.0.0.53, він по суті питає
самого себе про DNS, і ніхто не відповідає.

Docker має вбудований DNS-сервер (зазвичай доступний як 127.0.0.11 всередині контейнерів)
для discovery між контейнерами у user-defined мережах. Але цей вбудований DNS все одно потребує upstream-серверів,
щоб форвардити запити у реальний світ. Якщо upstream вказано на хостовий stub (127.0.0.53), форвардинг ламається.

Є інші варіанти:

  • Docker підхоплює nameserver 127.0.0.53 з хоста і записує його в контейнери. Миттєве падіння.
  • Docker використовує свій вбудований DNS (127.0.0.11), але upstream недоступний — внутрішні імена працюють, зовнішні — ні.
  • Split DNS (корпоративні домени, спрямовані на конкретні резолвери) працює на хості через systemd-resolved,
    але контейнери минають цю логіку і запитують публічний DNS для внутрішніх зон, отримуючи NXDOMAIN або таймаути.

Мета тут не в тому, щоб «перемогти», відключивши systemd-resolved. Він дає реальну користь:
per-link DNS, стан DNSSEC, кешування та хорошу інтеграцію з NetworkManager і netplan. Мета — змусити
Docker використовувати resolv.conf з доступними upstream-серверами й робити це передбачувано.

Одна цитата для правильного налаштування мислення (перефразована думка): Джон Оллспоу наполягав, що надійність походить із розуміння систем, а не з пошуку винних.
DNS-збої майже завжди — це взаємодії систем, а не недбалість оператора.

Жарт №1: DNS — єдина частина стека, де «вчора працювало» вважається форматом конфігурації.

Цікаві факти та історичний контекст (щоб це не здавалося випадковим)

  1. Stub systemd-resolved на 127.0.0.53 існує, щоб уникнути колізій з локальними резолверами типу dnsmasq,
    і щоб зберегти консистентну loopback-крапку доступу навіть коли upstream-адреси змінюються.
  2. Вбудований DNS Docker (127.0.0.11) був введений для підтримки discovery контейнерів між собою в
    user-defined мережах, а не як загального клієнта «корпоративного DNS».
  3. Бібліотека резолвера glibc читає /etc/resolv.conf і має правила типу «максимум 3 nameserver»,
    які можуть мовчки відкидати записи, що весело, коли VPN-клієнти додають п’ять резолверів.
  4. Ubuntu все глибше інтегрується з systemd у останніх релізах; симлінк на /etc/resolv.conf
    тепер нормальний, а не екзотичний.
  5. Split DNS раніше був рідкістю поза підприємствами. Тепер навіть домашні користувачі натрапляють на нього з mesh-VPN, dev‑інструментами
    і «магічним» DNS для внутрішніх доменів.
  6. Мережеві неймспейси означають, що loopback є на рівні неймспейсу. 127.0.0.1 в контейнері — не хост.
    Якщо запам’ятати тільки одне — запам’ятайте це.
  7. resolv.conf в контейнерах не святиня. Він генерується при старті контейнера і може змінюватися залежно від
    налаштувань Docker, драйвера мережі та runtime.
  8. systemd-resolved може показувати «реальний» resolv.conf, який перераховує upstream-сервери через
    /run/systemd/resolve/resolv.conf, що часто є найчистішим джерелом для Docker.

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

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

Перший крок: підтвердьте, що і де падає

  • Всередині контейнера: чи можете ви щось розв’язати? Публічні імена? Внутрішні домени?
  • Всередині контейнера: що насправді містить /etc/resolv.conf?
  • На хості: чи systemd-resolved здоровий і які upstream-сервери він знає?

Другий крок: класифікуйте збій

  • nameserver 127.0.0.53 всередині контейнера → майже напевно некоректна передача resolv.conf.
  • nameserver 127.0.0.11 всередині контейнера, але таймаути → upstream вбудованого DNS Docker неправильний/недоступний.
  • Публічні домени працюють, корпоративні не працюють → розбіжність split DNS між хостом і контейнерами.
  • Падають тільки певні мережі → маршрути VPN, firewall або MTU/фрагментація впливають на DNS-трафік.

Третій крок: виправляйте на найвужчому шарі

  • Віддавайте перевагу вказанню Docker справжнього resolv.conf або явних DNS-серверів.
  • Використовуйте DNS-на рівні мережі або per-compose, коли це доречно.
  • Уникайте відключення systemd-resolved, якщо лише ви не замінюєте його краще визначеним резолвером і приймаєте зону впливу змін.

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

Це перевірки, які я реально виконую на Ubuntu-хостах, коли контейнери починають падати DNS. Кожне завдання містить (1) команду, (2) що означає вивід і (3) яке рішення з цього випливає.
Виконуйте їх по черзі, поки не отримаєте впевнену діагностику.

Завдання 1: Підтвердіть симптом Docker DNS всередині мінімального контейнера

cr0x@server:~$ docker run --rm alpine:3.20 sh -lc 'cat /etc/resolv.conf; nslookup -type=a example.com 2>&1 | sed -n "1,12p"'
nameserver 127.0.0.53
options edns0 trust-ad
nslookup: can't resolve '(null)': Name does not resolve

Значення: контейнер намагається говорити з 127.0.0.53 (собою), а не з резолвером хоста.
Запит відразу невдалий.
Рішення: перестаньте звинувачувати upstream DNS. Виправте спосіб, у який Docker заповнює /etc/resolv.conf контейнера (або DNS демона Docker).

Завдання 2: Перевірте, чи resolv.conf хоста — це systemd stub

cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Oct 10 09:12 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

Значення: хост вказує на stub-резолвер.
Рішення: якщо Docker наслідує це, він передасть контейнерам тупиковий nameserver. Ви хочете, щоб Docker використовував «реальний» resolv.conf.

Завдання 3: Перегляньте «реальний» resolv.conf, який перераховує upstream

cr0x@server:~$ sed -n '1,20p' /run/systemd/resolve/resolv.conf
# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
nameserver 10.20.0.10
nameserver 10.20.0.11
search corp.example

Значення: це доступні upstream-резолвери з погляду мережі хоста.
Рішення: найкраща практика зазвичай: змусити Docker читати цей файл замість stub-файла.

Завдання 4: Переконайтеся, що systemd-resolved працює і не в деградованому стані

cr0x@server:~$ systemctl status systemd-resolved --no-pager -l
● systemd-resolved.service - Network Name Resolution
     Loaded: loaded (/usr/lib/systemd/system/systemd-resolved.service; enabled; preset: enabled)
     Active: active (running) since Thu 2025-12-28 10:20:18 UTC; 3h 12min ago
       Docs: man:systemd-resolved.service(8)
   Main PID: 812 (systemd-resolve)
     Status: "Processing requests..."
      Tasks: 1 (limit: 38291)
     Memory: 6.1M
        CPU: 1.228s

Значення: resolved здоровий.
Рішення: не «виправляйте» це відключенням сервісу. Проблема — у інтеграції Docker, а не в резолвері.

Завдання 5: Подивіться, які DNS-сервери resolved вважає потрібними для підключення

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
 resolv.conf mode: stub

Link 2 (ens160)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.20.0.10
       DNS Servers: 10.20.0.10 10.20.0.11
        DNS Domain: corp.example

Значення: хост використовує DNS через інтерфейс ens160 і має search-домен.
Рішення: якщо контейнерам потрібна резолюція corp.example, потрібно змусити Docker використовувати ці upstream-сервери, інакше ви втратите поведінку split DNS.

Завдання 6: Перевірте, який resolv.conf Docker вважає за потрібне використовувати

cr0x@server:~$ docker info 2>/dev/null | sed -n '/DNS/,+3p'
 DNS: 127.0.0.53

Значення: демон Docker налаштований (явно або неявно) на використання stub-резолвера.
Рішення: перевизначте DNS Docker, щоб використовувати реальні upstream або вкажіть Docker на non-stub resolv.conf.

Завдання 7: Підтвердіть, який resolv.conf отримав запущений контейнер

cr0x@server:~$ cid=$(docker run -d alpine:3.20 sleep 300); docker exec "$cid" cat /etc/resolv.conf; docker rm -f "$cid" >/dev/null
nameserver 127.0.0.53
options edns0 trust-ad

Значення: це не вбудований DNS Docker; це пряме успадкування stub.
Рішення: виправте вхідні дані, які використовує Docker (resolv.conf хоста або налаштування демона).

Завдання 8: Перевірте, чи присутній вбудований DNS Docker (сценарій 127.0.0.11)

cr0x@server:~$ docker network create testnet >/dev/null
cr0x@server:~$ cid=$(docker run -d --network testnet alpine:3.20 sleep 300)
cr0x@server:~$ docker exec "$cid" cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0
cr0x@server:~$ docker rm -f "$cid" >/dev/null; docker network rm testnet >/dev/null

Значення: у user-defined мережах Docker використовує свій вбудований DNS.
Рішення: якщо запити і тут падають, проблема в upstream, який використовує вбудований DNS Docker.

Завдання 9: Перегляньте iptables/nft правила, які дозволяють форвардинг DNS від контейнерів

cr0x@server:~$ sudo nft list ruleset | sed -n '1,80p'
table inet filter {
  chain input {
    type filter hook input priority filter; policy accept;
  }
  chain forward {
    type filter hook forward priority filter; policy accept;
  }
}
table ip nat {
  chain POSTROUTING {
    type nat hook postrouting priority srcnat; policy accept;
    oifname "ens160" ip saddr 172.17.0.0/16 masquerade
  }
}

Значення: існує NAT masquerade, політика forward — accept (у цьому фрагменті).
Рішення: якщо ви бачите суворі політики forward або відсутній masquerade, DNS-запити можуть ніколи не покидати хост. Виправляйте firewall перед зміною DNS-конфіг.

Завдання 10: Дивіться логи systemd-resolved під час спроби DNS з контейнера

cr0x@server:~$ sudo journalctl -u systemd-resolved -n 30 --no-pager
Dec 28 13:21:04 server systemd-resolved[812]: Using degraded feature set UDP instead of UDP+EDNS0 for DNS server 10.20.0.10.
Dec 28 13:21:10 server systemd-resolved[812]: DNS server 10.20.0.11 does not support DNSSEC, disabling DNSSEC validation for this server.

Значення: resolved узгоджує можливості; ці повідомлення самі по собі не фатальні.
Рішення: якщо є таймаути, хвилі SERVFAIL або часте перемикання серверів, у вас також є проблеми з upstream DNS. Не зупиняйтеся лише на проблемі stub у Docker.

Завдання 11: Перевірте, що хост розв’язує те, що не може контейнер (перевірка split DNS)

cr0x@server:~$ resolvectl query registry.corp.example
registry.corp.example: 10.30.40.50                      -- link: ens160

-- Information acquired via protocol DNS in 12.7ms.
-- Data is authenticated: no

Значення: хост розв’язує корпоративне ім’я через налаштований DNS інтерфейсу.
Рішення: якщо контейнери не можуть це розв’язати, треба передати ті корпоративні резолвери у Docker, а не публічні.

Завдання 12: Перевірте розв’язання в контейнері через явні DNS-сервери (швидке підтвердження)

cr0x@server:~$ docker run --rm --dns 10.20.0.10 --dns 10.20.0.11 alpine:3.20 sh -lc 'nslookup example.com | sed -n "1,8p"'
Server:		10.20.0.10
Address:	10.20.0.10:53

Non-authoritative answer:
Name:	example.com
Address: 93.184.216.34

Значення: з явними upstream контейнер розв’язує нормально.
Рішення: постійне виправлення — налаштувати DNS демона Docker (або DNS у Compose/мережі) на доступні сервери або вказувати реальний resolv.conf.

Завдання 13: Перевірте наявність файлу конфігурації демона Docker і його ефективні налаштування

cr0x@server:~$ sudo test -f /etc/docker/daemon.json && sudo cat /etc/docker/daemon.json || echo "no /etc/docker/daemon.json"
no /etc/docker/daemon.json

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

Завдання 14: Підтвердіть, який resolv.conf Docker використовує при зміні файлу на хості

cr0x@server:~$ sudo readlink -f /etc/resolv.conf
/run/systemd/resolve/stub-resolv.conf

Значення: Docker зазвичай підхоплює це при запуску демона, а не динамічно для кожного контейнера.
Рішення: якщо ви змінюєте симлінк resolv.conf на хості, перезапустіть Docker, щоб він перечитав файл (плануйте вікно обслуговування при потребі).

Жарт №2: Нічого не каже «high availability», як розгортання DNS‑фіксу, що потребує перезапуску демона о 14:00 у вівторок.

Патерни виправлення, що працюють (без відключення systemd-resolved)

Є три розумні підходи. Оберіть один залежно від того, наскільки «корпоративний» у вас DNS і наскільки ви цінуєте збереження стандартної мережевої конфігурації хоста.
Я підкажу, що б я робив у продакшні для кожного випадку.

Патерн виправлення A (рекомендовано): скажіть Docker використовувати реальні upstream DNS

Це найчистіше в сенсі «зробити відмови нудними». Ви припиняєте успадкування stub-резолвера Docker і даєте йому
резолвери, до яких він має форвардити.

Створіть або відредагуйте /etc/docker/daemon.json:

cr0x@server:~$ sudo install -d -m 0755 /etc/docker
cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "dns": ["10.20.0.10", "10.20.0.11"],
  "dns-search": ["corp.example"]
}
EOF
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ docker info 2>/dev/null | sed -n '/DNS/,+3p'
 DNS: 10.20.0.10
 DNS: 10.20.0.11

Що це робить: вбудований DNS Docker і/або resolv.conf контейнерів тепер вказують на резолвери, доступні з контейнерів (через NAT хоста).
Чому це добре: детерміноване, без ігор із симлінками, стійке до оновлень Ubuntu.
Мінус: якщо upstream DNS змінюється (DHCP, роумінг ноутбуків), потрібно оновлювати конфіг або автоматизувати процес.

Патерн виправлення B: вкажіть /etc/resolv.conf хоста на non-stub файл systemd-resolved

Це поширено і добре працює на серверах зі стабільною мережею. Хитрість у тому, що ви не відключаєте resolved; ви просто
робите так, щоб /etc/resolv.conf перераховував реальні upstream-сервери замість 127.0.0.53.
Docker тоді наслідує працездатні резолвери.

cr0x@server:~$ sudo mv /etc/resolv.conf /etc/resolv.conf.bak
cr0x@server:~$ sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 32 Dec 28 14:01 /etc/resolv.conf -> /run/systemd/resolve/resolv.conf
cr0x@server:~$ sudo systemctl restart docker

Що це робить: glibc‑резолвер хоста тепер запитує upstream напряму.
Чому це добре: Docker наслідує коректні nameserver без додаткової конфігурації демона.
Мінус: деякі налаштування покладаються на stub-режим для певних поведінок; також ноутбуки/VPN можуть часто змінювати DNS.
Якщо хост роумить по мережах, я б радше явно конфігурував Docker.

Патерн виправлення C: per-Compose / per-container перевизначення DNS (сурогатне, іноді необхідне)

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

Приклад для Docker Compose:

cr0x@server:~$ cat compose.yaml
services:
  app:
    image: alpine:3.20
    command: ["sh", "-lc", "cat /etc/resolv.conf; nslookup example.com | sed -n '1,8p'; sleep 3600"]
    dns:
      - 10.20.0.10
      - 10.20.0.11
    dns_search:
      - corp.example

Що це робить: перевизначає resolv.conf всередині контейнера.
Чому це добре: обмежена зона впливу; підходить для контролю змін.
Мінус: розростання конфігів. Якщо у вас 40 compose-проєктів, ви якийсь забудете.

Чого уникати: «просто відключити systemd-resolved» як рефлекс

Ви цілком можете видалити systemd-resolved і використовувати статичний resolv.conf або інший локальний резолвер.
Іноді це правильний крок, особливо якщо ви вже запускаєте dnsmasq/unbound і хочете простіший стек.
Але як стандартна відповідь це надмірно грубо.

Відключення resolved часто ламає:

  • Поводження split DNS у VPN, інтегроване з NetworkManager
  • Per-interface DNS на хостах з кількома інтерфейсами
  • Інструменти, які очікують stub від resolved (різні desktop та dev інструменти)

Якщо ви хочете надійності — не видаляйте компоненти, поки чітко не поясните, що саме вони робили для вас.

Одна тонка деталь для продакшну: поведінка при перезапусках і життєвий цикл контейнерів

Docker зазвичай записує /etc/resolv.conf контейнера при старті. Якщо ви виправили DNS і не перезапустили контейнери,
деякі з них збережуть стару поломанту конфігурацію. Отже план розгортання має значення:

  • Виправте DNS демона Docker.
  • Перезапустіть Docker, якщо потрібно.
  • ПереDEPLOY/перезапустіть контейнери, щоб оновити їхні resolv.conf.

Три міні-історії з корпоративного світу (як це ламається в реальних організаціях)

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

Команда, з якою я працював, запускала build-агенти на Ubuntu-хостах. Нічого особливого: Docker builds, кілька інтеграційних тестів,
потім пуш артефактів у внутрішній реєстр. Рутинне оновлення ОС пройшло: Ubuntu 24.04, оновлення ядра,
ребут, і все здавалося ок. Хост міг розв’язувати будь-що. CI-агенти? Половина з них почала падати
з помилкою «could not resolve host» посеред пайплайну.

Хибне припущення було болісно просте: «Якщо хост розв’язує DNS, то і контейнери теж».
Це правило трималося роками на старіших конфігураціях, бо /etc/resolv.conf хоста містив реальні upstream.
Оновлення змінило це на systemd stub, і Docker старанно скопіював адресу stub у контейнери.
Контейнери потім питали себе про DNS. Вони не були такими розумними, як здавалося.

Початкова реакція була класичним корпоративним театром ескалації: тікети в мережеву команду, питання, чи упав DNS,
повторні збірки, звинувачення «флейкій інфраструктури». Тим часом підказка лежала на виду: nameserver 127.0.0.53
всередині контейнера. Виправлення полягало в тому, щоб задати DNS демону Docker на корпоративні резолвери і перезапустити сервіс Docker
у вікні обслуговування.

Висновок не був «systemd-resolved — поганий». Він був таким: «неймспейси змінюють значення localhost».
На кожному перегляді інциденту команда додала одну лінію: завжди перевіряти /etc/resolv.conf контейнера
перед діагностикою upstream DNS.

Міні-історія 2: оптимізація, що обернулася проти

Інша організація вирішила «зменшити латентність», жорстко прописавши публічні DNS-сервери в Docker на всіх дев-машинах.
Мотив звучав непогано: менше рухомих частин, незалежність від корпоративного DNS і швидша резолюція ніж через VPN.
Вони запровадили стандартний /etc/docker/daemon.json з двома публічними резолверами.

Це працювало тиждень. Потім внутрішні інструменти почали падати в контейнерах: mirror-репозиторії, внутрішні Git-хости,
discovery під приватним доменом. На хості все і далі працювало, бо systemd-resolved
застосовував split DNS: корпоративні домени шли на корпоративні резолвери, решта — у публічні.
Контейнери обминали цю логіку і зверталися до публічного DNS по приватних зонах, отримуючи NXDOMAIN швидко і впевнено.

«Оптимізація» не просто провалилася; вона провалилася швидко, найводно у найзаплутаніший спосіб. Розробники бачили «хост працює, контейнер — ні»,
думали, що мережа Docker зламана, і почали додавати хаки: додаткові --add-host записи, специфічні DNS-настройки для проєктів,
і кеші, які маскували проблему до наступної зміни мережі.

Відкат полягав у припиненні глобального примусу публічного DNS, і замість цього налаштуванні Docker на ті ж upstream-резолвери, що й хост,
або використанні per-project DNS лише коли є справжня необхідність. Глибша вигода — політика: DNS є частиною environment parity.
Якщо ваш контейнеризований дев-оточення не дотримується тих же правил маршрутизації DNS, що і продакшн, ви будете дебажити примарні проблеми.

Міні-історія 3: нудна, але правильна практика, що врятувала день

Одна SRE‑команда мала неефектну, але корисну звичку: на кожному хості був невеликий “network sanity” скрипт, який запускався через cron і був доступний як фрагмент runbook.
Він перевіряв три речі: резолюцію DNS на хості через resolvectl, резолюцію DNS в контейнері через відомий мінімальний контейнер,
і чи /etc/resolv.conf хоста — stub чи upstream.

Коли Ubuntu 24.04 розгорнули, оповіщення почали спрацьовувати ще до того, як клієнти помітили щось: «виявлено невідповідність DNS контейнер/хост».
Нічого ще не було впало, бо більшість сервісів використовували вже запущені контейнери з кешованими результатами, але команда знала,
що наступний деплой зазнає невдачі.

Вони використали скриптовий вивід, щоб класифікувати проблему як «stub потрапив у контейнери» і застосували стандартне виправлення:
задали DNS демону Docker на upstream-резолвери з /run/systemd/resolve/resolv.conf, потім у контрольованому вікні перезапустили Docker,
а далі перезапускали лише безстанні сервіси першими.

Практика була не гламурною. Вона не включала сервіс-меші чи ШІ. Це була базова валідація на межах між хостом і контейнером.
І вона перетворила потенційний багатомовний інцидент на планову зміну.

Типові помилки: симптоми → корінь проблеми → виправлення

1) Контейнери видають «Temporary failure in name resolution» миттєво

Симптом: будь-який DNS-запит миттєво падає в контейнерах; хост працює.

Корінь: /etc/resolv.conf контейнера має nameserver 127.0.0.53.

Виправлення: налаштуйте DNS демона Docker (/etc/docker/daemon.json) або вкажіть resolv.conf хоста на
/run/systemd/resolve/resolv.conf, потім перезапустіть Docker і перезапустіть уражені контейнери.

2) Внутрішні імена сервісів резольвляться, зовнішні — таймаутяться

Симптом: ping other-container працює в user-defined мережі, але apt update падає.

Корінь: вбудований DNS Docker (127.0.0.11) працює для внутрішнього discovery, але форвардинг upstream ламається
через успадкування stub або недоступні upstream-сервери.

Виправлення: задайте DNS демону Docker на реальні upstream-резолвери; переконайтеся, що NAT/firewall дозволяє вихідний трафік на UDP/TCP 53.

3) Працює без VPN, падає з VPN (або навпаки)

Симптом: корпоративні домени падають тільки коли VPN підключено.

Корінь: хост використовує split DNS через resolved; контейнери спрямовані на публічні або старі резолвери.

Виправлення: передайте Docker ті самі корпоративні резолвери (і search-домени), що й хост, або додайте per-Compose DNS для стеків, які цього потребують.

4) Тільки деякі контейнери падають після зміни DNS

Симптом: старі контейнери продовжують працювати; нові розгортання падають.

Корінь: контейнери зберігають resolv.conf, з яким були створені; нові отримують нову поламанту конфігурацію.

Виправлення: після виправлення DNS демона виконайте ролловий перезапуск контейнерів (в пріоритеті — безстанні), або пересоздайте compose-стеки.

5) DNS працює для малих відповідей, але падає для більших

Симптом: деякі запити проходять; інші зависають або повертають SERVFAIL; часто гірше при VPN.

Корінь: MTU/фрагментація призводять до втрати UDP-фрагментів, або проблеми з EDNS0 розміром на шляху.

Виправлення: зменшіть MTU на docker-бріджі або VPN-інтерфейсі, або забезпечте роботу через TCP; перевірте логи resolved на повідомлення “degraded feature set.”

6) Ви «виправили» це, встановивши 8.8.8.8, тепер внутрішнє не працює

Симптом: публічні домени резольвляться; внутрішні — ні.

Корінь: ви обійшли корпоративний DNS і split-horizon зони.

Виправлення: використовуйте корпоративні резолвери для Docker, а не публічні, якщо тільки ви впевнені, що ваші робочі навантаження ніколи не потребуватимуть внутрішнього DNS.

7) DNS хоста ламається після зміни симлінка /etc/resolv.conf

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

Корінь: інструменти очікували режим stub; або ви перезаписали resolv.conf так, що він конфліктує з NetworkManager/netplan.

Виправлення: віддавайте перевагу конфігу демона Docker (патерн A). Якщо змінюєте симлінк, збережіть бекап і перевіряйте через resolvectl.

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

План A (рекомендований): стабільні сервери, ви контролюєте демон Docker

  1. Підтвердіть збій: запустіть одноразовий контейнер і перевірте /etc/resolv.conf.
    Якщо бачите 127.0.0.53, далі.
  2. Визначте upstream-резолвери з /run/systemd/resolve/resolv.conf або resolvectl status.
  3. Налаштуйте DNS демона Docker у /etc/docker/daemon.json з цими upstream-серверами.
  4. Перезапустіть Docker у контрольованому вікні.
  5. Перезапустіть контейнери (рестарт/рекреат) щоб оновити їхній resolv.conf.
  6. Провалідуйте як публічні, так і внутрішні домени (якщо релевантно).
  7. Задокументуйте рішення в runbook: «Ми фіксуємо DNS Docker на upstream-резолверах; не використовувати 127.0.0.53».

План B: потрібно зберегти точну поведінку split DNS

  1. Використайте корпоративні резолвери, які показує resolved для конкретного інтерфейсу, і додайте search-домени за потреби.
  2. Для стеків, яким потрібно по-різному резольвити декілька зон, віддавайте перевагу per-Compose DNS замість глобального налаштування.
  3. Провалідуйте через resolvectl query на хості і nslookup всередині контейнерів для тих же імен.

План C: тимчасове пом’якшення, поки чекаєте change control

  1. Запускайте критичні контейнери з --dns явно, щоб відновити функціональність.
  2. Задокументуйте, які сервіси ви змінили і чому, бо це інакше перетвориться на «таємну конфігурацію» через три місяці.
  3. Заплануйте правильне виправлення на рівні демона, щоб уникнути постійних «сніжинок» конфігурації.

FAQ

1) Чому 127.0.0.53 працює на хості, але не в контейнерах?

Тому що loopback існує в межах мережевого неймспейсу. 127.0.0.53 контейнера — не резолвер хоста; це адреса всередині контейнера.
Якщо ви не запускаєте resolved у тому контейнері (не робіть цього), там ніхто не слухає.

2) Хіба Docker не повинен використовувати 127.0.0.11 для DNS?

Зазвичай так, особливо на user-defined мережах. Але вбудований DNS Docker і далі форвардить на upstream-резолвери.
Якщо Docker дізнався upstream як 127.0.0.53 з хоста, форвардинг все одно ламається.

3) Чи можна просто змінити /etc/resolv.conf на non-stub файл?

Можна, і це часто добре працює на серверах. На ноутбуках або в середовищах з активним VPN поведінка може відрізнятися від режиму stub.
Якщо ви хочете найменше несподіванок — конфігуруйте DNS демона Docker явно.

4) Чи потрібно перезапускати Docker після зміни DNS-настроєк?

Так, для змін на рівні демона. Також перезапустіть або пересоздайте контейнери, щоб їхній /etc/resolv.conf було згенеровано заново з новими налаштуваннями.
Інакше ви виправите хост, але будете мати працюючі поламані контейнери.

5) Що робити, якщо мої DNS-сервери видаються DHCP і часто змінюються?

Утримування DNS у /etc/docker/daemon.json тоді крихке, якщо ви не автоматизуєте оновлення.
Для хостів, що роумлять, розгляньте невеликий автомат, який оновлює DNS Docker з /run/systemd/resolve/resolv.conf і перезапускає Docker у безпечні вікна.

6) Чому б не встановити Docker на публічний резолвер і не забути?

Тому що split-horizon DNS існує. Внутрішні зони не будуть доступні публічно, і деякі організації навмисно повертають інші відповіді внутрішньо та зовні.
Публічний DNS може «працювати», поки ви не торкнетеся нічого корпоративного — тоді він дасть збій заплутано.

7) Чи стосується це також Kubernetes/containerd?

Так, по суті. Механіка відрізняється (CNI, налаштування kubelet resolvConf), але основна проблема залишається:
контейнери потребують резолверів, досяжних з їхнього неймспейсу. Loopback‑stub — повторювана пастка.

8) Я бачу таймаути, а не миттєві відмови. Це теж проблема зі stub?

Іноді. Миттєві «can’t resolve» часто вказують на 127.0.0.53 в контейнері.
Таймаути також можуть свідчити про firewall/NAT, маршрутизацію VPN, проблеми MTU або здоров’я upstream. Використовуйте швидкий план діагностики, щоб класифікувати.

9) Чи можна запустити DNS-кеш на хості і вказати Docker на нього?

Так, але робіть це свідомо. Якщо ви запускаєте unbound/dnsmasq, прив’язаний до не-loopback адреси, доступної з мереж контейнерів,
контейнери можуть його використовувати. Прив’язка тільки до 127.0.0.1 створить ту саму проблему неймспейсу в іншій формі.

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

Повторювана помилка Ubuntu 24.04 + Docker DNS — це не історія «Docker зламався». Це прогнозована невідповідність:
systemd-resolved рекламуватиме loopback stub, а Docker (або ваші контейнери) не зможуть дістатися до цього loopback в іншому неймспейсі.
Як тільки ви це побачите, ви вже не зможете цього не помітити.

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

  1. Запустіть одноразовий контейнер і подивіться на /etc/resolv.conf. Не вгадуйте.
  2. Якщо бачите 127.0.0.53, оберіть патерн виправлення: DNS демона (переважний) або симлінк resolv.conf на non-stub файл.
  3. Перезапустіть Docker у контрольованому вікні, потім перезапустіть/пересоздайте контейнери, щоб вони підхопили новий конфіг резолвера.
  4. Перевірте як публічні, так і внутрішні домени (якщо у вас є split DNS — вважайте, що він є).
  5. Запишіть рішення в runbook, щоб наступне оновлення не навчило ту саму жорстку науку знову.

Найкраще DNS‑виправлення — те, що робить DNS знову нудною інфраструктурою. Залиште адреналін на ті речі, що його справді заслуговують.

← Попередня
Postfix “Relay access denied”: виправлення ретрансляції без створення відкритого ретранслятора
Наступна →
Ubuntu 24.04 PostgreSQL autovacuum «загадкова повільність»: як безпечно налаштувати

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