image


В Asana мы используем Kubernetes для развертывания сервисов и управления ими независимо от монолитной инфраструктуры. Поначалу у нас были некоторые проблемы, и чтобы стандартизировать создание и обслуживание приложений Kubernetes, мы создали фреймворк с незамысловатым названием KubeApps. В последние два года наша команда по инфраструктурной платформе вносила улучшения в KubeApps, чтобы упростить развертывание сервисов в Asana. Здесь мы расскажем, какие проблемы мы хотели решить в Kubernetes и как мы это сделали с помощью фреймворка KubeApp.


Введение


Мы уже рассказывали о legacy-системе деплоймента в Asana — наша основная инфраструктура организована как монолит инстансов AWS EC2 с кастомными скриптами конфигурации для каждого сервиса. Никакого масштабирования у нас не было: отправляешь новый код в монолит — переконфигурируй все хосты. Чтобы добавить новый сервис, нужен был новый набор кастомных скриптов конфигурации. Все это было сложно обслуживать, плюс был риск увеличить нестабильность деплоев в монолите.


Когда мы обдумывали инфраструктуру для Luna2, решили использовать Kubernetes, чтобы деплоить сервисы и управлять ими независимо от монолита. Оркестрация контейнеров пошла хорошо, и остальные инженеры захотели использовать Kubernetes для других систем Asana. Мы доработали эти инструменты, чтобы и другие команды могли собирать и деплоить сервисы в контейнерах.


Kubernetes в Asana


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


Расширять инфраструктуру за пределы монолита EC2 стало, конечно, проще, но Kubernetes пока не решал всех наших проблем (примечание: В Kubernetes есть Cloud Controller Manager, который управляет нодами, настраивает маршруты между контейнерами и интегрируется с компонентами облачной инфраструктуры).


  • Управление ресурсами AWS никак не связано с Kubernetes. Разработчикам надо было заранее создавать ASG и готовить их для контейнеров Docker и любых других ресурсов, которые могут понадобиться приложению.
  • Мы тратили слишком много времени на инструментирование во всех сервисах, например, для сборки метрик и настройки разрешений для секретов, причем мы постоянно делали одно и то же — настраивали ELB, управляли группами конфигурации для каждого приложения...
  • Continuous delivery в Kubernetes не встроена. На уровне абстракции Kubernetes логика приложения должна быть упакована в образы, доступные в container registry.

Чтобы решить эти проблемы и стандартизировать создание и обслуживание приложений Kubernetes, мы создали фреймворк с незамысловатым названием KubeApps. Изначально мы задумали KubeApp для обработки всех аспектов развертывания — от сборки необходимых ресурсов до обновления DNS во время деплоя и плавного перехода между версиями кода. Каждый KubeApp определяется набором требований к «железу», определений подов и спецификаций сервиса.


Конфигурация как код


Все конфигурации KubeApp существуют в виде Python-кода, поэтому поддерживают программно созданные конфигурации и обеспечивают согласованность с помощью общих библиотек. С этими конфигурациями мы можем объявлять и настраивать внешние ресурсы, необходимые для KubeApp.


На схеме показаны спецификации примера веб-приложения и способ его представления в системе конфигурации KubeApp. Деплоймент запрашивает оборудование, ELB и сборки образов. Мы предоставляем дефолтные спецификации для обработки конфигураций DNS и включаем определения подов для обработки метрик и логирования.



Образы Docker с continuous deployment


Код приложения пакуется в Docker-образы с помощью образов Bazel или традиционных Dockerfile. Наш KubeApps собирает и отправляет эти образы в рамках пайплайна CD, так что образы для любого сервиса доступны в нашем registry.


Для сборки зависимостей и управления ими в большинстве случаев мы используем Bazel. Это особенно удобно с контейнеризованными приложениями, потому что таким образом мы объявляем зависимости и упаковываем сторонние библиотеки в исполняемые файлы. Так мы получаем детерминированные выходные данные при настройке контейнерных окружений для каждого приложения.


По одному кластеру на KubeApp


При использовании Kubernetes нужно решить, как делить сервисы между кластерами (примечание: некоторые соображения о компромиссах при выборе числа кластеров приводятся в этой статье на learnk8s.io: Проектирование кластеров Kubernetes: сколько их должно быть?). Деплоить сервисы в один кластер Kubernetes — это экономично и удобно в плане администрирования, потому что сервисы используют одни ресурсы мастер-ноды, а такие операции, как апгрейд версий Kubernetes и деплойменты, нужно выполнять всего один раз. Но модель с одним кластером не особо хороша для мультитенантности, а кластер становится единой точкой отказа. Один кластер Kubernetes ограничен в плане масштабирования, так что при достижении определенного размера неплохо было бы его разделить.


В Asana каждый KubeApp деплоится в своем кластере Kubernetes через AWS EKS. Такой подход гарантирует безопасность и устойчивость приложения. Каждый кластер отвечает за один сервис, так что можно не волноваться о состязании за ресурсы между сервисами, а если кластер упадет, пострадает всего один сервис.


Управлять несколькими кластерами сложновато, потому что дефолтные инструменты Kubernetes могут взаимодействовать только с одним кластером за раз. Поэтому мы создали инструменты для управления несколькими кластерами одновременно в KubeApps. Еще мы обнаружили, что такая модель позволяет владельцам отдельных KubeApp независимо управлять кластером (апгрейдить ноды, масштабировать деплои и т. д.)


Процесс развертывания KubeApp


image


Для развертываний KubeApp мы используем центральный хаб управления, который мы назвали kubecontrol. Его можно настроить для обновления вручную или автоматически, через cron. Развертывание KubeApp включает несколько шагов:


  1. Для обновления или создания KubeApp мы вводим команды в kubecontrol.
  2. Из спецификаций приложения мы запрашиваем набор ресурсов, необходимых для KubeApp (Auto Scaling Groups, спотовые инстансы и т. д.). Создается новый кластер EKS.
  3. Мы делаем запрос к сервису сборки образов, чтобы собрать образ docker с определенной версией кода. Сборщик образов компилирует код для KubeApp и отправляет образ в ECR (Elastic Container Registry), если его там еще нет.
  4. После сборки всех ресурсов мы передаем спецификации компонентов кластеру Kubernetes, чтобы он получил нужные контейнеры docker от ECR и задеплоил их на нодах.

Полное обновление KubeApps — это blue-green деплой, для которого нужно запустить и настроить новый кластер EKS в AWS. Убедившись, что новый KubeApp работает, мы переключаем нагрузку на новый кластер и сносим старый. В KubeApps можно делать rolling апдейт, при котором обновляются образы на запущенном кластере. Это позволяет быстро и незаметно переходить с одной версии кода на другую без запуска целого нового кластера.


Консоль управления KubeApp


До недавнего времени единственным способом прямого мониторинга или управления KubeApp было ssh-подключение к kubecontrol и взаимодействие с приложением через CLI. Искать информацию о деплоях было непросто, так что пользователям приходилось изучать логи, чтобы понять, когда в KubeApp была развернута определенная версия кода. Чтобы внести больше ясности, мы создали KubeApp Management Console (KMC), которая отвечает за запись информации о прошлых деплоях. Мы хотим использовать эту консоль как централизованный веб-интерфейс, в котором пользователи могут взаимодействовать с KubeApps.


Что собой представляет KubeApps сегодня и что будет дальше


Сейчас у нас больше 60 KubeApp в Asana, которые поддерживают самые разные рабочие нагрузки — от управления релизами до распределенного кэширования. Пока мы сохраняем монолит инстансов EC2, но уже переделываем эти сервисы в контейнеры в KubeApps.


Команда инфраструктурной платформы продолжает активную работу над KubeApps. В будущем мы планируем поддерживать больше архитектур (ARM64) и провайдеров инфраструктуры (AWS Fargate). Еще мы планируем создать инструменты для более удобной разработки в KubeApps, чтобы их можно было запускать в песочницах и локальных окружениях. Благодаря этим и другим изменениям фреймворк KubeApps можно будет приспособить для любой рабочей нагрузки, которая может нам понадобиться.