Helm Template Dili: Go Template, Fonksiyonlar, _helpers.tpl, include, Flow Control ve Whitespace
Helm template'leri Go text/template ile Sprig ve Helm fonksiyonlarından oluşur; ortak label ve isimler _helpers.tpl'de define ile tanımlanıp include ile çağrılır, scope değişince köke $ ile ulaşılır. Bookstore chart'ına bookstore-web'i ikinci component olarak ekliyor, config değişince pod'ları yeniden başlatan checksum annotation'ını, whitespace ve girinti hatalarını, tpl ve lookup'ın dry run'daki davranışını 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, .NET 10 ve Angular 22 ile denenmiştir.
Önce Tahmin Et
API'nin ayarları bir ConfigMap'ten envFrom ile environment variable olarak geliyor. helm upgrade --set config.storeName="Bookstore (test)" çalıştırıyorsun, Helm "upgraded" diyor ve kubectl get configmap yeni değeri gösteriyor. API'ye /api/info ile sorduğunda hangi mağaza adını duyarsın? Pod yeniden başlar mı? Cevap ConfigMap bölümünde, iki ayrı denemenin gerçek çıktısıyla.
Pipeline'lar ve Sık Kullanılan Fonksiyonlar
Helm'in template dili Go'nun text/template paketi ve üzerine eklenmiş fonksiyonlardan oluşuyor; fonksiyonların çoğu Sprig kütüphanesinden geliyor, birkaçı (include, tpl, required, lookup, toYaml) Helm'e özgü. {{ }} arasındaki her şey bir ifade ve | işareti, soldaki değeri sağdaki fonksiyonun son argümanı olarak verir:
image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}
storeName: {{ .Values.config.storeName | quote }}
replicas: {{ .Values.replicaCount | default 1 }}
default değer boşsa (boş string, sıfır, false, nil, boş liste) yerine ikinciyi koyar. Bu yüzden replicaCount: 0 verip default 1 kullanırsan sıfır asla yazılmaz, bilinçli bir tercihse dikkat. quote değeri çift tırnağa alır: YAML'ın (manifest'lerin yazıldığı metin formatı) true, 1.10, no gibi değerleri string olmaktan çıkarmasını önler; environment variable değerleri ve annotation'lar her zaman string olmalı. required değer yoksa render'ı anlamlı bir mesajla durdurur. toYaml bir map'i ya da listeyi YAML'a çevirir, nindent N başına yeni satır ekleyip her satırı N boşluk girintiler. tpl bir string'i template olarak render eder; values'ta template ifadesi taşımanın yolu bu. lookup cluster'dan canlı bir resource okur; davranışı dry run modlarında değişiyor, aşağıda ayrıca bakacağız.
define, include ve _helpers.tpl
İkinci yazıda helm create'in helper'larını olduğu gibi kullandık. Bu yazıda bookstore-web ikinci component olarak geliyor ve label'ların component'e göre değişmesi gerekiyor. Helper'ları bu ihtiyaca göre yeniden yazdık. Alt çizgiyle başlayan dosyalar (_helpers.tpl) manifest üretmez, yalnızca define ile adlandırılmış template'ler taşır:
{{/*
Selector labels for one component. Usage: include "bookstore.selectorLabels" (dict "ctx" . "component" "api")
These never change during the life of a release: Deployment selectors are immutable.
*/}}
{{- define "bookstore.selectorLabels" -}}
app.kubernetes.io/name: {{ include "bookstore.name" .ctx }}
app.kubernetes.io/instance: {{ .ctx.Release.Name }}
app.kubernetes.io/component: {{ .component }}
{{- end }}
{{/*
All labels for one component: selector labels plus version and chart information.
*/}}
{{- define "bookstore.labels" -}}
{{ include "bookstore.selectorLabels" . }}
app.kubernetes.io/version: {{ .ctx.Chart.AppVersion | quote }}
app.kubernetes.io/part-of: bookstore
app.kubernetes.io/managed-by: {{ .ctx.Release.Service }}
helm.sh/chart: {{ printf "%s-%s" .ctx.Chart.Name .ctx.Chart.Version | replace "+" "_" | trunc 63 }}
{{- end }}
{{/*
Image reference. Usage: include "bookstore.image" (dict "ctx" . "image" .Values.web.image)
An empty tag falls back to appVersion from Chart.yaml.
*/}}
{{- define "bookstore.image" -}}
{{- $repository := required "image.repository is required" .image.repository -}}
{{- printf "%s:%s" $repository (.image.tag | default .ctx.Chart.AppVersion) -}}
{{- end }}
{{- define "bookstore.podSecurityContext" -}}
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
{{- end }}
{{- define "bookstore.containerSecurityContext" -}}
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
{{- end }}
Helm, chart'taki bütün define'ları tek bir isim alanında toplar; bir subchart'taki labels adlı template seninkini ezebilir. Bu yüzden isimler chart adıyla başlar: bookstore.labels. Bir template'e tek bir argüman geçebiliyorsun; birden fazla değer gerektiğinde dict ile bir map kurup onu gönderiyoruz: (dict "ctx" . "component" "api"). Template'in içinde .ctx kök context, .component component adı.
Go'nun kendi template action'ı da adlandırılmış template çağırır, ama sonucu bir pipeline'a veremezsin: {{ template "bookstore.labels" . | nindent 4 }} çalışmaz, çünkü template bir fonksiyon değil, çıktıyı doğrudan yazan bir action. include aynı işi yapıp sonucu string olarak döndürür, böylece nindent ile girintilenebilir. Helm'in Chart Template Guide'ı da çıktı biçimini yönetebildiği için include'u öneriyor. Deployment artık bu helper'ları kullanıyor:
{{- $component := dict "ctx" . "component" "api" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "bookstore.fullname" . }}-api
labels:
{{- include "bookstore.labels" $component | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "bookstore.selectorLabels" $component | nindent 6 }}
template:
metadata:
annotations:
# Config değişince hash değişir, pod template değişir ve Deployment rollout yapar.
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "bookstore.selectorLabels" $component | nindent 8 }}
spec:
serviceAccountName: {{ include "bookstore.fullname" . }}-api
automountServiceAccountToken: false
securityContext:
{{- include "bookstore.podSecurityContext" . | nindent 8 }}
containers:
- name: api
image: {{ include "bookstore.image" (dict "ctx" . "image" .Values.image) | quote }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
envFrom:
- configMapRef:
name: {{ include "bookstore.fullname" . }}-api
securityContext:
{{- include "bookstore.containerSecurityContext" . | nindent 12 }}
...
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
Selector label'larında (name, instance, component) sürüm yok; sürüm ve chart bilgisi yalnızca bookstore.labels'ta. Selector'a değişen bir değer koymak, ilk upgrade'de field is immutable hatası demek; Kubernetes serisinin 14. yazısında aynı tuzağı Kustomize'ın commonLabels'ında görmüştük. securityContext da tek yerde tanımlandı; iki component aynı restricted ayarları kullanıyor.
Flow Control ve Scope: if, with, range ve $
if boş olmayan değerde çalışır; with ise aynı kontrolü yapıp bloğun içinde .'yı o değere çevirir. {{- with .Values.resources }} bloğunda . artık resources map'i; toYaml . bu yüzden çalışıyor ve resources boşsa resources: anahtarı hiç yazılmıyor. range bir liste ya da map üzerinde döner ve .'yı her elemana çevirir. ConfigMap'te extraEnv map'ini böyle dönüyoruz:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "bookstore.fullname" . }}-api
labels:
{{- include "bookstore.labels" (dict "ctx" . "component" "api") | nindent 4 }}
data:
ASPNETCORE_ENVIRONMENT: {{ .Values.environment | quote }}
Database__Provider: {{ .Values.database.provider | quote }}
Bookstore__StoreName: {{ .Values.config.storeName | quote }}
{{- range $key, $value := .Values.extraEnv }}
{{ $key }}: {{ $value | quote }}
{{- end }}
helm template bookstore ./bookstore -n bookstore-dev --show-only templates/configmap.yaml \
--set extraEnv.Logging__LogLevel__Default=Debug --set extraEnv.Bookstore__Features__Wishlist=true
data:
ASPNETCORE_ENVIRONMENT: "Development"
Database__Provider: "InMemory"
Bookstore__StoreName: "Bookstore"
Bookstore__Features__Wishlist: "true"
Logging__LogLevel__Default: "Debug"
Go template'leri map'leri anahtara göre sıralı döner; çıktı her çalıştırmada aynı oluyor, bu da gereksiz diff'leri önlüyor. Scope değişince kök context'e $ ile ulaşılır. bookstore-web template'inde with .Values.web kullandık; bloğun içinde . web ayarları, release ve chart bilgisi için $ gerekiyor:
{{- if .Values.web.enabled }}
{{- $component := dict "ctx" . "component" "web" -}}
{{- with .Values.web }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "bookstore.fullname" $ }}-web
labels:
{{- include "bookstore.labels" $component | nindent 4 }}
spec:
replicas: {{ .replicaCount }}
selector:
matchLabels:
{{- include "bookstore.selectorLabels" $component | nindent 6 }}
template:
metadata:
labels:
{{- include "bookstore.selectorLabels" $component | nindent 8 }}
spec:
automountServiceAccountToken: false
securityContext:
{{- include "bookstore.podSecurityContext" $ | nindent 8 }}
containers:
- name: web
image: {{ include "bookstore.image" (dict "ctx" $ "image" .image) | quote }}
imagePullPolicy: {{ .image.pullPolicy }}
ports:
- name: http
containerPort: 8080
env:
- name: API_UPSTREAM
value: {{ tpl .apiUpstream $ | quote }}
securityContext:
...
{{- end }}
{{- end }}
Bunu unutup with bloğunda include "bookstore.fullname" . yazdığımızda Helm'in hata mesajı yolu gösteriyor ama sebebi söylemiyor:
# web.yaml içinde, with bloğunun içinde $ yerine . kullanıldı
name: {{ include "bookstore.fullname" . }}-web
Error: bookstore/templates/web.yaml:7:11
executing "bookstore/templates/web.yaml" at <include "bookstore.fullname" .>:
error calling include:
bookstore/templates/_helpers.tpl:13:14
executing "bookstore.fullname" at <.Values.fullnameOverride>:
nil pointer evaluating interface {}.fullnameOverride
"nil pointer evaluating interface {}.fullnameOverride": helper .Values.fullnameOverride'ı aradı, ama . web ayarlarıydı ve içinde Values yoktu. required ise boş değeri yakalayıp okunur bir mesaja çeviriyor:
helm template bookstore ./bookstore --set image.repository=
Error: execution error at (bookstore/templates/deployment.yaml:27:20): image.repository is required
Whitespace ve Girinti Hataları
{{- soldaki, -}} sağdaki boşlukları ve satır sonlarını siler. {{- if }} ve {{- end }} satırlarının çıktıda boş satır bırakmamasını sağlayan bu. Girinti ise tamamen senin sorumluluğunda: Helm metni render eder, YAML'ın anlamını bilmez. nindent yerine indent yazınca ne olduğuna bakalım:
# nindent yerine indent
resources:
{{- toYaml . | indent 12 }}
Error: YAML parse error on bookstore/templates/deployment.yaml: error converting YAML to JSON: yaml: line 67: mapping values are not allowed in this context
Use --debug flag to render out invalid YAML
helm template bookstore ./bookstore --debug
periodSeconds: 5
resources: limits:
memory: 256Mi
requests:
cpu: 100m
indent yeni satır eklemediği için ilk anahtar resources: ile aynı satıra yapıştı. Hata mesajındaki "line 67" render edilmiş dosyanın satırı, template'in değil; --debug geçersiz YAML'ı da basıyor ve sorunun yerini orada görmek daha kolay. Pratik kural: bir bloğu yeni satırda başlatacaksan {{- ... | nindent N }} kalıbını kullan; N, bloğun girmesi gereken sütun.
ConfigMap ve Checksum Annotation'ı
Yeni values bölümü: gizli olmayan ayarlar config altında, chart'ın modellemediği ayarlar için extraEnv ve ikinci component için web.
# Gizli olmayan ayarlar; ConfigMap üzerinden API'ye environment variable olarak gider.
config:
storeName: Bookstore
# Chart'ın modellemediği ayarlar için kaçış yolu, ör. Logging__LogLevel__Default: Debug
extraEnv: {}
# bookstore-web (nginx'in sunduğu Angular build'i; /api istekleri API Service'ine gider)
web:
enabled: true
replicaCount: 1
image:
repository: helmseries-bookstore-web
tag: ""
pullPolicy: IfNotPresent
# tpl ile render edilir, template ifadesi içerebilir.
apiUpstream: 'http://{{ include "bookstore.fullname" . }}-api.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}'
service:
port: 8080
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64Mi
Şimdi Önce Tahmin Et'teki soruya gelelim. Kubernetes serisinin yedinci yazısında gördüğümüz gibi, envFrom ile okunan bir ConfigMap değişince çalışan pod'lar bunu görmez; environment variable'lar container başlarken okunur. Önce checksum annotation'ı olmayan bir chart kopyasıyla denedik:
# checksum/config annotation'ı olmayan bir kopya: önce annotation'sız sürüme geçiş, sonra yalnızca config değişikliği
helm upgrade bookstore ./nochecksum -n bookstore-dev --set config.storeName="Bookstore (dev)" --wait
helm upgrade bookstore ./nochecksum -n bookstore-dev --set config.storeName="Bookstore (test)" --wait
kubectl -n bookstore-dev get configmap bookstore-api -o jsonpath='{.data.Bookstore__StoreName}'
kubectl -n bookstore-dev get pods -l app.kubernetes.io/component=api
Bookstore (test)
NAME READY STATUS RESTARTS AGE
bookstore-api-58d5657cc5-s6pcf 1/1 Running 0 3s
bookstore-api-845594587f-t2hsv 0/1 Completed 0 58s
{"version":"1.1.0","environment":"Development","pod":"bookstore-api-58d5657cc5-s6pcf","database":"InMemory","storeName":"Bookstore (dev)"}
ConfigMap "Bookstore (test)" diyor, pod yeniden başlamadı ve API hâlâ eski değeri döndürüyor. Deployment'ın pod template'i değişmediği için Kubernetes'in rollout yapması için bir sebep yok. Çözüm, Deployment template'indeki checksum/config annotation'ı: ConfigMap template'i render edilip sha256sum'ı annotation'a yazılıyor. Config değişince hash, hash değişince pod template değişiyor ve Deployment rolling update yapıyor:
helm upgrade bookstore ./bookstore -n bookstore-dev --set config.storeName="Bookstore (test)" --wait
kubectl -n bookstore-dev get pods -l app.kubernetes.io/component=api
NAME READY STATUS RESTARTS AGE
bookstore-api-58d5657cc5-s6pcf 0/1 Completed 0 9s
bookstore-api-dd6c449fb-q9f6x 1/1 Running 0 3s
{"version":"1.1.0","environment":"Development","pod":"bookstore-api-dd6c449fb-q9f6x","database":"InMemory","storeName":"Bookstore (test)"}
Kustomize bunu ConfigMap adına eklediği hash suffix ile çözüyordu (Kubernetes serisinin 14. yazısı); Helm'de aynı sonucu bu annotation veriyor. Bir fark var: Kustomize her değişiklikte yeni bir ConfigMap bırakır ve rollback eski ConfigMap'e döner, Helm'de ConfigMap'in adı sabit ve helm rollback eski içeriği tekrar yazar.
İkinci Component: Tek Chart mı, Umbrella mı?
bookstore-web, Angular 22 build'ini nginx ile sunuyor ve /api isteklerini API Service'ine iletiyor. nginx'in upstream adresi values'ta template ifadesi olarak duruyor ve tpl ile render ediliyor:
helm template bookstore ./bookstore --show-only templates/web.yaml | grep -A1 API_UPSTREAM
- name: API_UPSTREAM
value: "http://bookstore-api.default.svc.cluster.local:8080"
(Bu çıktıda namespace default, çünkü helm template'e -n vermedik.) nginx, Kubernetes'in DNS search domain'lerini kullanmadığı için adresin tam olması gerekiyor; tpl sayesinde bu bilgiyi chart kullanıcısının yazmasına gerek kalmıyor. Upgrade'den sonra iki Deployment:
helm upgrade bookstore ./bookstore -n bookstore-dev --wait
kubectl -n bookstore-dev get deploy,pods
Release "bookstore" has been upgraded. Happy Helming!
...
NOTES:
Bookstore 1.1.0 (chart bookstore-0.2.0), release bookstore, revision 5
WARNING: namespace bookstore-dev has no pod-security.kubernetes.io/enforce label.
...
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/bookstore-api 1/1 1 1 2m24s
deployment.apps/bookstore-web 1/1 1 1 4s
NAME READY STATUS RESTARTS AGE
pod/bookstore-api-758bb6899-clvxf 1/1 Running 0 3s
pod/bookstore-api-cc5f75d5-mjx42 0/1 Completed 0 93s
pod/bookstore-web-76c9696d44-5klq5 1/1 Running 0 3s
Cluster içindeki bir curl pod'undan web'e istek attık:
# bookstore-web üzerinden: nginx /api isteğini API'ye iletir
curl -s http://bookstore-web:8080/api/info
curl -s http://bookstore-web:8080/ | head -5
{"version":"1.1.0","environment":"Development","pod":"bookstore-api-758bb6899-clvxf","database":"InMemory","storeName":"Bookstore"}
<!doctype html>
<html lang="en" data-beasties-container>
<head>
<meta charset="utf-8">
<title>Bookstore</title>
Web ve API'yi aynı chart'ta tutmak bir karar. Tek chart, ikisi birlikte sürümlenip birlikte deploy edildiğinde uygun: Bookstore'da web ve API aynı ekibin, aynı release'in parçası, chart sürümü de tek. Umbrella chart, yani her component'in kendi chart'ı olup bir üst chart'ın onları dependency olarak toplaması, component'ler farklı ekiplerdeyse, ayrı ayrı yayımlanıyorsa ya da tek başına kurulabilmeleri gerekiyorsa mantıklı. Bedeli, values'ın her subchart için ayrı bir anahtar altında durması ve ortak ayarların global ile taşınması; dependency'leri altıncı yazıda göreceğiz. Bookstore için tek chart'ta kalıyoruz ve web.enabled ile web'i kapatma seçeneği bırakıyoruz.
lookup ve Dry Run
lookup render sırasında cluster'dan resource okur. Pratik bir kullanım: release'in kurulduğu namespace'te Pod Security Standards etiketi yoksa NOTES.txt'de uyarı vermek.
Bookstore {{ .Chart.AppVersion }} (chart {{ .Chart.Name }}-{{ .Chart.Version }}), release {{ .Release.Name }}, revision {{ .Release.Revision }}
{{- $ns := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- if and $ns (not (index ($ns.metadata.labels | default dict) "pod-security.kubernetes.io/enforce")) }}
WARNING: namespace {{ .Release.Namespace }} has no pod-security.kubernetes.io/enforce label.
{{- end }}
...
lookup, cluster'a bağlanmayan modlarda boş bir map döner: helm template ve --dry-run=client. Bu yüzden template, sonucun boş olabileceğini hesaba katmalı; yukarıdaki if and $ns ... kontrolü bunun için. İki modu karşılaştırdık:
helm upgrade bookstore ./bookstore -n bookstore-dev --dry-run=client
helm upgrade bookstore ./bookstore -n bookstore-dev --dry-run=server
NOTES:
Bookstore 1.1.0 (chart bookstore-0.2.0), release bookstore, revision 6
Try it:
...
NOTES:
Bookstore 1.1.0 (chart bookstore-0.2.0), release bookstore, revision 6
WARNING: namespace bookstore-dev has no pod-security.kubernetes.io/enforce label.
Try it:
...
Uyarı yalnızca server modunda çıktı. Aynı fark, lookup ile karar veren her template için geçerli: CI'da helm template ile ürettiğin çıktı, cluster'a kurulan çıktıyla aynı olmayabilir. lookup'ı mevcut bir Secret'ı okuyup değeri yeniden kullanmak gibi işlere sokmak bu yüzden riskli; GitOps araçları da çoğunlukla helm template ile render eder ve lookup sonucunu görmez. Namespace'i etiketleyip tekrar kurunca uyarı kalktı ve iki component restricted profilde sorunsuz çalıştı:
kubectl label namespace bookstore-dev pod-security.kubernetes.io/enforce=restricted
helm upgrade bookstore ./bookstore -n bookstore-dev --set config.storeName="Bookstore (dev)" --wait
kubectl -n bookstore-dev get pods
namespace/bookstore-dev labeled
...
NOTES:
Bookstore 1.1.0 (chart bookstore-0.2.0), release bookstore, revision 6
Try it:
...
NAME READY STATUS RESTARTS AGE
bookstore-api-758bb6899-clvxf 0/1 Completed 0 47s
bookstore-api-845594587f-t2hsv 1/1 Running 0 4s
bookstore-web-76c9696d44-5klq5 1/1 Running 0 47s
{"version":"1.1.0","environment":"Development","pod":"bookstore-api-845594587f-t2hsv","database":"InMemory","storeName":"Bookstore (dev)"}
Üretimde Dikkat
Checksum annotation'ını yalnızca gerçekten pod'a giren config için kullan; her şeyin hash'ini annotation'a yazarsan alakasız bir değişiklik bütün pod'ları yeniden başlatır. Secret'lar için aynı kalıp çalışır, ama Secret'ın içeriğini template'te render ediyorsan zaten secret'ı values'ta tutuyorsun demektir; dördüncü yazıda buna bakacağız. Template'lerde lookup sonucuna göre resource üretmek, GitOps ile render eden araçlarla farklı sonuç verir; mümkünse karar values'tan gelsin. tpl güçlü ama values'ı da template'e çevirir: values dosyasını yazan herkes chart'ın içinde kod çalıştırabilir hâle gelir, bu yüzden tpl'i yalnızca belgelenmiş alanlarda kullan.
Sık Düşülen Tuzaklar
Her Kubernetes alanını values'a açıp YAML'ı YAML ile yazmak: helm create'in podSecurityContext: {}, affinity: {}, tolerations: [] listesi bunun başlangıcı ve sonunda chart, manifest'ten daha zor okunur hâle gelir. Gerçekten değişen ayarları aç, geri kalanı için extraEnv gibi dar bir kaçış yolu bırak. template ile çağırıp nindent kullanamamak. with ya da range içinde .'nın değiştiğini unutmak. default'un 0 ve false'u da boş saydığını gözden kaçırmak. indent ile nindent'i karıştırmak. Config değişikliklerinin pod'lara kendiliğinden yansıdığını sanmak. lookup'ın helm template'te de cluster'ı okuduğunu varsaymak.
Küçük Bir Deneme
★☆☆ web.enabled=false ile render et ve NOTES.txt'nin API için port-forward komutunu gösterdiğini kontrol et.
★★☆ bookstore.labels helper'ına, values'tan gelen opsiyonel bir commonLabels map'ini ekle (with ve toYaml ile). Bu label'ların selector'lara girmediğini helm template çıktısında doğrula.
★★★ Chart'ı bir umbrella yapısına çevirmeyi dene: charts/api ve charts/web adlı iki subchart ve onları toplayan bir üst chart. Values'ın nasıl bölündüğünü ve API Service adını web'e nasıl taşıdığını karşılaştır, sonra tek chart'a neden geri döndüğümüzü kendi gerekçelerinle yaz.
Template'ler artık okunur ve iki component'i taşıyor. Bir sonraki yazıda values'ı tasarlıyoruz: environment başına values dosyaları, birleştirme sırası, values.schema.json ile doğrulama ve secret'ların values'a girmemesi.