Алексей Меленчук
вся записка
дата
объём 5 мин чтения

Kubernetes раньше, чем он нужен

Оркестратор решает задачу, которой у вас может не быть, и создаёт задачу, которая появится обязательно. Разбор того, что именно он покупает и чем за это платят.

Коротко. Kubernetes решает задачу планирования множества сервисов на множестве машин. Если у вас четыре сервиса и две машины, вы купили решение и получили новую систему, которую надо эксплуатировать.

Позиция, с которой начну: Kubernetes — хорошая технология, и я не буду советовать «просто возьмите виртуалку». Он делает ровно то, что обещает.

Вопрос не в качестве инструмента, а в том, совпадает ли ваша задача с той, для которой он сделан.

Какую задачу он решает

Kubernetes — планировщик. Его работа: разместить N экземпляров сервисов на M машинах, следить, чтобы заявленное состояние совпадало с реальным, и перепланировать при изменениях.

Задача становится настоящей, когда сервисов и машин достаточно много, чтобы человек перестал справляться руками. Тогда декларативное описание и контроллер, приводящий систему к нему, — единственный разумный выход.

Пока сервисов четыре, а машин две, планирования как задачи нет. Размещение очевидно и не меняется месяцами.

Что покупается вместе с ним

Не «сложность» вообще, а конкретный набор новых сущностей, каждую из которых надо понимать в момент аварии.

Сетевая модель: сервисы, ingress, политики. Отладка «почему один под не видит другой» требует знать, как это устроено, и знание это не общее с обычной сетью.

Ресурсные лимиты. Запросы и лимиты по процессору и памяти — не формальность: занизили лимит памяти, и процесс убивается по OOM в момент пиковой нагрузки, а в журнале приложения нет ничего, потому что его убили снаружи. Завысили — планировщик не может разместить поды, и они висят в Pending.

Проверки готовности и живости. Перепутать их — классика: проверка живости, которая ходит в базу, при недоступности базы устраивает перезапуск всех экземпляров сразу. База не поднимается быстрее, а приложение теперь ещё и не прогрето.

Хранилище. Тома, классы хранения, привязка к зоне. Всё это работает, пока не понадобится переместить под с диском в другую зону.

Обновления самого кластера. Он выходит с новой минорной версией несколько раз в год, старые перестают поддерживаться, API-группы устаревают, манифесты приходится переписывать.

Ни один из пунктов не является аргументом против. Каждый из них — работа, которую надо кому-то делать постоянно.

Что обычно называют причиной

«Нам нужно автомасштабирование». Уточняющий вопрос: какая метрика и в каких пределах? Если нагрузка колеблется вдвое между днём и ночью, это не задача для автомасштабирования — это задача «поставить машину с запасом», и она решается дешевле. Автомасштабирование окупается при кратных всплесках, которые нельзя предсказать.

«Чтобы не было простоя при выкате». Скользящий выкат делается и без оркестратора: два экземпляра за балансировщиком, обновляем по очереди. Это несколько строк в скрипте выката, а не платформа.

«Так делают все». Часто это правда, и она даже не худший аргумент: знакомая технология проще нанимать. Но у аргумента есть цена, и её стоит назвать вслух, а не прятать за «индустриальным стандартом».

«Мы вырастем». Единственная из четырёх причин, которая может быть верной. Проверяется вопросом: вырастете к какому сроку и до чего конкретно? Если ответ «когда-нибудь», это не план, а надежда.

Что ломается у тех, кто взял рано

Не гипотезы — типовые последствия, которые видно по симптомам.

Проверка живости ходит в базу. База моргнула на десять секунд — оркестратор перезапустил все экземпляры разом. Приложение теперь холодное, пулы пустые, кэш пустой, и оно ложится под нагрузкой, которую до этого держало. Проверка живости обязана отвечать на вопрос «этот процесс надо перезапустить», а недоступность внешней зависимости перезапуском не лечится.

Лимит памяти меньше, чем нужно рантайму. JVM без явной настройки под контейнер видит память хоста, а не лимит, и планирует кучу по ней. Убивается по OOM при обращении к памяти, которую считала своей. В журнале приложения при этом пусто: его убили снаружи, и последнее сообщение — обычное.

Нет PodDisruptionBudget. Обновление узлов выселяет поды, и при неудачном стечении обстоятельств все экземпляры сервиса переезжают одновременно. Формально всё по правилам, фактически — простой.

Каждый пункт решается одной строкой в манифесте. Проблема в том, что узнают о них по инциденту: это не то знание, которое приходит вместе с решением взять оркестратор.

Чем я пользуюсь до

Docker Compose на одной машине с обратным прокси, который сам получает сертификаты. Скользящий выкат — скриптом. Мониторинг — обычным агентом. Резервные копии — по расписанию, с проверкой восстановления.

Это скучно, помещается в голову одного человека и отлаживается тем же инструментом, которым отлаживается всё остальное на Linux.

Граница, после которой я меняю мнение, звучит так: когда размещение сервисов на машинах перестаёт быть очевидным. Не по числу сервисов и не по нагрузке — по тому моменту, когда на вопрос «где это работает и почему именно там» нельзя ответить не подумав.

Второй признак — несколько команд, которые выкатываются независимо и не хотят согласовывать друг с другом машины. Это, кстати, ровно та же организационная причина, по которой берут микросервисы, и появляется она обычно одновременно.

Где я могу быть неправ. Если у вас уже есть кластер и человек, который его знает, то маржинальная стоимость ещё одного сервиса в нём близка к нулю, и весь разговор теряет смысл. Дорого — начинать, а не продолжать.