ArgoCD ile GitOps: Helm Tabanlı Mikroservisler için Mono-Repo Düzeni
İ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ı kat kat azaldı.
Bu yazıda vardığımız düzen ve onu çalıştıran ArgoCD yapılandırması var, trade-off'larıyla birlikte.
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:
- Bir alan üç ortamda da aynıysa, ortam başına dosyalarda değil, chart'ın default
values.yaml'ında olmalı. - 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çinse genellikle araya bir insan girsin 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 promote etmek, dev tag'ini
values/stage.yaml'a kopyalayan bir PR. ArgoCD merge'de yakalar. - Prod'a promote etmek de aynı işlem, bu kez
values/prod.yaml'a karşı; PR'ı dev release'ini yapan kişi açar, ikinci bir insan review eder.
Evet, tam otomasyona göre daha fazla tıklama var; bilerek böyle. Promotion, yavaşlamanız gereken an.
Sıralama için sync wave'leri
Kaynak uygulama sırası önemli olduğunda (CRD'ler onları kullanan operator'lerden önce, namespace'ler içlerindeki iş yüklerinden önce), 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 tek Application'ın içinde saklanan 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 her seferinde bunu servis başına repo'ya tercih ederim; tutarlılık kazancı çok büyük.
Açıkça söylenmesi gereken diğer trade-off: ArgoCD'de auto-sync + selfHeal + prune üçü birlikte açıkken, Git'te olmayan her cluster state'i silinir. Prod için doğru default bu (source of truth Git'tir) ama elle kubectl apply yapanı da 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.