~/yazilar/helm-ile-paket-yonetimi
Helm ile chart kurmak ve kendi chart'ını yazmak
içindekiler
k3s'i kurduğun anda kümende Helm ile kurulmuş iki şey vardı, sen Helm'i hiç kurmamış olsan da. İlk yazıdaki helm-install-traefik pod'larını hatırlarsan Traefik böyle geliyordu. k3s içinde bir Helm denetleyicisi çalışıyor ve HelmChart nesnelerini okuyup kuruyor:
kubectl -n kube-system get helmchartsNAME JOB CHART ...
traefik helm-install-traefik https://%{KUBERNETES_API}%/static/charts/traefik-....tgz ...
traefik-crd helm-install-traefik-crd https://%{KUBERNETES_API}%/static/charts/traefik-crd-....tgz ...Benim için asıl işe yarayan tarafı, bir önceki yazının sonunda kalan sorunu çözmesi: aynı uygulamayı farklı ayarlarla birden çok yere kurmak için YAML kopyalamak yerine tek bir şablon ve ortam başına küçük bir ayar dosyası tutuyorsun.
Chart, values ve release
Chart, şablonlaştırılmış Kubernetes YAML'larının paketi. İçinde Deployment, Service, Ingress gibi dosyalar var ama değerlerin yerinde {{ .Values.replicaCount }} gibi yer tutucular duruyor.
Values, bu yer tutucuları dolduran ayarlar. Her chart bir values.yaml ile varsayılanlarını getiriyor, sen sadece değiştirmek istediklerini kendi dosyanda veriyorsun.
Release, bir chart'ın belirli değerlerle kümeye kurulmuş hali. Aynı chart'ı aynı kümeye iki farklı isimle kurarsan iki ayrı release olur.
Helm 3 ile birlikte eski sürümlerdeki Tiller bileşeni tamamen kalktı. Helm artık kümede ayrı bir şey çalıştırmıyor, senin kubeconfig'inle ve senin yetkilerinle doğrudan API'ye konuşuyor. Release geçmişini de kurulduğu namespace'te Secret olarak tutuyor (sh.helm.release.v1.<isim>.v<sürüm> adında). Rollback buradan çalışıyor.
Kurulum
Helm'in resmi kurulum script'i var. k3s'te yaptığım gibi önce indirip okuyorum:
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
less get_helm.sh
bash get_helm.sh
helm versionversion.BuildInfo{Version:"v3.18.6", GitCommit:"...", GitTreeState:"clean", GoVersion:"go1.24.6"}Script /usr/local/bin/helm dosyasını koyuyor ve indirdiği arşivin checksum'ını doğruluyor. İmza kontrolü varsayılan olarak kapalı, VERIFY_SIGNATURES=true ile açılıyor. Paket yöneticisiyle kurmayı tercih edersen Helm'in dokümantasyonunda apt deposu da var.
İlk komutta şu hatayı görürsen:
Error: Kubernetes cluster unreachable: Get "http://localhost:8080/version": dial tcp 127.0.0.1:8080: connect: connection refusedHelm kubeconfig bulamamış ve varsayılan adrese gitmiş demektir. k3s'in kubeconfig'i /etc/rancher/k3s/k3s.yaml altında, Helm ise KUBECONFIG değişkenine ya da ~/.kube/config dosyasına bakıyor. İlk yazıda kopyayı alıp KUBECONFIG'i ayarladıysan bu hatayı görmezsin.
Grafana'yı hazır chart ile kurmak
Örnek olarak Grafana'nın resmi chart'ını kuruyorum. Kurum içinde izleme paneli olarak sık karşılaşılan bir uygulama ve chart'ı kalıcı depolama, Ingress ve Secret kullanımını bir arada gösteriyor.
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
helm search repo grafana/grafana --versions | head -5NAME CHART VERSION APP VERSION DESCRIPTION
grafana/grafana x.y.z a.b.c The leading tool for querying and visualizing t...
...İki ayrı sürüm var: chart sürümü ve uygulama sürümü. Ben kurarken her zaman chart sürümünü --version ile sabitliyorum. Vermezsen o gün en yeni chart geliyor ve aynı komut bir ay sonra başka bir şey kuruyor. Image etiketini sabitlemekle aynı mantık.
Chart'ın neleri ayarlamana izin verdiğini görmek için varsayılan değerleri dosyaya döküyorum:
helm show values grafana/grafana --version x.y.z > grafana-varsayilan.yaml
wc -l grafana-varsayilan.yamlBu dosya uzun. Hepsini kopyalayıp düzenleme hatasına düşme. Kendi dosyanda sadece değiştirdiğin değerler olsun. Böylece chart'ı yükselttiğinde yeni varsayılanlar otomatik geliyor ve dosyana bakan biri neyi bilerek değiştirdiğini hemen görüyor.
Yönetici parolası
Chart'a parolayı values içinde düz metin verebiliyorsun ama o zaman parola values dosyasında ve release geçmişinde duruyor. Onun yerine önce bir Secret açıp chart'a onun adını veriyorum. Parolayı da kendim yazmak yerine ürettiriyorum:
kubectl create namespace monitoring
kubectl -n monitoring create secret generic grafana-admin \
--from-literal=admin-user=admin \
--from-literal=admin-password="$(openssl rand -base64 24)"values dosyası
# grafana-values.yaml
admin:
existingSecret: grafana-admin
userKey: admin-user
passwordKey: admin-password
persistence:
enabled: true
size: 5Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- grafana.k8s.example.com
path: /
pathType: Prefix
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mipersistence açık olmazsa Grafana pano ve kullanıcılarını pod içinde tutuyor, pod yeniden açılınca hepsi gidiyor. k3s'te varsayılan depolama sınıfı local-path olduğu için storageClassName vermeye gerek yok, veri sunucunun diskinde /var/lib/rancher/k3s/storage altında duruyor.
Kurmak
Kurmadan önce chart'ın ne üreteceğini görmek istiyorum. helm template kümeye dokunmadan YAML'ı ekrana basıyor:
helm template grafana grafana/grafana --version x.y.z \
-n monitoring -f grafana-values.yaml | lessİçinde Ingress'in doğru host'la, PVC'nin istediğim boyutla oluştuğunu görünce kuruyorum:
helm install grafana grafana/grafana --version x.y.z \
-n monitoring -f grafana-values.yaml --wait --timeout 5mNAME: grafana
LAST DEPLOYED: ...
NAMESPACE: monitoring
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:
...--wait, Helm'in kaynaklar hazır olana kadar beklemesini sağlıyor. Vermezsen Helm YAML'ı API'ye gönderip "deployed" diyor, pod'lar o sırada daha image çekiyor bile olabilir. Otomasyonda bu fark önemli.
Durumu görmek için:
helm list -n monitoring
helm get values grafana -n monitoring
kubectl -n monitoring get pods,pvc,ingresshelm get values sana sadece senin verdiğin değerleri gösteriyor. Kurulumdan çok sonra hangi ayarların verildiğini buradan görebilirsin. Yine de asıl kaynak grafana-values.yaml dosyası olmalı ve bir repoda durmalı.
Yükseltmek
Bir ayarı değiştirmek de chart sürümünü yükseltmek de aynı komutla yapılıyor. Mesela bellek limitini artırdım:
helm upgrade grafana grafana/grafana --version x.y.z \
-n monitoring -f grafana-values.yaml --waitBurada Helm'in değerleri nasıl taşıdığını bilmek gerekiyor. Hiç -f ya da --set vermezsen Helm 3 önceki release'in değerlerini kendisi kullanıyor. Ama tek bir --set ya da başka bir -f verdiğin anda önceki değerler bırakılıyor ve vermediğin her şey chart varsayılanına dönüyor. Bellek limitini --set ile değiştirip -f grafana-values.yaml'ı unutursan persistence kapanıyor, Ingress siliniyor ve sen sadece bir değeri değiştirdiğini sanıyorsun. --reuse-values diye bir bayrak var ama yeni chart sürümünde eklenen değerlerle birlikte kullanınca tuhaf sonuçlar verebiliyor. Bence her seferinde values dosyasını açıkça vermek en güvenlisi.
Chart sürümünü yükseltmeden önce iki şeye bakıyorum: chart'ın değişiklik notları (özellikle major sürüm atlıyorsa) ve iki sürüm arasındaki farkın ne olacağı. Fark için helm-diff eklentisi işe yarıyor:
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade grafana grafana/grafana --version x.y.w \
-n monitoring -f grafana-values.yamlÇıktı, kümedeki kaynaklarla yeni sürümün üreteceği kaynaklar arasındaki farkı diff biçiminde gösteriyor. Silinecek bir PVC görüyorsan orada dur.
Geri almak
Her install, upgrade ve rollback yeni bir revizyon oluşturuyor:
helm history grafana -n monitoringREVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 ... superseded grafana-x.y.z ... Install complete
2 ... deployed grafana-x.y.w ... Upgrade completeYükseltmeden memnun değilsem birinci revizyona dönüyorum:
helm rollback grafana 1 -n monitoring --waitGeri alma, eski revizyonu yeni bir numarayla tekrar uyguluyor. Geçmişe bakınca 3 numaralı, açıklaması Rollback to 1 olan bir satır görürsün. Geçmiş hiç silinmiyor, bu da neyin ne zaman olduğunu takip etmeyi kolaylaştırıyor.
Rollback'in yapmadığı şeyleri bilmek de gerekiyor. PVC içindeki veriye dokunmuyor: yeni Grafana sürümü açılırken veritabanı şemasını güncellediyse eski sürüm o veriyle açılmayabilir. CRD'leri de geri almıyor. Yani rollback uygulamanın tanımını geri alıyor, verisini değil. Veri taşıyan bir uygulamayı yükseltmeden önce yedek almak bu yüzden hâlâ gerekli.
Kendi chart'ın
Bir önceki yazıdaki podinfo YAML'larını bir chart'a çeviriyorum. helm create bir iskelet üretiyor:
helm create podinfo
tree podinfopodinfo
├── Chart.yaml
├── charts
├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── service.yaml
│ ├── serviceaccount.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yamlBu iskelet her durumu düşünen genel bir şablon ve ilk okumada kafa karıştırıyor. Ben templates/ içini boşaltıp kendi dosyalarımı yazıyorum. Ne yazdığımı satır satır bildiğim bir chart, her şeyi yapan ama anlamadığım bir chart'tan daha kolay bakım istiyor.
rm -rf podinfo/templates/* podinfo/chartsChart.yaml:
apiVersion: v2
name: podinfo
description: podinfo için basit bir chart
type: application
version: 0.1.0
appVersion: "6.7.1"version chart'ın kendi sürümü, şablonu her değiştirdiğinde artırıyorsun. appVersion içindeki uygulamanın sürümü.
values.yaml:
replicaCount: 2
image:
repository: ghcr.io/stefanprodan/podinfo
tag: "" # boş kalırsa Chart.yaml içindeki appVersion kullanılıyor
ui:
message: "Merhaba"
color: "#2f5d7c"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
ingress:
enabled: true
host: podinfo.k8s.example.com
tlsSecret: ""templates/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}
data:
PODINFO_UI_MESSAGE: {{ .Values.ui.message | quote }}
PODINFO_UI_COLOR: {{ .Values.ui.color | quote }}templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
containers:
- name: podinfo
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- name: http
containerPort: 9898
envFrom:
- configMapRef:
name: {{ .Release.Name }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
readinessProbe:
httpGet:
path: /readyz
port: http
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]securityContext'i bir önceki yazıdaki Deployment'tan aynen taşıdım. YAML'ı şablona çevirirken bu tür satırları kaybetmek kolay ve chart ilk halinden daha gevşek çıkıyor.
İki satır özellikle işe yarıyor. checksum/config notu, ConfigMap'in içeriğinin özetini pod şablonuna yazıyor. ConfigMap değişince özet değişiyor, pod şablonu değişmiş sayılıyor ve Kubernetes rolling update başlatıyor. Bir önceki yazıda elle yaptığımız rollout restart artık gerekmiyor. toYaml ... | nindent 12 ise resources bloğunu values'tan olduğu gibi alıp doğru girintiyle yerleştiriyor. Girinti sayısı yanlış olursa YAML bozuluyor, chart yazarken en çok zaman kaybettiren şey bu.
templates/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
selector:
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: 80
targetPort: httptemplates/ingress.yaml:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}
spec:
ingressClassName: traefik
{{- if .Values.ingress.tlsSecret }}
tls:
- hosts:
- {{ .Values.ingress.host }}
secretName: {{ .Values.ingress.tlsSecret }}
{{- end }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}
port:
name: http
{{- end }}Kontrol etmek için:
helm lint ./podinfo
helm template deneme ./podinfo | less==> Linting ./podinfo
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failedicon uyarısı önemsiz. lint sözdizimini yakalıyor ama mantık hatalarını yakalamıyor, template çıktısını gözle okumak hâlâ gerekli.
Ortam başına values
Asıl kazanç burada. Test ortamı için küçük bir dosya:
# values-test.yaml
replicaCount: 1
ui:
message: "Test ortamı"
ingress:
host: podinfo-test.k8s.example.comhelm install podinfo-test ./podinfo -n test --create-namespace -f values-test.yamlAynı chart'ı bir önceki yazıdaki demo namespace'ine podinfo adıyla kurmaya kalkarsan şuna benzer bir hata alırsın:
Error: INSTALLATION FAILED: rendered manifests contain a resource that already exists. Unable to continue with install: ConfigMap "podinfo" in namespace "demo" exists and cannot be imported into the current release: invalid ownership metadata; ...Helm, kendisinin oluşturmadığı kaynakları sahiplenmiyor. Doğru davranış bu, yoksa elle kurulmuş bir şeyi yanlışlıkla ezebilirdi. Geçiş için eski kaynakları silip chart'ı kurmak en temiz yol. Dizini toptan silme, 00-namespace.yaml da içinde olduğu için namespace'i ve içindeki Secret'ları da götürür. Dosyaları tek tek ver:
for f in ingress service deployment configmap; do
kubectl delete -f "k8s/podinfo/$f.yaml"
done
helm install podinfo ./podinfo -n demoArada kısa bir kesinti oluyor, bunu bir bakım penceresine koy.
Chart'ı paylaşmak istersen helm package ./podinfo bir podinfo-0.1.0.tgz dosyası üretiyor. Kurumda OCI destekli bir registry varsa (Harbor gibi) chart'ı image'ların yanına itebilirsin:
helm package ./podinfo
helm push podinfo-0.1.0.tgz oci://registry.example.com/chartsSıradaki yazı: Dokploy ile kendi sunucunda küçük bir PaaS