136 lines
11 KiB
Markdown
136 lines
11 KiB
Markdown
# DNS: делегирование, кэширование и ошибки
|
||||
|
|
|
|||
|
|
В Consul мы получали адрес из реестра сервисов. Теперь рассмотрим DNS: как найти сервер, который знает нужную запись, и почему разные клиенты могут получать разные адреса одного имени.
|
|||
|
|
|
|||
|
|
**Авторитетный сервер** хранит записи обслуживаемой зоны. **Резолвер** получает ответы для клиента и может сохранять их в кэше. В опытах мы будем обращаться к обоим и сравнивать результаты.
|
|||
|
|
|
|||
|
|
Все команды выполняются из каталога `seminar`; [подготовка стенда](../readme.md#самостоятельный-запуск-опытов).
|
|||
|
|
|
|||
|
|
## 1. Прочитать DNS-ответ
|
|||
|
|
|
|||
|
|
В стенде подготовлена отдельная учебная иерархия:
|
|||
|
|
|
|||
|
|
| Сервер | Роль |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `root-dns` | Обслуживает учебный корень `.` и делегирует зону `test.` |
|
|||
|
|
| `tld-dns` | Обслуживает зону верхнего уровня (TLD) `test.` и делегирует `lab.test.` |
|
|||
|
|
| `auth` | Хранит записи зоны `lab.test.` |
|
|||
|
|
| `resolver` | Получает записи учебной зоны у `auth` и кэширует их |
|
|||
|
|
|
|||
|
|
Делегировать зону — значит указать серверы, отвечающие за неё. Учебная иерархия работает внутри Docker и не меняет публичный DNS.
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose up -d --wait auth resolver root-dns tld-dns
|
|||
|
|
docker compose run --rm -T tools dig "@resolver" orders.lab.test A
|
|||
|
|
docker compose run --rm -T tools dig "@auth" www.lab.test A +norecurse
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
В команде `dig` аргумент `@сервер` выбирает получателя запроса, далее идут имя и тип записи. Параметр `+norecurse` отключает запрос рекурсивного поиска: сервер отвечает сведениями, которыми уже располагает.
|
|||
|
|
|
|||
|
|
В первом ответе найдите **A-запись** — IPv4-адрес `orders.lab.test`. Во втором имя `www.lab.test` связано с `orders.lab.test` записью **CNAME**, за которой следует A-запись целевого имени.
|
|||
|
|
|
|||
|
|
При чтении вывода обращайте внимание на:
|
|||
|
|
|
|||
|
|
- `SERVER` — какой сервер ответил;
|
|||
|
|
- `status` — код результата;
|
|||
|
|
- `ANSWER` — записи, отвечающие на запрос;
|
|||
|
|
- число перед `IN` в строке записи — **TTL** — сколько секунд запись ещё можно использовать из кэша.
|
|||
|
|
|
|||
|
|
Адреса `192.0.2.10` и `192.0.2.20` используются как учебные значения записей. Приложений по этим адресам в стенде нет.
|
|||
|
|
|
|||
|
|
## 2. Пройти цепочку делегирований
|
|||
|
|
|
|||
|
|
**Вопрос:** если корневой сервер не хранит адрес `orders.lab.test`, как его ответ помогает продолжить поиск?
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose run --rm -T tools python3 dns/delegation.py
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
Скрипт выводит команды и ответы: сначала трассировку `dig +trace`, затем отдельные запросы к корню, TLD-серверу и авторитетному серверу.
|
|||
|
|
|
|||
|
|
В отдельных ответах корня и TLD-сервера секция `ANSWER` пуста. В секции `AUTHORITY` находятся **NS-записи** — имена серверов делегированной зоны. В `ADDITIONAL` даны их адреса, или **glue-записи**, чтобы можно было обратиться к следующему серверу. Последний ответ содержит искомую A-запись.
|
|||
|
|
|
|||
|
|
При `+trace` утилита сама выполняет итеративный поиск: переходит от одного сервера к следующему по делегированиям. Обычный запрос к резолверу может целиком обслужиться из кэша. В нашем опыте `resolver` настроен пересылать запросы учебной зоны сразу к `auth`; трассировка отдельно показывает путь через корень и TLD.
|
|||
|
|
|
|||
|
|
<details>
|
|||
|
|
<summary>Что означают флаги в ответе</summary>
|
|||
|
|
|
|||
|
|
- `aa` — ответ авторитетен.
|
|||
|
|
- `ra` — сервер поддерживает рекурсивные запросы.
|
|||
|
|
- `rd` — клиент запросил рекурсию. Сам по себе этот флаг не означает, что сервер действительно выполнял такой поиск.
|
|||
|
|
|
|||
|
|
</details>
|
|||
|
|
|
|||
|
|
### Сравнение с публичным DNS
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose run --rm -T tools dig wikipedia.org A
|
|||
|
|
docker compose run --rm -T tools dig "@1.1.1.1" wikipedia.org A +trace +time=2 +tries=1
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
В конце полной трассировки должна появиться A-запись `wikipedia.org`. Если вывод заканчивается раньше, поиск не завершён, даже если команда вернула код 0. Публичные записи меняются, а сеть может ограничивать прямые обращения к внешним DNS-серверам. Для разбора делегирований в таком случае используйте локальную иерархию.
|
|||
|
|
|
|||
|
|
## 3. Изменить адрес и наблюдать старый TTL
|
|||
|
|
|
|||
|
|
**Предскажите:** если одновременно изменить адрес и уменьшить TTL с 60 до 5 секунд, когда изменится ответ уже заполненного кэша?
|
|||
|
|
|
|||
|
|
Верните исходную запись командой `reset`, очистите кэш командой `flush` и запросите адрес:
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose exec -T auth python3 dns/control.py reset
|
|||
|
|
docker compose exec -T resolver python3 dns/control.py flush
|
|||
|
|
docker compose run --rm -T tools dig "@resolver" orders.lab.test A +noall +answer
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
Параметры `+noall +answer` оставляют в выводе только записи ответа. Получим `192.0.2.10` с TTL около 60. Отсчитывая время от этого запроса, примерно через 20 секунд измените запись и сравните ответы:
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose exec -T auth python3 dns/control.py update --address 192.0.2.20 --ttl 5
|
|||
|
|
docker compose run --rm -T tools dig "@auth" orders.lab.test A +noall +answer
|
|||
|
|
docker compose run --rm -T tools dig "@resolver" orders.lab.test A +noall +answer
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
Авторитетный сервер уже выдаёт `192.0.2.20` с TTL 5. Резолвер продолжает возвращать `192.0.2.10` с остатком прежних 60 секунд. Уменьшение TTL на авторитетном сервере не меняет срок ранее сохранённой записи. Повторный запрос к кэшу тоже не начинает отсчёт заново.
|
|||
|
|
|
|||
|
|
Дождитесь истечения **TTL старого ответа** и повторите запрос:
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose run --rm -T tools dig "@resolver" orders.lab.test A +noall +answer
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
Теперь ожидаем `192.0.2.20` с TTL не больше 5. Если при выполнении предыдущих команд прошла минута и старый адрес уже исчез, повторите опыт с `reset` и `flush`. Для более длинного опыта можно задать `reset --ttl 120`.
|
|||
|
|
|
|||
|
|
Здесь кэш настроен без предварительного обновления и выдачи просроченных записей. После истечения TTL следующий запрос клиента заставляет резолвер получить запись заново.
|
|||
|
|
|
|||
|
|
**Вопросы для самопроверки:** когда уменьшать TTL перед переездом сервиса? Почему старый адрес нужно поддерживать некоторое время? Где ещё, кроме резолвера, могут кэшироваться адреса?
|
|||
|
|
|
|||
|
|
Восстановите исходную запись и очистите кэш:
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose exec -T auth python3 dns/control.py reset
|
|||
|
|
docker compose exec -T resolver python3 dns/control.py flush
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
## 4. Различить отсутствие записи и ошибку поиска
|
|||
|
|
|
|||
|
|
Отсутствие адреса в ответе может означать разные вещи. Сравните четыре результата:
|
|||
|
|
|
|||
|
|
~~~powershell
|
|||
|
|
docker compose run --rm -T tools python3 dns/errors.py
|
|||
|
|
~~~
|
|||
|
|
|
|||
|
|
| Запрос / условие | Результат | Что можно заключить |
|
|||
|
|
| --- | --- | --- |
|
|||
|
|
| `missing.lab.test A` | `NXDOMAIN` | Запрошенного имени нет |
|
|||
|
|
| `orders.lab.test AAAA` | `NOERROR`, пустая секция `ANSWER` | Имя существует, но IPv6-адреса для него нет |
|
|||
|
|
| `name.broken.test A` через резолвер | `SERVFAIL` | Резолвер не смог получить пригодный ответ |
|
|||
|
|
| На запрос никто не отвечает | Тайм-аут | Ответ не получен за отведённое время |
|
|||
|
|
|
|||
|
|
**NODATA** — название ситуации, когда имя существует, но записи нужного типа нет; отдельного кода DNS с таким именем нет. В нашем примере запрос AAAA возвращает именно такой результат.
|
|||
|
|
|
|||
|
|
SERVFAIL в опыте возникает из-за пересылки запроса серверу, который не обслуживает нужную зону. Ни SERVFAIL, ни тайм-аут не доказывают отсутствия имени.
|
|||
|
|
|
|||
|
|
**Вопрос:** почему недавно созданное имя может некоторое время оставаться недоступным клиентам, которые запрашивали его до создания? Учтите, что ответы об отсутствии имени тоже могут кэшироваться.
|
|||
|
|
|
|||
|
|
Исходники: [настройка серверов](server.py), [изменение записи и сброс кэша](control.py), [делегирование](delegation.py), [ошибки](errors.py). Справочник: [BIND 9](https://bind9.readthedocs.io/en/v9.18.39/reference.html).
|