Mert Özen Her Satırda Daha İleri
DevOps Helm — #06

Dependency'ler, OCI Registry ve Chart'ı Test Etmek: Chart.lock, helm push, lint, helm test ve CI

Mert Özen 15 Kas 2026 15 dk 6 görüntülenme
Dependency'ler, OCI Registry ve Chart'ı Test Etmek: Chart.lock, helm push, lint, helm test ve CI

Chart dependency'leri Chart.yaml'da aralıkla tanımlanır ve Chart.lock ile sabitlenir; chart helm package ve helm push ile bir OCI registry'ye yayımlanır, ama aynı sürümü farklı içerikle yeniden push etmeyi registry engellemez. Bookstore chart'ına opsiyonel bir Valkey subchart'ı ve global.imageRegistry ekliyor, Helm 4'ün registry login ve digest ile kurulum davranışını, kubeconform, helm test ve chart-testing'i ve bunları çalıştıran bir GitHub Actions workflow'unu 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, kubeconform 0.8, chart-testing 3.15, actionlint 1.7 ve .NET 10 ile denenmiştir.

Önce Tahmin Et

Bookstore chart'ının 0.6.0 sürümünü OCI (Open Container Initiative; container image'ları için açık standart) registry'sine push ettin ve bir environment onu kurdu. Ertesi gün values.yaml'da küçük bir düzeltme yapıyorsun ama Chart.yaml'daki sürümü artırmayı unutuyorsun ve tekrar helm push çalıştırıyorsun. Registry "bu sürüm zaten var" diye reddeder mi? Reddetmezse, yarın --version 0.6.0 ile kuran biri hangi içeriği alır? Cevap sürümleme bölümünde, gerçek push çıktısıyla.

Dependency'ler ve Chart.lock

Bir chart başka chart'lara bağımlı olabilir; Helm onları charts/ klasörüne indirip birlikte render eder. Bookstore'a (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) örnek bir dependency olarak ilk yazıda kurduğumuz Valkey chart'ını ekledik. Bookstore.Api bugün bir cache kullanmıyor; amaç mekanizmayı görmek, bu yüzden dependency varsayılan olarak kapalı:

dependencies:
  - name: valkey
    version: "1.0.x"
    repository: https://valkey.io/valkey-helm/
    condition: valkey.enabled
# Subchart'larla paylaşılır: her chart .Values.global'ı okuyabilir.
global:
  # Bütün image'lar için ayna registry, ör. registry.example.com/mirror (boş: image adları olduğu gibi).
  imageRegistry: ""

...

# Opsiyonel dependency (subchart). Values'ı kendi adının altında; Bookstore.Api henüz kullanmıyor.
valkey:
  enabled: false

version bir aralık olabilir: 1.0.x, 1.0 serisindeki en yeni patch'i kabul eder. condition, values'taki bir boolean'a bakar; valkey.enabled false ise subchart hiç render edilmez. Birden fazla dependency'yi bir arada açıp kapatmak için tags da var. Aralığı gerçek bir sürüme çözen ve paketi indiren komut helm dependency update:

helm dependency list charts/bookstore
helm dependency update charts/bookstore
cat charts/bookstore/Chart.lock
NAME  	VERSION	REPOSITORY                    	STATUS
valkey	1.0.x  	https://valkey.io/valkey-helm/	missing

Saving 1 charts
Downloading valkey from repo https://valkey.io/valkey-helm/
Deleting outdated charts
dependencies:
- name: valkey
  repository: https://valkey.io/valkey-helm/
  version: 1.0.0
digest: sha256:5fa5e0af5553cc2d852e9e55b70d34b3aa9b4992cc44b5fad4625f44f5b3e02e
generated: "2026-11-15T07:28:21.468136+03:00"

Chart.lock, aralığın hangi sürüme çözüldüğünü ve bir digest'i kaydeder. helm dependency build bu dosyayı okuyup tam o sürümü indirir; update ise aralığı yeniden çözüp lock dosyasını günceller. İndirilen .tgz dosyaları build çıktısı sayılır ve Git'e girmez:

# Paketlenmiş chart'lar ve indirilen dependency'ler build çıktısıdır.
*.tgz

Chart.lock'u commit'lemek

Lock dosyası olmadan helm dependency build sessizce update gibi davranıyor ve aralığa uyan en yeni sürümü indiriyor; aynı commit'ten iki farklı günde iki farklı paket çıkabilir. Lock dosyası Chart.yaml'la uyuşmadığında ise açıkça hata veriyor:

# Chart.lock yok: build, aralığa uyan en yeni sürümü çözüp indiriyor
rm charts/bookstore/Chart.lock
helm dependency build charts/bookstore
# Chart.yaml'da aralık değişti, Chart.lock eski kaldı
helm dependency build charts/bookstore
Saving 1 charts
Downloading valkey from repo https://valkey.io/valkey-helm/
Deleting outdated charts
Error: the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies with 'helm dependency update'

Dependency hiç indirilmemişse render da duruyor; CI'da helm template'ten önce helm dependency build çalıştırmak bu yüzden şart:

Error: an error occurred while checking for chart dependencies. You may need to run 'helm dependency build' to fetch missing dependencies: found in Chart.yaml, but missing in charts/ directory: valkey

Subchart Values ve global

Subchart'ın values'ı parent chart'ta subchart'ın adıyla açılan anahtarın altına yazılır: valkey.resources.requests.memory, Valkey chart'ının kendi resources.requests.memory değeridir. Subchart parent'ın values'ını göremez; tek istisna global. global altındaki her şey bütün chart'lara aynı yoldan ulaşır. Valkey chart'ı global.imageRegistry'yi destekliyor; aynı değeri kendi helper'ımızda da okuduk:

{{- define "bookstore.image" -}}
{{- $repository := required "image.repository is required" .image.repository -}}
{{- with .ctx.Values.global.imageRegistry }}
{{- $repository = printf "%s/%s" (trimSuffix "/" .) $repository }}
{{- end }}
{{- printf "%s:%s" $repository (.image.tag | default .ctx.Chart.AppVersion) -}}
{{- end }}
helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set valkey.enabled=true \
    --set global.imageRegistry=registry.example.com/mirror | grep -E "^\s+image: " | sort -u
          image: "registry.example.com/mirror/helmseries-bookstore-api:1.2.0"
          image: "registry.example.com/mirror/helmseries-bookstore-web:1.2.0"
          image: registry.example.com/mirror/valkey/valkey:9.1.3
      image: curlimages/curl:8.17.0

Kurumsal bir ayna registry kullanan bir ekip, tek bir değerle bütün image'ları oraya yönlendirebiliyor. Test pod'unun curl image'ı bu kuralın dışında kaldı; deneme bölümünde düzeltmesi bir alıştırma. Subchart'a values vermek de aynı yoldan:

helm template bookstore charts/bookstore -f deploy/values-dev.yaml --set valkey.enabled=true \
    --set valkey.resources.requests.memory=64Mi | grep "^# Source" | sort -u
# Source: bookstore/charts/valkey/templates/deploy_valkey.yaml
# Source: bookstore/charts/valkey/templates/init_config.yaml
# Source: bookstore/charts/valkey/templates/service.yaml
# Source: bookstore/charts/valkey/templates/serviceaccount.yaml
# Source: bookstore/templates/configmap.yaml
# Source: bookstore/templates/deployment.yaml
# Source: bookstore/templates/service.yaml
# Source: bookstore/templates/serviceaccount.yaml
# Source: bookstore/templates/tests/smoke-test.yaml
# Source: bookstore/templates/web.yaml

Bir not: dördüncü yazıda kök seviyede additionalProperties: false koymuştuk; bir dependency eklenince şemaya subchart'ın anahtarını (valkey) da eklemek gerekiyor, yoksa --set valkey.enabled=true şema hatası verir. Valkey chart'ının kendi values.schema.json'u subchart'ın values'ını ayrıca doğruluyor.

Chart'ı OCI Registry'de Yayımlamak

Chart'ı paylaşmak için önce paketliyoruz. Paket, .helmignore'un dışında kalan her şeyi ve charts/ altındaki dependency'leri içeren bir .tgz:

helm package charts/bookstore -d dist
Successfully packaged chart and saved it to: dist/bookstore-0.6.0.tgz

Lab'da 127.0.0.1 üzerinde, kullanıcı adı ve şifre isteyen bir OCI registry container'ı çalışıyor. Helm 4'te helm registry login yalnızca registry'nin adresini kabul ediyor; Helm 3'te çalışan, repository yolunu da içeren adresler artık hata veriyor:

# Lab registry: 127.0.0.1:52570, düz HTTP; kullanıcı ve şifre yalnızca lab'a ait test değerleri
echo "$REGISTRY_PASSWORD" | helm registry login http://127.0.0.1:52570/charts -u helm-ci --password-stdin --plain-http
echo "$REGISTRY_PASSWORD" | helm registry login 127.0.0.1:52570/charts -u helm-ci --password-stdin --plain-http
echo "$REGISTRY_PASSWORD" | helm registry login 127.0.0.1:52570 -u helm-ci --password-stdin --plain-http
level=WARN msg="registry login currently only supports registry hostname, not a repository path" host=http://127.0.0.1:52570/charts suggested=http:
Error: invalid reference: invalid registry "http://127.0.0.1:52570/charts"
level=WARN msg="registry login currently only supports registry hostname, not a repository path" host=127.0.0.1:52570/charts suggested=127.0.0.1:52570
Error: invalid reference: invalid registry "127.0.0.1:52570/charts"
Login Succeeded

--plain-http yalnızca lab için: registry'de TLS (Transport Layer Security; HTTP trafiğini şifreleyen protokol) yok. GHCR (GitHub Container Registry; GitHub'ın registry servisi) ya da bir bulut sağlayıcının registry'si HTTPS ile çalışır ve bu bayrak gerekmez. Bu yazıda bir bulut registry'sine ya da GHCR'a push etmedim; aşağıdaki GitHub Actions workflow'u bu adımı yazıyor ama GitHub'da çalıştırılmadı.

helm push dist/bookstore-0.6.0.tgz oci://127.0.0.1:52570/charts --plain-http
Pushed: 127.0.0.1:52570/charts/bookstore:0.6.0
Digest: sha256:49cf7df10a95…

Helm 4 chart arşivlerini tekrarlanabilir biçimde üretiyor: aynı içerikten iki kez paket alınca dosya byte byte aynı. Bu, aynı kaynaktan üretilen paketin aynı digest'i taşıması demek.

helm package charts/bookstore -d dist2
shasum -a 256 dist/bookstore-0.6.0.tgz dist2/bookstore-0.6.0.tgz
567da13ec613…  dist/bookstore-0.6.0.tgz
567da13ec613…  dist2/bookstore-0.6.0.tgz
helm show chart oci://127.0.0.1:52570/charts/bookstore --version 0.6.0 --plain-http
Pulled: 127.0.0.1:52570/charts/bookstore:0.6.0
Digest: sha256:49cf7df10a95…
apiVersion: v2
appVersion: 1.2.0
dependencies:
- condition: valkey.enabled
  name: valkey
  repository: https://valkey.io/valkey-helm/
  version: 1.0.x
description: Bookstore API (ASP.NET Core 10) on Kubernetes
name: bookstore
type: application
version: 0.6.0

Helm 4'ün getirdiği bir güvenlik özelliği de digest ile kurulum: tag yerine içerik özetine bağlanırsın ve digest eşleşmezse kurulum yapılmaz.

helm install bookstore oci://127.0.0.1:52570/charts/bookstore@sha256:49cf7df10a95… \
    -n bookstore-preview -f deploy/values-dev.yaml --plain-http --wait
helm install other oci://127.0.0.1:52570/charts/bookstore@sha256:0000000000000000000000000000000000000000000000000000000000000000 \
    -n bookstore-preview --plain-http
Pulled: 127.0.0.1:52570/charts/bookstore@sha256:49cf7df10a95…
Digest: sha256:49cf7df10a95…
NAME: bookstore
LAST DEPLOYED: Sun Nov 15 07:36:13 2026
NAMESPACE: bookstore-preview
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
...
Error: INSTALLATION FAILED: failed to perform "FetchReference" on source: 127.0.0.1:52570/charts/bookstore@sha256:0000000000000000000000000000000000000000000000000000000000000000: not found

Sürümleme ve Değişmezlik

Önce Tahmin Et'e dönelim. Values'ta replicaCount'u değiştirdik, sürümü 0.6.0'da bıraktık ve tekrar push ettik:

# values.yaml'da replicaCount: 3 yapılmış bir kopya, sürüm numarası değişmeden
helm package repush -d dist3
helm push dist3/bookstore-0.6.0.tgz oci://127.0.0.1:52570/charts --plain-http
helm show values oci://127.0.0.1:52570/charts/bookstore --version 0.6.0 --plain-http | grep -n "^replicaCount"
Pushed: 127.0.0.1:52570/charts/bookstore:0.6.0
Digest: sha256:f134e0e03d18…
13:replicaCount: 3

Registry hiçbir şey demedi; OCI tag'leri, container image tag'leri gibi değiştirilebilir. --version 0.6.0 artık farklı bir içerik kuruyor ve dün kuran environment ile bugün kuran environment aynı sürüm numarasıyla farklı chart'lar çalıştırıyor. Korunmanın üç yolu var: CI'da push'tan önce sürümün registry'de olup olmadığını kontrol edip varsa durmak (aşağıdaki workflow bunu yapıyor), registry'nin tag değişmezliği özelliğini açmak (birçok bulut registry'si destekliyor) ve kritik environment'larda chart'ı digest ile kurmak. Sürüm numarası semver: template'leri bozan bir değişiklik major, yeni bir opsiyonel ayar minor, düzeltme patch. Chart sürümü ile appVersion'ın bağımsız olduğunu ikinci yazıda gördük; uygulama 1.2.0 iken chart 0.6.0 olabilir. Git'te sürüm tag'i ve release yönetimi Git & GitHub serisinin 23. yazısında, sürümleme stratejisi ise Versioning ve Release Management serisinde.

Kalite: lint, Render ve kubeconform

Dördüncü yazıda helm lint'in eksik zorunlu values'ı uyarıya çevirdiğini gördük. Lint'in yakalamadığı bir sınıf daha var: Kubernetes şemasına uymayan alanlar. kubeconform, render edilmiş manifest'leri Kubernetes'in OpenAPI (API'yi makine tarafından okunabilir biçimde tanımlayan standart) şemasından üretilmiş JSON Schema'larla doğrular; cluster gerekmez.

for env in dev staging prod; do
    helm template bookstore charts/bookstore -n "bookstore-$env" -f "deploy/values-$env.yaml" \
        | kubeconform -strict -summary -kubernetes-version 1.37.0
done
Summary: 7 resources found parsing stdin - Valid: 7, Invalid: 0, Errors: 0, Skipped: 0
Summary: 8 resources found parsing stdin - Valid: 8, Invalid: 0, Errors: 0, Skipped: 0
Summary: 8 resources found parsing stdin - Valid: 8, Invalid: 0, Errors: 0, Skipped: 0

startupProbe'da bir harf eksik yazılmış bir kopyayla:

# startupProbe'da failureThreshold yerine failureTreshold yazılmış bir kopya
helm lint typo -f deploy/values-dev.yaml | tail -1
helm template bookstore typo -f deploy/values-dev.yaml | kubeconform -strict -kubernetes-version 1.37.0
1 chart(s) linted, 0 chart(s) failed
stdin - Deployment bookstore-api is invalid: problem validating schema. Check JSON formatting: jsonschema validation failed with 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.37.0-standalone-strict/deployment-apps-v1.json#' - at '/spec/template/spec/containers/0/startupProbe': additional properties 'failureTreshold' not allowed

Lint geçti, kubeconform yakaladı; -strict olmadan bilinmeyen alanlar sessizce kabul edilirdi. Aynı kopyayı kurmayı denediğimizde Helm 4'ün server-side apply'ı da alanı reddetti:

Error: INSTALLATION FAILED: server-side apply failed for object bookstore-typo/t-bookstore-api apps/v1, Kind=Deployment: failed to create typed patch object (bookstore-typo/t-bookstore-api; apps/v1, Kind=Deployment): .spec.template.spec.containers[name="api"].startupProbe.failureTreshold: field not declared in schema

Yani hata eninde sonunda yakalanıyor, ama kurulum anında, cluster'da bir release failed olduktan sonra. kubeconform aynı hatayı pull request'te, cluster'a dokunmadan gösteriyor. CRD (Custom Resource Definition; Kubernetes'e eklenen özel resource tipi) kullanan chart'lar için kubeconform'a CRD şemalarını ayrıca vermek gerekiyor.

helm test ve chart-testing

helm test, chart'taki helm.sh/hook: test annotation'lı pod'ları çalıştırır ve sonucu release'e kaydeder. İkinci yazıda sildiğimiz test pod'unu Bookstore'a göre yeniden yazdık: API'nin readiness endpoint'ini ve kataloğu yokluyor.

{{- $api := printf "http://%s-api:%v" (include "bookstore.fullname" .) .Values.service.port -}}
apiVersion: v1
kind: Pod
metadata:
  name: {{ include "bookstore.fullname" . }}-smoke-test
  labels:
    {{- include "bookstore.labels" (dict "ctx" . "component" "test") | nindent 4 }}
  annotations:
    # Yalnızca "helm test" ile çalışır; pod bir sonraki koşudan önce silinir.
    "helm.sh/hook": test
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  restartPolicy: Never
  automountServiceAccountToken: false
  securityContext:
    {{- include "bookstore.podSecurityContext" . | nindent 4 }}
    runAsUser: 100
  containers:
    - name: smoke
      image: curlimages/curl:8.17.0
      command: ["sh", "-c"]
      args:
        - >-
          curl -sSf -o /dev/null -m 5 --retry 5 --retry-connrefused --retry-delay 2 {{ $api }}/health/ready &&
          curl -sSf -m 5 "{{ $api }}/api/books?pageSize=1" | grep -q '"isbn"' &&
          echo "smoke test passed: {{ $api }}"
      securityContext:
        {{- include "bookstore.containerSecurityContext" . | nindent 8 }}
helm upgrade bookstore charts/bookstore -n bookstore-dev -f deploy/values-dev.yaml --wait
helm test bookstore -n bookstore-dev --logs
...
TEST SUITE:     bookstore-smoke-test
Last Started:   Sun Nov 15 07:30:18 2026
Last Completed: Sun Nov 15 07:30:27 2026
Phase:          Succeeded

POD LOGS: bookstore-smoke-test (smoke)
curl: (28) Connection timed out after 5003 milliseconds
smoke test passed: http://bookstore-api:8080

Logdaki zaman aşımı satırı gerçek: test, upgrade'den hemen sonra çalıştı ve ilk deneme yeni pod'a denk geldi; curl'ün --retry'ı ikinci denemede başardı. Bu yeniden denemenin neden orada olduğunu aşağıda chart-testing'le gördük.

chart-testing (ct), Helm projesinin chart repo'ları için yazdığı araç. ct lint değişen chart'ları bulur, sürümün hedef branch'e göre artırıldığını kontrol eder, Chart.yaml'ı ve YAML (manifest'lerin ve values dosyalarının yazıldığı metin formatı) dosyalarını doğrular, chart'ı ci/ klasöründeki her values dosyasıyla lint eder. ct install her chart'ı geçici bir namespace'e kurar, helm test çalıştırır ve namespace'i siler. Ayarlar ve CI için values:

chart-dirs:
  - charts
target-branch: main
validate-maintainers: false
check-version-increment: true
helm-extra-args: --timeout 300s
# chart-testing, ci/ altındaki her dosya için chart'ı bir kez kurar; CI'da veritabanı sunucusu yok.
database:
  provider: InMemory

ct, Chart.yaml şemasını ve yamllint ayarlarını kendi kurulum klasöründeki etc/ altında arıyor; binary'yi tek başına kurunca ilk çalıştırmada bu hatayı aldık ve dosyaları ~/.ct altına kopyalamak gerekti. GitHub Actions'taki chart-testing action'ı bunu kendisi yapıyor.

Error: failed loading configuration: 'chart_schema.yaml' neither specified nor found in default locations
ct lint --config ct.yaml
Linting charts...
...
 bookstore => (version: "0.6.0", path: "charts/bookstore")
...
Checking chart "bookstore => (version: \"0.6.0\", path: \"charts/bookstore\")" for a version bump...
Old chart version: 0.5.0
New chart version: 0.6.0
Chart version ok.
Validating charts/bookstore/Chart.yaml...
Validation success! 👍

Linting chart with values file "charts/bookstore/ci/inmemory-values.yaml"...

==> Linting charts/bookstore
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed
...
 ✔︎ bookstore => (version: "0.6.0", path: "charts/bookstore")
...
All charts linted successfully

ct install'ı kind cluster'ında çalıştırdık. İlk koşu başarısız oldu:

ct install --config ct.yaml
Creating namespace "bookstore-hhu5m6jzfx"...
...
deployment "bookstore-hhu5m6jzfx-api" successfully rolled out
deployment "bookstore-hhu5m6jzfx-web" successfully rolled out
...
TEST SUITE:     bookstore-hhu5m6jzfx-smoke-test
Last Started:   Sun Nov 15 07:29:10 2026
Last Completed: Sun Nov 15 07:29:12 2026
Phase:          Failed
...
==> Logs of container bookstore-hhu5m6jzfx-smoke-test
...
curl: (7) Failed to connect to bookstore-hhu5m6jzfx-api port 8080 after 1 ms: Could not connect to server
...
 ✖︎ bookstore => (version: "0.6.0", path: "charts/bookstore") > failed waiting for process: exit status 1

ct, Deployment'ların rollout'unu bekledikten sonra testi başlattı, ama test pod'u Service'e bağlanamadı: pod'lar hazırdı ama Service'in endpoint'leri ve kube-proxy kuralları birkaç milisaniye geriden geliyordu. İlk akla gelen helm-extra-args'a --wait eklemekti; ct bu argümanları helm test'e de verdiği için başka bir hata çıktı:

Error: unknown flag: --wait

Doğru çözüm testin kendisini dayanıklı yapmak: curl'e bağlantı reddedildiğinde birkaç kez yeniden denemesini söyledik.

          curl -sSf -o /dev/null -m 5 --retry 5 --retry-connrefused --retry-delay 2 {{ $api }}/health/ready &&
Creating namespace "bookstore-jo87mrr8sa"...
...
TEST SUITE:     bookstore-jo87mrr8sa-smoke-test
Last Started:   Sun Nov 15 07:29:56 2026
Last Completed: Sun Nov 15 07:29:58 2026
Phase:          Succeeded
...
Deleting release "bookstore-jo87mrr8sa"...
release "bookstore-jo87mrr8sa" uninstalled
Deleting namespace "bookstore-jo87mrr8sa"...
namespace "bookstore-jo87mrr8sa" deleted
Namespace "bookstore-jo87mrr8sa" terminated.
...
 ✔︎ bookstore => (version: "0.6.0", path: "charts/bookstore")
...
All charts installed successfully

Chart template'leri için birim testi isteyen ekipler helm-unittest plugin'ine bakabilir: YAML ile yazılmış test dosyalarında "şu values ile render edilince Deployment'ta şu alan şu olsun" gibi kontroller tanımlanır ve cluster gerekmez. Bu plugin'i bu yazı için kurmadım ve çalıştırmadım.

GitHub Actions'ta

Akış iki parça: pull request'te lint, render, kubeconform, ct lint ve kind üzerinde ct install; chart-v* tag'inde paketleme ve GHCR'a push. Workflow, Bookstore repo'sunun kökünde duruyor ve chart'ın bulunduğu helm/ klasöründe çalışıyor:

name: helm-chart

on:
  pull_request:
    paths:
      - "helm/**"
      - "src/**"
      - "web/**"
      - ".github/workflows/helm-chart.yaml"
  push:
    tags:
      - "chart-v*"

permissions:
  contents: read

env:
  HELM_VERSION: v4.3.2

jobs:
  lint-test:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: helm
    steps:
      - uses: actions/checkout@v7
        with:
          # ct, chart'ı hedef branch ile karşılaştırır (sürüm artışı, değişen chart'lar)
          fetch-depth: 0
      - uses: azure/setup-helm@v5
        with:
          version: ${{ env.HELM_VERSION }}
      - uses: helm/chart-testing-action@v2.9.0

      - name: Lint and render every environment
        run: |
          helm dependency build charts/bookstore
          for env in dev staging prod; do
            helm lint charts/bookstore --strict -f "deploy/values-$env.yaml"
            helm template bookstore charts/bookstore -n "bookstore-$env" -f "deploy/values-$env.yaml" > "rendered-$env.yaml"
          done

      - name: Validate manifests with kubeconform
        run: |
          curl -sSL https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-amd64.tar.gz | tar -xz kubeconform
          ./kubeconform -strict -summary -kubernetes-version 1.37.0 rendered-*.yaml

      - name: ct lint
        run: ct lint --config ct.yaml --target-branch "$BASE_REF"
        env:
          BASE_REF: ${{ github.base_ref }}

      - uses: helm/kind-action@v1.15.2
        with:
          node_image: kindest/node:v1.37.0

      - name: Build images and load them into kind
        working-directory: .
        run: |
          version=$(yq '.appVersion' helm/charts/bookstore/Chart.yaml)
          docker build -f src/Bookstore.Api/Dockerfile --build-arg APP_VERSION="$version" -t "helmseries-bookstore-api:$version" .
          docker build -t "helmseries-bookstore-web:$version" web/bookstore-web
          kind load docker-image "helmseries-bookstore-api:$version" "helmseries-bookstore-web:$version" --name chart-testing

      - name: ct install (install + helm test in a throwaway namespace)
        run: ct install --config ct.yaml --target-branch "$BASE_REF"
        env:
          BASE_REF: ${{ github.base_ref }}

  publish:
    if: startsWith(github.ref, 'refs/tags/chart-v')
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    defaults:
      run:
        working-directory: helm
    steps:
      - uses: actions/checkout@v7
      - uses: azure/setup-helm@v5
        with:
          version: ${{ env.HELM_VERSION }}

      - name: Package
        run: |
          version=$(yq '.version' charts/bookstore/Chart.yaml)
          if [ "chart-v$version" != "$GITHUB_REF_NAME" ]; then
            echo "::error::tag $GITHUB_REF_NAME does not match Chart.yaml version $version"
            exit 1
          fi
          helm dependency build charts/bookstore
          helm package charts/bookstore -d dist

      - name: Push to GHCR (never overwrite a published version)
        env:
          REGISTRY_USER: ${{ github.actor }}
          REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          repo="oci://ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/charts"
          version=$(yq '.version' charts/bookstore/Chart.yaml)
          echo "$REGISTRY_TOKEN" | helm registry login ghcr.io -u "$REGISTRY_USER" --password-stdin
          if helm show chart "$repo/bookstore" --version "$version" > /dev/null 2>&1; then
            echo "::error::bookstore $version is already published; bump the chart version"
            exit 1
          fi
          helm push "dist/bookstore-$version.tgz" "$repo"
actionlint .github/workflows/helm-chart.yaml && echo ok
ok

Bu workflow'u actionlint ile doğruladım; GitHub'da ve act ile çalıştırmadım. İçindeki adımları (dependency build, lint, render, kubeconform, ct lint, ct install, paketleme, push) yukarıda lab'da aynı komutlarla tek tek çalıştırdım; GHCR'a push ve GITHUB_TOKEN ile login adımı çalıştırılmadı. Yetkiler en küçük hâlinde: iş bazında contents: read, yalnızca publish işinde packages: write; bu modelin ayrıntısı GitHub Actions serisinin sekizinci, GHCR ise on birinci yazısında. Tag ile Chart.yaml sürümünün eşleşmesini ve sürümün registry'de daha önce yayımlanmamış olmasını kontrol eden iki adım, Önce Tahmin Et'teki sessiz üzerine yazmanın önüne geçiyor.

Üretimde Dikkat

Dependency'leri aralıkla tanımla ama Chart.lock'u commit'le ve CI'da update değil build çalıştır; sürüm yükseltmesi bilinçli bir update commit'iyle gelsin. Üçüncü parti chart'ları dependency olarak çekmek, ilk yazıdaki tedarik riskini kendi chart'ına taşır; kritik dependency'leri kendi registry'ne kopyalamak ve oradan çekmek bu riski azaltır. Chart paketlerini imzalamak için Helm'in provenance dosyaları ve OCI dünyasında cosign gibi araçlar var. CI'da ct install'ın kind'da kurduğu image'ların, production'a gidecek image'larla aynı build'den gelmesine dikkat et; aksi hâlde test başka bir şeyi doğrular.

Sık Düşülen Tuzaklar

Chart.lock'u commit'lememek ve dependency sürümünün kendiliğinden değişmesine şaşırmak. charts/*.tgz'yi commit'leyip lock dosyasıyla çelişen paketler taşımak. Aynı chart sürümünü farklı içerikle yeniden push etmek. Subchart'a values verirken subchart'ın adını anahtar olarak kullanmayı unutmak ya da parent values'ının subchart'a kendiliğinden geçtiğini sanmak. Helm 4'te helm registry login'e repository yolu vermek. Yalnızca helm lint çalıştırıp şema hatalarını production'da bulmak. Zamanlamaya duyarlı bir helm test yazıp CI'da ara sıra kırmızıya dönen bir pipeline'a alışmak.

Küçük Bir Deneme

★☆☆ valkey.enabled=true ile dev release'ini upgrade et, kubectl get pods ile Valkey pod'unu gör, sonra helm get manifest çıktısında subchart resource'larının release'in parçası olduğunu doğrula.

★★☆ Test pod'unun image'ını values'a taşı (tests.image) ve global.imageRegistry'ye uymasını sağla. --set global.imageRegistry=... ile render edip bütün image: satırlarının aynı registry'yi gösterdiğini kontrol et.

★★★ Workflow'a bir adım ekle: pull request'te, değişen chart'ın bir önceki sürümünü registry'den çekip yeni sürümle aynı values'la render etsin ve iki çıktının diff'ini pull request yorumu olarak yazsın (yorum adımını act ya da GitHub olmadan da test edebilmek için önce diff'i bir dosyaya yaz).

Chart'ımız artık bir paket: sürümlü, test edilmiş ve registry'de. Serinin son yazısında Helm 3'ten Helm 4'e geçişi ele alıyoruz: neyin değiştiği, neyin bozulduğu, Helm 3'ün destek takvimi ve geçiş checklist'i.