ArgoCD ile GitOps: Helm Tabanlı Mikroservisler için Mono-Repo Düzeni

2026-04-30 11 min

İnşa ettiğim ilk GitOps kurulumu yanlış şekildeydi. Her mikroservisin kendi repo'su, her repo'nun kendi Helm chart'ı, her chart'ın aylar içinde birbirinden ayrışan üç neredeyse-aynı values-{env}.yaml dosyası vardı. Ortak bir label'ı güncellemek 30 repo'da 30 PR demekti. Tek mono-repo'ya refactor ettik ve operasyonel acı bir kat azaldı.

Bu yazı vardığımız düzen, beraberindeki trade-off'lar ve bunu çalıştıran ArgoCD yapılandırması.

Repo düzeni

infra/
├── apps/
│   ├── checkout/
│   │   ├── chart/                 # Helm chart (templates/, Chart.yaml, base values)
│   │   └── values/
│   │       ├── dev.yaml
│   │       ├── stage.yaml
│   │       └── prod.yaml
│   ├── search/
│   │   ├── chart/
│   │   └── values/
│   └── ...
├── platform/
│   ├── argocd/                    # ArgoCD'nin kendisi, bir kez manuel bootstrap
│   ├── ingress-nginx/
│   ├── cert-manager/
│   ├── kube-prometheus-stack/
│   └── argo-applications/         # App-of-apps ArgoCD Application'ları burada
│       ├── dev/
│       ├── stage/
│       └── prod/
└── shared/
    ├── charts/                    # App'ler tarafından referans verilen ortak subchart
    └── policies/                  # Cluster genelinde uygulanan OPA/Kyverno policy'leri

Üç üst seviye alan: apps (mikroservisler), platform (uygulama katmanının altındaki her şey) ve shared (gerçekten ortak varlıklar). Daha şık yapıları denedim; ölçeklenen en küçük yapı bu.

App-of-apps deseni

En önemli tek ArgoCD deseni: ortam başına bir kök Application, başka Application manifest'leriyle dolu bir dizini işaret eder. ArgoCD kök'ü reconcile eder, kök çocukları oluşturur/günceller, çocuklar gerçek iş yüklerini deploy eder. Yeni bir servis eklemek tek bir dosya ekleyen tek bir PR'dır.

# platform/argo-applications/prod/root.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prod-root
  namespace: argocd
spec:
  destination:
    namespace: argocd
    server: https://kubernetes.default.svc
  source:
    repoURL: https://bitbucket.org/org/infra.git
    targetRevision: main
    path: platform/argo-applications/prod
    directory:
      recurse: true
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Her çocuk Application belirli bir app'in chart'ını ve values dosyasını işaret eder:

# platform/argo-applications/prod/checkout.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: checkout-prod
  namespace: argocd
spec:
  destination:
    namespace: checkout
    server: https://kubernetes.default.svc
  source:
    repoURL: https://bitbucket.org/org/infra.git
    targetRevision: main
    path: apps/checkout/chart
    helm:
      valueFiles:
        - ../values/prod.yaml
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Chart ortamlar arasında aynı. Yalnızca values dosyası değişir.

Values dosya tasarımı

Chart'ın kendi values.yaml'ı güvenli-ama-minimal default'larla gelir. Ortam başına values/{env}.yaml yalnızca farklı olanı override eder:

# apps/checkout/values/prod.yaml
replicaCount: 6
resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    memory: 2Gi
image:
  tag: v1.42.3
ingress:
  host: checkout.example.com

PR review'de zorladığım iki kural:

  1. Bir alan üç ortamda da aynıysa, ortam başına dosyalarda değil, chart'ın default values.yaml'ında olmalı.
  2. Bir alan ortamlar arasında farklıysa, default'a güvenmek yerine her ortam dosyasında olmalı (açık). Sonraki okuyucu üç ortam dosyasını diff'leyip tam hikayeyi görebilmeli.

İmaj tag'leri: düşünmeden bozulan kısım

GitOps'un en basit hali CI'nın imajı build etmesi, ECR/SWR'ye push etmesi ve bir commit-back adımıyla uygun values/{env}.yaml'a yeni tag'i yazmasıdır. Bu dev için çalışır. Prod için genellikle döngüde bir insan istersiniz.

Vardığımız nokta:

  • Feature branch'teki CI imajı build eder, push eder, yeni tag ile values/dev.yaml'a karşı bir PR açar.
  • Merge dev'e otomatik deploy eder.
  • Stage'e terfi, dev tag'ini values/stage.yaml'a kopyalayan bir PR'dır. ArgoCD merge'de yakalar.
  • Prod'a terfi, dev release'ini yapan kişi tarafından values/prod.yaml'a karşı açılan aynı operasyon, ikinci bir insan tarafından review edilir.

Bu, kasıtlı olarak tam otomasyondan daha çok tıklamadır. Terfi yavaşlama anıdır.

Sıralama için sync wave'leri

Kaynak uygulama sırası önemli olduğunda — CRD'leri onları kullanan operator'lerden önce kurmak, iş yüklerini barındıran namespace'leri önce yaratmak — ArgoCD'nin argocd.argoproj.io/sync-wave annotation'ı tek bir Application içinde sıralar.

metadata:
  annotations:
    argocd.argoproj.io/sync-wave: "-1"  # default 0'dan önce

Wave'leri tutumlu kullanıyorum. Tek bir Application'da üç veya daha fazla wave varsa, genellikle aslında birinin içinde gizlenmiş birden fazla Application var demektir.

Trade-off'lar

Mono-repo GitOps'un gerçek dezavantajları var. Bir infra PR'ın code review'u aynı repo'daki feature işiyle yarışır, kötü bir merge'ün blast radius'u daha yüksektir ve repo büyüdükçe CI yavaşlar. Yine de servis başına repo'lara her seferinde tercih ederim, çünkü tutarlılık üzerindeki kazanç çok büyük.

İsmiyle çağrılması gereken diğer trade-off: ArgoCD auto-sync + selfHeal + prune birlikte demek ki Git'te olmayan cluster üzerindeki herhangi bir state silinir. Bu prod için doğru default (Git source of truth), ve elle kubectl apply yapanları cezalandırır. Açmadan önce ekibin bunu bildiğinden emin olun.

Bu düzende olmayan

Secret'lar repo'da yaşamaz, şifreli bile olsa. AWS Secrets Manager'dan çeken External Secrets Operator kullanıyoruz, secret referansları values dosyalarında parametreli. Helm hook'ları minimum tutulur — ArgoCD'nin sync modeliyle dövüşürler. Çoklu-cluster ApplicationSet'ler güçlü ama onlarla başlamazdım; bir seferde bir cluster, gerçekten ikinci bir cluster olduğunda genelleyin.

Tüm düzen tek bir tree çıktısı ekranına sığar ve mesele de bu. Ekibe katılan herkes bir şeyi dosya adlarını okuyarak bulabilir.