Init-контейнеры и ресурсы пода
Выпуск открывается знакомством с гостями: Максим в клубе впервые и рассказывает, что его команда делает автоматизацию доставки Kubernetes — крупные общие кластеры, разнесённые по зонам доступности, и интеграцию с инфраструктурой; Миша ведёт языковую платформу, то есть библиотеки и фреймворки, на которых строятся все сервисы компании. По традиции нового гостя спрашивают, для кого книга: Максим считает её вводной для разработчиков, которые не видят ограничений платформы исполнения, но советует фильтровать написанное через реалии конкретной инсталляции. Кто-то предлагает назвать книгу «Kubernetes Patterns and Antipatterns» — каждый раз участники сходятся на том, что часть описанного лучше не делать. Первый структурный паттерн, init-контейнер, авторы объясняют через аналогию с конструктором класса: отдельная секция манифеста гарантирует, что подготовка отработает до приложения. Один из ведущих добавляет свой пример — init-контейнер собирает переменные из Vault и отдаёт их приложению.
Пример с миграцией базы вызывает возражения: init-контейнер стартует при каждом рестарте пода, а миграция разовая и занимает непредсказуемое время, поэтому её, как обсуждали в прошлых выпусках, выносят в Job. Остаются прогрев, проверка готовности базы и утилиты, которые не хочется держать в образе приложения. Init-контейнеры выполняются последовательно — один из гостей видел поды с четырьмя, где прямых зависимостей нет, но важен детерминированный порядок; контейнеры приложения, наоборот, стартуют параллельно, и пробы задаются только для них, а init-контейнеру достаточно завершиться успешно. Неочевидное следствие — ресурсы: эффективный запрос пода считается как максимум из суммы по контейнерам приложения и максимума по init-контейнерам, так что тяжёлый init зарезервирует ресурсы и оставит в продакшене пустой запас. В конце главы — врезка про admission-контроллеры: мутирующий вебхук сам добавляет init-контейнер в приходящую нагрузку, а у Миши init-контейнер ходит во внутренний сервис за метаинформацией о зоне, потому что вебхук отрабатывает до планировщика.
Sidecar против библиотеки
Sidecar в книге — способ расширить приложение, не переписывая его; наивный пример оттуда: HTTP-сервер со статикой и соседний контейнер, который по интервалу делает git pull. Александр раскладывает старую дилемму: общий сервис гибок, но добавляет нагрузку и точку отказа, а библиотеку нельзя заставить всех обновить и приходится тянуть обратную совместимость; sidecar даёт плюсы обоих вариантов, потому что за контейнер сбоку отвечает платформа. Миша спорит: образы sidecar приходится обновлять так же, как библиотеки, только сразу по сотням сервисов, а плотно упакованные ноды начинают напрягаться от количества контейнеров — поэтому у них sidecar сознательно минимизируют. Трактовку из книги, где любой неглавный контейнер называют sidecar, он считает притянутой за уши. Максим ближе к позиции Миши: накладные расходы на грядку sidecar больше, чем обработка внутри приложения, а прослойки он допускает для «глупых» клиентов и вендорского софта, в который не встроиться.
Из комментариев приходит вопрос, можно ли ресурсами init-контейнера обмануть планировщик и попасть на менее загруженную ноду: гости отвечают, что их платформа валидирует нагрузку и не даст запросить в init больше, чем в основном контейнере. Дальше разбирают два типа sidecar — прозрачные, как в service mesh, где трафик перехватывается, а ретраи, лимиты и трейсинг настраиваются мимо приложения, и явные, с которыми приложение интегрируется через общий диск или сеть. Адаптер и амбассадор один из ведущих объясняет по картинке из white paper 2016 года про контейнерные нагрузки: адаптер делает контейнеры однообразными для внешнего мира, как переходник для розетки, — классика тут отдать метрики в нужном формате за legacy-систему, — а амбассадор приводит внешний мир к удобному для контейнера виду. У Миши sidecar остался ровно один: прокси к PostgreSQL для стороннего софта, который не умеет service discovery, остальным его использовать запрещено, а устаревшие библиотеки там ловят фитнес-функциями из прошлой книги клуба — сервис с просроченной версией просто не задеплоится.
Конфигурация: переменные, ConfigMap, шаблоны
Конфигурационная часть начинается с вопроса, где хранить конфиг: держать его в коде — значит на каждое изменение править, пересобирать и катить заново, поэтому первый вариант авторов — переменные окружения, они же базовый сценарий двенадцати факторов. Их неизменяемость одновременно плюс и минус: поменять параметр на лету нельзя, но точно известно, с какими значениями приложение запущено, и обрабатывать изменения не нужно. Минусы — вложенные структуры в переменные ложатся плохо, а слои переопределения (дефолт в коде, ENV в Dockerfile, значение в манифесте) без конвенций не распутать. Один из ведущих признаётся, что читает такие книги ещё и чтобы вычёркивать: совет положить уровень логирования в переменную его раздражает, ведь поменять её без рестарта нельзя, а рестарт уносит сбойное состояние. Он же расширяет разговор до фича-флагов: в компании с потоком экспериментов перпендикулярный способ менять состояние приложения делает результат эксперимента ничего не гарантирующим.
Максим выбирает между ручкой в приложении, которая расходится с задеплоенным конфигом, и структурами Kubernetes с механизмом обновления; у Миши конфиги лежат в etcd с подпиской на изменения, в переменные попадают четыре-пять внутренних адресов, которые не поменяются никогда, а подробность логов поднимает специальный заголовок на один запрос, снаружи недоступный. ConfigMap и секреты меняются на лету, но ограничены по размеру из-за etcd, а секрет — это base64, который легко развернуть обратно; гости куберовскими секретами почти не пользуются. Immutable configuration авторы неожиданно уводят в отдельный контейнер с данными, собранный from scratch, и init-контейнер, копирующий модель в общий том: решение тяжёлое, доставка едет через registry, на каждое окружение нужен свой образ. Шаблонизацию конфигов один из ведущих называет паттерном, который не надо использовать: ошибку видно только при старте пода, а для отладки нужен доступ к ноде. Следующий выпуск, последний по книге, — про безопасность, контроллеры и операторы.
Что стоит унести с собой
- 01Эффективный запрос пода считается как максимум из суммы по контейнерам приложения и максимума по init-контейнерам: тяжёлый init резервирует ресурсы, которые потом простаивают.
- 02Init-контейнер стартует при каждом рестарте пода, поэтому разовую миграцию базы выносят в Job, а в init оставляют прогрев, проверки и сборку конфигурации из Vault.
- 03Sidecar не отменяет проблему обновления: образы приходится катить по сотням сервисов, а плотные ноды страдают от числа контейнеров — на гомогенном стеке библиотеками закрывать, возможно, проще, но трейдоффы есть и там и там.
- 04Шаблонизация конфига init-контейнером проверяется только при старте пода: подстановка легко даёт пустую строку вместо значения, а на отладку нужен доступ к ноде в продакшене.