Двигайтесь быстро и (не) ломайте вещи
Почему именно динамическая конфигурация?
Ваш код часто основан на множестве констант и конфигураций. Представьте, что вы передаете эту статическую конфигурацию вашему коду:
Config { whitelisted_customers: [customer_1, customer_2], feature_1_turn_on_percentage: 0.1, }
Вы написали свой код с этой статической конфигурацией и развернули его на 100 машинах. В мире все хорошо, пока тебе не нужно это менять. Для нас эти изменения часто были мотивированы одной из следующих причин:
- Добавление клиента к новой альфа-функции
- Удалите клиента из указанной альфа-версии, чтобы отладить сбои запросов
- Ограничьте скорость конкретного клиента, чтобы избежать ресурсного голодания
- Тщательное развертывание функции в системе с отслеживанием состояния
- Изменение тайм-аутов, количества повторных попыток, ограничений скорости и т. Д.
В мире статической конфигурации вы вносите изменения в конфигурацию в коде, перестраиваете двоичный файл службы и повторно развертываете. Это может занять много времени, особенно для систем с большим количеством реплик или систем с отслеживанием состояния, которые имеют тщательно продуманные процедуры постепенного завершения работы. В идеале вы могли бы обновлять эти конфигурации «вживую» (без перезагрузки) для более быстрой итерации.
Система динамической конфигурации позволяет разработчикам обновлять конфигурации без повторного развертывания своих сервисов. Такие компании, как Facebook и Twitter, создали довольно сложные системы именно для этой цели. В Mixpanel мы решили написать небольшой способ управления динамической конфигурацией для наших сервисов Golang, построенный на Kubernetes.
Требования к динамическим конфигам
- Быстро. После внесения изменений в конфигурацию все связанные развернутые службы должны увидеть эффект менее чем за минуту.
- Гибкость: конфигурации должны храниться в формате, допускающем произвольные значения.
- Безопасно: в конфигурациях должна быть возможность писать валидацию.
- Контроль версий: как естественное расширение, должно быть легко вернуться к более старой версии конфигурации.
- Простота в использовании: должны быть простые в использовании SDK для служб, которые создают впечатление, будто они просто получают обычные значения конфигурации.
Как Mixpanel управляет динамической конфигурацией
Федерация
Помня об этих принципах дизайна, мы решили быстро написать что-то, что охватит 90% наших сценариев использования. Mixpanel работает на GKE, и большинство наших сервисов написано на Go. В Kubernetes уже есть нечто, называемое configmaps, которое позволяет разработчикам использовать SDK Kube для создания, обновления и применения конфигураций к службам или развертываниям.
Монтирование configmap просто помещает конфигурации в файлы (ключ - имя файла, значение - содержимое) в файловой системе модуля. Любые изменения в configmap выполняются так же, как и любые другие изменения в объекте kube. Это надежно решает проблему доставки конфигураций в модули согласованным образом и в режиме реального времени.
Контроль версий и проверка
Наши конфигурации записываются в файлы YAML, как описано в разделе ниже. Эти файлы регистрируются в нашем репозитории git, и изменения в них следуют тому же рабочему процессу разработки, что и изменения в коде. Таким образом, все наши конфигурации имеют контроль версий и проходят процесс проверки + CI.
Область конфигурации
Изначально мы решили сгруппировать конфигурации для одного и того же сервиса вместе. Например, для нашей службы запросов у нас будет единая область «запрос», в которой действуют все конфигурации, используемые в этой службе.
Позже мы разделили конфигурации и сервисы (хотя они часто были тесно связаны) по следующим причинам:
- Некоторые конфигурации (например, белые списки) поддерживали большую функцию, охватывающую несколько служб.
- Две разные команды, работающие в одном сервисе, хотят обновлять конфигурации независимо друг от друга, без лишних умственных затрат на рассуждения о конфигурациях друг друга.
Сегодня большинство конфигураций основаны на продуктовой вертикали, а не на отдельных услугах.
Схема объекта конфигурации
Вот пример того, как выглядит наша конфигурационная карта:
Data ==== configs.json: - — — [ { "key": "feature_enabled_customers", "value": { "123": {}, "456": {} } }, { "key": "scaling_percentage", "value": 1 }, { "key": "timeout_secs", "value": 5 } ]
В приведенном выше примере мы смонтируем configmap и создадим файл с именем configs.json с содержимым JSON внутри него. Вы можете заметить, что каждая конфигурация
{ “key”: “key_name”, “value”: <JSON object, string, bool, number … > }
SDK
На данный момент у нас есть только один SDK, написанный на Go. SDK делает следующее:
- Загружать содержимое конфигов из файла configs.json в память при его изменении. Это достигается с помощью fsnotify.
- Предоставьте интерфейс для получения конфигурации по имени конфигурации. На момент написания интерфейс SDK выглядел так:
Этот SDK имеет открытый исходный код! Вы можете найти его на https://github.com/mixpanel/configmanager.
Изменение конфигурации
Kubernetes позволяет разработчикам описывать ресурсы (сервисы, задания, конфигурации) как декларативный YAML. В нашем масштабе услуг мы обнаружили, что рукописный текст YAML дублируется и подвержен ошибкам. Вместо этого мы пишем Jsonnet, который затем компилируется для генерации файлов YAML. И Jsonnet, и YAML файлы проверяются в нашем репо.
Чтобы сделать изменения конфигурации декларативными и простыми для наших разработчиков, мы написали следующую библиотеку Jsonnet.
Учитывая такой синтаксический сахар, мы можем написать наш образец конфигурации:
Компиляция этого приведет к приведенному выше примеру YAML.
В дикой природе
Configmanager (так мы творчески называем эту библиотеку) широко используется в наших серверных службах Mixpanel. Вот лишь небольшая часть его использования:
- Хранилище: кнопка паузы и возобновления для функций, интенсивно использующих ЦП, таких как выполнение запросов на удаление GDPR.
- Прием: развертывание важных функций, таких как дедупликация событий и управление идентификацией, на нашем уровне приема. Configmanager использовался для добавления и удаления клиентов из белого списка и постепенного увеличения трафика.
- Запрос: недействительность кешей для отдельного клиента или глобально в случае изменения формата кеширования или ошибок.
- Везде: в различных частях нашего стека он используется для управления степенью параллелизма, доступной для обработки нашей рабочей нагрузки. Другой распространенный вариант использования - изменение лимитов скорости.
Чтобы отладить сам configmanager, мы интегрировали его с нашей системой отчетов и регистрации метрик, чтобы сообщать о различных типах ошибок. Он также имеет некоторые конечные точки HTTP для отладки, которые возвращают состояние конфигурации в памяти в памяти или вызывают перезагрузку конфигурации. Мы также написали фиктивную реализацию configmanager с тем же интерфейсом для использования в модульных тестах.
Будущая работа
Мы очень довольны гибкостью и ускорением разработчика, обеспечиваемыми configmanager. Тем не менее, есть возможности для улучшения; вот что мы планируем изменить в будущем:
Добавление пользовательского интерфейса
В настоящее время разработчики используют свой любимый редактор и Kubernetes CLI для просмотра и редактирования конфигураций. Хотя они просты и выполняют свою работу, для гигантских файлов конфигурации может быть проще работать с пользовательским интерфейсом, чем с одним массивным файлом JSON. Вы можете редактировать карты конфигурации kube в облаке Google, но это не лучший опыт.
Более мощный язык конфигурации
В настоящее время конфигурации представляют собой простые пары ключ-значение. Любая дополнительная логика должна быть закодирована клиентом. Вот пример гипотетического конфигурационного ключа route_to_queue:
if {customer: c1, event: alias_call} route to high priority queue if {customer: c1, event: track_call, lib_version: v2.1} route to fail queue ...
Чтобы выполнить этот вид маршрутизации в стиле оператора переключения, вы можете сохранить объект JSON и написать некоторый код, который исключает объект JSON, и записать оператор переключения в коде. В качестве альтернативы вы можете выразить сами значения конфигурации в гибкой грамматике и закодировать эту логику переключения в самих конфигурациях. Это также избавляет нас от необходимости писать одноразовые методы, такие как IsCustomerWhitelisted.
Раньше я работал в группе динамической конфигурации в Uber, и мы моделировали наши значения конфигурации так, чтобы каждый ключ конфигурации имел значение по умолчанию и список исключений, каждое из которых имело правило, написанное на грамматике PEG, и соответствующее значение. Это оказалось довольно гибким и полезным для большого количества команд, но с обратной стороной - сложнее было рассуждать о большом конфиге с исключениями.
Вот пост, который я написал по этому поводу. В какой-то момент, если в Mixpanel это будет достаточно, мы можем расширить configmanager таким образом.