Компании Cloudflare уже пошёл 6-й год и предоставление авторитативных DNS серверов было основной нашей инфраструктуры с самого начала. С тех пор мы выросли, став самым большим и быстрым поставщиком услуг DNS в Интернете, обслуживая около 100 000 сайтов из списка Alex top 1M sites, и более 6 миллионов DNS зон.
На сегодняшний день наш сервис DNS отвечает около 1 миллиона запросов в секунду — не считая трафика во время атак — с помощью глобальной anycast сети. Разумеется, технологии, которые мы, будучи растущим стартапом, использовали для того, чтобы обслуживать сотни и тысячи зон несколько лет назад уже не справляются с миллионами, которые мы имеем сегодня. В прошлом году мы решили заменить два ключевых элемента в нашей DNS инфраструктуре: часть нашего DNS сервера, который отвечает на авторитативные запросы и систему, которая берёт пользовательские изменения и обновляет их на пограничных серверах по всему миру.
Примерная картина нашей архитектуры показана на рисунке выше. Мы храним пользовательские DNS записи и прочую оригинальную информацию в центральной базе данных, затем конвертируем сырые данные в формат, который могут использовать наши сервера, и затем раздаем их в >100 дата центров (мы называем их PoPs — Points of Presents ("Точки присутствия")), используя KV (ключ/значение) хранилище.
Сами запросы обслуживает наш собственный DNS сервер, rrDNS, который мы разрабатываем и используем уже несколько лет. В ранние дни Cloudflare, наше DNS решение было основано на PowerDNS, но оно было полностью заменено rrDNS в 2013 году.
Команда Cloudflare DNS таким образом отвечает за два элемента системы: систему передачи данных между серверами и rrDNS. Начальная идея была заменить нашу систему передачи данных на полностью новое решение, поскольку нынешнее решение стало устаревать — как и любая инфраструктура, которой больше 5 лет. Нынешнее решение было построено для того, чтобы работать поверх PowerDNS и постепенно разививалось со временем. В нём накопилось достаточно изъянов и скрытых фич, потому как изначально оно было создано для трансляции DNS записей в формат PowerDNS.
Новая модель данных
В старой системе, модель данных была достаточно простой. Мы хранили DNS записи примерно в том же виде, в котором они представлены в наших графических интерфейсах или API: одна запись на ресурсную запись (resource record — RR). Это означало, что сервис передачи данных между серверами должен был заниматься лишь простой сериализацией, когда нужно было отдавать данные зон для наших пограничных серверов.
Метаданные зоны и RR сериализировались используя смесь JSON и Protocol Buffers, хотя мы никак особо не использовали схематическую природу этих протоколов, и в итоге схемы становились раздутыми и финальные данные были больше, чем это было необходимо. Не говоря уже о том, что по мере того, как количество ресурсных записей в базе данных выросло до 100 миллионов, эта, казалось бы, небольшая разница в размере становилась очень заметной.
Стоит напомнить, что DNS на самом деле не оперирует на уровне ресурсных записей, когда отвечает на запросы. Вы запрашиваете имя и тип (например, example.com
и AAAA
), и в ответ вам вернётся RRSet — который является набором из нескольких RR. В старой модели данных RRSets были разбиты на множественные записи для RR (один ключ на запись), что обычно означало несколько обращений к KV-хранилищу чтобы ответить на один DNS-запрос. Мы хотели это исправить и сгруппировать данные вокруг RRSet так, чтобы один запрос требовал лишь одно обращение к KV-хранилищу. Поскольку для Cloudflare очень важно оптимизировать производительность DNS, множественные запросы к KV-хранилищу сильно мешали сделать rrDNS настолько быстрым, насколько это возможно.
В том же духе, для запросов вроде A/AAAA/CNAME, мы решили сгруппировать данные в один ключ по "адресу", вместо одного ключа на RRSet. Это ещё более помогает избавиться от дополнительных обращение к хранилищу во многих случаях. Объединение ключей также помогает уменьшить использование памяти на кэше, который мы используем для KV-хранилища, посколько мы храним больше информации по одному ключу.
Определившись с новой моделью данных, нам требовалось определиться с тем, как мы сериализируем данные и отправляем их на пограничные сервера. Как было уже сказано, мы ранее использовали смесь JSON и Protocol Buffers, но мы решили заменить это на полностью MessagePack реализацию.
Почему MessagePack?
MessagePack является бинарным типизированным форматом сериализации данных, но при этом не привязывает жестко данные к схеме. В этом смысле он похож немного на JSON. Как отправитель, так и получатель может добавлять или игнорировать дополнительные поля — это уже на совести приложения.
Для сравнения, Protocol Buffers (или другие форматы вроде Cap'n Proto) требуют сначала определить схему данных в языконезависимом формате, и затем генерируют код для конкретной реализации. Поскольку DNS уже имеет большую структурированную схему, мы не хотели повторять её реализацию на другом языке, и затем это поддерживать. В прошлой реализации на Protocol Buffers, мы не описывали схемы для всех типов DNS как следует — чтобы избежать необходимости этой поддержки — и в итоге это вылилось в очень запутанную модель данных для rrDNS.
И когда мы смотрели на новые форматы, мы хотели что-то быстрое, легкое в использовании и которое сможет легко интегрироваться с нашей кодовой базой и библиотеками, которые мы использовали. rrDNS активно использует библиотеку miekg/dns на Go, которая оперирует большой коллекцией структур для каждого RR типа, например:
type SRV struct {
Hdr RR_Header
Priority uint16
Weight uint16
Port uint16
Target string `dns:"domain-name"`
}
При декодировании данных из наших сервисов в rrDNS нам нужно конвертировать RR в эти структуры. И оказалось, что библиотека tinylib/msgp, которую мы пробовали, представляет хорошие возможности для кодогенерации. Это позволяет автоматически генерировать эффективный код на Go из этих описаний структур без надобности поддерживать определение схемы в другом формате.
Это означало, что мы могли работать и дальше с RR структурами из miekg (с которыми мы уже были знакомы в rrDNS), сериализовать их напрямую в бинарные данные, а затем десеериализовать их на пограничных серверах прямо в нужные нам структуры. Нам не нужно было теперь заботиться от соответствии одного набора структур другим, что сильно упрощало работу.
MessagePack также невероятно быстр, в сравнении с другими форматами. Вот вырезка из сравнения скорости сериализации в разных форматах для Go; мы можем увидеть, что он значительно превосходит по скорости другие кросс-платформенные решения, и это также очень повлияло на наш выбор.
Сюрпризом для нас оказалось то, что после перехода на новую модель мы на самом деле уменьшили количество места, занимаемое данными на пограничных серверах в 9 раз, что было гораздо лучше того, что мы ожидали. Это ещё раз доказывает, какой сильный эффект на систему может оказывать раздутая модель данных.
Новый сервис передачи данных
Ещё одной важной особенностью Cloudflare DNS является наша способность обновлять изменения по всей планете в считанные секунды, а не минуты или часы. Наша существующая система передачи данных едва справлялась с растущим количеством зон, и даже при изменении 5 зон в секунду, в самые спокойные периоды, нам необходимо было уже новое решение.
Обновление данных по всему миру это непросто
С недавнего времени мы мониторим этот процесс и можем визуализировать время обновления изменения по всему миру. График ниже показывает данные с нашего end-to-end мониторинга: сначала мы делаем изменения в DNS с помощью нашего API, и мониторим как быстро данные обновились в разных точках планеты. Каждая точка на графике представляет отдельную пробу, тестирующую один из наших PoPs (Точек Присутствия) и замеряется задержка между реальным изменением через API и тем, когда она становится видима миру.
Из-за нескольких уровней кеша — как внутренних, так и не подконтрольных нам — мы можем видеть распределение по 10 секундным интервалам и значения меньше 1 минуты, и они постоянно меняются. Для мониторинга и уведомлений этого разрешения достаточно, но мы безусловно планируем улучшить это. В нормальных условиях, новые данные о DNS в 99% случаях становятся доступны в течении 5 секунд.
На первом графике мы видим несколько инцидентов, где задержки в пару минут были видны на небольшом количестве PoPs из-за проблем с соединением, но в целом все пробы показывают очень стабильный результат.
Для контраста, вот график со старой системы передачи данных за тот же период. Легко увидеть растущие задержки для всех PoPs.
С новой моделью данных, которая лучше подходила под наши паттерны запросов, мы реализовали новый сервис, который подхватывает изменения в зонах из центральной базы данных, делает необходимую обработку и отправляет данные в KV-хранилище.
Новый сервис (написанный на нашем любимом языке Go) работает в продакшене с июля 2016 года, и на текущий момент мы мигрировали на него более 99% всех пользовательских зон. Если убрать случаи с задержками по причине сетевых проблем, то новая система передачи данных пока что показала нулевые задержки.
Авторитативный rrDNS v2
rrDNS является модульной программой, что позволяет писать различные "фильтры", которые могут передавать обработку определённых типов запросов другому коду. Авторитативный фильтр берет входящий DNS запрос, смотрит на зоны, которым принадлежит запрос и выполняет всю необходимую логику, чтобы найти RRSet и вернуть обратно клиенту.
Поскольку мы полностью пересмотрели низлежащую модель данных DNS на наших пограничных серверах, нам необходимо было значительно переделать и "Авторитативный Фильтр" в rrDNS. Это тоже было одним из тёмных уголков нашей кодовой базы, которая не менялась много лет. Как и с любой устаревающей кодовой базой, изменение её было непростой задачей, и мы решили переписать этот фильтр полностью с нуля. Это позволило начать всё с чистого листа, основываясь на новой модели данных, с прицелом на производительность и с более подходящими решениями для того объема и формата DNS трафика, который мы имеем сегодня. Решение переписать с нуля также позволило гораздо легче вести разработку с использованием лучших практик, таких как высокое покрытие кода тестами и хорошая документация.
Новая версия авторитативного фильтра работает в продакшене с конца 2016 года и уже сыграла ключевую роль в работе с DNS нашего нового решения для load-balancing.
Результаты перехода на новый фильтр нас порадовали: мы начали отвечать на DNS запросы в среднем в 3 раза быстрее, чем раньше, что было отличной новостью для наших клиентов, и улучшили наши силы в обходе масштабных DNS атак. Мы можем видеть на этом графике, что по мере того, как количество мигрировавших зон увеличилось, среднее время ответа значительно уменьшилось.
Переобувание на лету
Самая затратная по времени часть проекта была миграция клиентов со старой на новую версию, причём так, чтобы никто не испытывал никаких проблем и даже не знал, что мы что-то делаем. Для достижения этого Cloudflare потребовались значительные усилия от массы людей в наших отделах связей с клиентами и поддержки. У Cloudflare много офисов в разных часовых поясах — Лондон, Сан-Франциско, Сингапур и Остин — и синхронизация всех была одним из ключевых элементов успеха.
Как один из элементов процесса релиза нового rrDNS мы автоматически собирали и повторяли запросы с продакшн системы на старом и новом коде, чтобы обнаружить любые неожиданные отличия. Эту же технику мы решили применить и для миграций зон. Для любой зоны, чтобы пройти тест, мы сравнивали возможные ответы для всей зоны между старой и новой системой. Малейшее отличие в результате автоматически исключало зону из процесса миграции.
Это позволило нам последовательно тестировать миграцию зон и исправлять недочёты по мере их возникновения, выпуская релизы регулярно. Мы решили не идти пугающим путём перехода со старой на новую систему одним махом, а запустили их параллельно и постепенно мигрировали зоны, поддерживая их в синхронизации. Это означало, что мы в любой момент можем мигрировать назад в случае, если что-то случится.
Как только мы запустили процесс, мы спокойно начали мигрировать по несколько сотен тысяч зон в день, и пристально наблюдали за тем, как близко мы приблизились к нашей начальной цели в 99%. Последняя миля всё ещё в процессе, так как всегда есть элемент вовлечения клиентов в определённых сложных конфигурациях.
Чего мы добились?
Заменить часть инфраструктуры ядра Cloudflare потребовало значительных усилий со стороны большого количества команд. Чего же мы добились?
- Прирост производительности в 3 раза в ответах на DNS запросы
- Более быстрое и надежное распространение обновлений DNS по всему миру
- Более цельный набор возможностей, основанный на нынешних реалиях трафика, и хорошая документация поведения пограничных серверов
- Большее покрытие тестами, лучше метрики и большая уверенность в нашем код, что позволяет нам более уверенно делать изменения и разрабатывать наши DNS решения
Теперь, когда мы можем более быстро обслуживать наших DNS клиентов, мы скоро выкатим поддержку для нескольких новых типов RR и некоторые новые интересные фичи в следующие месяцы.
Автор: divan0