Привет! Меня зовут Михаил Кажемский, я ведущий DevOps-инженер в ИТ‑интеграторе Hilbert Team. В этой статье я расскажу о различных подходах к созданию унифицированных типовых R&D-окружений. Прочитав её, вы поймёте, как разрабатывать цифровые продукты быстрее и эффективнее и избавить команды разработчиков от лишней рутины.

В чём суть проблемы

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

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

Такая инфраструктура должна соответствовать трём основным требованиям:

  1. Унификация
    Процесс развёртывания различных компонентов окружений должен быть общим для всех команд. Например, компонент PostgreSQL должен быть один для всех.

  2. Кастомизация
    Разные команды имеют разный набор параметров и компонентов, поэтому необходима возможность кастомизировать окружения под себя.

  3. Безопасность
    В компании есть различные требования к безопасности, и все окружения должны им соответствовать.

Глобально существует три общих подхода к созданию таких окружений:

  • динамический, при котором окружения создаются и удаляются по какому-то событию, например, при создании и удалении ветки в Git);

  • статический, при котором окружения создаются заранее и существуют до тех пор, пока в них есть необходимость;

  • подход GitOps.

Дальше я расскажу подробнее о реализации таких подходов:

  • создание динамических окружений в Kubernetes;

  • создание статических окружений с помощью Terraform и Terragrunt;

  • GitOps-подход — создании окружений с помощью CrossPlane и ArgoCD.

Динамические окружения в Kubernetes

Окружения в Kubernetes
Окружения в Kubernetes

Динамические окружения в Kubernetes представляют собой полностью изолированные Kubernetes namespaces, содержащие как необходимые инфраструктурные сервисы, например, БД и KVS, так и сами приложения и сервисы компании. Они динамически разворачиваются во время работы CD-пайплайна, например, с помощью Helm. 

Далее на схеме представлен пример реализации CI/CD-пайплайна динамических окружений:

  1. В отдельном централизованном репозитории хранится Helm-чарт деплоя приложения, содержащий в качестве subchart чарты деплоя ресурсов, например, PostgreSQL. Сами subcharts по умолчанию выключены (disabled).

  2. В репозитории R&D-команды хранится values.yaml, который переопределяет дефолтные значения чарта приложения, в том числе необходимые компоненты приложения.

  3. В Hashicorp Vault хранятся секреты, например, пароли к БД. Монтирование происходит на этапе деплоя чарта.

  4. С помощью GitLab CI/CD-пайплайна объединяются чарт приложения, кастомные значения и секреты. С помощью Helm Release приложение отправляется в нужный namespace со всеми зависимостями. 

  5. Namespace для деплоя соответствует имени ветки репозитория.

Какие плюсы имеет этот подход:

  • Простота реализации и поддержки 
    Достаточно реализовать относительно простой CI/CD-пайплайн с деплоем приложения в нужный кластер.

  • Простота кастомизации разработчиками
    Сервисы разворачиваются с помощью Helm-чартов, и у разработчиков есть возможность поправить values чарта. А в случае острой необходимости, разобраться с go-темплейтами Helm не составит большого труда, это поможет разгрузить инженеров, отвечающих за написание чартов.

  • Возможность использования с on-premise инфраструктурой  
    Поскольку в этом подходе всё окружение разворачивается в нужном namespace и не требует никаких облачных зависимостей, то нет принципиальной разницы, где развёрнут кластер Kubernetes: в облаке или на физических серверах.

  • Экономическая выгода 
    При развёртывании окружений можно включать только те компоненты, которые необходимо протестировать, то есть не включать все зависимости. Это существенно сэкономит ресурсы в кластере, если не забывать про ограничения.

Какие ограничения имеют динамические окружения:

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

  • Необходимость настраивать сетевые политики в кластере
    По умолчанию окружения в Kubernetes никак не изолированы на сетевом уровне — сервисы из одного namespace по определённому fqdn могут достучаться до сервиса в другом namespace Потенциально это может привести к ошибкам, поэтому необходимо изолировать namespace на сетевом уровне, например, с помощью Kubernetes Network Policies.

  • Невозможность управлять всей инфраструктурой
    Настройка инфраструктуры и управление ей происходит другим способом. В данном методе создаётся namespace в кластере Kubernetes, но сам кластер разворачивается другими инструментами, в том числе вручную. 

Альтернативой такого подхода являются статические окружения.

Статические окружения

Статические окружения, в отличие от динамических, создаются по предварительному запросу какой-либо из команд. То есть, например, под каждую из R&D-команд уже создано окружение со всеми необходимыми зависимостями. Сами зависимые компоненты могут быть сервисами IaaS и PaaS. Также не исключается возможность использования зависимых компонентов, общих для всех окружений, например, общего хранилища логов или метрик.

На схеме выше приводится пример реализации унифицированных статических окружений в Yandex Cloud. Они состоят из набора PaaS-сервисов, предоставляемых облачным вендором:

  • Yandex Managed Service for Kubernetes для compute-нагрузки, например, для размещения приложений и сервисов заказчика.

  • Yandex Managed Service for PostgreSQL, Yandex Managed Service for Elasticsearch и Yandex Object Storage для хранения данных приложений и диагностической информации, например, логов.

Создание статических окружений с помощью Terraform и Terragrunt

Cхема взаимодействия Terragrunt и Terraform
Cхема взаимодействия Terragrunt и Terraform

Одним из способов создания статических окружений является использование подхода Infrastructure as Code (IaC) и инструментов Terraform и Terragrunt:

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

  • Terragrunt-модули представляют собой шаблоны для развёртывания и кастомизации конечных окружений. Кастомизация определяется фактическими значениями параметров.

Причиной использования Terragrunt является возможность расширения Terraform:

  • Terragrunt позволяет повторно использовать Terraform-код и упрощает процесс его поддержки: один и тот же модуль Terraform можно параметризировать и использовать для развёртывания различных окружений,  изменив значения параметров.

  • Terragrunt позволяет декларативно определять зависимости модулей Terraform и порядок их исполнения.

  • С помощью Terragrunt можно фиксировать версии модулей Terraform и контролировать объём изменений.

На схеме представлен пример реализации CI/CD-пайплайна для статических окружений:

  1. Репозиторий с модулями Terraform содержит декларативное описание необходимых компонентов, например, сервисов Managed PostgreSQL, Managed Elasticsearch, описания чартов приложений и чартов инфраструктурных компонентов. При слиянии в мастер в этом репозитории происходит автоматическое тегирование ветки в соответствии с Semantic Versioning.

  2. Репозиторий с манифестами Terragrunt содержит в каждой папке манифесты, которые ссылаются на терраформ модули Terraform, и запускает их в нужной последовательности через dependency. Каждая папка соответствует разворачиваемому окружению — в данном примере common для общих сервисов, prod, dev, stage и testing. При запросах слияния  для каждого изменённого манифеста прогоняется Tterragrunt plan. Вывод отдаётся в форму мерж-реквеста, чтобы было видно, какие изменения планируется выполнить для более быстрого ревью.

  3. Из репозитория с манифестами запускается GitLab Runner, который рекурсивно проходит по папкам в репозитории и выполняет Terragrant apply. Tfstate хранится в S3-storage.

  4. Изменения применяются в облаке.

Схема развёрнутых окружений в облаке
Схема развёрнутых окружений в облаке

В итоге получается следующее: каждой папке в манифест репозитории соответствует каталог в облаке. В каталоге common содержатся общие сервисы, такие как Managed Service for Elasticsearch, Managed Service for PostgreSQL, инфраструктурный Managed Kubernetes с хранилищем метрик и indenty provider. В других каталогах развёрнуты приложения, которые могут использовать общие сервисы из каталога common, например, БД. Эти каталоги объединены в одну сеть VPC.

Структура каталогов с модулями и манифестами выглядит следующим образом:

Также существует каталог testing для тестирования инфраструктурных сервисов. Он не связан с другими окружениями, поэтому его можно удалить при необходимости без вреда для прода. В основном он нужен для команды DevOps.

Также существует каталог testing для тестирования инфраструктурных сервисов. Он не связан с другими окружениями и его можно ломать как душе угодно. В основном он нужен для команды DevOps.

Схема каталога testing в облаке
Схема каталога testing в облаке

Плюсы данного подхода: 

  • Прогнозируемость ресурсов 
    За счёт того, что окружения статичны, текущие и будущие затраты на ресурсы легко прогнозируются.

  • Управление ресурсами одной командой
    Управлять ресурсами может одна команда, например, команда DevOps, поэтому окружения легче адаптировать под требования безопасности компании.

  • Унификация окружений Prod и Dev
    Благодаря унификации артефакт, разработанный и протестированный в одном окружении, будет корректно работать в другом, например, в выделенном prod-окружении.

  • Возможность использования сервисов Iaas и PaaS
    Поскольку окружения являются каталогами-облаками, а не изолированными namespaces, есть возможность использовать managed-сервисы для их работы и снижать нагрузку на их обслуживание. В том числе можно использовать один managed-сервис для нескольких окружений, это положительно влияет на итоговую стоимость облака.

  • Возможность управления инфраструктурой
    Помимо управления окружениями для R&D-команд этот подход даёт возможность управлять всей облачной инфраструктурой организации.

Ограничения подхода

  • Сложность кастомизации разработчиками
    Язык HCL для Terraform/Terragrunt не самый очевидный для понимания разработчиками. В случае острой необходимости научить разработчиков писать манифесты Terragrunt или модули Terraform будет сложнее и дольше, чем объяснить, что следует поправить или дополнить в YAML-манифесте.

  • Относительно сложный процесс деплоя
    Деплой происходит посредством раннера, который последовательно проходит по всем манифестам в репозитории. В случае ошибки в одном из манифестов, деплой последующих не происходит, и процесс останавливается до решения проблемы. Ошибки могут возникнуть из-за configuration drift или других причин. Также не стоит забывать об управлении Terraform state, в данном случае оно решено хранением его в S3-бакете.

  • Расширяемость 
    Последовательность прохода раннера по репозиторию создаёт проблему расширяемости. Если инфраструктура большая и содержит много окружений, то процесс деплоя может быть долгим и увеличиваться с ростом количества манифестов. Если манифестов немного, то добавить окружение можно, создав новую папку с его описанием по имеющемуся шаблону.

Получается, что Terraform/Terragrunt даёт возможность управлять инфраструктурой целиком с помощью одного инструмента. Но узким горлышком тут является сложность распараллеливания этого процесса. Также увеличивается вероятность ошибок из-за configuration drift, поскольку  обновление state инфраструктуры происходит только при запуске раннера.

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

GitOps

По своей сути GitOps — это инфраструктура на основе кода и операционные процедуры, использующие Git в качестве исходной системы управления.

Push-модель

В этой модели используются CI/CD-пайплайны, чтобы отправить изменения инфраструктуры из состояния Git в окружение. В такой схеме пайплайн обычно запускается по событию коммита в репозиторий. При деплое с помощью Terraform как раз используется такой подход.

Pull-модель

Чаще всего под GitOps понимают именно Pull-модель взаимодействия с Git. Эта модель предполагает, что информация о том, что и где мы разворачиваем, расположена в Git-репозитории. Внешний агент мониторит обновления в нём, сравнивает с состоянием Kubernetes и при необходимости меняет конфигурацию. Чаще всего агент расположен в том кластере, где происходит развёртывание.

Использование этого подхода защищает от некоторых проблем. Например, если пользователь напрямую внесёт изменения в кластер Kubernetes, внешний агент увидит это и вернёт кластер в состояние, прописанное в Git. Это мотивирует пользователей вместо прямого внесения изменений в кластер делать правки в единственном источнике, которым выступает репозиторий.

Создание окружений с помощью CrossPlane и Argo CD

Пример инструмента, который нужен для развёртывания облачной инфраструктуры и при этом хорошо ложится на модель GitOps — это Crossplane. В практическом плане Crossplane с Terraform во многом похожи.

Главное отличие Crossplane в основной идее проекта: он работает как оператор Kubernetes, и в итоге Kubernetes, в том числе Managed, может являться Control Plane для всего облака. Состояние описывается именно в виде Custom Resources в Kubernetes, и Crossplane постоянно поддерживает его актуальность. Terraform, напротив, синхронизирует состояние только во время выполнения вышеупомянутых CLI-команд.

Если посмотреть на популярность и распространённость, то вокруг Terraform за годы образовалось большое сообщество и множество провайдеров. Crossplane пока не так развит, но поддерживает три самых популярных облачных провайдера: AWS, Google Cloud, Azure.

Благодаря инструменту Upjet есть возможность за минимальное время сгенерировать Crossplane-провайдер из Terraform-провайдера. Так, например, портирован провайдер для Yandex Cloud.

На схеме представлен один из вариантов реализации GitOps-подхода с использованием ArgoCD и Crossplane:

Предлагается использовать два репозитория: для инфраструктуры (1) и для сервисов (2)  с ресурсами (приложениями) ArgoCD. В них указано, что и куда деплоить. Сам ArgoCD и Crossplane располагаются в кластере по инфраструктуре (3). В качестве манифестов в приложении Argo указывается Helm-чарт по ссылке на другой репозиторий с чартами (4), которые параметризуется через values. Сами сервисы хранятся в отдельных репозиториях (5), а обновление Argo, например, версии образа, происходит через CI\CD-пайплайн в репозитории сервиса.

Если приложению необходима база в Managed Postgres, то шаблон манифеста для создания базы и пользователя через Crossplane лежит в чарте приложения, а описание самого кластера Managed Postgres — в отдельном чарте и относится к infra-репозиторию.

В Argo есть так называемое App of Apps, которое следит за вложенными приложениям в деплойных репах (1, 2). Если указать папку для просмотра, вложенные приложения добавятся к этому приложению и будут поддерживать их состояние.

По сути реализованный вариант похож на Terraform\Terragrunt. Отличием является то, что состояние инфраструктуры обновляется и мониторится постоянно. Кроме того, инфраструктура, задеплоенная таким способом, расширяется достаточно просто: для этого необходимо добавить Argo приложений в деплойный  репозиторий (1, 2) с описанием, что и куда деплоить.

Pull Request Generator

Окружения с помощью GitOps оператора ArgoCD можно также создавать динамически при создании Pull/Merge-реквестов. Например, с помощью GitLab и ArgoCD Pull Request генераторов. Генераторы Pull Request работают не только с GitLab, но и другими сервисами, GitHub, Gitea и Bitbucket.

На картинке выше изображён объект ArgoCD ApplicationSet с заданным шаблоном приложения. При создании Merge Request в наблюдаемом репозитории, создаётся также и review-окружение в Kubernetes. Остановить и удалить его можно непосредственно в пайплайне Merge Request Gitlab.

Во многом такой подход обладает теми же преимуществами, что и подход с использованием Terraform, при этом решает некоторые ограничения. Кроме унификации окружений Prod и Dev, возможности использования сервисов IaaS и PaaS и возможности управления инфраструктурой, плюсом данного подхода являются простота кастомизации и расширения. Все сущности описываются с помощью манифестов YAML, и добавление или изменение приложения заключается в добавлении или изменении манифеста в нужном репозиторий.

Ограничения данного подхода

  • Относительно новая технология
    Несмотря на то, что CrossPlane уже далеко не сырой продукт, по сравнению c Terraform он достаточно молод. Если вы столкнётесь с какой-либо ошибкой, то не факт, что кто-то сталкивался с ней до вас, поэтому процесс дебага может затянуться. Большинство статей по развёртыванию инфраструктуры с помощью CrossPlane в основном описывают базовый простой сценарий, а по Terraform есть множество материалов, где рассматриваются сложные большие системы с большим количеством особенностей, в которых вы можете узнать свой пример и потратить меньше времени на самостоятельный разбор. 

  • Отсутствие провайдеров для некоторых облаков
    Далеко не под все облака есть провайдеры. И часто бывает, что провайдеры для некоторых облаков не развиваются и не обновляются. Также нередки ситуации, когда для провайдера отсутствует документация, и вам придется погружаться в CustomReosurceDefenition и смотреть, какие поля можно настроить или задать.

Сравнение подходов

Таким образом можно подвести следующие итоги:

Kubernetes Namespace

Terraform Terragrunt

Crossplane Argo CD

Простота реализации

Просто

Средне

Средне

Кастомизация разработчиками

Легко

Сложно

Легко

Простота поддержки

Легко

Средне

Сложно

Расширяемость

Легко

Средне

Легко

Управление инфраструктурой

Нет

Да

Да

  • Легче и быстрее всего создавать окружения в изолированных namespaces в Kubernetes.

  • Если вы захотите делегировать часть кастомизации разработчикам, сложнее всего будет использовать Terraform, поскольку язык HCL не часто встречается в разработке.

  • Проще всего поддерживать и исправлять ошибки в namespaces в Kubernetes — с очень высокой вероятностью такую проблему уже решали до вас.

  • Расширить подход на большое количество приложений сложнее с помощью Terraform из-за ограничения последовательного выполнения.

  • С помощью Kubernetes Namespaces не получится управлять инфраструктурой целиком — для этого нужен дополнительный инструмент.

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

Kubernetes Namespace

Terraform/Terragrunt

Crossplane/Argo CD

Cloud Native / Multicloud

Enterprise

Фанаты K8s, хейтеры HCL

  • Динамические окружения в Kubernetes больше подходит для тех команд, которые любят подход Cloud Native и плотно работают с Kubernetes. Если у вас Saas-решение, то для каждого отдельного клиента можете создать отдельный namespaces.

  • Статические окружения Terraform/Terragrunt являются вариантом для более зрелых и крупных Enterprise-команд, которым важно прогнозировать расход ресурсов. 

  • GitOps-подход CrossPlane/Argo CD оптимален для любителей Kubernetes и YAML и тех, кто не любит HCL.

Полезные ссылки:

Если вам нужна помощь в создании унифицированных окружений для R&D-команд, воспользуйтесь бесплатной консультацией от Hilbert Team, где мы с коллегами поможем составить роадмап по достижению ваших целей.

Комментарии (0)