ArgoCD: деплой в несколько кластеров через ApplicationSet

Общие сведения

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

Задача такая: есть k8s-кластер N, N1, N2 & etc, в каждом из которых есть свой локальный арго. В эти кластеры необходимо деплоить инфровые приложения (ингрес-контроллер, графана и т.п.).

Как правило, приложения поставляются в виде хельм-чартов. Для разделения по окружениям используется отдельный каталог под values.

Можно реализовать деплой по-разному:

  • сделать application на каждое окружение
  • сделать applicationset на каждое окружение
  • сделать applicationset на все окружения

Последний вариант и будет описан далее.

Структура репозитория

Таким образом описанная выше структура репозитория выглядит так:

➜  gitops tree
.
├── charts
│   ├── app1
│   ├── app2
│   └── app3
└── values
    ├── develop
    │   ├── app1.yaml
    │   ├── app2.yaml
    │   ├── app3.yaml
    │   └── config.json
    └── production
        ├── app1.yaml
        ├── app2.yaml
        ├── app3.yaml
        └── config.json

8 directories, 8 files

Есть каталог с чартами, есть каталог с values под условные два окружения – develop и production. Внутри них расположены app-name.yaml под каждое окружение каждого приложения. Вроде всё стандартно.

Но тут появляется config.json в каждом окружении, который содержит следующее:

[
  {
    "appname": "app1",
    // "version": "6.3.2",
    "cluster": "production",
    "namespace": "default"
  },
  {
    "appname": "app2",
    "cluster": "production",
    // "version": "6.3.3",
    "namespace": "default"
  },
  {
    "appname": "app3",
    "cluster": "production",
    // "version": "6.3.3",
    "namespace": "test"
  }
]

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

Например, если указать namespace или cluster в values.yaml, необходимые для использования в applicationset, это может вызвать конфликт с наименованием переменных, которые уже изначально есть в чарте.

То есть мы укажем переменную .values.cluster: develop, которая нужна для арго и его applicationset, а это может оказаться какое-то приложение, которое тоже ожидает эту переменную c другим значением.

Поэтому удобно все переменные для чарта, как и обычно, указывать в values.yaml. А переменные для деплоя и applicationset – в config.json, в котором обязательными полями являются cluster и namespace.

Подготовка кластера

В самом начале было упомянуто, что в каждом кластере есть свой локальный ArgoCD. В манифесте Applicationset есть поле destination, где обычно прописан локальный кластер по умолчанию с именем in-cluster и адресом kubernetes.cluster.local – это имя сервиса из дефолтного namespace.

Основная проблема при деплое в несколько кластеров – как на уровне appset разделить, в какое окружение деплоить, если они все одинаково называются и имеют одинаковые адреса по умолчанию.

Можно настроить внешний доступ, но это скорее в случае когда из одного арго идёт деплой в кластеры, а тут у каждого свой.

И вот есть какое решение: есть опция переименовать дефолтный кластер из in-cluster в нужное окружение, задав ему имя и аннотацию. Это можно сделать либо в настройках самого арго, либо создав секрет:

cat <<EOF | kubectl apply -n argocd -f -
apiVersion: v1
kind: Secret
metadata:
  labels:
    argocd.argoproj.io/secret-type: cluster
    cluster: dev
  name: dev
  namespace: argocd
type: Opaque
stringData:
  name: develop
  server: https://kubernetes.default.svc
EOF

По сути после деплоя самого инстанса арго ему надо явно указать через аннотацию и имя кластера, что он находится в develop или production окружении.

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

Applicationset

А теперь, собственно, как это будет выглядеть:

cat <<EOF | kubectl apply -n argocd -f -
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: appset
spec:
  goTemplate: true
  goTemplateOptions: ["missingkey=error"]
  generators:
    - matrix:
        generators:
          - clusters:
              selector:
                matchExpressions:
                  - key: cluster
                    operator: In
                    values:
                      - dev
                      - prod
          - git:
              repoURL: YOUR_REPO.git
              revision: appset
              files:
                - path: test/values/{{.name}}/config.json
  template:
    metadata:
      name: '{{ .appname }}'
    spec:
      project: default
      sources:
        - repoURL: YOUR_REPO.git
          targetRevision: appset # change branch
          path: test/apps/{{ .appname }}/
          helm:
            valueFiles:
              - "$values/values/{{.name}}/{{.appname}}.yaml"
        - repoURL: 'YOUR_REPO.git'
          targetRevision: appset # change branch
          ref: values
      destination:
        name: "{{.name}}"
        namespace: "{{.namespace}}"
EOF

Как это работает:

  • здесь используется matrix генератор – он необходим, когда есть потребность в создании множества комбинаций, например как случай с несколькими окружениями и кластерами
  • в matrix генераторе могут использоваться только два дочерних генератора – как раз больше и не нужно, в данном случае это генератор кластеров для чтения созданного секрета и гит-генератор для чтения config.json
  • с помощью matchExpressions в генераторе кластеров арго считывает аннотации у созданных секретов, помещая имя кластера и его адрес в переменные .name и .server соот-но. Имя кластера .name равно develop или production.
  • И конкретно на основе переменной .name и настроено разделение по окружениям, в т.ч. название каталогов в гит-репозитории – это самый важный момент и основная идея логики работы апсета
  • Далее арго, получив значение .name, читает значение ./values/{develop,production}/config.json и также позволяет использовать их использовать в шаблоне манифеста для дальнейшей генерации приложений
  • В списке приложения для деплоя, каждый из которых хранится в .appname, поэтому арго пройдется по всем чартам и всем values для них и выполнит рендеринг приложений
  • И в заключении в поле destination будет подставлено имя кластера: develop или production. В каждом окружении будет свой кластер с одним и тем же локальным адресом, но разными именами.
  • Плюс поле namespace, уникальное для каждого приложения каждого кластера.

Заключение

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

Ну, и может быть удобнее, когда есть один апсет на десятки кластеров, вместо кучи однотипных application.

Но тут есть и минусы – схема по началу в целом может показаться излишне сложной, особенно в части отладки. Мне, например, не всегда удобно дебажить по логам applicationset-контроллера, что же пошло не так, и где возникла ошибка.

Да и вообще по сути вместо всего описанного можно было просто использовать один applicationset на каждый кластер. То есть как компромиссное решение. Так меньше вероятность ошибиться и снести\изменить лишнее, или наоборот, затащить лишнее куда-то.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: