Values Tasarımı: Environment Başına Values, --set, values.schema.json ile Doğrulama ve Secret'lar
Helm values'ı chart varsayılanı, sırayla -f dosyaları ve en son --set olarak birleştirir; values.schema.json bu birleşmiş sonucu doğrular, secret'lar ise values'a değil var olan bir Secret'a referansla girer. Bookstore için dev, staging ve prod values dosyalarını, Helm 4'ün multi-document values ve --set-json desteğini, --set'in virgül ve nokta tuzaklarını, required ve fail ile anlamlı hataları ve --reuse-values'un yeni varsayılanları yuttuğu bir upgrade'i gerçek çıktılarla görüyoruz.
Sürüm notu: Bu yazıdaki örnekler Helm 4.3.2, kind 0.33 ile Kubernetes 1.37, kubectl 1.37 ve .NET 10 ile denenmiştir.
Önce Tahmin Et
Chart'ın yeni sürümünde values.yaml'a yeni bir anahtar ekledin: database.secretKey: connection-string. Template bu anahtarı Secret'tan hangi key'in okunacağını söylemek için kullanıyor. Ekipteki biri upgrade'i her zamanki gibi --reuse-values ile yapıyor, "önceki ayarlar korunsun" diye. Yeni varsayılan release'e girer mi? Girmezse ne olur: Helm mi hata verir, Kubernetes mi, yoksa pod sessizce mi bozulur? Cevap yazının sonundaki tuzak bölümünde, gerçek hata mesajıyla.
Environment Başına Values Dosyaları
Bookstore (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) üç environment'ta çalışıyor. Chart'ın values.yaml dosyası güvenli varsayılanları taşıyor, her environment yalnızca farkını kendi dosyasına yazıyor:
helm/
charts/
bookstore/
Chart.yaml
values.yaml
values.schema.json
templates/
deploy/
values-dev.yaml
values-staging.yaml
values-prod.yaml
environment: Development
database:
provider: InMemory
config:
storeName: Bookstore (dev)
environment: Staging
database:
provider: SqlServer
existingSecret: bookstore-db-connection
config:
storeName: Bookstore (staging)
environment: Production
replicaCount: 2
database:
provider: SqlServer
existingSecret: bookstore-db-connection
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
memory: 512Mi
web:
replicaCount: 2
Dosyaların küçük olması bilinçli bir tercih. Bir environment dosyası chart'ın values.yaml'ından uzunsa, varsayılanlar yanlış seçilmiş demektir. Chart'ın kendi varsayılanında database.provider artık SqlServer: values dosyası vermeden yapılan bir kurulum, aşağıda göreceğimiz gibi açık bir hatayla duruyor, sessizce in-memory veritabanıyla çalışmaya başlamıyor. Bookstore'un veritabanı SQL Server; bu chart onu kurmuyor, yalnızca bağlanıyor. Kubernetes serisinin 12. yazısındaki "veritabanı cluster dışında" kararı geçerli.
Birleştirme Sırası

Helm values'ı şu sırayla birleştirir: önce chart'ın values.yaml'ı, sonra -f ile verilen dosyalar komut satırındaki sırayla, en son --set ailesi. Sonra gelen öncekini ezer. Bunu mağaza adıyla gördük; local.yaml yalnızca config.storeName: Bookstore (local) içeriyor:
render() {
helm template bookstore charts/bookstore --set database.provider=InMemory \
--show-only templates/configmap.yaml "$@" | grep StoreName
}
render
render -f deploy/values-dev.yaml
render -f deploy/values-dev.yaml -f local.yaml
render -f local.yaml -f deploy/values-dev.yaml
render -f deploy/values-dev.yaml -f local.yaml --set config.storeName=Demo
Bookstore__StoreName: "Bookstore"
Bookstore__StoreName: "Bookstore (dev)"
Bookstore__StoreName: "Bookstore (local)"
Bookstore__StoreName: "Bookstore (dev)"
Bookstore__StoreName: "Demo"
Dördüncü ve beşinci satır aynı iki dosyayı farklı sırayla veriyor ve sonuç değişiyor. CI script'lerinde dosya sırası bu yüzden önemli: ortak dosyalar önce, environment dosyası sonra, lokal geçersiz kılmalar en son. Map'ler anahtar anahtar birleşir; dev'den sonra prod dosyasını verdiğimizde resources map'i chart'ın varsayılanının üzerine yazıldı:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml -f deploy/values-prod.yaml \
--show-only templates/deployment.yaml | grep -A6 "resources:"
resources:
limits:
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
volumeMounts:
Listeler ise birleşmez, tamamen yer değiştirir: chart'ta iki elemanlı bir liste varsa ve sen tek elemanlı bir liste verirsen sonuç tek elemanlıdır. Bir anahtarı tamamen silmek için null verilir:
helm template bookstore charts/bookstore -f deploy/values-prod.yaml --set resources.limits=null \
--show-only templates/deployment.yaml | grep -A4 "resources:"
resources:
requests:
cpu: 250m
memory: 256Mi
volumeMounts:
Helm 4 iki kolaylık getirdi. Birincisi multi-document values: tek bir values dosyası --- ile ayrılmış birden fazla doküman içerebiliyor ve dokümanlar sırayla birleştiriliyor.
# Ortak ayarlar
config:
storeName: Bookstore
extraEnv:
Logging__LogLevel__Default: Information
---
# Lokal geçersiz kılmalar
extraEnv:
Logging__LogLevel__Default: Debug
helm template bookstore charts/bookstore -f deploy/values-dev.yaml -f multi.yaml --show-only templates/configmap.yaml
data:
ASPNETCORE_ENVIRONMENT: "Development"
Database__Provider: "InMemory"
Bookstore__StoreName: "Bookstore"
Logging__LogLevel__Default: "Debug"
İkincisi JSON argümanları: --set-json artık yalnızca anahtar=json değil, bütün bir JSON object'i de alıyor. Bunu aşağıda --set'in tuzaklarıyla birlikte göreceğiz.
--set'in Ayrıştırma Kuralları
--set küçük bir dil: virgül iki ataması ayırır, nokta iç içe anahtar demektir, köşeli parantez liste index'idir. Bu yüzden içinde virgül olan bir değer bozuluyor:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set config.storeName="Bookstore, Ankara"
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set config.storeName="Bookstore\, Ankara" \
--show-only templates/configmap.yaml | grep StoreName
Error: failed parsing --set data: key " Ankara" has no value
Bookstore__StoreName: "Bookstore, Ankara"
Kaçış karakteri işe yarıyor ama okunması zor. Daha kötüsü virgülden sonra nokta içeren bir değer: CORS (Cross-Origin Resource Sharing; tarayıcının başka origin'lere yaptığı istekleri sınırlayan mekanizma) için iki origin vermek istedik:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml \
--set extraEnv.Bookstore__Cors__Origins="https://www.example.com,https://admin.example.com"
Error: failed parsing --set data: key "com" has no value
Helm ikinci origin'i yeni bir atama sandı, içindeki noktaları da iç içe anahtar olarak böldü ve son parçada değer bulamadı. JSON burada en güvenli yol, çünkü string'in nerede başlayıp bittiği belli:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --show-only templates/configmap.yaml \
--set-json 'extraEnv={"Bookstore__Cors__Origins":"https://www.example.com,https://admin.example.com"}' \
| grep Cors
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --show-only templates/configmap.yaml \
--set-json '{"config":{"storeName":"Bookstore (json)"},"extraEnv":{"Logging__LogLevel__Default":"Debug"}}'
Bookstore__Cors__Origins: "https://www.example.com,https://admin.example.com"
data:
ASPNETCORE_ENVIRONMENT: "Development"
Database__Provider: "InMemory"
Bookstore__StoreName: "Bookstore (json)"
Logging__LogLevel__Default: "Debug"
Tiplere de dikkat. --set tam sayıları ve true/false'u tipine çevirir, diğer her şeyi string bırakır. 1.10 string olarak kaldı, 110 sayıya döndü ve şema onu reddetti:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set image.tag=1.10 \
--show-only templates/deployment.yaml | grep image:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set image.tag=110
image: "helmseries-bookstore-api:1.10"
Error: values don't meet the specifications of the schema(s) in the following chart(s):
bookstore:
- at '/image/tag': got number, want string
--set-string değeri her zaman string yapar, --set-file değeri bir dosyanın içeriğinden okur (sertifika, uzun bir config parçası). Pratik kural: --set'i yalnızca CI'ın image tag'i gibi tek ve basit değerler için kullan, gerisini values dosyasına yaz.
values.schema.json ile Doğrulama
Chart'ın kök klasörüne konan values.schema.json, birleşmiş values'ın son hâline JSON Schema ile uygulanır; install, upgrade, template ve lint bunu kontrol eder. Bookstore chart'ının şeması:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["image", "replicaCount", "environment", "database", "service"],
"properties": {
"global": { "type": "object" },
"nameOverride": { "type": "string" },
"fullnameOverride": { "type": "string" },
"image": { "$ref": "#/$defs/image" },
"replicaCount": { "type": "integer", "minimum": 1, "maximum": 20 },
"environment": { "enum": ["Development", "Staging", "Production"] },
"database": {
"type": "object",
"additionalProperties": false,
"required": ["provider"],
"properties": {
"provider": { "enum": ["SqlServer", "InMemory"] },
"existingSecret": { "type": "string" },
"secretKey": { "type": "string", "minLength": 1 }
}
},
"config": {
"type": "object",
"properties": {
"storeName": { "type": "string", "minLength": 1 }
}
},
"extraEnv": {
"type": "object",
"additionalProperties": { "type": ["string", "number", "boolean"] }
},
"service": {
"type": "object",
"properties": {
"type": { "enum": ["ClusterIP", "NodePort", "LoadBalancer"] },
"port": { "type": "integer", "minimum": 1, "maximum": 65535 }
}
},
"probes": { "type": "object" },
"resources": { "type": "object" },
"web": {
"type": "object",
"properties": {
"enabled": { "type": "boolean" },
"replicaCount": { "type": "integer", "minimum": 1 },
"image": { "$ref": "#/$defs/image" },
"apiUpstream": { "type": "string" },
"service": { "type": "object" },
"resources": { "type": "object" }
}
}
},
"$defs": {
"image": {
"type": "object",
"additionalProperties": false,
"required": ["repository"],
"properties": {
"repository": { "type": "string", "minLength": 1 },
"tag": { "type": "string" },
"pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] }
}
}
}
}
Kök seviyede additionalProperties: false yazım hatalarını yakalar; global anahtarı ise şimdiden serbest, çünkü altıncı yazıda dependency eklediğimizde Helm bu anahtarı bütün chart'lara dağıtacak. Hatalı değerlerle denedik:
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set replicaCount=two
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set environment=Prod --set replicas=3
Error: values don't meet the specifications of the schema(s) in the following chart(s):
bookstore:
- at '/replicaCount': got string, want integer
Error: values don't meet the specifications of the schema(s) in the following chart(s):
bookstore:
- at '/environment': value must be one of 'Development', 'Staging', 'Production'
- at '': additional properties 'replicas' not allowed
replicas diye yazılmış bir anahtar şema olmadan sessizce yok sayılırdı ve kimse replica sayısının neden değişmediğini anlamazdı. Image tag'i için "type": "string" şart koşmanın sebebi de şu: YAML (manifest'lerin ve values dosyalarının yazıldığı metin formatı) dosyasında tırnaksız yazılan 1.10 bir sayıdır.
image:
tag: 1.10
helm template bookstore charts/bookstore -f deploy/values-dev.yaml -f tag.yaml
Error: values don't meet the specifications of the schema(s) in the following chart(s):
bookstore:
- at '/image/tag': got number, want string
Aynı dosyayı şeması silinmiş bir kopyayla render ettiğimizde Helm hata vermedi; image satırı şöyle çıktı:
image: "helmseries-bookstore-api:%!s(float64=1.1)"
Go'nun printf fonksiyonu bir float'u %s ile basamadığını bu biçimde söylüyor. Bu satır Kubernetes'e giderse geçersiz bir image adıyla ImagePullBackOff olur. Şema bu hatayı render'dan önce yakaladı.
required ve fail ile Anlamlı Hatalar
Şema tek tek alanları iyi doğrular, ama "provider SqlServer ise Secret adı zorunlu" gibi alanlar arası kurallar şemada yazılabilse de okunması zorlaşır. Bu kuralları template'te tutmak daha açık:
database:
# Gerçek ortamlarda SqlServer; InMemory yalnızca demo ve chart testleri için.
provider: SqlServer
# Connection string'i taşıyan, önceden var olan Secret'ın adı. Chart bu Secret'ı asla oluşturmaz.
existingSecret: ""
secretKey: connection-string
{{- if eq .Values.database.provider "SqlServer" }}
env:
- name: ConnectionStrings__BookstoreDb
valueFrom:
secretKeyRef:
name: {{ required "database.existingSecret is required when database.provider is SqlServer" .Values.database.existingSecret }}
key: {{ .Values.database.secretKey }}
{{- end }}
{{- if and (eq .Values.environment "Production") (eq .Values.database.provider "InMemory") }}
{{- fail "database.provider InMemory is for demos and tests; Production needs SqlServer" }}
{{- end }}
helm template bookstore charts/bookstore -f deploy/values-prod.yaml --set database.provider=InMemory
helm template bookstore charts/bookstore -f deploy/values-staging.yaml --set database.existingSecret=
Error: execution error at (bookstore/templates/deployment.yaml:3:4): database.provider InMemory is for demos and tests; Production needs SqlServer
Error: execution error at (bookstore/templates/deployment.yaml:43:27): database.existingSecret is required when database.provider is SqlServer
Bir ayrıntı: helm lint lint modunda required hatalarını uyarıya çeviriyor ve chart'ı başarılı sayıyor.
helm lint charts/bookstore
level=WARN msg="missing required values" message="database.existingSecret is required when database.provider is SqlServer"
==> Linting charts/bookstore
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
CI'da yalnızca helm lint'e güvenirsen eksik values'ı yakalayamazsın. Lint'i her environment'ın values dosyasıyla çalıştırmak ve ayrıca helm template ile render etmek gerekiyor; altıncı yazıdaki CI adımları bunu yapıyor. Dev release'ini values dosyasıyla güncelledik:
helm upgrade bookstore charts/bookstore -n bookstore-dev -f deploy/values-dev.yaml --wait
helm get values bookstore -n bookstore-dev
Release "bookstore" has been upgraded. Happy Helming!
NAME: bookstore
LAST DEPLOYED: Sun Nov 15 07:18:15 2026
NAMESPACE: bookstore-dev
STATUS: deployed
REVISION: 10
DESCRIPTION: Upgrade complete
...
USER-SUPPLIED VALUES:
config:
storeName: Bookstore (dev)
database:
provider: InMemory
environment: Development
Secret'lar Values'a Girmez
Connection string'i values'a yazıp chart'ın bir Secret oluşturmasını sağlamak kolay görünür. Üç sorunu var. Values dosyası Git'e girer. Helm, ilk yazıda gördüğümüz gibi release'in bütün values'ını release Secret'ında saklar; Secret'ı okuyabilen herkes şifreyi de okur. helm get values şifreyi düz metin olarak ekrana basar. Bu yüzden chart, var olan bir Secret'ın adını alıyor ve asla Secret oluşturmuyor. Secret'ı chart'tan bağımsız bir süreç oluşturuyor:
# Değer environment variable'dan gelir; komut satırı geçmişine ve Git'e girmez
kubectl -n bookstore-staging create secret generic bookstore-db-connection \
--from-literal=connection-string="Server=mssql.bookstore-db.svc.cluster.local,1433;Database=BookstoreDb;User Id=sa;Password=$DB_PASSWORD;TrustServerCertificate=True"
Lab'da bu Secret'ı ve SQL Server'ı bir script rastgele bir şifreyle oluşturuyor; şifre hiçbir dosyaya yazılmıyor. Production'da bu işi bir secret yöneticisi yapar: External Secrets Operator, Azure Key Vault gibi bir secret servisindeki değeri bir Kubernetes Secret'ına senkronize eder ve chart yalnızca o Secret'ın adını bilir. Bu yapı Configuration ve Secret Management serisinde ayrıntılı anlatılıyor.
Şifreli değerleri Git'te tutmak isteyen ekipler için bir seçenek de helm-secrets plugin'i: values dosyası SOPS (Secrets OPerationS; dosyadaki değerleri bir anahtar yönetim servisiyle şifreleyen araç) ile şifrelenip commit'lenir, plugin kurulum sırasında dosyayı çözüp Helm'e verir. Bu yöntemi bu yazı için çalıştırmadım. Akılda tutulması gereken nokta şu: çözülen değerler yine values'a girdiği için release Secret'ında düz olarak saklanır. Şifreleme Git'teki dosyayı korur, cluster'daki release kaydını korumaz.
--reuse-values Tuzağı
Şimdi Önce Tahmin Et'e dönelim. Chart'ın 0.2.0 sürümüyle kurulmuş bir release'i, database.secretKey varsayılanını ekleyen 0.3.0'a --reuse-values ile taşıdık:
# 0.2.0 ile kurulu release; 0.3.0'a --reuse-values ile geçiş
helm install bookstore v020/charts/bookstore -n bookstore-qa --set config.storeName="Bookstore (qa)" --wait
helm upgrade bookstore charts/bookstore -n bookstore-qa --reuse-values \
--set database.provider=SqlServer --set database.existingSecret=bookstore-db-connection
Error: UPGRADE FAILED: server-side apply failed for object bookstore-qa/bookstore-api apps/v1, Kind=Deployment: Deployment.apps "bookstore-api" is invalid: spec.template.spec.containers[0].env[0].valueFrom.secretKeyRef.key: Required value
helm get values bookstore -n bookstore-qa --all
...
database:
existingSecret: bookstore-db-connection
provider: SqlServer
environment: Development
...
Helm hata vermedi, şema da geçti; hatayı Kubernetes verdi. --reuse-values, önceki release'in values'ını alırken chart'ın varsayılanlarını da eski chart'tan alıyor. Yeni sürümde eklenen secretKey: connection-string hiç gelmedi ve template boş bir key üretti. Bu davranış belgelenmiş ama çoğu kişinin beklediği şey değil. Helm 3.14'ten beri bunun için ayrı bir bayrak var: --reset-then-reuse-values yeni chart'ın varsayılanlarını alır, üzerine önceki release'in kullanıcı values'ını, en son da komut satırını koyar.
helm upgrade bookstore charts/bookstore -n bookstore-qa --reset-then-reuse-values \
--set database.provider=SqlServer --set database.existingSecret=bookstore-db-connection \
--dry-run=server | grep -A4 ConnectionStrings__BookstoreDb
- name: ConnectionStrings__BookstoreDb
valueFrom:
secretKeyRef:
name: bookstore-db-connection
key: connection-string
İkinci yazıda gördüğümüz başka bir davranış da burada önemli: helm upgrade'e hiç -f ya da --set verilmezse Helm önceki values'ı kendiliğinden yeniden kullanıyor. En güvenli düzen, her upgrade'de environment'ın values dosyasını açıkça vermek; o zaman release'in values'ı her seferinde Git'teki dosyadan gelir ve önceki bir komut satırı değeri taşınmaz.
Üretimde Dikkat
Values dosyalarını chart'la aynı repo'da tutmak, chart değişikliği ile environment ayarını aynı pull request'te görmeyi sağlar; GitOps ile deploy ediyorsan bu dosyalar zaten Argo CD'nin okuyacağı yerdir (GitOps ve Argo CD serisinde). Şemayı chart'ın sözleşmesi olarak gör: bir alanı kaldırmak ya da tipini daraltmak, values dosyası o alanı kullanan herkes için breaking change'tir ve chart'ın major sürümünü artırmalı. --skip-schema-validation bayrağı var, ama CI'da kullanmak şemanın varlık sebebini ortadan kaldırır. Release Secret'larında values düz durduğu için values'a giren hiçbir şeyin gizli olmaması gerekir; bir kontrol olarak helm get values --all çıktısında şifre arayabilirsin.
Sık Düşülen Tuzaklar
--set ile virgül ya da nokta içeren değer vermek. Tırnaksız 1.10 ya da on/yes gibi YAML'ın başka tipe çevirdiği değerler yazmak. -f dosyalarının sırasını rastgele vermek. Listelerin de map'ler gibi birleştiğini sanmak. --reuse-values ile yeni chart sürümüne geçip yeni varsayılanları kaybetmek. helm lint'in eksik zorunlu değerleri hata olarak yakaladığını varsaymak. Secret değerini values'a koyup Git'i ve release Secret'ını aynı anda kirletmek. Şema olmadan kök seviyede yazım hatası yapmak ve değerin neden etki etmediğini saatlerce aramak.
Küçük Bir Deneme
★☆☆ values-dev.yaml dosyasına yanlış bir anahtar ekle (replica: 2) ve hatayı şema ile ve şemasız bir kopyayla karşılaştır.
★★☆ Şemaya bir kural ekle: environment Production ise replicaCount en az 2 olsun. JSON Schema'nın if/then yapısını kullan ve aynı kuralı template'te fail ile yazmakla okunabilirlik açısından karşılaştır.
★★★ Bir CI adımı yaz: her environment için helm template çalıştırsın, sonra helm get values --all benzeri bir çıktıda (render edilmiş ConfigMap'ler ve values) Password= ya da password: geçen bir satır bulursa pipeline'ı durdursun.
Values düzenli, doğrulanıyor ve secret'lar dışarıda. Bir sonraki yazıda release yönetimine geçiyoruz: upgrade --install, rollback, history, --wait, server-side apply ve Entity Framework Core migration'larını SQL Server'a uygulayan bir pre-upgrade hook.