В серии статей по теме DevOps мы вместе с Lead DevOps инженером департамента информационных систем ИТМО Михаилом Рыбкиным рассказываем о проверенных инструментах выстраивания инфраструктуры, которыми с недавнего времени пользуемся сами. В предыдущих статьях мы уже рассмотрели предпосылки перехода на новую инфраструктуру и познакомились с азами Kubernetes, теперь пора перейти к следующему шагу – доставке кода. В рамках этой статьи мы подробно рассмотрим методологию GitOps и ее реализацию на примере ArgoCD. Почему вообще мы задумались о каких-то методологиях и инструментах для доставки кода? Все просто — у нас слишком много проектов, чтобы небольшая группа девопсов могла глубоко участвовать в деплое каждого из них.  По-простому код, сохраненный в GitLab, можно пушить в мастер-ветку с помощью любого CI/CD инструмента, потом устанавливать все в кластер с помощью команды helm install и с этим жить. Изначально мы так и делали. Все работало по принципу push, то есть по триггеру кто-то приходил и отправлял весь код в кластер, используя конфиги из того же GitLab. Но схема оказалась не самая удобная — она требовала от девопсов много времени и усилий на управление приложениями. Так что началось все с идеи автоматизации. Напомню, что нашей задачей было минимизировать зависимость между количеством проектов и количеством девопсов, которые их обслуживают. А лучший способ ее решить — вообще исключить девопса из процесса деплоя, по возможности автоматизировать даже восстановление после сбоев. Очевидно, что все это должно работать с Kubernetes (раз уж мы строим инфраструктуру вокруг него) и иметь определенный уровень секьюрности. Но, кроме того, хотелось решить некоторые частные проблемы. Метод деплоя, который мы использовали ранее — helm install в рамках gitlab ci — работает как катапульта. Код куда-то отправляется, но разработчики понятия не имеют, что с ним происходит. Они видят, что команда выполнена, но это не означает, что все работает. В большинстве случаев все изменения раскатываются, конфиги применяются — все хорошо. Но примерно в 40% случаев процесс падает: В 10-15% случаев — все установлено, но не работает. Helm посылает команду в Kubernetes, но не контролирует, что происходит с манифестами после установки. Тот факт, что все конфиги применились, еще не означает, что приложение корректно функционирует. Если все отправлено, провалидировано, выполнена команда применения манифестов, но контейнер (на который есть ссылка в манифесте) не работает, и, например, постоянно перегружается, система не будет функционировать как надо. А главная проблема, что разработчик этого никак не увидит. Придется звать девопса и разбираться. В остальных случаях сам helm завершается с ошибкой. Самый распространенный вариант — helm вылетел за таймаут. И это тоже ничего не говорит разработчику об истинных причинах падения. Предположим, мы ставим огромное приложение из нескольких десятков манифестов, и helm может просто не успевать в отведенные ему 5 минут все провалидировать, отправить в Kubernetes, запустить там команду установки и получить от него ответ. Казалось бы, можно увеличить допустимое время. Но расширение таймаута до часа проблемы не решает — если на самом деле манифесты не установились и застряли в цикле, мы получим ту же ошибку, просто потратим на это больше времени. Да и в целом helm может завершиться с другой ошибкой. Во всех подобных ситуациях к решению проблемы придется подключать девопса. Чтобы девопсам не приходилось решать подобные проблемы, надо подобрать прозрачную методологию развертывания, в рамках которой разработчик будет получать более полный фидбек относительно того, что произошло с его сервисом в процессе деплоя. Довольно часто в работе с сервисами появляется необходимость получить последний примененный файл конфигурации. Например, если нужно обновить приложение, внести какие-то изменения или мигрировать приложение в другой кластер/неймспейс. Если в команде девопсов больше одного человека, проблема поддержания актуальности конфигурации встает особенно остро. Тем более, что конфиг может меняться и незаметно – например, если ресурсы заденет “каскадным” удалением других приложений. И чем больше приложений, тем сложнее управлять актуальностью конфигурации. В результате преобразований мы хотели прийти к схеме, где у нас есть только один источник актуальных конфигов. В поисках решения этих проблем мы пришли к методологии GitOps. Это подход к управлению инфраструктурой, в рамках которого все конфигурации приложений Kubernetes хранятся в git, откуда они применяются в автоматическом режиме посредством GitOps оператора. В отличие от helm install, который работает по push-схеме, эта методология работает по принципу pull. GitOps оператор, имеющий доступ к конфигам в git и к кластеру Kubernetes, регулярно проверяет изменения состояния одного относительно другого - в нашем случае это происходит раз в 3 минуты. Обнаружив изменения, GitOps оператор берет новый конфиг, рендерит его и применяет полученные манифесты на кластер — приводит “actual state” кластера к “desired state”, описанному в конфигах. GitOps оператор — это фактически дополнительный сервис, который следит за состоянием приложения и гарантирует, что оно всегда соответствует заданной конфигурации. В соответствии с базовым принципом методологии, git является единственным “источником правды” для кластера Kubernetes. Если у нас что-то есть в конфиг-репозиториях в git, то оно точно применено в кластер. А если кто-то пошел в обход и что-то поменял в кластерах напрямую, GitOps оператор это быстро увидит и затрет все изменения. Мы исключаем ситуацию, в которой кто-то вместо корректировки нашего источника правды (конфигов в git) тихонечко вносит изменения в кластер. Такие исправления все равно долго не живут — через пару дней сервис задеплоят заново, конфиги перезапишутся и исправления потеряются. Мы снова столкнемся с той же проблемой, которую спешно фиксили, и не будем понимать, в чем же дело. Хранение всех конфигураций в git дает и второй удобный момент — видна история изменений – кто, что и когда менял. Это помогает решать вопросы безопасности и прозрачности: всегда можно откатить манифест назад, всегда понятно, какой шаг помог решить ту или иную проблему. Из дополнительных преимуществ – возможность настроить уведомления на изменения в репозиториях и схема пулл-реквестов для ревью предлагаемой конфигурации (с автоматическими линтами, например).  Все настройки Kubernetes хранятся в git в декларативной форме — мы видим, что у нас установлено в кластере. В этом смысле методология GitOps близка к идее infrastructure-as-code, где принято декларативное описание серверов, баз данных и т.п. Но GitOps не только помогает декларативно описать конфигурацию, но и гарантирует ее актуальность. Стоит сделать оговорку, что GitOps не нужен тем, у кого мало проектов или сами по себе эти проекты небольшие. Он будет отнимать слишком много времени на развертывание и настройку. GitOps оператор — это дополнительный сервис, который необходимо поднимать и поддерживать (как правило, внутри кластера Kubernetes). А кроме того у него есть важная особенность, на которой мы подробнее остановимся ниже — это хранение секретов. Обычно в организациях с небольшими проектами или малым их количеством и нет отдельной роли девопса — всем занимается разработчик. Он может использовать классический подход, т.е. вручную применять манифесты на продакшене и вносить изменения на серверах. Или же может воспользоваться любым инструментом CI/CD - в соответствии с этим подходом доставка кода осуществляется автоматически вместе со всеми сопутствующими проверками. В обоих случаях он будет сразу же видеть результат своих действий и то, насколько живы его приложения. Когда в компании появляется выделенный девопс, вопросы о статусе приложений разработчик пытается адресовать ему, и вот здесь процесс усложняется. И только когда он становится совсем сложным, стоит обращаться к GitOps.  Для конфигураций стоит использовать отдельный git-репозиторий, в котором хранятся только эти файлы (он существует отдельно от репозитория с кодом). Это необходимо по нескольким причинам: Прозрачность окружений — если проект есть в деплой-репозитории, значит, он установлен в кластере, и можно легко найти того, кто его установил, а также посмотреть всю связанную с этим проектом конфигурацию. Разделение обязанностей — разработчики следят за репозиториями кода, девопсы — за деплой-репозиториями. Прозрачность операций — история коммитов репозитория хранит информацию только о тех изменениях, которые касаются напрямую конфигурации развернутых приложений, без лишнего шума.  Разделение доступа — доступы к деплой-репозиторию отличаются от доступов к коду приложения. Можно выдать права на редактирование кода без возможности выкатывать изменения или наоборот. Объединения связанных приложений — некоторые взаимозависимые приложения должны устанавливаться вместе, и их манифесты также должны храниться в одном месте. Имея всю конфигурацию в git, мы должны где-то хранить секреты — пароли от баз данных и т.п. На старте возникает желание также держать их в git, но надо понимать, что это недостаточно безопасная среда (она задумывалась для другого и не имеет инструментов усиления безопасности, которые хотелось бы иметь в хранилище секретов). Кроме того, доступ к коду у нас должен отличаться от доступа к секретам. К коду может иметь доступ стажер, который что-то написал и закинул merge реквест, чтобы более опытные коллеги посмотрели. Ему не следует выдавать доступ от секретов продакшена. Отметим, что есть работающие паттерны хранения секретов в git (шифрование, отдельные репозитории, CI/CD переменные и тд), но в рамках GitOps-методологии их настройка сильно усложняется. В идеале в pull-схеме необходимо организовать внешнее специализированное хранилище секретов, куда GitOps оператор сможет регулярно обращаться. Если значение поменяется в хранилище, его надо будет применить, и оператор с этим справится. О специализированных хранилищах секретов мы поговорим отдельно в будущих статьях. Мы в ДИС ИТМО в качестве GitOps оператора выбрали ArgoCD. Этот инструмент был разработан специально для работы с кластерами Kubernetes. Безусловно, ArgoCD — не единственный доступный GitOps оператор. Автоматизировать управление кластером на Kubernetes помогают, например, FluxCD, Werf и другие. ArgoCD и FluxCD — нативные для Kubernetes и они очень похожи между собой. Werf несколько отличается от них, но мы подробно на этом останавливаться не будем. Сама концепция GitOps оператора везде одна. Важными для нас критериями выбора был графический интерфейс с управлением доступами (чтобы выдать его разработчикам), простота установки и настройки, а также возможность работы с несколькими кластерами. Мы выбирали больше года назад (с тех пор многое поменялось), тогда под эти критерии нативно подходил только ArgoCD, поэтому на нем мы и остановились.  Рассмотрим основные компонентах инструмента: ArgoCD API Server — главный компонент, который обеспечивает доступ к API инструмента. ArgoCD Repository Server  – компонент, работающий с git-репозиториями, в которых хранятся конфигурации приложений. Он авторизуется в git, скачивает и рендерит конфиги, сравнивает состояния и применяет изменения, если это необходимо. ArgoCD умеет работать как с обычными kubernetes-манифестами, так и с утилитой kustomize, а также helm-чартами. Важная особенность установки через helm – арго не использует helm install, он выполняет helm template – рендерит манифесты в kubernetes-формате, а затем применяет их через kubectl apply. Это надо учитывать и не использовать в helm-шаблонах команды, которые работают только при установке через Helm.  ArgoCD Application Controller работает с такой абстракцией как Application — выдает ArgoCD Repository Server команды на получение манифестов и сверяет их. Подробнее об этой абстракции мы расскажем ниже. ApplicationSet Controller работает с шаблонами для Application. До недавнего времени он был отдельным плагином, а сейчас входит в поставку ArgoCD. Для работы с уведомлениями предусмотрен опциональный компонент - Notification Controller. Ему можно задать триггеры и каналы для уведомлений, например о том, что не удалось применить манифесты. Два дополнительных внешних компонента — это хранилище Redis и oAuth-провайдер авторизации Dex (вместо последнего можно использовать любой другой аналогичный инструмент). ArgoCD — инструмент микросервисный. Redis обеспечивает связь компонентов, это общая прослойка между ними. Например, если мы делаем запрос к ArgoCD API Server, чтобы узнать состояние приложения, сервер не пойдет в кластер, а сначала обратится к Redis и заберет состояние оттуда. Если же мы выполним Hard Refresh (для обновления кеша), ArgoCD API Server отправит запрос к Application Controller, который сходит в кластер и обновит информацию о нем в Redis, откуда ее заберет и вернет нам API-server. Важное уточнение — у ArgoCD нет как таковой базы данных. Redis — это кэш. Мы можем полностью его удалить, потом поставить заново и ArgoCD выполнит весь процесс по его наполнению. Данные самого ArgoCD — это информация о пользователях, данные по кластерам и конфигурациям. Пользователи хранятся в провайдере авторизации, а все остальное задается при установке ArgoCD и сохраняется в нем же в Kubernetes-манифестах. У ArgoCD есть веб-интерфейс, к которому можно предоставлять доступ разработчикам. Поскольку ArgoCD умеет работать через oAuth, управление доступом можно проводить на стороне idP (identity provider), назначая там пользователям нужные группы. В интерфейсе разработчик может видеть основные данные по своему приложению — состояние, статус и время последней синхронизации. Так мы решаем проблему обратной связи, о которой говорили в самом начале.  В ArgoCD у приложения есть три вида статусов: health status говорит, работает ли приложение (дополнительно можно посмотреть логи и event-ы контейнера); sync status позволяет сделать вывод о различиях между примененными и желаемыми манифестами; last sync status — информация о том, когда и с каким статусом последний раз проводилась синхронизация. Также в веб-интерфейсе разработчик может наблюдать за тем, как создаются новые и удаляются старые поды при обновлении, смотреть логи приложения и ошибки, которые оно возвращает, и даже использовать терминал внутри контейнеров. Первая и главная абстракция, с которой работает ArgoCD — это кластер. По умолчанию ArgoCD работает с тем же кластером, в котором установлен сам. Однако можно добавить и другие. Так мы можем из одного интерфейса конфигурировать приложения в нескольких кластерах – например, develop и production. В ДИС ИТМО мы с помощью ArgoCD управляем шестью разными кластерами. Добавить кластер в ArgoCD можно через Helm Chart с помощью специальных ключей или с помощью веб-интерфейса — Argocd cluster add. Второй вариант не очень хороший, поскольку информация о кластере остается в ArgoCD и пропадет, если с ним что-то пойдет не так. Есть и третий вариант — любым другим путем создать секрет по заданным параметрам.  После подключения кластера можно устанавливать в него приложения. В корне всей настройки — Application — это кастомный ресурс Kubernetes, который создает сам ArgoCD. Application конфигурируется несколькими параметрами: destination — место установки нашего приложения – имя / IP-адрес кластера и namespace; source (или sources) — источник(и) конфигурации. Конфиги могут быть в одном из нескольких форматов – просто git-файл или git-директория, в которых Kubernetes- или kustomize-манифесты хранятся в явном формате, а также helm-chart (или целиком, или только values-файл). У нас используется последний вариант; project — указание “проекта”, к которому принадлежит приложение. Проект – также одна из абстракций ArgoCD, ее возможности мы рассмотрим чуть ниже. sync policy — опции процесса синхронизации (например, если не существует указанного namespace, его надо создать и т.п.). Следующей абстракцией ArgoCD, которую мы рассмотрим, является Project – проект. Это группа приложений, объединенных одними правилами. На проект можно выписывать доступы пользователям, там же ему можно задавать белые и черные списки кластеров и namespace установки, ограничивать список ресурсов, которые можно устанавливать через приложения проекта. Проект также позволяет задать “окна синхронизации” – временной период, вне которого синхронизация выполняться не будет. Например, если окно задано с 8 утра до 20 вечера, ночью конфигурация приложения применяться не будет. Еще одна интересная абстракция ArgoCD — ApplicationSet — шаблонизатор для Application, который позволяет не писать новую (отдельную) конфигурацию для каждого приложения. Это может быть полезно, например при использовании микросервисной архитектуры.  У ApplicationSet есть два пункта конфигурации: generator — генератор, позволяющий динамически собирать для Application необходимую информацию. В ArgoCD реализовано несколько разных вариантов. Например, есть генератор, который конфигурируется адресом git-репозитория и регулярным выражением. Все файлы из этого репозитория, подходящие под регулярное выражение, будут использованы для генерации Application. Другой генератор позволяет создавать приложения из всех репозиториев в группе. К слову, генераторы можно комбинировать. Более подробный список генераторов доступен по ссылке. template — динамический шаблон для Application, позволяющий генерировать приложения с использованием переменных из генератора. Например, приложение может называться также, как и директория, в которой лежит его values-файл в git. Когда читаешь про ApplicationSet, появляется соблазн создать большой шаблон, который будет сам автоматически ставить все, что попадает в git. Но так делать не стоит, потому что задача девопса, среди прочего, — минимизировать scope ошибок. Предположим, нам потребуется отредактировать этот ApplicationSet и мы ошибемся в запятой. В этом случае сломается вообще все — scope ошибки слишком большой. У нас, например, один applicationset управляет одним окружением одной группы проектов. Таким образом, мы можем атомарно управлять не просто группами, а их окружениями, и тестировать все изменения сначала на develop, а потом переносить на production. Мы решили поставленную задачу — свели к минимуму участие девопсов в деплое за счет перехода на другую методологию развертывания.  В рамках GitOps мы установили дополнительное приложение для отслеживания изменений состояния наших проектов на предмет различия с заданными в git конфигами. Для себя в качестве GitOps-оператора мы выбрали ArgoCD, предоставляющий удобный веб-интерфейс и методы управления мультикластерной инфраструктурой. Сам ArgoCD состоит из минимум четырех микросервисов, связанных друг с другом через кэш redis, которые встраиваются в нашу существующую инфраструктуру — за авторизацию отвечает внешний oauth-провайдер, а в качестве хранилища конфигурации используется сам Kubernetes.  ArgoCD предоставил нам набор абстракций для управления приложениями, с помощью которых мы можем легко управлять сотнями приложений, установленных в кластера.