GitOps c ArgoCD для конфигурации кластеров K8S/OKD/Openshift и приложений, размещаемых в них

Всем привет, у нас в НЛМК создается множество приложений и систем для производства и поддержки бизнеса. Большинство размещаются в нашей Единой Цифровой Платформе (коротко ЕЦП), а по-современному: Internal Developer Platform.

0d358ce78e04f74cddf1c79fef5f3708.jpeg

Меня зовут Денис Воронов, я с коллегами как раз занимаюсь развитием Единой Цифровой Платформы НЛМК, частью которой являются кластеры 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 синхронизировать свою конфигурацию с этим репозиторием.

c35b149e4a38d32226eee708e8961f38.jpg

Манифест этого первого 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-ов с конфигурациями кластеров.

Получаем такую структуру репозитория:

d4538fbd3b3f8562f12c3a988894e704.jpg

Предварительно, для подключения в 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": "",
      "tlsClientConfig": {
        "insecure": false,
        "caData": "" # Base64 encoded PEM-encoded bytes
      }
    }

Cертификат кластера и токен service account-а, под которым будет подключаться ArgoCD к кластеру, загружаются из Vault-а, причем, при обновлении значений в Vault-е, ArgoCD сам это замечает и предлагает обновить манифест. Значения для каждого отдельного кластера вносятся в vault после базового развертывания инфраструктуры кластера.

В ArgoCD видим все наши новые кластеры и еще локальный, в котором работает сам ArgoCD:

Application view

Application view

Раздел Settings (Clusters)

Раздел Settings (Clusters)

Реализация доставки разных типов конфигураций в кластеры

И тут нам понадобиться такой ресурс ArgoCD, как ApplicationSet, выступающий в роли генератора дочерних, итоговых Application-ов.

В нашем простом случае, ApplicationSet controller будет отслеживать появление нового кластера, как сущности ArgoCD и генерировать нужные Application, согласно зашитой в его манифесте логике.

Все «слои» наших конфигураций для кластеров разместим в отдельном репозитории clusters-configuration и разложим по подходящей под нашу задачу иерархии, тут очень важно следить за именованием всех директорий, т.к. далее это будет играть важную роль при генерации итоговых Application-ов для кластеров.

35f1ebd642aa25a1d253987a176fb869.jpg

ApplicationSet предоставляет возможность использования различных Generators, которые реализуют логику генерации из шаблона (template) итоговых Application с подстановкой значений.

Рассмотрим на примере одного из самых простых генераторов, необходимого, для решения нашей задачи — Cluster Generator. Данный генератор получает списком все кластеры, подключенные в ArgoCD и определенные параметры из их манифестов ArgoCD этих кластеров (те, что мы создали выше).

Согласно текущей документации, нам доступны из манифеста кластера:

  • Имя кластера (name)

  • Нормализованное имя кластера (nameNormalized)
    (имя в формате прописные буквы/цифры, знак »-» и ».»)

  • URL api сервера (server)

  • Ключи и значения metadata.Labels

  • Ключи и значения секции metadata.annotation

Эти сущности мы можем использовать как для фильтрации, так и как элементы для подстановки в template ApplicationSet-а.

Допустим, мы договорились, что у нас есть некий набор манифестов, который должен быть абсолютно на всех кластерах, для примера Cluster Role и Cluster Role Binding:

88f9820b6861458126ed455d66c6c162.jpg

В этом простом случае, нам необходимо создать 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}}.

Сгенерированные Applications

Сгенерированные Applications

Второй шаг в нашей задаче — деплой манифестов, для каждого кластера будут уникальными (по значениям или по типу).

Как одна из задач: деплой secret-а, содержащего выделенную для конкретного кластера учетную запись для доступа в s3 хранилище. Она может применятся в типовых сервисах, описанных в Base конфигурации.

В репозитории с конфигурациями создаем директорию на каждый кластер

3bfa2709fdc5bc258f7c2a2eab464aa4.jpg

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.

b7da6196991e3f14fba595fca3063633.jpg

Воспользуемся возможностью 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

65cd7ee1685b564362c8cc2d6889e5be.jpg027a9c79570170caa27a53958cb7466e.jpg77f75cb60535dde825fb9f5136bd1f4f.jpg

При использовании данного подхода, установка и настройка базовой конфигурации нового кластера сводится к созданию одного манифеста Secret с описанием кластера для ArgoCD и создания некоторых custom манифестов, а для масштабирования типовых приложений используются labels в манифестах кластеров. Все остальное вручную или автоматически приезжает через ArgoCD.

Чтобы не перегружать статью, я сознательно не стал заострять внимание на большом количестве технических нюансов, чтобы донести только саму суть подхода с использованием ArgoCD, ApplicationSet и Generator

Приведу ссылки на полезную документацию, которая поможет вам разобраться в тонкостях настройки:

© Habrahabr.ru