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ı 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:
- 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ç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.