Всем привет, у нас в НЛМК создается множество приложений и систем для производства и поддержки бизнеса. Большинство размещаются в нашей Единой Цифровой Платформе (коротко ЕЦП), а по-современному: Internal Developer Platform.
Меня зовут Денис Воронов, я с коллегами как раз занимаюсь развитием Единой Цифровой Платформы НЛМК, частью которой являются кластеры Openshift/OKD/K8S, количество которых постепенно увеличивается, что требует понятного, прозрачного подхода к управлению и конфигурированию как самих кластеров, так и различных приложений, работающих в них. Далее я расскажу, как мы это делаем с помощью ArgoCD и подхода GitOps.
Мы сторонники и адепты контейнеров, поэтому стараемся максимально размещать как наши платформенные сервисы, так и разрабатываемые для производства или бизнеса системы в контейнерных оркестраторах. Исторически, мы предоставляем командам и проектам namespace-ы в кластерах OKD, но также используем в отдельных случаях и ванильный Kubernetes, поэтому далее я буду указывать оба решения.
Как и во всех компаниях, которые делают упор на ИТ технологии и Цифровую трансформацию бизнеса, количество команд разработчиков в НЛМК постоянно растет, растет количество систем и их сложность. Появляются архитектурные требования, которые не удается решить централизованными кластерами OKD/K8S. Для определенных задач требуется максимальная локализация к пользователям/данным, а производства компании находятся по всему миру. Для кого-то требуется эксклюзивная изоляция и все это приводит к увеличению кол-ва кластеров, управляемых командой, численность которой увеличивается не столь быстро, поэтому единственным путем выживания остается стандартизация и автоматизация.
Базовое развертывание инфраструктуры под стандартный кластер OKD автоматизировано, но появляется вопрос: что делать дальше, как максимально быстро и предсказуемо настроить кластер в части:
Настройки уровня K8S: роли, специальные service account-ы, операторы и другие манифесты, которые должны быть в каждом кластере и в определенных случаях немного отличаться (к примеру, использовать локальные, а не удаленные dns/ntp/dc сервера)
Развертывание на кластере систем сбора логов, метрик, трейсов и т.п.
Бэкап сущностей кластера (у нас все в git, но есть еще команды разработчиков, которых мы не контролируем, поэтому бэкапим все манифесты в кластере)
Установка различных системных приложений, которые должны быть в каждом или в ограниченном списке кластеров
Решать задачи выше можно различными способами: через CI-CD pipelines, дополняя автоматизацию развертывания инфраструктуры скриптами и т.п.
Т.к. все манифесты и конфигурации мы стараемся хранить и версионировать в git, то первым вариантом доставки был стандартный процесс CD, с которым мы жили продолжительное время и для некоторых операций используем до сих пор, но хотелось более гибкого инструмента в плане поддержания кластеров в состоянии равным тому, что сейчас в git. Т.к. так или иначе все сталкиваются с подходом «я сейчас быстро починю/проверю локально, а потом точно сделаю коммит» и сейчас чаще коммит действительно происходит, но когда нет – такие изменения с каждым разом приводят к еще большему расхождению между тем, что реально в кластере и что лежит в git (Configuration drift)
Конечно, одним из правильных способов может быть полное ограничение и запрет на изменения напрямую в кластерах, чтобы не было выбора, кроме как проводить изменения через git commit, но это отдельная тема для статьи.
Пропустим часть про то, что такое ArgoCD, как его установить и интегрировать с Hashicorp Vault т.к. эти темы уже подробно описаны на Хабре и других ресурсах и перейдем к решаемой задаче.
Что бы с этим придумать
У нас есть некоторое количество кластеров, которое планомерно увеличивается
Нам хочется применять базовые настройки после развертывания нового кластера максимально быстро
Применять массово изменения за максимально короткое время
А также учесть некоторое расхождение в базовых настройках кластеров в разных географических расположениях или по другим признакам
(значения одного и того же манифеста могут отличаться, но сам манифест одинаков на всех кластерах)
Если описать схематично, то получается подход со слоями конфигураций кластеров, которыми должен управлять ArgoCD.
Base Configuration – манифесты, которые в неизменном виде должны присутствовать на всех кластерах, вне зависимости от их типа, расположения и прочих отличий.
Пример: Cluster Role + Cluster Role Binding для инженеров ИБ, системные Service Account-ы для различных интеграций и Role+Rolebinding для них, описание deployment/stateful set технических сервисов.
Custom Base Configuration – манифесты, которые должны быть на всех кластерах, но отличаются значениями в их полях.
Пример: В каждом кластере есть Rolebinding на группу администраторов кластера, но имя этой группы у каждого кластера свое.
Также сюда можно отнести различные Secret-ы с учетными записями/паролями/токенами, созданные для конкретного кластера.
Cluster Custom Configuration - сервисы и приложения, размещаемые на определенном списке серверов по какой-либо логике.
Пример: Разработчики сделали новый сервис и этот сервис должен быть развернут на всех площадках, где есть определенная производственная линия или технологический сервис, но там, где их нет – сервис не сможет работать и размещать его нет смысла.
Тут хочется сделать небольшое отступление на тему, чем удобна сборка OKD в описываемом подходе
Т.к. OKD, он же покойный Openshift для Российских заказчиков, использует в основе immutable Fedora CoreOS и практически все сервисы, включая хостовые, запускаются в виде контейнеров и управляются манифестами уровня кластера OKD, то это позволяет также через манифесты управлять конфигурацией самих нод и сервисов уровня хоста.
т.е. мы можем гибко сменить NS или NTP-сервера на всех хостах, просто обновив специальный custom resource k8S.
Как еще один пример: по определенным причинам, нам потребовалось откатить cgroup v2, которые включены уже по умолчанию в новых версиях OKD, до v1 и это было сделано через деплой одного единственного custom resource манифеста на несколько строк, без прогона по нодам кластера ansible playbook-ов и прочих подобных инструментов настройки конфигураций.
Таким образом, практически единственным инструментом управления кластером является написание и деплой манифестов k8s, что выглядит удобно, но возможно мы пока не столкнулись с какими-то минусами.
Заканчиваем с теоретическими изысканиями и переходим к практике: как же можно концепцию, описанную выше, «натянуть» на ArgoCD.
Чтобы не усложнять статью, все манифесты были описаны в явном виде, но описанный подход применим и для helm values с некоторой доработкой.
ArgoCD пропагандирует декларативное описание для своих внутренних сущностей: applications, projects, settings, etc, поэтому, первым делом создадим репозиторий для конфигураций и сущностей самого ArgoCD и выложим в него базовый Application, который позволит ArgoCD синхронизировать свою конфигурацию с этим репозиторием.
Манифест этого первого Application-а необходимо применить в namespace-е с ArgoCD вручную, далее ArgoCD будет автоматически определять изменения своей собственной конфигурации в репозитории (появление новых Clusters, Applications, ApplicationSets, etc.)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd-itself-config
namespace: argocd
spec:
destination:
namespace: argocd
server: 'https://kubernetes.default.svc'
project: default
source:
path: .
repoURL: 'https://git.com/agocd-itself-configs.git'
targetRevision: HEAD
plugin:
env:
- name: AVP_TYPE
value: vault
- name: AVP_AUTH_TYPE
value: k8s
- name: AVP_K8S_ROLE
value: argocd
- name: VAULT_ADDR
value: https://vault.com
- name: AVP_K8S_MOUNT_PATH
value: auth/internal
- name: AVP_KV_VERSION
value: "1"
name: argocd-vault-plugin
Чтобы ArgoCD смог подключаться к Vault и забирать оттуда необходимые секреты, нужно при установке интегрировать его с помощью плагина argocd-vault-plugin. Подробную инструкцию по такой интеграции Вы также можете найти на Хабре или в ссылках в конце моей статьи.
Добавление в ArgoCD кластеров K8S для управления
Прежде всего, нам необходимо добавить наши кластера в ArgoCD и т.к., т.к. у нас уже есть git-репозиторий, с которым синхронизируется конфигурация самого ArgoCD, то просто делаем в нем папку clusters чтобы отделить манифесты кластеров и добавим новый Project для Application-ов с конфигурациями кластеров.
Получаем такую структуру репозитория:
Предварительно, для подключения в ArgoCD нового кластера, необходимо в этом кластере создать отдельный service account с нужной ролью/правами на те манифесты, которые Вы планируете синхронизировать с помощью ArgoCD. В нашем случае, токен от таких service account мы храним в Vault-е.
Формат манифеста Secret, описывающего cluster для ArgoCD можно посмотреть в официальной документации, а для нашей статьи будем описывать кластеры следующим образом:
apiVersion: v1
kind: Secret
metadata:
name: east
labels:
argocd.argoproj.io/secret-type: cluster # указание, что данный Secret содержит описание кластера, как сущности ArgoCD
type: IT
distr: okd
location: east
type: Opaque
stringData:
name: east
server: https://api.east.local:6443
config: |
{
"bearerToken": "<path:clusters/east/argocd#token>",
"tlsClientConfig": {
"insecure": false,
"caData": "<path:clusters/east/certs#root_ca | base64encode>" # Base64 encoded PEM-encoded bytes
}
}
Cертификат кластера и токен service account-а, под которым будет подключаться ArgoCD к кластеру, загружаются из Vault-а, причем, при обновлении значений в Vault-е, ArgoCD сам это замечает и предлагает обновить манифест. Значения для каждого отдельного кластера вносятся в vault после базового развертывания инфраструктуры кластера.
В ArgoCD видим все наши новые кластеры и еще локальный, в котором работает сам ArgoCD:
Реализация доставки разных типов конфигураций в кластеры
И тут нам понадобиться такой ресурс ArgoCD, как ApplicationSet, выступающий в роли генератора дочерних, итоговых Application-ов.
В нашем простом случае, ApplicationSet controller будет отслеживать появление нового кластера, как сущности ArgoCD и генерировать нужные Application, согласно зашитой в его манифесте логике.
Все «слои» наших конфигураций для кластеров разместим в отдельном репозитории clusters-configuration и разложим по подходящей под нашу задачу иерархии, тут очень важно следить за именованием всех директорий, т.к. далее это будет играть важную роль при генерации итоговых Application-ов для кластеров.
ApplicationSet предоставляет возможность использования различных Generators, которые реализуют логику генерации из шаблона (template) итоговых Application с подстановкой значений.
Рассмотрим на примере одного из самых простых генераторов, необходимого, для решения нашей задачи - Cluster Generator. Данный генератор получает списком все кластеры, подключенные в ArgoCD и определенные параметры из их манифестов ArgoCD этих кластеров (те, что мы создали выше).
Согласно текущей документации, нам доступны из манифеста кластера:
Имя кластера (name)
Нормализованное имя кластера (nameNormalized)
(имя в формате прописные буквы/цифры, знак ‘-’ и ‘.’)URL api сервера (server)
Ключи и значения metadata.Labels
Ключи и значения секции metadata.annotation
Эти сущности мы можем использовать как для фильтрации, так и как элементы для подстановки в template ApplicationSet-а.
Допустим, мы договорились, что у нас есть некий набор манифестов, который должен быть абсолютно на всех кластерах, для примера Cluster Role и Cluster Role Binding:
В этом простом случае, нам необходимо создать ApplicationSet, который бы сгенерировал дочерний Application для каждого кластера и взял манифесты для деплоя из папки Base репозитория «слоев» наших конфигураций.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: base-clusters-config
namespace: argocd
spec:
syncPolicy:
preserveResourcesOnDeletion: true
generators:
- clusters: {}
template:
metadata:
labels:
cluster: '{{name}}'
config: base
name: '{{name}}-base-config'
spec:
syncPolicy: #по умолчанию, применение всех манифестов в кластер потребует ручного подтверждения, но это можно отключить изменив syncPolicy: automated: {}
syncOptions:
- CreateNamespace=true
destination:
server: '{{server}}'
project: clusters-configuration
source:
path: base-config
repoURL: 'https://git.com/clusters-configuration.git'
targetRevision: HEAD
directory:
recurse: true
Используя конструкцию generators: - clusters: {} мы проходим по всему списку кластеров без каких-либо условий, далее, мы увидим один из вариантов фильтрации кластеров по лейблам.
В разделе template описан шаблон для генерации Application, который использует готовые переменные name, server, все манифесты мы берем из фиксированного расположения /base-config и поддерикторий внутри /base-config/* (directory: recurse: true).
После деплоя подобного ApplicationSet на выходе мы получаем три Application-а, по количеству существующих кластеров, с уже подставленными значениями вместо переменных {{server}}, {{name}}.
Второй шаг в нашей задаче – деплой манифестов, для каждого кластера будут уникальными (по значениям или по типу).
Как одна из задач: деплой secret-а, содержащего выделенную для конкретного кластера учетную запись для доступа в s3 хранилище. Она может применятся в типовых сервисах, описанных в Base конфигурации.
В репозитории с конфигурациями создаем директорию на каждый кластер
Cоздаем немного отличающийся от предыдущего ApplicationSet, в котором также пробегаем в цикле по всем кластерам, но теперь используем переменную {{name}} для генерации пути (source: path:) в репозитории, откуда брать манифесты для конкретного кластера.
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
labels:
config: custom
name: custom-base-config
namespace: argocd
spec:
syncPolicy:
preserveResourcesOnDeletion: true
# это позволит оставить сгенерированные Applications, при удалении родительского ApplicationSet
generators:
- clusters: {}
template:
metadata:
labels:
cluster: '{{name}}'
config: custom
name: '{{name}}-custom-base-config'
spec:
syncPolicy:
syncOptions:
- CreateNamespace=true
destination:
server: '{{server}}'
project: clusters-configuration
source:
path: 'custom-config/{{name}}'
repoURL: 'https://git.com/clusters-configuration.git'
targetRevision: HEAD
plugin:
env:
- name: AVP_TYPE
value: vault
- name: AVP_AUTH_TYPE
value: k8s
- name: AVP_K8S_ROLE
value: argocd
- name: VAULT_ADDR
value: https://vault.com
- name: AVP_K8S_MOUNT_PATH
value: auth/argocd
- name: AVP_KV_VERSION # версия vault engine, по умолчанию v2
value: "1"
name: argocd-vault-plugin
Т.к. наш ArgoCD интегрирован с Vault-ом, о чем говорилось в начале, то в git репозитории мы без проблем выкладываем манифесты Secret, с указанием расположения секретов в Vault для подстановки значений в момент генерации Application-ов с помощью argo vault plugin.
Выборочное развертывание конфигураций/сервисов на кластеры
Т.к. размещение каких-либо приложений выходит за рамки конфигурации кластера, сделаем отдельный Project в ArgoCD и будем в нем создавать Application-ы, положим декларативное описание Project также в репозиторий с конфигурацией ArgoCD.
Воспользуемся возможностью ApplicationSet с Cluster Generator фильтровать по лейблам кластеров (label selector). Согласно схеме в начале статьи, мы можем пометить label-ом appX=’true’ конкретные манифесты кластеров и использовать label selector в описании ApplicationSet для этих приложений:
apiVersion: v1
kind: Secret
metadata:
name: south
labels:
argocd.argoproj.io/secret-type: cluster
location: south
appX: ‘true’
Label Selector в ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
labels:
name: app-x-deploy
namespace: argocd
spec:
syncPolicy:
preserveResourcesOnDeletion: true
generators:
- clusters:
selector:
matchLabels:
appX: "true"
В итоге у нас получается такое содержимое репозиториев:
Репозиторий с конфигурацией самого ArgoCD: clusters, projects, applicationsets
Репозиторий со «слоями» конфигураций кластеров
Репозиторий с мини приложением, деплой которого осуществляется в избранные кластера
При использовании данного подхода, установка и настройка базовой конфигурации нового кластера сводится к созданию одного манифеста Secret с описанием кластера для ArgoCD и создания некоторых custom манифестов, а для масштабирования типовых приложений используются labels в манифестах кластеров. Все остальное вручную или автоматически приезжает через ArgoCD.
Чтобы не перегружать статью, я сознательно не стал заострять внимание на большом количестве технических нюансов, чтобы донести только саму суть подхода с использованием ArgoCD, ApplicationSet и Generator
Приведу ссылки на полезную документацию, которая поможет вам разобраться в тонкостях настройки:
OkGoLove
Спустя довольно длительное время использования единого ArgoCD инстанса пришли к тому, что все же лучше иметь ArgoCD per cluster. Как минимум бласт радиус снижается значительно.
Sammyant Автор
Согласен + каждый выбирает из своих условий и ограничений)
Если у вас несколько команд, каждая из которых управляет своим кластером - действительно, можно делать ArgoCD на кластер или разграничить правами в рамках одного ArgoCD.
Если у вас одна команда, отвечающая за все кластера - централизованный ArgoCD выглядит удобнее (единая консоль управления).
Но во всех случаях не исключается человеческая ошибка, когда зашли не в тот ArgoCD или не в тот репозиторий положили обновленный манифест)
OkGoLove
Даже в контексте одной команды у нас был кейс, когда человек случайно поставил галочки Force и Replace при синке main app (app of apps). Нас спасло то, что ArgoCD зареплейсил сам себя (и пересоздал под в результате) и процесс остановился.