[Перевод] Выводы Grofers после двух лет Kubernetes в production

Прим. перев.: эта статья — опыт миграции на Kubernetes одного из крупнейших в Индии онлайн-магазинов продуктов. В ней Vaidik Kapoor, software engineer из Grofers, рассказывает о главных ошибках и препятствиях этого долгого путешествия, а также делится своими мыслями о целесообразности и плюсах подобного переезда в целом.

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

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

Четко представляйте себе причины перехода на Kubernetes


Все эти serverless-штуки и контейнеры — это, конечно, хорошо. Если вы запускаете новый бизнес и все начинаете с нуля, рекомендую идти именно по такому пути. Развертывайте приложения в контейнерах и дирижируйте ими с помощью Kubernetes, если располагаете достаточной пропускной способностью (или даже не располагаете — читайте об этом дальше) и имеете технические навыки, позволяющие конфигурировать и эксплуатировать Kubernetes, а также деплоить приложения.

Впрочем, даже если вы отдадите эксплуатацию на откуп одному из managed-решений вроде EKS, GKE или AKS, процесс деплоя и эксплуатации самих приложений все равно потребует специального обучения. И ваши разработчики должны быть к этому готовы. Многие преимущества станут доступны, только если команда следует философии DevOps. Если в вашей компании созданием манифестов для приложений, разработанных другими командами, занимается выделенная группа сисадминов, то преимущества Kubernetes, с точки зрения DevOps’а, будут менее очевидны. Хотя, конечно, у K8s есть множество других плюс — например, сокращение издержек, простота в проведении экспериментов, быстрое автомасштабирование, устойчивость и т.д.

Если вы уже деплоите в облачные виртуальные машины или, возможно, в другую PaaS, зачем вам мигрировать в Kubernetes с существующей архитектуры? Уверены ли вы в том, что Kubernetes — единственный способ решить все проблемы? Вы должны ясно представлять себе все мотивы и предпосылки, поскольку перенос существующей архитектуры в Kubernetes — масштабная задача.

Мы и сами совершили несколько ошибок в этом смысле. В нашем случае главной причиной перехода на Kubernetes было желание построить инфраструктуру для непрерывной интеграции. Она была призвана помочь быстро перестроить микросервисы, накопившие массу архитектурных долгов за все эти годы. Практически весь новый функционал требовал внесения изменений во множество кодовых баз, и разработка/их совместное тестирование сильно бы замедлили работу. Мы чувствовали необходимость предоставить для каждого разработчика и каждого изменения интегрированную среду, которая ускорила бы циклы разработки и тестирования без необходимости координировать, кто и когда получит доступ к «общему stage-окружению».

0qxmawwgpsietpcust0q4vqyfl0.jpeg
Один из наших CI-пайплайнов. Он запускает новую интегрированную среду со всеми микросервисами и проводит автоматизированные тесты.

Мы добились больших успехов в этом направлении. Сегодня интегрированная среда с 21 микросервисом запускается в Kubernetes всего за 8 минут. Любой разработчик может воспользоваться нашим доморощенным инструментом для ее создания. Мы также выделяем подмножество этого окружения для каждого pull request’а, созданного для любого из двух десятков микросервисов. Полный цикл тестирования (подготовка среды и запуск тестов) занимает менее 12 минут. На первый взгляд, это ужасно долго. Но такой подход позволяет нам не отправлять плохие решения в тот архитектурный хаос, в котором мы сейчас пребываем.

1tc6yg9pdzql6grdfagdmbr0h-4.jpeg
Отчет о выполнении конвейера непрерывной интеграции

Класс! Сколько времени все это заняло? На это ушло почти полтора года. Стоило ли оно того?


У нас ушло почти полтора года, чтобы стабилизировать эту непростую CI-конфигурацию. Для этого мы разработали новые инструменты, телеметрию и переосмыслили способы деплоя каждого приложения. Ради соблюдения паритета между dev и prod нам также приходилось деплоить все эти микросервисы и в production, иначе разница между конфигурациями в бою и для деплоя изрядно усложнила бы для разработчиков понимание ситуации и превратила бы эксплуатацию в сущий кошмар.

У нас смешанные чувства по этому поводу. Если оглянуться назад, то кажется, что мы только усугубили проблему поиска лучшего решения для CI, поскольку сложность выката всех микросервисов в production ради паритета dev/prod сделала нашу задачу по ускорению сборки в рамках CI гораздо более многогранной и сложной. До Kubernetes мы использовали связку Ansible + Consul от Hashicorp + Vault для подготовки инфраструктуры, управления конфигурациями и развертывания. Занимало ли это много времени? Да, безусловно. Но service discovery можно было бы реализовать и с помощью Consul и оптимизированных конфигураций Ansible, тем самым подобравшись к цели гораздо быстрее.

Стоило ли нам мигрировать на Kubernetes? Да, безусловно. Использование Kubernetes обеспечивает массу преимуществ: обнаружение сервисов, лучшее управление издержками, надежность, управляемость, абстрагирование от облачной инфраструктуры и др. И сегодня мы в полной мере пользуемся этими преимуществами. Но это не было нашей главной целью, когда мы только начинали. Боль и давление, не дававшие нам покоя в процессе реализации задуманного, пожалуй, можно было избежать.

Значимым озарением для нас стало то, что существовал другой, менее напряженный путь к Kubernetes. Просто с самого начала мы решили, что Kubernetes — это единственное возможное решение, и даже не попытались оценить другие варианты.

Далее мы увидим, что миграция и эксплуатация в Kubernetes сильно отличаются от деплоя в облачные VM или в bare-metal-серверы. Командам облачного инжиниринга/разработки приходится переучиваться. Вероятно, это идет им только на пользу. Но вопрос в том, стоит ли это делать прямо сейчас. Вы должны для себя однозначно ответить на этот вопрос.

Kubernetes’а «из коробки» почти всегда недостаточно


Многие путают Kubernetes с PaaS-решением. Это не PaaS-решение! Это платформа для создания PaaS-решений. OpenShift — один из таких примеров.

Коробочная версия Kubernetes мало кого устроит. Это отличная песочница, чтобы учиться и исследовать. Но вам наверняка понадобятся дополнительные инфраструктурные компоненты и придется связать их воедино в решение для приложений, чтобы разработчики смогли им пользоваться. Часто подобную сборку Kubernetes c дополнительными инфраструктурными компонентами и политиками называют Internal Kubernetes Platform (внутренней Kubernetes-платформой). Это исключительно полезная парадигма. Дополнить Kubernetes можно несколькими путями.

Придется подумать и принять решение о таких вещах, как метрики, логи, обнаружение сервисов, распределенная трассировка, управление конфигурациями и секретами, CI/CD, локальная разработка, автомасштабирование, привязанное к кастомным метрикам. И этот перечень далеко не полон. Наверняка придется принять множество других решений и настроить массу инфраструктурных компонентов. Также придется подумать о такой важной области, как работа с ресурсами и манифестами Kubernetes (подробнее об этом ниже).

Вот некоторые из наших решений и мотивы, лежащие в их основе.

Метрики


Мы остановились на Prometheus. Сегодня он является фактическим стандартом для сбора метрик. Его очень любят CNCF и разработчики Kubernetes. И он отлично вписался в экосистему Grafana. А мы обожаем Grafana! Единственная проблема — мы использовали InfluxDB. Но мы решили отказаться от InfluxDB и сфокусироваться на Prometheus.

Логи


Логи всегда были нашей большой проблемой. У нас так и не получилось создать стабильную платформу для логов на стеке ELK. Да, в ELK масса всевозможных функций, но наша команда ими не пользуется. А все эти функции имеют свою цену. Также мы считаем, что использованию Elasticsearch для логирования изначально присущи трудности, которые делают его дорогостоящим решением. В итоге мы остановились на Loki от Grafana. Эта система проста и понятна, обладает всеми возможностями, нужными нашей команде. Также Loki чрезвычайно выгодна с экономической точки зрения. Но самое главное — это шикарный UX, ставший возможным благодаря языку запросов, сильно похожему на PromQL. И да, Loki отлично работает вместе с Grafana. Таким образом, мы соединили весь мониторинг метрик и логирование в одном пользовательском интерфейсе.

s-iexxxdqg4scswpmx-ttagmvxq.png
Пример панели мониторинга Grafana с визуализацией метрик и соответствующими логами

Управление конфигурациями и секретами


В большинстве статей про Kubernetes используются ConfigMap«ы и секреты. Этого хватает для начала, но, по опыту, недостаточно для наших сценариев использования. ConfigMap для существующих сервисов приводит к определенным издержкам. Работу с ConfigMap’ом в pod’ах можно организовать определенным образом — обычно это делают с помощью переменных окружения. Если у вас куча старых микросервисов, которые считывают конфигурацию из файлов, сгенерированных инструментом для управления конфигурациями вроде Puppet, Chef или Ansible, придется переделать работу с конфигурацией во всех кодовых базах, чтобы микросервисы могли использовать переменные окружения для получения конфигурационных данных. Мы не нашли достаточно причин, чтобы привнести такие изменения там, где это не имело смысла. Кроме того, изменение конфигурации или секрета означает, что придется провести редеплой deployment’а с соответствующим исправлением. Это будет дополнительная императивная оркестровка команд kubectl…

bmo-gmqjwjbi2tussm8ebrioy8s.png
Схема от Asif Jamal

Чтобы избежать всего этого, мы решили использовать Consul, Vault и Consul Template для управления конфигурациями. Consul Template запускается как init-контейнер, а в будущем планируется запускать его как sidecar к pod’ам, чтобы он следил за изменениями конфигурации в Consul и обновлял секреты с истекающим сроком действия в Vault и мягко (gracefully) перезапускал процессы приложений.

CI/CD


До миграции в Kubernetes мы использовали Jenkins. После миграции мы решили остаться на нем. Пока наш опыт показывает, что Jenkins — не самое лучшее решение для работы с cloud-native-инфраструктурой. Мы оказались в ситуации, когда приходилось организовывать массу доделок на базе Python, Bash, Docker и скриптовые/декларативные пайплайны Jenkins, чтобы все завелось. Со временем разработка этих инструментов/пайплайнов и их обслуживание начали негативно сказываться на расходах. В настоящее время мы изучаем возможность использования Tekton и Argo Workflows в качестве новой CI/CD-платформы. При этом в области CI/CD существует и масса других инструментов, достойных изучения — Jenkins X, Screwdriver, Keptn и т.д.

Удобство для разработчиков


Встроить Kubernetes в workflow разработки можно разными способами. Мы ограничились двумя вариантами: Telepresence.io и Skaffold. Skaffold может отслеживать локальные изменения и развертывать их в кластер Kubernetes (см. также наш обзор этой утилиты — прим. перев.). С другой стороны, Telepresence позволяет запускать сервис локально, попутно организуя прозрачный сетевой прокси в кластер K8s (обзор Telepresence — прим. перев.). Таким образом, локальный сервис имеет возможность взаимодействовать с сервисами в Kubernetes так, словно он развернут в кластере. Конечно, здесь стоит вопрос убеждений и личных предпочтений.

Было сложно остановиться на одном из инструментов. Пока мы преимущественно экспериментируем с Telepresence, но и не отказываемся от мысли, что Skaffold может оказаться более подходящим инструментом в нашем случае. Только время покажет, на чем мы остановимся в конечном итоге (или будем продолжать использовать оба инструмента). Есть и другие решения. В частности, упоминания заслуживает проект Draft.

(Прим. перев.: проект Draft уже долго время не развивается, его репозиторий помещен в архив. А вот в новой версии werf — v1.2 — как раз появилась возможность локальной разработки, что позволяет легко интегрировать этот процесс с полноценным деплоем в Kubernetes.)

Распределенная трассировка


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

Упаковка, развертывание приложений и инструментарий


При работе с Kubernetes важно понимать, как разработчики будут взаимодействовать с кластером и развертывать свои рабочие нагрузки. Нужно, чтобы процесс был простым и легко масштабировался, поэтому мы склоняемся в сторону Kustomize и Skaffold, а также к нашим собственным CRD как к способу деплоя и управления приложениями для разработчиков. При этом любая команда может использовать инструменты по своему желанию для взаимодействия с кластером (конечно, если они имеют открытый исходный код и построены на открытых стандартах).

Эксплуатировать кластер Kubernetes сложно


Мы преимущественно работаем в сингапурском регионе AWS. Когда только начинали переход на Kubernetes, EKS не был доступен в качестве сервиса в сингапурском регионе. Поэтому нам пришлось самостоятельно устанавливать кластер K8s с помощью kops.

Установка базового кластера, пожалуй, не так сложна. Мы смогли запустить свой первый работающий кластер в течение недели. Но основные проблемы возникают, когда вы приступаете к деплою рабочих нагрузок. Начиная от настройки кластерного автоскейлера до выделения ресурсов в подходящее время и правильной настройки сети — все приходится исследовать и конфигурировать вручную. Настройки по умолчанию не работают в большинстве случаев (ну, или они не работали для нас тогда) для production.

Опытным путем мы установили, что эксплуатировать Kubernetes непросто. В нем много движущихся частей. А изучение всех этих нюансов вряд ли является основной целью вашей компании. Поэтому максимально перекладывайте подобные задачи на плечи поставщиков облачных услуг (EKS, GKE, AKS). Нет никакого смысла заниматься этим самостоятельно.

Все равно приходится заботиться об обновлениях


Kubernetes настолько сложен, что даже если вы пользуетесь услугой managed Kubernetes, над обновлениями все равно придется попотеть.

Даже при использовании managed Kubernetes не забывайте про инфраструктуру как код. Этот подход позволит сделать процесс аварийного восстановления и обновления менее болезненным в будущем и ускорит восстановление в случае сбоев.

Также вы можете попробовать GitOps, если есть желание. Если это по каким-то причинам не реально, постарайтесь свести к минимуму этапы, на которых приходится все делать вручную. Мы используем комбинацию eksctl, terraform и собственных манифестов для конфигурации кластера (включая манифесты для платформенных сервисов) для организации того, что гордо величаем «Grofers Kubernetes Platform». Чтобы сделать процесс установки и деплоя более простым и повторяемым, мы разработали автоматизированный пайплайн для создания новых кластеров и деплоя изменений в существующие.

Ресурсы: request’ы и limit’ы


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

В частности, из-за недостатка памяти на узлах случился eviction («вытеснение») pod’ов. Причиной тому были непропорционально высокие limit’ы по сравнению с их request’ами на ресурсы. С ростом трафика повышение потребления памяти могло приводить с насыщению памяти на узлах, что в дальнейшем приводило к вытеснению pod’ов.

Поэтому мы стали указывать достаточно высокие запросы на ресурсы, но не слишком высокие, чтобы в периоды низкой нагрузки не тратить ресурсы впустую. Также мы сделали лимиты близкими к запросам на ресурсы, чтобы обеспечить некоторый запас на случай кратковременных всплесков трафика и не рисковать выселением pod’ов из-за недостатка памяти на узлах. Степень близости лимитов к запросам зависит от того, как именно у вас распределен трафик во времени.

Это не относится к не-production средам, таким как development, staging и CI. Такие окружения не испытывают всплесков трафика. Теоретически можно запустить бесконечное количество контейнеров, если установить CPU requests на 0 и установить достаточно высокий CPU limit для контейнеров. Если контейнеры начнут активно использовать CPU, они просто столкнутся с троттлингом (мы переводили другой материал на эту тему — прим. перев.).

То же самое можно сделать с request’ами и limit’ами на память. Однако поведение при достижении ограничений по памяти отличается от поведения при нехватке CPU. При попытке использовать больше памяти, чем установленный limit, контейнер будет убит из-за OOM и перезапущен. Если лимит памяти ненормально высок (скажем, больше, чем имеется на узле), можно продолжать использовать память, но рано или поздно планировщик начнет вытеснять pod’ы, когда память на узле начнет подходить к концу.

В non-production мы спокойно выделяем как можно больше ресурсов, указывая минимальные request’ы и максимальные limit’ы. В этом случае ограничивающим фактором выступает память: независимо от того, насколько низкий request и высокий limit, вытеснение pod’ов зависит от суммарной памяти, используемой всеми контейнерами, запланированными на узле.

Безопасность и управление


Kubernetes призван «открыть» облачную платформу для разработчиков и сделать их более независимыми, попутно продвигая культуру DevOps. Такая доступность платформы для разработчиков, сокращение вмешательства со стороны команд облачных инженеров (или сисадминов) и повышение независимости dev-команд должны стать одной из ключевых целей [внедрения Kubernetes].

Иногда эта независимость может представлять серьезную опасность. Например, при использовании сервиса типа LoadBalancer в EKS по умолчанию создается ELB, обращенный в публичную сеть. Добавление определенной аннотации позволит использовать внутренний ELB. В самом начале мы допустили массу подобных ошибок.

Для снижения всех видов рисков безопасности мы используем Open Policy Agent. Также он позволяет снизить стоимость и сократить риски, связанные с техническим долгом.

Развертывание Open Policy Agent позволило организовать правильные элементы управления и помогло автоматизировать весь процесс управления изменениями. Кроме того, он позволил разработать соответствующие механизмы безопасности для разработчиков. Благодаря Open Policy Agent теперь можно запрещать сценарии, подобные упомянутому выше: например, организовать запрет на создание сервисных объектов без соответствующей аннотации (то есть разработчики не смогут случайно запустить публичный ELB).

Стоимость


Миграция позволила нам значительно снизить издержки. Однако финансовая выгода пришла не сразу.

Более эффективное использование имеющихся ресурсов


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

При этом изначально во время миграции мы тратили впустую огромные ресурсы. Из-за неспособности настроить self-managed-кластер Kubernetes и кучи проблем с производительностью, вызванной этим, приходилось запрашивать массу ресурсов в pod’ах. Их избыток служил буфером и чем-то вроде страховки от сбоев и проблем с производительностью из-за недостатка вычислительной мощности или памяти.

Высокие инфраструктурные издержки из-за избытка ресурсов стали серьезной проблемой. Мы так и не смогли воспользоваться преимуществами при использовании мощностей, предлагаемыми Kubernetes (хотя должны были). Только перейдя на EKS и убедившись в стабильности, которую он обеспечил, мы поверили в себя и предприняли необходимые шаги для исправления request’ов на ресурсы, кардинально сократив объем невостребованных ресурсов.

Spot


Использовать spot-инстансы с Kubernetes гораздо проще, чем с обычными виртуальными машинами. В случае VM можно самостоятельно управлять spot-инстансами, при этом определенные трудности связаны с обеспечением бесперебойной работы приложений (также можно использовать сервис вроде SpotInst). То же самое справедливо для Kubernetes, но свойственная ему эффективность использования ресурсов позволяет создать некий буфер. В этом случае, если несколько инстансов в кластере прекратят работать, контейнеры с них будут быстро перепланированы в другое место. Существует несколько способов эффективной обработки отключений инстансов.

Spot-инстансы помогли нам существенно сэкономить. Сегодня весь stage-кластер работает на spot-инстансах и 99% production-кластера покрыты резервными экземплярами, сберегательным планом и spot’ами.

Следующий этап оптимизации — перевести весь production-кластер на spot-инстансы. Подробнее об этом поговорим в другой публикации.

Консолидация ELB


Ingress использовался для консолидации ELB в stage-окружении и значительного снижения расходов на ELB. Чтобы избежать несоответствия между кодом dev и prod, мы решили создать контроллер, который будет преобразовывать сервисы типа LoadBalancer в NodePort вместе с ingress-объектом в stage-кластере.

Благодаря идее с контроллером миграция на Nginx ingress оказалась относительно простой и не потребовала внесения серьезных изменений. Дополнительные средства можно сэкономить, если использовать ingress и в production. Хотя этот переход нельзя назвать простым. Для правильной настройки ingress для production следует учитывать множество моментов, в том числе в области безопасности и работы с API. Этим мы планируем заняться в ближайшем будущем.

Возросшие потоки данных между зонами доступности


Мы сэкономили много денег на инфраструктуре, но в одной области расходы выросли — это потоки данных между зонами доступности (AZ).

Pod’ы могут быть запланированы на любом узле. Даже если вы контролируете размещение pod’ов в своем кластере, нет простого способа контролировать межсервисное взаимодействие, чтобы pod одного сервиса связывался с pod’ом другого сервиса в той же AZ (тем самым сокращая потоки данных между AZ).

После долгих исследований и консультаций с коллегами из других компаний пришли к выводу, что помочь проконтролировать движение трафика между pod’ами может service mesh. Однако мы не были готовы повесить на себя сложность эксплуатации service mesh исключительно ради сокращения издержек на передачу данных между AZ.

CRD, операторы и контроллеры — шаг к упрощению эксплуатации и более интегрированному опыту


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

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

Поэтому мы начали работать над различными контроллерами и CRD. Например, как уже говорилось, конверсия LoadBalancer’а в ingress выполняется контроллером. Также контроллеры автоматически создают записи CNAME у DNS-провайдера всякий раз, когда деплоится новый сервис. Это лишь несколько примеров. У нас есть еще пять других случаев использования, в которых внутренние контроллеры упрощают повседневные операции и сокращают трудозатрат.

Мы также написали несколько CRD. Один из них активно используется для генерации панелей мониторинга в Grafana, декларативно указывая, какие компоненты должны включаться в каждую из панелей. В результате разработчики получили возможность параллельно работать с кодовой базой приложения и панелями мониторинга, а деплоить все с помощью одного и того же рабочего процесса на основе kubectl apply -f.

Мы на своем опыте убедились в несомненных преимуществах контроллеров и CRD. Тесное сотрудничество с поставщиком облачных услуг (AWS) в деле упрощения эксплуатации кластерной инфраструктуры позволяет сосредоточиться над созданием собственной «Kubernetes-платформы», нацеленной на максимальную поддержку своих разработчиков.

P.S. от переводчика


Читайте также в нашем блоге:

© Habrahabr.ru