Release Yönetimi: install, upgrade --install, rollback, history, --wait ve Hook'larla Migration
helm upgrade --install her çalıştırmada aynı sonuca götüren deploy komutudur; pre-upgrade hook'u migration'ı Deployment'tan önce çalıştırır, ama rollback veritabanı migration'ını geri almaz. Bookstore'un EF Core migration bundle'ını SQL Server'a karşı hook olarak çalıştırıyor, hook'un üç gerçek başarısızlığını, Helm 4'ün kstatus tabanlı --wait'ini, --rollback-on-failure'ı, pending-upgrade'den kurtarmayı ve server-side apply çakışmaları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, Entity Framework Core 10.0.15 ve SQL Server 2025 ile denenmiştir.
Önce Tahmin Et
Bookstore.Api'nin 1.2.0 sürümü, kitaplara Subtitle kolonu ekleyen bir Entity Framework Core (EF Core; .NET'in veritabanı erişim kütüphanesi) migration'ı getiriyor ve bu migration bir Helm hook'u ile upgrade'den önce çalışıyor. Upgrade başarılı. Yarım saat sonra bir sorun yüzünden helm rollback ile 1.1.0'a dönüyorsun. Helm "Rollback was a success" diyor. Veritabanındaki Subtitle kolonu ne oldu? Helm migration'ı geri aldı mı? Cevap rollback bölümünde, veritabanına doğrudan sorulmuş bir sorgunun çıktısıyla.
upgrade --install: Tek Komutla Kurulum ve Güncelleme
Bu yazıda Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) staging environment'ı gerçek bir SQL Server ile çalışıyor. Lab'da SQL Server kind cluster'ının içinde, chart'tan bağımsız bir Deployment olarak duruyor; production'da veritabanı cluster dışında kalır. Connection string'i taşıyan Secret'ı ve SQL Server'ı lab script'leri oluşturuyor:
# Lab: chart'ın dışında, kind cluster'ında SQL Server ve connection string Secret'ı
./lab/scripts/create-db-secrets.sh bookstore-staging
kubectl apply -f lab/sqlserver.yaml
kubectl label namespace bookstore-staging pod-security.kubernetes.io/enforce=restricted
CI'da helm install ile helm upgrade arasında seçim yapmak istemezsin: release yoksa kurulsun, varsa güncellensin. helm upgrade --install tam olarak bunu yapar ve aynı komut her çalıştırmada aynı sonuca götürür. -f ile environment dosyasını açıkça vermek de bu yüzden önemli; dördüncü yazıda gördüğümüz gibi values her seferinde Git'teki dosyadan gelir.
Migration'ı Hook ile Çalıştırmak
Kubernetes serisinin 13. yazısında EF Core migration bundle'ını bir Job olarak çalıştırmıştık; Job'ı elle, deploy'dan önce başlatmak gerekiyordu. Helm hook'ları bu sıralamayı release'in parçası yapıyor. Hook, Helm'in belirli bir anda (install öncesi, upgrade sonrası, rollback öncesi gibi) oluşturduğu sıradan bir Kubernetes resource'u; onu hook yapan şey helm.sh/hook annotation'ı.
# EF Core migration bundle; yalnızca SqlServer ile, pre-install/pre-upgrade hook olarak çalışır.
migrations:
enabled: true
image:
repository: helmseries-bookstore-migrations
tag: ""
pullPolicy: IfNotPresent
backoffLimit: 1
activeDeadlineSeconds: 300
{{- if and .Values.migrations.enabled (eq .Values.database.provider "SqlServer") }}
{{- $component := dict "ctx" . "component" "migrate" -}}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "bookstore.fullname" . }}-migrate
labels:
{{- include "bookstore.labels" $component | nindent 4 }}
annotations:
# Chart'ın hiçbir resource'u oluşturulmadan ya da güncellenmeden önce çalışır; başarısız olursa release durur.
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "0"
# Yenisinden önce eski Job'ı sil; başarıda sil, başarısızlıkta bırak.
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
backoffLimit: {{ .Values.migrations.backoffLimit }}
activeDeadlineSeconds: {{ .Values.migrations.activeDeadlineSeconds }}
template:
metadata:
labels:
{{- include "bookstore.selectorLabels" $component | nindent 8 }}
spec:
restartPolicy: Never
automountServiceAccountToken: false
securityContext:
{{- include "bookstore.podSecurityContext" . | nindent 8 }}
containers:
- name: migrate
image: {{ include "bookstore.image" (dict "ctx" . "image" .Values.migrations.image) | quote }}
imagePullPolicy: {{ .Values.migrations.image.pullPolicy }}
env:
# Bundle tek dosyalık bir uygulama, kendini buraya açıyor (root filesystem salt okunur).
- name: DOTNET_BUNDLE_EXTRACT_BASE_DIR
value: /tmp
# Bundle BookstoreContext'i Program.cs üzerinden kuruyor ve bu ayarı okuyor.
- name: ConnectionStrings__BookstoreDb
valueFrom:
secretKeyRef:
name: {{ required "database.existingSecret is required when database.provider is SqlServer" .Values.database.existingSecret }}
key: {{ .Values.database.secretKey }}
securityContext:
{{- include "bookstore.containerSecurityContext" . | nindent 12 }}
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir:
sizeLimit: 64Mi
{{- end }}

pre-install,pre-upgrade: Job, chart'ın diğer resource'larından önce oluşturulur ve Helm Job tamamlanana kadar bekler. Job başarısız olursa Deployment'a hiç dokunulmaz; yeni şemayı bekleyen kodun eski şemaya karşı çalışmaya başlaması böylece engellenir. Birden fazla hook varsa helm.sh/hook-weight sırayı belirler: küçük olan önce çalışır, eşit olanlar kind ve isme göre sıralanır, sonra her biri hazır olana kadar beklenir. Örneğin veritabanına erişimi kontrol eden bir Job'a -5, migration'a 0 verirsin. helm.sh/hook-delete-policy Job'ın ne zaman silineceğini söyler: before-hook-creation bir sonraki upgrade'de eski Job'ı silip yenisini oluşturur (annotation yoksa varsayılan bu), hook-succeeded başarıdan sonra siler. hook-failed eklemediğimiz için başarısız bir Job, loglarıyla birlikte incelemek üzere yerinde kalıyor.
Hook resource'larının bir özelliği daha var: release'in parçası sayılmazlar. helm uninstall onları silmez ve helm get manifest çıktısında görünmezler. Bu yüzden her hook'a bir silme politikası yazmak gerekiyor; yoksa namespace'te tamamlanmış Job'lar birikir. Migration bundle'ı ayrı bir image: chart'ta migrations.image olarak duruyor ve tag'i boş olduğunda API ile aynı appVersion'ı kullanıyor, böylece kod ve şema aynı sürümle ilerliyor.
Hook Başarısız Olunca
İlk kurulum düzgün gitmedi ve bu iyi oldu, çünkü hook'un başarısızlıkta ne yaptığını üç farklı hatayla gördük. İlk deneme:
helm upgrade --install bookstore charts/bookstore -n bookstore-staging \
-f deploy/values-staging.yaml --wait --timeout 5m
Release "bookstore" does not exist. Installing it now.
Error: failed pre-install: resource Job/bookstore-staging/bookstore-migrate not ready. status: Failed, message: Job Failed. failed: 2/1
helm list -n bookstore-staging
kubectl -n bookstore-staging get jobs,pods,deploy
kubectl -n bookstore-staging logs -l app.kubernetes.io/component=migrate
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
bookstore bookstore-staging 1 2026-11-15 07:19:09.298785 +0300 +03 failed bookstore-0.4.0 1.1.0
NAME STATUS COMPLETIONS DURATION AGE
job.batch/bookstore-migrate Failed 0/1 18s 18s
NAME READY STATUS RESTARTS AGE
pod/bookstore-migrate-6277g 0/1 Error 0 18s
pod/bookstore-migrate-9xg4z 0/1 Error 0 7s
Default extraction directory [/home/app] either doesn't exist or is not accessible for read/write.
Failure processing application bundle.
Failed to determine location for extracting embedded files.
DOTNET_BUNDLE_EXTRACT_BASE_DIR is not set, and a read-write cache directory couldn't be created.
Release failed, Job iki kez denendi (backoffLimit: 1), Deployment hiç oluşmadı. Hata ise bizim: migration bundle tek dosyalık bir .NET uygulaması ve çalışırken kendini diske açıyor; root filesystem salt okunur olduğu için yazacak yer bulamadı. DOTNET_BUNDLE_EXTRACT_BASE_DIR=/tmp ekleyip /tmp'ye bir emptyDir bağladık. İkinci deneme (upgrade --install, failed bir ilk kurulumu upgrade olarak devam ettiriyor) ve Job'ın logu:
Error: UPGRADE FAILED: pre-upgrade hooks failed: resource Job/bookstore-staging/bookstore-migrate not ready. status: Failed, message: Job Failed. failed: 2/1
An error occurred while accessing the Microsoft.Extensions.Hosting services. Continuing without the application service provider. Error: Connection string 'BookstoreDb' is missing. Set it with user-secrets or the ConnectionStrings__BookstoreDb environment variable (see README.md).
Unable to create a 'DbContext' of type 'Bookstore.Data.BookstoreContext'. ...
Bundle, BookstoreContext'i uygulamanın Program.cs'i üzerinden kuruyor ve Program, connection string yoksa açılışta hata veriyor. Bundle'ın --connection argümanı context kurulduktan sonra devreye girdiği için işe yaramadı; Secret'ı ConnectionStrings__BookstoreDb adıyla environment variable olarak verdik. Üçüncü deneme:
System.NotSupportedException: Globalization Invariant Mode is not supported.
Bu kez image'ın sorunu: chiseled .NET image'ları ICU (International Components for Unicode; kültüre özgü metin ve tarih işlemlerini sağlayan kütüphane) içermez ve globalization invariant mode'da çalışır, Microsoft.Data.SqlClient ise bu modu desteklemiyor. Çözüm, ICU'yu içeren -extra varyantı; aynı değişikliği API image'ına da yaptık. Image tasarımı ilerideki Docker serisinin konusu.
# -extra: Microsoft.Data.SqlClient'ın ihtiyaç duyduğu ICU'yu içerir (globalization invariant mode'da çalışmaz).
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled-extra
COPY --from=build /out/efbundle /efbundle
ENTRYPOINT ["/efbundle"]
Release "bookstore" has been upgraded. Happy Helming!
NAME: bookstore
LAST DEPLOYED: Sun Nov 15 07:21:35 2026
NAMESPACE: bookstore-staging
STATUS: deployed
REVISION: 4
DESCRIPTION: Upgrade complete
...
helm history bookstore -n bookstore-staging
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Sun Nov 15 07:19:09 2026 failed bookstore-0.4.0 1.1.0 Release "bookstore" failed: failed pre-install: res...
2 Sun Nov 15 07:19:37 2026 failed bookstore-0.4.0 1.1.0 Upgrade "bookstore" failed: pre-upgrade hooks faile...
3 Sun Nov 15 07:20:03 2026 superseded bookstore-0.4.0 1.1.0 Upgrade "bookstore" failed: pre-upgrade hooks faile...
4 Sun Nov 15 07:21:35 2026 deployed bookstore-0.4.0 1.1.0 Upgrade complete
{"version":"1.1.0","environment":"Staging","pod":"bookstore-api-788648899-kgbcq","database":"SqlServer","storeName":"Bookstore (staging)"}
History bir günlük gibi okunuyor: başarısız denemeler de birer revision. Dikkat çeken bir ayrıntı, 3 numaralı revision'ın açıklaması "failed" ama durumu superseded: Helm, başarılı yeni bir revision geldiğinde öncekilerin durumunu bu şekilde güncelliyor.
Revision, history ve rollback
Şimdi Önce Tahmin Et'teki senaryo. Chart 0.5.0, appVersion 1.2.0; 1.2.0 image'ında AddBookSubtitle migration'ı var.
# Chart.yaml: version 0.5.0, appVersion "1.2.0"; 1.2.0'da Book'a Subtitle kolonu ekleyen bir migration var
helm upgrade --install bookstore charts/bookstore -n bookstore-staging \
-f deploy/values-staging.yaml --wait --timeout 5m
Release "bookstore" has been upgraded. Happy Helming!
NAME: bookstore
LAST DEPLOYED: Sun Nov 15 07:23:28 2026
NAMESPACE: bookstore-staging
STATUS: deployed
REVISION: 5
DESCRIPTION: Upgrade complete
Hook Job'ı başarıdan sonra silindiği için logunu upgrade sırasında ayrı bir terminalde kubectl logs -f ile aldık; kısaltılmış hâli:
info: Microsoft.EntityFrameworkCore.Migrations[20411]
Acquiring an exclusive lock for migration application. See https://aka.ms/efcore-docs-migrations-lock for more information if this takes too long.
...
info: Microsoft.EntityFrameworkCore.Migrations[20402]
Applying migration '20261115042231_AddBookSubtitle'.
...
ALTER TABLE [catalog].[Books] ADD [Subtitle] nvarchar(200) NULL;
...
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20261115042231_AddBookSubtitle', N'10.0.15');
...
Done.
EF Core 9'dan beri migration uygulanırken veritabanında bir kilit alınıyor; iki pod aynı anda migration çalıştırmaya kalksa bile ikincisi bekliyor. Veritabanına doğrudan sorduk:
# sa şifresi pod'un environment'ından okunur, komut satırına yazılmaz
kubectl -n bookstore-db exec deploy/mssql -- bash -c '/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa \
-P "$MSSQL_SA_PASSWORD" -C -d BookstoreDb -W -Q "SET NOCOUNT ON; SELECT MigrationId FROM __EFMigrationsHistory"'
MigrationId
-----------
20261114005447_InitialCreate
20261115042231_AddBookSubtitle
Rollback, revision numarası verilerek yapılır:
helm rollback bookstore 4 -n bookstore-staging --wait
helm history bookstore -n bookstore-staging | tail -3
Rollback was a success! Happy Helming!
4 Sun Nov 15 07:21:35 2026 superseded bookstore-0.4.0 1.1.0 Upgrade complete
5 Sun Nov 15 07:23:28 2026 superseded bookstore-0.5.0 1.2.0 Upgrade complete
6 Sun Nov 15 07:23:48 2026 deployed bookstore-0.4.0 1.1.0 Rollback to 4
{"version":"1.1.0","environment":"Staging","pod":"bookstore-api-788648899-mqmtn","database":"SqlServer","storeName":"Bookstore (staging)"}
Pod adındaki ReplicaSet hash'i (788648899) 4 numaralı revision'dakiyle aynı: Deployment'ın template'i birebir eski hâline döndü ve Kubernetes eski ReplicaSet'i yeniden büyüttü. Veritabanı ise yerinde kaldı:
MigrationId
-----------
20261114005447_InitialCreate
20261115042231_AddBookSubtitle
SubtitleColumn
--------------
1
Önce Tahmin Et'in cevabı: Helm rollback'te pre-upgrade hook'larını çalıştırmaz (rollback'in kendi pre-rollback/post-rollback hook'ları var), EF Core bundle'ı da yalnızca ileri gider. Kolon ve history kaydı duruyor. Bu sefer sorun olmadı, çünkü migration yalnızca nullable bir kolon ekliyordu ve 1.1.0 kodu bu kolonu görmezden geliyor. Kolonu silen ya da yeniden adlandıran bir migration'dan sonra rollback, eski kodu uyumsuz bir şemaya karşı çalıştırırdı. Bu yüzden migration'lar expand-contract biçiminde yazılmalı: önce eski ve yeni kodun birlikte çalışabildiği bir şemaya genişlet, eski kod tamamen kalktıktan sonraki bir sürümde daralt. Ayrıntılar Deployment Stratejileri serisinin altıncı yazısında.
--wait, kstatus ve --rollback-on-failure
Helm 4'te --wait artık bir strateji alıyor:
helm upgrade --help | grep -A1 -- "--wait "
--wait WaitStrategy[=watcher] wait until resources are ready (up to --timeout). Use '--wait' alone for 'watcher' strategy, or specify one of: 'watcher', 'hookOnly', 'legacy'. Default when flag is omitted: 'hookOnly'. (default hookOnly)
Bayrak hiç verilmezse varsayılan hookOnly: Helm yalnızca hook'ları bekler, Deployment'ların hazır olmasını beklemez. --wait tek başına watcher demek; Helm resource'ları kstatus kütüphanesiyle izler, Deployment için yeni ReplicaSet'in hazır olduğunu ve eski pod'ların bittiğini kontrol eder. legacy, Helm 3'ün polling ile bekleyen eski davranışı. Olmayan bir image tag'iyle, otomatik geri dönüşü açarak denedik:
helm upgrade bookstore charts/bookstore -n bookstore-staging -f deploy/values-staging.yaml \
--set image.tag=1.2.1 --rollback-on-failure --timeout 60s
helm history bookstore -n bookstore-staging | tail -1
level=WARN msg="upgrade failed" name=bookstore error="resource Deployment/bookstore-staging/bookstore-api not ready. status: InProgress, message: Pending termination: 1\ncontext deadline exceeded"
Error: UPGRADE FAILED: release bookstore failed, and has been rolled back due to rollback-on-failure being set: resource Deployment/bookstore-staging/bookstore-api not ready. status: InProgress, message: Pending termination: 1
context deadline exceeded
12 Sun Nov 15 07:25:53 2026 deployed bookstore-0.5.0 1.2.0 Rollback to 10
Hata mesajı kstatus'un dilinde: Deployment InProgress, sebep olarak da beklenen bir pod sonlanması. Helm 60 saniye bekledi, release'i failed işaretledi ve son başarılı revision'a döndü. Helm 3'teki --atomic aynı işi yapıyor ama Helm 4'te adı değişti, eski ad uyarıyla çalışmaya devam ediyor:
Flag --atomic has been deprecated, use --rollback-on-failure instead
CI'da --rollback-on-failure kullanmak, başarısız bir deploy'un cluster'ı yarım bırakmasını önler. Bedeli, hatanın izlerinin de silinmesi: rollback olduğunda kırık pod'lar kalkar, olay sonrası inceleme için logları ve event'leri pipeline'ın kendisinin toplaması gerekir.
pending-upgrade'de Kalan Release
Helm bir işleme başladığında release'i pending-install, pending-upgrade ya da pending-rollback durumuna alır ve işlem bitince günceller. İşlem yarıda kesilirse ne olduğunu iki sinyalle denedik. Önce SIGTERM (bir sürece düzgün kapanmasını söyleyen sinyal):
# Olmayan bir image tag'iyle upgrade; 15 saniye sonra helm sürecine SIGTERM
helm upgrade bookstore charts/bookstore -n bookstore-staging -f deploy/values-staging.yaml \
--set image.tag=1.2.1 --wait --timeout 3m &
sleep 15; kill $!
Release bookstore has been cancelled.
level=WARN msg="upgrade failed" name=bookstore error="context canceled"
Error: UPGRADE FAILED: context canceled
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
bookstore bookstore-staging 7 2026-11-15 07:24:02.359507 +0300 +03 failed bookstore-0.5.0 1.2.0
Helm 4 sinyali yakalayıp release'i failed olarak kapattı; bir sonraki upgrade sorunsuz çalıştı. Bir CI runner'ı zaman aşımında süreci SIGKILL (sürecin yakalayamadığı, anında sonlandıran sinyal) ile öldürürse durum farklı:
# Aynı deneme, bu kez SIGKILL: CI runner'ının süreci sert biçimde öldürmesi gibi
helm upgrade bookstore charts/bookstore -n bookstore-staging -f deploy/values-staging.yaml \
--set image.tag=1.2.1 --wait --timeout 3m &
sleep 15; kill -9 $!
helm history bookstore -n bookstore-staging | tail -2
helm upgrade bookstore charts/bookstore -n bookstore-staging -f deploy/values-staging.yaml
8 Sun Nov 15 07:24:17 2026 deployed bookstore-0.5.0 1.2.0 Upgrade complete
9 Sun Nov 15 07:24:28 2026 pending-upgrade bookstore-0.5.0 1.2.0 Preparing upgrade
Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress
Helm'in temizlik yapacak fırsatı olmadı; revision 9 pending-upgrade'de kaldı ve bundan sonraki her upgrade bu hatayla reddediliyor. Kurtarmanın en temiz yolu son başarılı revision'a rollback; revision numarası verilmezse Helm bir önceki revision'a döner:
helm rollback bookstore -n bookstore-staging
helm history bookstore -n bookstore-staging | tail -3
Rollback was a success! Happy Helming!
8 Sun Nov 15 07:24:17 2026 superseded bookstore-0.5.0 1.2.0 Upgrade complete
9 Sun Nov 15 07:24:28 2026 pending-upgrade bookstore-0.5.0 1.2.0 Preparing upgrade
10 Sun Nov 15 07:24:43 2026 deployed bookstore-0.5.0 1.2.0 Rollback to 8
Revision 9 history'de pending-upgrade olarak duruyor ama artık bir engel değil. Eski blog yazılarında önerilen "release Secret'ını elle sil" yolu da çalışır, ama hangi Secret'ı sildiğini bilmeden yapılırsa release geçmişini bozar; rollback hem kayıt bırakır hem de cluster'ı bilinen bir duruma getirir.
Server-Side Apply ve Field Ownership
Helm 4 yeni release'leri SSA (server-side apply; nesneyi API server'ın birleştirdiği ve her alanın sahibini kaydettiği apply yöntemi) ile kurar. Helm 3 ile kurulmuş release'ler ise upgrade'de CSA (client-side apply; Helm'in farkı kendisinin hesaplayıp patch gönderdiği yöntem) ile devam eder; Helm 4 her release için hangi yöntemin kullanıldığını kaydedip sonraki upgrade'lerde aynısını seçiyor. Bunu yedinci yazıda gerçek çıktılarla göreceğiz. SSA'nın günlük işletimdeki etkisini görmek için, bir olay sırasında birinin Deployment'a elle dokunduğunu düşünelim:
kubectl -n bookstore-staging scale deploy/bookstore-api --replicas=2
kubectl -n bookstore-staging set env deploy/bookstore-api Logging__LogLevel__Default=Debug
helm upgrade bookstore charts/bookstore -n bookstore-staging -f deploy/values-staging.yaml --wait
deployment.apps/bookstore-api scaled
deployment.apps/bookstore-api env updated
level=WARN msg="upgrade failed" name=bookstore error="conflict occurred while applying object bookstore-staging/bookstore-api apps/v1, Kind=Deployment: Apply failed with 1 conflict: conflict with \"kubectl\" with subresource \"scale\" using apps/v1: .spec.replicas"
Error: UPGRADE FAILED: conflict occurred while applying object bookstore-staging/bookstore-api apps/v1, Kind=Deployment: Apply failed with 1 conflict: conflict with "kubectl" with subresource "scale" using apps/v1: .spec.replicas
Helm, replicas alanının artık kubectl'e ait olduğunu gördü ve ezmek yerine durdu. Alanların sahipleri:
kubectl -n bookstore-staging get deploy bookstore-api -o json --show-managed-fields \
| jq -r '.metadata.managedFields[] | "\(.manager)\t\(.operation)\t\(.subresource // "-")"'
helm Apply -
kubectl Update scale
kubectl-set Update -
kube-controller-manager Update status
Burada iki ayrı manager var: kubectl scale scale subresource'u üzerinden replicas'ın sahibi oldu, kubectl set env ise kubectl-set adıyla yeni environment variable'ın sahibi oldu. Çakışma yalnızca Helm'in de yönettiği alanda çıktı. Değişikliğin bilinçli olarak geri alınacağı durumda --force-conflicts var:
helm upgrade bookstore charts/bookstore -n bookstore-staging -f deploy/values-staging.yaml --force-conflicts --wait
kubectl -n bookstore-staging get deploy bookstore-api \
-o jsonpath='{.spec.replicas}{"\n"}{range .spec.template.spec.containers[0].env[*]}{.name}{"\n"}{end}'
Release "bookstore" has been upgraded. Happy Helming!
1
ConnectionStrings__BookstoreDb
Logging__LogLevel__Default
replicas Helm'in değerine döndü, ama elle eklenen Logging__LogLevel__Default yerinde duruyor: SSA, başka bir manager'ın sahip olduğu ve Helm'in hiç bilmediği alanları silmez. Bir olaydan sonra "Helm upgrade'i her şeyi Git'teki hâline döndürür" varsayımı bu yüzden yanlış. Kalıcı çözüm ya elle değişiklik yapmamak ya da driftleri sürekli düzelten bir GitOps aracı kullanmak; GitOps ve Argo CD serisinde Argo CD'nin self-heal özelliğinin bile bu tür alanları görmediğini gördük.
Üretimde Dikkat
Migration hook'u yalnızca expand adımları için güvenlidir; kolon silen bir değişikliği aynı sürümde yayımlama. Hook Job'ına activeDeadlineSeconds ve küçük bir backoffLimit ver; sonsuza kadar tekrar eden bir migration, --timeout dolana kadar pipeline'ı da bekletir. Upgrade'leri --wait ve ölçülü bir --timeout ile çalıştır; --timeout toplam süre değil, her bir Kubernetes işlemi (hook Job'ının beklenmesi, resource'ların hazır olması) için ayrı ayrı uygulanıyor, pipeline'ın toplam süresini hesaplarken bunu da say. Aynı release'e aynı anda iki pipeline'ın dokunmaması için CI'da environment başına eşzamanlılık sınırı koy (GitHub Actions'ta concurrency). Chart'ı Argo CD ile deploy ediyorsan, Argo CD Helm hook'larını kendi hook'larına çevirir: pre-install ve pre-upgrade birer PreSync hook'u olur ve sıralamayı sync wave'ler belirler; Argo CD helm upgrade çalıştırmadığı için release kaydı ve helm history da oluşmaz. Ayrıntılar GitOps ve Argo CD serisinin sync wave ve hook'ları anlatan yazısında.
Sık Düşülen Tuzaklar
Rollback'in veritabanı migration'ını da geri aldığını sanmak. Hook'a silme politikası yazmayıp namespace'te Job biriktirmek ya da hook-succeeded ile başarısız Job'ın loglarını da kaybetmek. --wait vermeden "deployed" görüp işin bittiğini düşünmek. CI'da sert biçimde öldürülen bir Helm sürecinin bıraktığı pending-upgrade'i release Secret'ını rastgele silerek çözmeye çalışmak. --force-conflicts'u her upgrade'e varsayılan olarak eklemek ve bir başkasının bilinçli değişikliğini sessizce ezmek. Helm upgrade'inin elle eklenen alanları da temizlediğini varsaymak. Chiseled image'larda ICU olmadığını unutup SqlClient'ın açılışta düşmesine şaşırmak.
Küçük Bir Deneme
★☆☆ Migration Job'ının hook-delete-policy'sinden hook-succeeded'ı çıkar, iki upgrade yap ve kubectl get jobs ile kaç Job kaldığına bak. Sonra helm uninstall çalıştırıp Job'ın hâlâ durduğunu gör.
★★☆ Ağırlığı -5 olan ikinci bir pre-upgrade hook'u ekle: SQL Server'a TCP bağlantısı açılabildiğini kontrol eden kısa bir Job. Veritabanını durdurup upgrade'in hangi hook'ta durduğunu ve Deployment'ın değişmediğini doğrula.
★★★ Subtitle kolonunu silen bir 1.3.0 migration'ı yaz ve aynı sürümde koddan kaldır. 1.3.0'a upgrade edip 1.2.0'a rollback yaptığında API'nin ne hata verdiğini gözle, sonra aynı değişikliği expand-contract ile iki sürüme bölerek tekrar dene.
Chart'ımız artık kurulup güncellenebiliyor ve veritabanını sırayla taşıyor. Bir sonraki yazıda chart'ı paylaşılabilir hâle getiriyoruz: dependency'ler ve Chart.lock, OCI (Open Container Initiative; container image'ları için açık standart) registry'sine helm push, helm lint, helm test, chart-testing ve GitHub Actions.