Как влияет возвращение нескольких IP-адресов DNS на обработку трафика
Что проверяют
Понимаете ли вы, что несколько A-записей это не балансировщик, и знаете ли, как с ними на самом деле обращаются клиент на Go и пул соединений. Следом часто спрашивают, почему после добавления новых инстансов трафик на них не пошёл.
Как отвечать
Несколько адресов в ответе DNS дают грубое распределение клиентов по серверам и запасной адрес при отказе. Обе вещи работают хуже, чем кажется.
Что происходит в Go:
- резолв:
net.Resolverвозвращает все адреса. Есть чистый Go-резолвер и cgo-вариант через системную библиотеку, выбор переключается черезGODEBUG=netdns=goилиnetdns=cgo. Своего кэша DNS в стандартной библиотеке нет, кэширует ОС или локальный резолвер; - dial:
net.Dialerперебирает адреса по очереди и делит оставшийся таймаут между ними. Если первый адрес отвечает отказом, переход к следующему быстрый, если пакеты теряются, попытка ждёт свою часть таймаута. При наличии IPv4 и IPv6 работает Happy Eyeballs, задержка задаётсяFallbackDelay; - пул:
http.Transportдержит keep-alive соединения по хосту, а не по IP. Пока соединение живо, все запросы идут на один и тот же адрес, и новый ответ DNS ни на что не влияет; - gRPC: HTTP/2 гоняет всё через одно соединение, политика по умолчанию
pick_first. Для распределения нужен резолверdns:///с политикойround_robinили внешний балансировщик.
Отсюда практические выводы: DNS не знает о здоровье серверов, мёртвый адрес остаётся в ответе до истечения TTL и дольше, если кэш его не соблюдает. Балансировку по нагрузке делают L4/L7-балансировщики, service mesh или сам клиент. В Kubernetes для клиентской балансировки берут headless Service, который отдаёт IP подов.
Где спотыкаются
Называют DNS round robin балансировкой нагрузки с проверкой здоровья. Сервер DNS просто отдаёт список, а порядок адресов ещё может поменять резолвер клиента.
Забывают про долгоживущие соединения. Сервис масштабировали, а старые клиенты часами ходят на прежние поды, особенно по gRPC через обычный ClusterIP Service.
Ставят TTL в пару секунд и считают, что переключение будет мгновенным. От промежуточных кэшей и уже открытых соединений короткий TTL не спасает.