Динамические тестовые окружения на ArgoCD и Crossplane

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

Всем привет! Хочу поделиться вариантом организации динамических окружений для разработки и тестирования с помощью ArgoCD и применением GitOps подхода на реальном примере.

Статья рассчитана на DevOps инженеров и разработчиков уже хорошо знакомых с такими инструментами как:

  • Kubernetes

  • Helm

  • Crossplane

  • ArgoCD

  • GitLab CI

Краткая логика работы пайплайна.

  1. Разработчик пушит новую ветку c постфиксом -dyn в названии в репозиторий с project-backend

  2. Стартует GitLabCI пайплайн:

    • билдит docker image, пушит его в image registry.

    • "идет" в репозиторий manifests, отрезает новую ветку - имя которой идентичное имени ветки в исходном репозитории - project-backend

    • обновляет image tag контейнера в Helm values для окружения и пушит изменения

  3. ApplicationSet в ArgoCD отслеживает изменение в репозитории manifests, "видит" новую ветку с постфиксом и создает новый Application вместе с базой.

  4. При удалении ветки из репозитория - удаляется App и БД из облака.

Из официальной документации ArgoCD с переводом:

Argo CD следует схеме GitOps, использующей репозитории Git в качестве источника достоверной информации для определения желаемого состояния приложения.

Argo CD реализован как контроллер kubernetes, который постоянно отслеживает запущенные приложения и сравнивает текущее рабочее состояние с желаемым целевым состоянием (как указано в репозитории Git). Развернутое приложение, текущее состояние которого отличается от целевого состояния, считается OutOfSync. Argo CD сообщает и визуализирует различия, предоставляя средства для автоматической или ручной синхронизации реального состояния с желаемым целевым состоянием. Любые изменения, внесенные в желаемое целевое состояние в репозитории Git, могут автоматически применяться и отражаться в указанных целевых средах.

Crossplane позволяет описывать облачные ресурсы в формате k8s yaml манифестов, "применять" их в кластер и создавать ресурсы в облаке согласно описанному состоянию.

Вводные:

  • используем ресурсы в Yandex Cloud

  • классический проект -2 репозитория - project-frontend, project-backend

  • артефакты (docker image) проекта "собираются" в GitLab CI

  • собранные артефакты проекта - 2 docker image - project-frontend, project-backend

  • приложение деплоится в Kubernetes cluster через Helm (имеет Helm chart)

  • Git репозиторий manifests с Helm charts приложения и crossplane ресурсов (БД, DataTransfer и т.д.)

  • для работы project-backend необходима база данных - PostgreSQL - для создания БД используем Yandex Cloud DataTransfer - копируем данные из исходной - "эталонной" БД в новую.

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

argocd:
  argo-application-sets:
    - project-dyn-envs-manifest-repo-scm.yaml # AppSet for project
  argo-projects:
    - project-dyn.yaml # ArgoCD project for dyn envs

charts: # project Helm Charts
  project-backend:
  project-dyn-infra
  project-frontend
  project-stack: # Main project chart - in deps has back, front and infra chart
    ci:
      dyn:
        - dyn-values.yaml

custom-manifests:
  crossplane-dyn-envs
    external: 
      # external resources created not from crossplane 
      # used for dyn envs (DB clusters, VPS, etc) - imported to crossplane
      folders
      postgres
      vpc
  crossplane-provider-config-yc # YC provider config

project-dyn-envs-manifest-repo-scm.yaml

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: project-stack-dyn
  namespace: argocd
spec:
  generators:
    - scmProvider:
        cloneProtocol: ssh
        filters:
          - branchMatch: .*PROJECT.*-dyn
            repositoryMatch: manifests
        gitlab:
          group: "12345678" 
          allBranches: true
          includeSubgroups: true
          tokenRef:
            secretName: gitlab-token
            key: token
  template:
    metadata:
      name: "{{branchNormalized}}"
    spec:
      source:
        path: charts/project-stack
        repoURL: https://gitlab.com/manifests.git
        targetRevision: "{{branch}}"
        helm:
          valueFiles:
            - ci/dyn/dyn-values.yaml
          values: |
            project-backend:
              extraVolumes:
                - name: dynenv
                  configMap: 
                    name: {{branchNormalized}}-project-backend
                - name: dynentrypointinitdta
                  configMap: 
                    name: {{branchNormalized}}-project-backend-entrypointinitdta
                    defaultMode: 0755
              envConfig:
                dynenv: |
                  BRANCH_NAME="{{branchNormalized}}"
                  TARGET_DB="{{branchNormalized}}-t-db"
                FRONT_BASE_URL: {{branchNormalized}}.dyn.project.ru
                APPLICATION_REDIS_KEY_PREFIX: "project:{{branchNormalized}}:"
                DATASOURCE_URL: "jdbc:postgresql://db-dyn-project.ru:6432/{{branchNormalized}}-t-db"
                DATASOURCE_USERNAME: "{{branchNormalized}}-t-db-user"
                DATASOURCE_PASSWORD: "12345678"
            project-ingress:
              hosts:
                - host: {{branchNormalized}}.dyn.project.ru
              tls:
                - secretName: {{branchNormalized}}-dyn-project-ru
                  hosts:
                    - {{branchNormalized}}.dyn.project.ru
            project-dyn-infra:
              enabled: true
              fullnameOverride: {{branchNormalized}}
              namespace: argo-app-project-dyn
              dataTransfer:
                enabled: "true"
                endpoints:
                  target:
                    dbName: db-project-dyn-{{branchNormalized}}
                    dbUser: db-user-project-dyn-{{branchNormalized}}
      project: project-dyn
      destination:
        namespace: 'argo-app-project-dyn'
        server: https://XXX.XXX.XXX.XXX
      syncPolicy:
        automated:
          selfHeal: true
          prune: true
          allowEmpty: true
        syncOptions:
          - CreateNamespace=true

ApplicationSet использует SCM provider geneator - позволяет по тригеру (появлению в репозитории manifestsновой ветки, имя которой попадает по Regexp создавать новое приложения в ArgoCD) AppSet генерирует параметры (helm values) для нового приложения на основе имени ветки. Можно использовать в параметрах sha комита или например имя репозитория, полный список параметров можно посмотреть в документации генератора.

project-stack Helm Chart

apiVersion: v2
description: project stack (project) Helm chart
type: application
maintainers:
  - name: xxx
    email: xxx
version: 0.1.0
appVersion: 1.0.0
kubeVersion: ">=1.23.0-0"
keywords:
- project
annotations:
  "finalizers": "resources-finalizer.argocd.argoproj.io"
  "gitlab.com/links": |
    - name: Chart Source
      url: https://gitlab.com/project
    - name: Upstream Projects
      url: https://gitlab.com/project
dependencies:
  # Project
  - name: project-frontend
    condition: project-frontend.enabled
    version: "0.1.*"
    repository: "file://../project-frontend"
  - name: project-backend
    condition: project-backend.enabled
    version: "0.1.*"
    repository: "file://../project-backend"
  - name: project-ingress
    condition: project-ingress.enabled
    version: "0.1.*"
    repository: "file://../project-ingress"
  - name: project-dyn-infra
    condition: project-dyn-infra.enabled
    version: "0.1.*"
    repository: "file://../project-dyn-infra"

Crossplane

Допустим crossplane уже установлен и сконфигурирован в кластере

Crossplane используем для создания новой БД для project-bakcned и DataTranser. При создании окружения БД через DataTransfer копируется из исходной - эталонной БД в новую созданную для окружения.

Манифесты Crossplane "упакованы" в Helm chart, приведу пример templates и valuesдля Yandex Cloud:

Новая БД для project-backend db-target.yaml

Тут важный момент - мы используем отдельно созданный кластер PostgreSQL для всех БД в динамических окружениях, который под управлением Terraform - он импортирован в Crossplane как внешний ресурс. Но никто не мешает создавать новые кластера Postgres из Crosplane.

{{- if (eq .Values.dataTransfer.enabled "true") }}
apiVersion: mdb.yandex-cloud.jet.crossplane.io/v1alpha1
kind: PostgresqlDatabase
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}-t-db
spec:
  #  deletionPolicy: Orphan
  providerConfigRef:
    name: {{ toYaml .Values.providerConfigRef.name }}
  forProvider:
    name: {{ template "project-dyn-infra.fullname" . }}-t-db
    clusterId: {{ toYaml .Values.targetDb.clusterId }}
    owner: {{ template "project-dyn-infra.fullname" . }}-t-db-user
    lcCollate: en_US.UTF-8
    lcType: en_US.UTF-8
    extension:
      - name: uuid-ossp
{{- end }}

БД пользователь - в Yandex Cloud пользователей можно создавать только через API:

db-user-target.yaml

{{- if (eq .Values.dataTransfer.enabled "true") }}
apiVersion: mdb.yandex-cloud.jet.crossplane.io/v1alpha1
kind: PostgresqlUser
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}-t-db-user
spec:
  #  deletionPolicy: Orphan
  providerConfigRef:
    name: {{ toYaml .Values.providerConfigRef.name }}
  forProvider:
    name: {{ template "project-dyn-infra.fullname" . }}-t-db-user
    passwordSecretRef:
      name: {{ template "project-dyn-infra.fullname" . }}-t-db-password
      namespace: {{ template "project-dyn-infra.namespace" . }}
      key: password
    clusterId: {{ toYaml .Values.targetDb.clusterId }}
    connLimit: 35
    login: true
    grants:
      - mdb_admin
      - mdb_replication
{{- end }}

datatransfer.yaml

# Secret с паролем от новой БД
{{- if (eq .Values.dataTransfer.enabled "true") }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}-t-db-password
type: Opaque
data:
  password: {{ toYaml .Values.targetDb.password }}

---
# Secret с паролем от БД источника
apiVersion: v1
kind: Secret
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}-s-db-password
type: Opaque
data:
  password: {{ toYaml .Values.dataTransfer.endpoints.source.password }}

---
apiVersion: datatransfer.yandex-cloud.jet.crossplane.io/v1alpha1
kind: Endpoint
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}-source
spec:
  #  deletionPolicy: Orphan
  forProvider:
    folderId: {{ toYaml .Values.dataTransfer.endpoints.source.folderId }}
    name: {{ template "project-dyn-infra.fullname" . }}-source
    settings:
      - postgresSource:
        - database: {{ toYaml .Values.dataTransfer.endpoints.source.dbName }}
          user: {{ toYaml .Values.dataTransfer.endpoints.source.dbUser }}
          password:
            - rawSecretRef:
                name: {{ template "project-dyn-infra.fullname" . }}-s-db-password
                namespace: {{ template "project-dyn-infra.namespace" . }}
                key: password
          objectTransferSettings:
            - function: AFTER_DATA
          connection:
          - mdbClusterIdRef:
              name: {{ toYaml .Values.dataTransfer.endpoints.source.mdbClusterIdRef }}
  providerConfigRef:
    name: {{ toYaml .Values.providerConfigRef.name }}

---
apiVersion: datatransfer.yandex-cloud.jet.crossplane.io/v1alpha1
kind: Endpoint
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}-target
spec:
  #  deletionPolicy: Orphan
  forProvider:
    folderId: {{ toYaml .Values.dataTransfer.endpoints.target.folderId }}
    name: {{ template "project-dyn-infra.fullname" . }}-target
    settings:
      - postgresTarget:
        - database: {{ template "project-dyn-infra.fullname" . }}-t-db
          user: {{ template "project-dyn-infra.fullname" . }}-t-db-user
          password:
            - rawSecretRef:
                name: {{ template "project-dyn-infra.fullname" . }}-t-db-password
                namespace: {{ template "project-dyn-infra.namespace" . }}
                key: password
          connection:
          - mdbClusterIdRef:
              name: {{ toYaml .Values.dataTransfer.endpoints.target.mdbClusterIdRef }}
  providerConfigRef:
    name: {{ toYaml .Values.providerConfigRef.name }}

---
apiVersion: datatransfer.yandex-cloud.jet.crossplane.io/v1alpha1
kind: Transfer
metadata:
  name: {{ template "project-dyn-infra.fullname" . }}
spec:
#  deletionPolicy: Orphan
  forProvider:
    folderId: {{ toYaml .Values.dataTransfer.config.folderId }}
    name: {{ template "project-dyn-infra.fullname" . }}
    sourceIdRef:
      name: {{ template "project-dyn-infra.fullname" . }}-source
    targetIdRef:
      name: {{ template "project-dyn-infra.fullname" . }}-target
    type: {{ toYaml .Values.dataTransfer.config.type }}
  providerConfigRef:
    name: {{ toYaml .Values.providerConfigRef.name }}
{{- end }}

project-dyn-infra - values

enabled: false

providerConfigRef:
  name: yc

targetDb:
  clusterId: XXX
  name: db-project-dyn-{{branchNormalized}}
  owner: db-user-project-dyn-{{branchNormalized}}
  user: db-user-project-dyn-{{branchNormalized}}
  password: XXX # base64

dataTransfer:
  enabled: "false"
  config:
    folderId: XXX
    type: "SNAPSHOT_ONLY"
  endpoints:
    source:
      folderId: XXX
      dbName: propject_source_db
      dbUser: project_source_db_user
      password: XXX
      mdbClusterIdRef: XXX
    target:
      folderId: XXX
      mdbClusterIdRef: project-dyn-envs

Через Crossplane невозможно изменять параметры endpoints (политика копирования, порядок переноса данных) в DataTransfer, так же нет возможности активировать DataTransfer с параметром SNAPSHOT_ONLY - это возможно сделать только через API.

Для обхода ограничений в values (project-backend) init container который изменяет параметры DataTransfer после его создания, активирует трансфер и проверяет его статус. После успешного завершения - стартует контейнер с беком.

В чарт с беком (project-backend) добавлен ConfigMap в котором описан скрипт - entrypoint для init container (dynInitEntrypointDta) - скрипт монтируется как extraVolumes в init container.

Для определения - с каким именно трансфером должен работать скрипт "проброшено" имя ветки через ConfigMap - dynenv в init container

Транспорт 

Что бы не генерить новые DNS записи для окружений заведена запись указывающая на балансир кластера - *.dyn.project.ru

Ограничение и недостатки 

  • имя ветки - id окружения

  • постфикс dyn и имя проекта в имени ветки (PROJECT-7777-my-branch-dyn)

  • одинаковое имя ветки в репозитории фронта и бека

  • длина имени ветки не более 63 символов

  • при создании окружения берется из эталонной БД  на момент деплоя окружения

  • при обновлении версии бека - БД не пересоздается - если нужно пересоздать - дропаем App в ArgoCD

  • трансфер БД требует времени

  • нет актуализации версии бека и фронта с продом (деплоим фронт - фронт соберется, версия бека будет из dev ветки manifests (project-stack/dyn/values.yaml)

  • при копировании БД не переносятся math views - ограничение DataTransfer

  • список созданных окружений можно посмотреть только в ArgoCD

  • удаление окружения и ресурсов = удаление ветки в manifests


Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Используете ArgoCD?
0% Да, в проде 0
0% Да, для тестовых окружений 0
0% Нет, но хотим 0
100% Нет 2
Проголосовали 2 пользователя. Воздержавшихся нет.
Источник: https://habr.com/ru/post/717392/


Интересные статьи

Интересные статьи

Если Вы используете Битрикс24 и у Вас один из архивных тарифов, то наверняка Вы уже в курсе того, что в 2023 году планируется прекратить их поддержку. Если нет, то сейчас самое время чтобы об этом пог...
Задача - сделать AMP версию всего сайта на 1С-Битрикс, чтобы содержание AMP страниц всегда соответствовало оригинальным и изменялось при изменении оригинальных.
Статья посвящена так называемым review-окружениям, реализуемым в рамках кластеров Kubernetes. Ранее эта тема затрагивалась, например, в нашем докладе «Лучшие практики CI/CD с Kubernetes и GitLab», но ...
Появившиеся в 2006 году сервисы Google по работе с текстовыми документами (Google Docs) и таблицами (Google Sheets), дополненные 6 лет спустя возможностями работы с вирту...
В этой статье мы расскажем, как оптимизировать крупный проект в «Битрикс24» и увеличить его производительность в 3 раза, изменяя настройки MySQL и режим питания CPU. Дано Корпоративн...