организовать 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.
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, если нужно неизменяемое продвижение.
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: 512MiapiVersion: 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: warnapiVersion: 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#!/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.
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# 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-teamname: 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 окружений и относитесь к раскладке репозиториев как к архитектуре, а не к косметике папок.
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.
