организовать GitOps-репозитории для нескольких окружений Kubernetes

Структура GitOps-репозиториев: несколько окружений Kubernetes в масштабе

14 минут

Плоские папки с манифестами ломаются, когда появляются кластеры и команды. Разделите репозитории приложений с оверлеями Kustomize, репозиторий платформы для общих компонентов и при необходимости отдельный control plane окружений — плюс продвижение через PR, CODEOWNERS и External Secrets.

Почему плоские папки GitOps не выдерживают рост флота

Один репозиторий с одной папкой манифестов подходит для одного приложения и одного кластера. Он ломается, когда появляются development, staging, production и несколько команд. Изменения для staging попадают в production, потому что границы держатся на договорённостях, а не на структуре. Команды копируют Deployment в папки окружений; копии расходятся, и непонятно, какой файл — источник истины. Все открывают pull request в один монорепозиторий, ревью копятся днями. Секреты оказываются в истории Git без явного владельца. Простые вопросы становятся сложными: кто владеет payment-service в production и какая версия чарта там крутится? Одна ошибка в раскладке репозитория проходит через каждый выкат и каждое расследование инцидента.

Многоуровневая стратегия: частота изменений и владение

Отделяйте то, что меняется часто, от того, что меняется редко, и конфигурацию команды от конфигурации платформы. Первый уровень — репозитории приложений на команду: код, Dockerfile, манифесты или чарты и CI. Второй — репозиторий инфраструктуры платформы для аддонов кластера, мониторинга, ingress и общих компонентов. Третий уровень опционален при пяти и более кластерах или в регулируемых средах: репозиторий control plane окружений с регистрацией кластеров, AppProject, шаблонами namespace и RBAC, политиками. Команды приложений не правят platform-infra. Команда платформы почти не трогает оверлеи приложений. Так pull request про лимиты ресурсов не переписывает production ingress controller.

Репозитории приложений с оверлеями Kustomize

Держите сервис рядом с конфигурацией выката. Если манифесты ваши — удобны base и оверлеи Kustomize: патчи видны в ревью без полного рендера Helm. Общие поля — в base; число реплик, лимиты, хосты ingress и ConfigMap окружения — только в оверлеях. Helm уместен для сторонних чартов: версию чарта фиксируйте в platform-infra или в репозитории приложения, а values по окружениям играют роль оверлея. CI должен обновлять тег или digest образа через images в Kustomize или через Helm set, а не руками править production YAML после merge.

Text · раскладка репозитория приложения
team-payment/
├── src/
├── Dockerfile
├── k8s/
│   ├── base/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   ├── hpa.yaml
│   │   └── kustomization.yaml
│   ├── overlays/
│   │   ├── dev/
│   │   ├── staging/
│   │   └── production/
│   └── README.md
└── .github/workflows/
    ├── build.yaml
    └── validate.yaml

Инфраструктура платформы и опциональный control plane окружений

Репозиторий платформы хранит кластерные и общие аддоны: cert-manager, ingress-nginx, values мониторинга, сетевые политики, External Secrets Operator и сам GitOps-контроллер. Раскладывайте по кластерам в clusters/ и выносите переиспользуемые компоненты в components/ с оверлеями по окружениям. Когда флотов становится больше, перенесите Application и ApplicationSet Argo CD, объекты GitRepository и Kustomization Flux, политики Kyverno или Gatekeeper, RBAC namespace в отдельный репозиторий окружений — чтобы операционные изменения не смешивались с выкатами приложений. Опишите продвижение: сначала нижний оверлей, затем pull request поднимает тот же образ и те же дельты конфигурации выше.

Оверлеи Kustomize, в которых видны отличия окружений

Base не привязан к окружению. Оверлей production задаёт namespace, метки, патчи реплик и ресурсов и добавляет ресурсы только для production: PodDisruptionBudget, Ingress, NetworkPolicy. Собирайте kustomize build в CI и локально до merge. Pull request только по overlays/production не меняет dev. В верхних окружениях фиксируйте образы по digest, если нужно неизменяемое продвижение.

YAML · базовый Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  labels:
    app.kubernetes.io/name: payment-service
    app.kubernetes.io/part-of: payments
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: payment-service
  template:
    metadata:
      labels:
        app.kubernetes.io/name: payment-service
    spec:
      containers:
        - name: payment
          image: registry.example.com/payment-service:placeholder
          ports:
            - containerPort: 8080
          env:
            - name: LOG_LEVEL
              value: info
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
YAML · оверлей production Kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: payment-prod
labels:
  - pairs:
      environment: production
      app.kubernetes.io/name: payment-service
    includeSelectors: false
resources:
  - ../../base
  - pdb.yaml
  - ingress.yaml
  - networkpolicy.yaml
images:
  - name: registry.example.com/payment-service
    newTag: "2.4.1"
    digest: sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
patches:
  - path: replicas-patch.yaml
  - path: resource-limits-patch.yaml
  - target:
      kind: Deployment
      name: payment-service
    patch: |-
      - op: replace
        path: /spec/template/spec/containers/0/env/0/value
        value: warn
YAML · патчи реплик и ресурсов для production
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 5
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  template:
    spec:
      containers:
        - name: payment
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              cpu: "1"
              memory: 1Gi
Bash · сборка и проверка оверлея
#!/usr/bin/env bash
set -euo pipefail
kustomize build k8s/overlays/production | kubeconform -strict -kubernetes-version 1.30.0
kubectl kustomize k8s/overlays/production | kubectl diff -f - || true

Связка репозиториев: ApplicationSet, CODEOWNERS и проверка в CI

Не выводите URL кластера из имени папки оверлея. Явно сопоставьте окружения через list- или matrix-генератор — так destination и namespace остаются осознанными. Ограничьте AppProject Argo CD: какие репозитории и кластеры доступны команде. В Flux ту же идею дают GitRepository и Kustomization на окружение. CODEOWNERS требуют ревью платформы на оверлеи production и пути platform-infra. На каждом pull request проверяйте все оверлеи через kubeconform — ошибки схемы должны падать в CI, а не в failed sync на production.

YAML · ApplicationSet Argo CD с явным соответствием окружений
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: payment-service
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - env: dev
            cluster: https://kubernetes.default.svc
            namespace: payment-dev
          - env: staging
            cluster: https://staging.cluster.example.com
            namespace: payment-staging
          - env: production
            cluster: https://prod.cluster.example.com
            namespace: payment-prod
  template:
    metadata:
      name: payment-service-{{env}}
    spec:
      project: payments
      source:
        repoURL: https://github.com/myorg/team-payment
        targetRevision: main
        path: k8s/overlays/{{env}}
      destination:
        server: '{{cluster}}'
        namespace: '{{namespace}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true
Text · CODEOWNERS для платформы и приложения
# platform-infra
/clusters/production/  @platform-team-sre
/clusters/staging/     @platform-team-sre
/components/           @platform-team-core

# team-payment
/k8s/overlays/production/  @payment-team-lead @platform-team-sre
/k8s/overlays/dev/         @payment-team
YAML · GitHub Actions: kubeconform по всем оверлеям
name: Validate Kubernetes manifests
on:
  pull_request:
    paths:
      - "k8s/**"
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install tools
        run: |
          curl -sL https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash
          sudo mv kustomize /usr/local/bin/
          curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz | tar xz
          sudo mv kubeconform /usr/local/bin/
      - name: Build and validate overlays
        run: |
          for dir in k8s/overlays/*/; do
            echo "=== Validating ${dir} ==="
            kustomize build "${dir}" | kubeconform -strict -kubernetes-version 1.30.0
          done

Секреты, дрифт, продвижение и документация

Не коммитьте значения секретов. Храните манифесты ExternalSecret, которые читают AWS Secrets Manager, GCP Secret Manager или Azure Key Vault; на актуальных операторах используйте apiVersion external-secrets.io/v1. Включайте selfHeal для безопасного дрифта и уведомления при сбоях sync, чтобы расхождения не оставались незаметными. Продвигайте через pull request: сначала staging, проверка, затем PR в оверлей production с тем же digest — история Git и есть аудит. В каждом репозитории держите README: владелец, как добавить оверлей, путь продвижения. Для трёх–десяти кластеров достаточно репозиториев приложений и одного platform-infra. Дальше или при жёстком аудите добавьте репозиторий control plane окружений и относитесь к раскладке репозиториев как к архитектуре, а не к косметике папок.

YAML · ExternalSecret со ссылкой на облачное хранилище
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: payment-db-credentials
  namespace: payment-prod
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: payment-db-credentials
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: /payment/prod/db-password

Выбор оператора и семантика синхронизации Argo CD и Flux разобраны в гайде по согласованности и соответствию требованиям в GitOps.

Preview-окружения в той же модели оверлеев описаны в гайде по эфемерным namespace для preview pull request.