Helm Chart Geliştirme: En İyi Uygulamalar
Helm, Kubernetes'in standart paket yöneticisidir ve uygulama deployment'larını tekrarlanabilir ve yönetilebilir kılar. Ancak chart kalitesi, operasyonel maliyetleri doğrudan etkiler. Aşırı soyutlanmış şablonlar, yetersiz test ve gözden kaçan güvenlik açıkları, production'da ciddi sorunlara neden olabilir. Bu makalede, production'a hazır Helm chart'ları geliştirmek için en iyi uygulamalar, yapısal tasarım prensipleri, test stratejileri ve güvenlik konuları detaylı olarak ele alınacaktır.
Chart Yapısı ve Organizasyonu
Standart Dizin Yapısı
Production'a hazır bir Helm chart, belirli bir dizin yapısına uymalıdır. Bu yapı, herhangi bir ekip üyesinin chart'ı kolayca anlamasını ve üzerinde çalışmasını sağlar. İyi organize edilmiş bir chart şu yapıyı takip eder:
``
my-chart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default değerler
├── values.schema.json # Değer doğrulama şeması
├── requirements.yaml # Dependencies (Helm 2 için)
├── charts/ # Subchart'lar
├── crds/ # Custom Resource Definitions
├── templates/ # Kubernetes manifest şablonları
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl # Paylaşılan template fonksiyonları
│ └── tests/ # Test pod'ları
├── ci/ # CI/CD için values dosyaları
└── README.md # Chart dokümantasyonu
`
Her Chart.yaml, metadata'yı açıkça declare etmelidir. Önemli kurallar: Her zaman Helm 3+ için apiVersion: v2 kullanın, chart'ı uygulamadan bağımsız olarak versiyonlayın ve uyumsuz cluster'larda yüklemeyi önlemek için kubeVersion belirtin.
Values Schema Validation
Values schema'sı, yanlış yapılandırmaların cluster'a ulaşmadan yakalanmasını sağlar. Helm, herhangi bir şablonu render etmeden önce bu şemayı doğrular:
`json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"image": {
"type": "object",
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" },
"pullPolicy": { "type": "string", "enum": ["IfNotPresent", "Always", "Never"] }
},
"required": ["repository"]
},
"replicaCount": {
"type": "integer",
"minimum": 1,
"maximum": 10
}
}
}
`
eksik bir required field hemen açık bir hata mesajıyla başarısız olur. Bu, şablon paniğinden çok daha iyidir.
Template Tasarım Prensipleri
DRY ve YAGNI Dengesi
İki kritik prensip, Helm template kalitesini belirler. DRY (Don't Repeat Yourself) prensibi, manifest tekrarını önler. Tekrarlayan kalıpları _helpers.tpl'e çıkarın ve template fonksiyonları olarak yeniden kullanın:
`yaml
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- define "myapp.labels" -}}
app.kubernetes.io/name: {{ include "myapp.fullname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/component: "backend"
app.kubernetes.io/part-of: {{ .Chart.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "myapp.chart" . }}
{{- end }}
`
63 karakter kısaltması opsiyonel değildir. Kubernetes, 63 karakterden uzun isimleri reddeder. Release adınız staging-my-long-application-name olduğunda, bu sınır hızla gelir. Uzun release isimleriyle deployment'ların CI'da başarısız olduğunu gördüm.
YAGNI (You Aren't Gonna Need It) prensibi, aşırı soyutlamayı önler. Her ek fonksiyon, koşullu ifade veya parametre cognitive overhead ekler. Mevcut gereksinimleri karşılayan basit şablonlar yazın. Gelecekteki olası senaryolar için karmaşıklık eklemeyin.
Doğru Deployment Template'i
Production için zorunlu pattern'leri içeren bir Deployment template'i:
`yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "myapp.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "myapp.serviceAccountName" . }}
securityContext:
runAsNonRoot: true
fsGroup: 65534
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- with .Values.readinessProbe }}
readinessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.livenessProbe }}
livenessProbe:
{{- toYaml . | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
`
Bu checksum/config annotation'ı, ConfigMap değiştiğinde rolling restart tetikler. Bunu olmadan, bir config değeri güncellediğinizde Helm başarılı der, ancak pod'lar eski config ile çalışmaya devam eder çünkü Deployment spec'i değişmemiştir. Bu saatlerce confusion'a neden olabilir.
Test Stratejileri
Çok Katmanlı Kalite Güvencesi
Helm chart testleri, CI/CD pipeline güvenilirliğini doğrudan etkiler. Çok katmanlı bir test yaklaşımı için Chart Testing (ct) aracını ve helm-unittest'i birleştirin:
Katman 1: Syntax ve Lint kontrolü
`bash
helm lint my-chart/
helm template my-chart/ --debug
`
Katman 2: Unit Test (helm-unittest)
`bash
helm unittest my-chart/
`
Katman 3: Template Rendering Validation
`bash
ct lint --config ct.yaml
`
Katman 4: Integration Tests
`bash
helm test my-chart/
`
CI'da bu pipeline, çoğu sorunu cluster'a herhangi bir şey dokunmadan yakalar.
Test Pod'ları
Helm'in dahili test framework'ünü kullanarak test pod'ları ekleyin:
`yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "myapp.fullname" . }}-test-ready"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget', '-q', '--spider', 'http://{{ include "myapp.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never
`
Değer Yönetimi Pattern'leri
Environment-Specific Values
Tek bir values.yaml'da tüm environment'ları yönetmeye çalışmak, şişmiş ve yönetilemez dosyalara yol açar. Her environment için ayrı values dosyaları kullanın:
`yaml
replicaCount: 1
image:
repository: myapp
tag: 'v1.0.0'
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi
replicaCount: 3
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
replicaCount: 2
resources:
limits:
cpu: 1000m
memory: 1Gi
`
Helm bunları sırayla birleştirir — sonraki dosyalar öncekileri override eder.
Nested Yapı Önerisi
`
image:
repository: nginx
tag: '1.25'
pullPolicy: IfNotPresent
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
`
Bu yapı, değerlerin gruplanmasını sağlar ve değer dosyasını okunabilir tutar.
Subchart ve Library Chart'lar
Subchart Yönetimi
Gerçek uygulamalar, database'ler, message queue'lar ve monitoring agent'ları gibi başka chart'lara bağımlıdır. Bunları Chart.yaml'da declare edin:
`yaml
dependencies:
- name: redis
version: "17.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled
tags:
- database
`
Bu, chart'ları charts/ klasörüne indirir ve bir Chart.lock dosyası oluşturur. Bu lock dosyasını commit edin — tekrarlanabilir build'ler için versiyonları sabitler.
Library Chart'lar
Birden fazla servis arasında paylaşılan şablonlar için library chart oluşturun:
`yaml
apiVersion: v2
name: common-library
type: library
version: 1.0.0
`
Library chart'ların kendi template'leri yoktur — sadece diğer chart'ların include edebileceği _helpers.tpl sağlar. Bu, 50+ mikroservis genelinde standardizasyon için mükemmeldir.
CI/CD Entegrasyonu
GitHub Actions Örneği
Kötü chart'ların production'a ulaşmasını önleyin:
`yaml
name: Helm Chart CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Helm
uses: azure/setup-helm@v3
- name: Run chart-testing
uses: helm/chart-testing-action@v2.4.0
with:
command: lint-and-validate
config: ct.yaml
- name: Helm unittest
run: |
helm unittest my-chart/
- name: Render templates
run: |
helm template my-chart/ --debug >/dev/null
`
Güvenlik En İyi Uygulamaları
Immutable Tags
`yaml
image:
tag: 'v2.1.0' # Asla "latest" kullanmayın
`
Pinned versiyonlar, tekrarlanabilir deployment'lar için vazgeçilmezdir.
External Secrets Management
Secrets'ı values.yaml'da plaintext olarak tutmayın. Bunun yerine External Secrets Operator, Sealed Secrets veya Vault kullanın:
`yaml
envFrom:
- secretRef:
name: {{ include "myapp.fullname" . }}-credentials
`
Security ContextDefaults
`yaml
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
`
Resource Limits
Environment başına resource değerlerini ayarlayın. Development için minimal tutun, production trafik için uygun değerler belirleyin:
`yaml
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
`
Helmfile ve Multi-Environment
Düzine cluster genelinde onlarca release yönetirken, raw helm komutları yönetilemez hale gelir. Helmfile, tüm Helm state'inizi açıklayan bir declarative katman sağlar:
`yaml
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
releases:
- name: myapp
namespace: production
chart: ./charts/myapp
values:
- values-production.yaml
secrets:
- secrets-production.yaml.enc
hooks:
- events: ["pre apply"]
showlogs: true
command: "./scripts/pre-deploy.sh"
`
Helmfile, release order'ı takip eder, dependencies'leri halleder ve apply etmeden önce helm diff çalıştırır. Helm için eksik olan orkestrasyon katmanıdır.
ArgoCD Entegrasyonu
ArgoCD, Helm chart'ları native olarak destekler. Bir Git repository'deki chart'a işaret edin:
`yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
source:
repoURL: https://github.com/myorg/helm-charts
targetRevision: main
path: charts/myapp
helm:
valueFiles:
- values.yaml
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
`
ArgoCD, sync sırasında Helm template'ini render eder, canlı state'e karşı karşılaştırır ve sadece farkı uygular.
Kaçınılması Gereken Anti-Pattern'ler
Birçok ekipte chart bakımından sonra, bunlar karşı çıktığım anti-pattern'lerdir:
Birincisi, image tag'ı latest olarak default etmek. Bunun yerine .Chart.AppVersion'ı default olarak kullanın.
İkincisi, secrets'ı values.yaml'da tutmak. Secrets, Vault, AWS Secrets Manager gibi external secret manager'larda olmalı ve envFrom veya external-secrets-operator üzerinden referans verilmeli. Credentials'ları asla bir chart'a check etmeyin.
Üçüncüsü, devasa monolithic şablonlar. Bir template dosyası 150 satırı aşarsa, bölün. Tekrarlayan bloklar için _helpers.tpl'deki named template'leri kullanın.
Dördüncüsü, resource request veya limit olmaması. Resource tanımı olmayan bir chart, onu kaldıramayacak node'larda schedule edilebilir veya daha kötüsü, sınırsız kaynak tüketerek diğer workload'ları starve edebilir.
Beşincisi, PodDisruptionBudget atlamak. Node drain ve cluster upgrade'ler sırasında availability'ı önemsiyorsanız, PDB zorunludur. Her multi-replica workload için minAvailable: 1 default edin.
Sonuç
Bir Helm chart, uygulamanız ve cluster arasındaki arayüzdür. Operational knowledge'inizi kodlar: uygulama nasıl deploy edilmeli, hangi kaynaklara ihtiyaç duyar, nasıl scale olur ve upgrade'ler sırasında ne olur. Chart'larınızı uygulama kodu kadar ciddiye alın. PR'larda gözden geçirin, CI'da test edin, düzgün versiyonlayın. Laptop'ta çalışan chart ile 3 AM'de production node failure'ından sağ çıkan chart çok farklı şeylerdir. 3 AM senaryosu için build edin, laptop senaryosu kendiliğinden hallolur.