a laptop computer sitting on top of a desk

Bir SaaS müşterimiz 7 microservice (auth, api, worker, scheduler, notifier, reporter, gateway) ve 4 environment (dev/staging/uat/prod) ile çalışıyordu. Her servis için ayrı Helm chart, her environment için ayrı values dosyası — toplam 28 deployment kombinasyonu, manuel yönetilmesi imkansızdı. Bu yazı 4 hafta süren refactor’dan notlar.

Chart Yapısı: Umbrella Chart Pattern

charts/
├── platform/                       # Umbrella chart
│   ├── Chart.yaml                  # Tüm sub-chart'ları dependency olarak listeler
│   ├── values.yaml                 # Common values
│   ├── values-dev.yaml
│   ├── values-staging.yaml
│   ├── values-uat.yaml
│   ├── values-prod.yaml
│   ├── templates/
│   │   ├── _helpers.tpl
│   │   ├── namespace.yaml
│   │   └── shared-configmap.yaml
│   └── charts/                     # Sub-chart'lar
│       ├── auth/
│       ├── api/
│       ├── worker/
│       ├── scheduler/
│       ├── notifier/
│       ├── reporter/
│       └── gateway/

Her sub-chart kendi Chart.yaml + values.yaml + templates’lerine sahip. Umbrella tüm sub-chart’ları tek seferde deploy ediyor.

# platform/Chart.yaml
apiVersion: v2
name: platform
version: 1.4.0
appVersion: "2026.04"
dependencies:
  - name: auth
    version: 1.2.0
    repository: "file://./charts/auth"
  - name: api
    version: 1.5.0
    repository: "file://./charts/api"
  - name: worker
    version: 1.1.0
    repository: "file://./charts/worker"
  - name: postgresql
    version: 14.0.0
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
  - name: redis
    version: 18.0.0
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

Values Hierarchy

Override sırası (sonraki öncekini ezer):

  1. Sub-chart values.yaml (default)
  2. Umbrella values.yaml (common override)
  3. Environment-specific values-prod.yaml
  4. –set CLI flag (CI/CD’den runtime values)
# platform/values.yaml (common, all envs)
global:
  imageRegistry: registry.example.com
  imagePullSecrets:
    - name: regcred
  domain: example.com

api:
  replicaCount: 2
  resources:
    requests: {cpu: 100m, memory: 128Mi}
    limits: {cpu: 500m, memory: 512Mi}

worker:
  replicaCount: 1

postgresql:
  enabled: true
  auth:
    database: app
  primary:
    persistence:
      size: 20Gi
# platform/values-prod.yaml (production override)
global:
  domain: app.example.com

api:
  replicaCount: 6
  resources:
    requests: {cpu: 500m, memory: 512Mi}
    limits: {cpu: 2000m, memory: 2Gi}
  hpa:
    enabled: true
    minReplicas: 6
    maxReplicas: 30
    targetCPUUtilization: 70

worker:
  replicaCount: 4

postgresql:
  enabled: false  # Prod'da Cosmos DB kullanılıyor, Helm postgres değil

externalDb:
  enabled: true
  host: postgres-prod.example.com
  port: 5432

Deploy Komutları

# Dev'e deploy
helm upgrade --install platform-dev ./charts/platform 
  --namespace dev 
  --create-namespace 
  --values ./charts/platform/values.yaml 
  --values ./charts/platform/values-dev.yaml 
  --set global.imageTag=$(git rev-parse --short HEAD)

# Prod'a deploy
helm upgrade --install platform-prod ./charts/platform 
  --namespace production 
  --create-namespace 
  --values ./charts/platform/values.yaml 
  --values ./charts/platform/values-prod.yaml 
  --set global.imageTag=v2026.04.18 
  --atomic --timeout 10m

–atomic flag: deploy başarısız olursa otomatik rollback. –timeout 10m: 10 dakika içinde tüm pod’lar Ready olmazsa fail.

Secret Management: External Secrets Operator

Hassas secret’lar Helm values’a yazılmaz (commit ediliyor!). External Secrets Operator ile Azure Key Vault / AWS Secrets Manager / Vault’tan çekilir.

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    kind: ClusterSecretStore
    name: azure-keyvault
  target:
    name: api-secrets  # Bu Kubernetes Secret oluşturulur
    creationPolicy: Owner
  data:
    - secretKey: db-password
      remoteRef:
        key: prod-api-db-password
    - secretKey: jwt-secret
      remoteRef:
        key: prod-api-jwt-secret
    - secretKey: stripe-api-key
      remoteRef:
        key: prod-stripe-api-key

Helm chart sadece Secret reference ediyor (secretKeyRef), secret içeriği Key Vault’tan otomatik geliyor.

Helm Hooks: Pre-Install Migration

Database migration deployment öncesi çalışmalı. Helm hook ile:

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate-{{ .Release.Revision }}
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: registry.example.com/api:{{ .Values.global.imageTag }}
          command: ["npm", "run", "db:migrate"]
          envFrom:
            - secretRef:
                name: api-secrets

helm upgrade çalıştırıldığında: önce migrate job çalışır, başarılıysa pod’lar deploy edilir. Job fail ederse upgrade durur.

Helm Test: Smoke Test

apiVersion: v1
kind: Pod
metadata:
  name: api-smoke-test
  annotations:
    "helm.sh/hook": test
spec:
  containers:
    - name: smoketest
      image: curlimages/curl
      command:
        - sh
        - -c
        - |
          curl -fsSL http://api/health || exit 1
          curl -fsSL http://api/api/v1/status || exit 1
  restartPolicy: Never
helm test platform-prod --namespace production

Deployment sonrası smoke test çalıştırır, fail ederse alarm.

Rollback Strategy

# Mevcut release history
helm history platform-prod -n production

# REVISION  STATUS    DESCRIPTION
# 14        deployed  Upgrade complete
# 13        superseded Upgrade complete
# 12        superseded Upgrade complete

# Bir önceki sürüme rollback
helm rollback platform-prod 13 -n production --wait --timeout 5m

Helm her revision’ı ConfigMap olarak saklıyor (default 10), rollback bir komut.

ArgoCD ile GitOps

Manuel helm install yerine ArgoCD: Git repo’da Helm chart + values, ArgoCD sync eder, drift detect eder.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform-prod
spec:
  destination:
    namespace: production
    server: https://kubernetes.default.svc
  source:
    repoURL: https://github.com/myorg/charts
    targetRevision: main
    path: charts/platform
    helm:
      valueFiles:
        - values.yaml
        - values-prod.yaml
      parameters:
        - name: global.imageTag
          value: v2026.04.18
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Sahada Düşülen Üç Tuzak

  1. Sub-chart version’ı pin’lememek: Bitnami postgres chart’ı major version atladığında schema breaking. ~14.x.x gibi semver pin gerekli.
  2. helm template ile diff yapmamak: Production’a değişiklik geçmeden önce helm diff plugin ile farkı incele, sürpriz değişiklik yakala.
  3. Secret’ları Helm values’a koymak: chart commit’lendiğinde secret git history’de. External Secrets Operator veya Sealed Secrets şart.

CloudSpark olarak Helm chart tasarımı, multi-environment values yönetimi, ArgoCD entegrasyonu ve secret rotation mimarileri için danışmanlık veriyoruz.

🇹🇷 Türkçe🇬🇧 English🇩🇪 Deutsch🇫🇷 Français🇸🇦 العربية🇷🇺 Русский🇪🇸 Español