Привет! Я Александр, DevOps-инженер Иностудио, и сегодня мы поговорим о Kubernetes. Но сначала я бы хотел описать проблему.
Среди наших проектов есть «Тайный Санта» — кроссплатформенное мобильное приложение для анонимного обмена подарками. Другими словами, легаси-проект с легаси-кодом, который ранее запускался на выделенном сервере. Однако с ростом нагрузки начали появляться и проблемы, мы стали получать больше ошибок отказа обслуживания. Ко всему прочему сама перспектива выхода из строя сервера уже не казалась такой нереальной, это привело бы к катастрофе. Ситуацию требовалось срочно менять.
Выбираем способ реализации. Разделяй и властвуй
Так как мы живём в мире микросервисов, мы приняли решение сделать новую версию продукта уже на микросервисной архитектуре. Нам были необходимы «бесшовные» обновления нашего кода и масштабируемость под нагрузкой. Был нужен какой-то инструмент похожий на кластерную операционную систему (ОС) для управления контейнерами. И, как вы уже поняли, мы его нашли — это Kubernetes.
Kubernetes, как я сказал выше, в моём представлении — это ничто иное, как «кластерная ОС для управления контейнерами». Это уровень абстракции над обычной операционной системой, который позволяет через определённые манифесты запускать контейнеризированные приложения.
-
Дополнительно можно управлять их распределением по серверам, так как Kubernetes подразумевает:
- объединение в кластер нескольких серверов;
- лимитирование потребления ресурсов;
- контроль над обновлениями;
- автомасштабируемость и отказоустойчивость.
Вообще о самом Kubernetes я готов говорить часами, но сегодня предлагаю акцентировать внимание именно на проекте «Тайный Санта».
Взгляд изнутри, готовим Kubernetes
Итак, мы подготовили микросервисы, используя классические frontend и backend. Далее нам нужно было только перенести их в Kubernetes. Всё.
Для данного проекта мы имеем два «контура». Первый — Dev-контур, кластер, который развёрнут у нас в компании. Внутри него есть пространство имён (namespace), в котором мы запускаем наше приложение.
Прошу запомнить это изображение, к нему ещё обязательно вернёмся. По сути своей в production-окружении всё выглядит также, за исключением большего числа подов и дополнительных ресурсов, например HPA (Horizontal Pod Autoscaler), который скалирует наши поды в зависимости от роста нагрузки.
И раз уж я заговорил о production-окружении, нужно отметить, что оно развёрнуто в Yandex Cloud. Также в обоих окружениях база данных (БД) вынесена за пределы кластера — в 2022 году, думаю, уже можно запускать БД в кластере Kubernetes. Но так как поды внутри без проблем могут заново создаваться и удаляться, то, на мой взгляд, велик шанс получить порчу данных. Поэтому базы данных внутри кластера — пока что не наш путь, но мы активно изучаем возможность запуска кластера БД через Crunchy Operator для Postgre или через Percona Cluster для MySQL.
Погружаемся глубже, на помощь приходит шаблонизация
Итак, первое, что нам необходимо было сделать, это написать Helm-чарт для каждого нашего микросервиса. Напомню, Helm — это менеджер пакетов для Kubernetes, который помогает установить приложения Kubernetes и управлять их жизненным циклом. Для понимания — всё аналогично диспетчерам пакетов Linux, например APT и Yum.
В результате мы успешно справились с написанием Helm-чарта и получили следующее:
В корне чарта находится Chart.yaml, в котором описано, что из себя представляет наше приложение. Файл values.yaml хранит переменные, которые будут отрендерены в наш конечный манифест. В папке templates хранятся шаблоны, вот некоторые из них:
Здесь представлен шаблон deployment.yaml, который содержит в себе, по сути, описание приложения. Deployment в Kubernetes — это абстракция, выполняющая 2 важные функции.
- Запуск приложения и поддержание реплик в указанном количестве, что позволяет нам добиться масштабируемости и отказоустойчивости. Это происходит через подчинённую ему абстракцию Replicaset.
- Управление выкладкой новой версии приложения. Для этой функции существуют несколько встроенных стратегий обновления, например Recreate (все поды приложения «убиваются» и запускаются с новой версией) и Rolling update (поды «убиваются» по очереди, и вместо старого запускается другой — с новой версией).
Далее идёт файл ingress.yaml, который описывает абстракцию ingress. Она является некой точкой входа, через которую внешний трафик попадает внутрь кластера.
И service.yaml — он описывает абстракцию Service, которая необходима для определения, в нашем случае, внутрикластерного взаимодействия контейнеров (на какие порты подам отправлять трафик). Дополнительно Ingress направляет трафик на Service, а тот уже непосредственно в поды.
Вот пример Ingress:
В фигурных скобках мы описываем те значения, которые необходимо будет прорендерить при деплое в кластер. Все значения берутся из values.yaml или подключаются из других шаблонов, описанных, например, в _helpers.tpl.
Как нам все это доставить в кластер? На сцену выходит GitOps
Так, теперь когда у нас появились чарты, было необходимо продумать план их доставки в наши окружения. Для этого использовали GitLab CI и GitLab как реестр контейнеров и сборочный процесс образов и ArgoCD как утилиту для выкладки с учётом GitOps-подхода (про GitOps вы можете почитать в этой статье).
Начнём со сборки. Она осуществляется через GitLab runner для Kubernetes. И чтобы максимально обезопасить сборку, наша команда использовала Kaniko — сборщик образов, для которого не нужен Docker.
В GitLab CI это выглядит так:
В результате в Container registry проекта появился образ, который нам и нужен:
Далее написали шаг deploy, чтобы этот образ обновился в нашем чарте:
CI-система просто берёт git-репозиторий и в values-файл нашего чарта прописывает новый тэг, а затем коммитит эти изменения обратно. Таким способом мы решили проблему обновлений, осталось добавить только процесс выкладки, за это отвечает ArgoCD.
ArgoCD имеет свой набор абстракций, который называется Application. Взглянем на него (на самом деле нам интересен только ключ spec):
Destination определяет, куда мы будем деплоить проект. ArgoCD помимо самого Kubernetes, в который он задеплоен, поддерживает добавление других кластеров для Production. Тут как раз-таки и указан production-кластер (кластер указывается в ключе server, а в namecspace указано пространство имён).
Source и RepoURL как раз отвечают за магию — мы говорим Argo, на какой репозиторий смотреть, где в нём лежит наш чарт и какой values-файл к нему подключить. Вот и всё. Применив наши application-манифесты, получим работающее приложение. Кстати, настроить Argo можно и через UI, тогда ваши приложения будут выглядеть примерно так:
Подведём итог
-
На этом всё. Так мы успешно перевезли кроссплатформенное приложение «Тайный Санта» в Kubernetes. Новый подход с новой архитектурой нам позволил:
- избавиться от проблем недоступности приложения за счёт репликации сервиса;
- организовать плавный выпуск новой версии.
И всё это, можно сказать, дал «из коробки» Kubernetes. Плюс подход GitOps позволил обрести «единую точку правды», которой является наш git-репозиторий с кодом и чартами.
Стоит отметить, что и минусы у данного решения тоже есть. Во-первых, не стоит стрелять из пушки по комарам. Если у вас маленькое приложение с минимумом микросервисов, то это решение не для вас. Во-вторых, стоит учесть, что поддержание кластера — довольно затратная вещь, даже если вы планируете поднимать кластер сами. Почему — потому что только для области управления вам нужно выделить 3 сервера с конфигурацией минимум 2 ядра, 4ГБ. В-третьих, стоит отметить, что существует некий порог входа — вам придётся изучить немало источников для того, чтоб начать работать с Kubernetes. А, ну и, конечно, вам необходимо настроить для кластера логирование и мониторинг (кстати, у моего коллеги Николая есть статья о мониторинге).