CI ile GitOps'u Bağlamak: Image Tag Promotion, Pull Request ile Environment Atlatmak ve Image Updater
GitOps'ta CI image'ı bir kez build edip registry'ye koyar ve hangi image'ın çalışacağını config repo'suna commit olarak yazar; deploy'u Argo CD yapar. Yerel bir CI script'iyle tag ve digest'i dev'e commit etmeyi, staging ve prod'a pull request ile promotion'ı, aynı dosyaya yazan iki pipeline'ın çakışmasını, Argo CD Image Updater'ın digest'i silmesini ve Source Hydrator'ı gerçek çıktılarla görüyoruz.
Sürüm notu: Bu yazıdaki örnekler kind 0.33 ile Kubernetes 1.37, Argo CD 3.6.1, Argo CD Image Updater 1.5.0, Kustomize 5.8, Docker 29 ve registry 3.1 (distribution) ile denenmiştir. GitHub Actions workflow'u yalnız actionlint 1.7.12 ile doğrulandı, GitHub'da koşulmadı.
Önce Tahmin Et
Dev overlay'i API image'ını tag ve digest ile sabitliyor: 1.2.0@sha256:.... Registry'de 1.2.0'dan daha yeni bir tag yok. Argo CD Image Updater'ı kurup ona "dev'de 1.2.x serisinin en yenisini çalıştır" diyorsun. İlk turda config repo'suna bir commit gelir mi? Gelirse neyi değiştirir ve dev'deki pod'lar bundan etkilenir mi? Cevap yazının ortasında, gerçek commit'iyle duruyor.
CI'ın Yeni İşi: Image ve Bir Commit
Beşinci yazının sonunda sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) api ve web uygulamaları üç environment'ta tek bir ApplicationSet'ten geliyordu, ama image tag'ini biz elle değiştiriyorduk. Pull modelinde CI'ın cluster'a dokunmadığını ilk yazıda gördük; o zaman CI'ın işi iki parçaya iniyor: image'ı bir kez build edip registry'ye koymak ve hangi image'ın çalışacağını config repo'suna bir commit olarak yazmak. Deploy'u Argo CD yapıyor. CI/CD Temelleri serisindeki "build once, deploy many" kuralı burada somut hâl alıyor: staging ve prod'a giden image dev'de denenmiş olanın aynısı, yeniden build edilmiyor.
Lab'da GitHub yok; CI'ı yerelde çalışan bir bash script'i olarak yazdık (ci/build-and-push.sh). Registry de kind node'unun yanında çalışan bir registry:3 container'ı: host'tan localhost:52612 adresine push ediyoruz, node aynı adı containerd'nin hosts.toml ayarıyla bu container'a yönlendiriyor. Gerçek hayatta bu GHCR (GitHub Container Registry) ya da şirketin registry'si olur. Script'in çekirdeği:
for entry in "${images[@]}"; do
name="${entry%%:*}"; dockerfile="${entry#*:}"
image="$registry/bookstore/$name"
echo "==> build $image:$version (source $source_commit)"
docker build -q -f "$context/$dockerfile" -t "$image:$version" \
--build-arg APP_VERSION="$version" --build-arg SOURCE_COMMIT="$source_commit" \
--label org.opencontainers.image.revision="$source_commit" \
--label org.opencontainers.image.version="$version" "$context" >/dev/null
docker push -q "$image:$version" >/dev/null
# Registry'nin sakladığı digest: Git'e tag VE digest yazılır, node digest'i çeker.
digest="$(docker buildx imagetools inspect "$image:$version" --format '{{json .Manifest.Digest}}' | tr -d '"')"
# Manifest'ler mantıksal bookstore/<name> adını kullanır; overlay onu registry, tag ve digest'e çevirir.
refs+=("bookstore/$name=$image:$version@$digest")
echo " pushed $image:$version@$digest"
done
[ "${PUSH_ONLY:-0}" = 1 ] && exit 0
# Dev'e promotion: config repo'suna bir commit, asla kubectl değil.
config_repo="${CONFIG_REPO:?set CONFIG_REPO to the config repo URL}"
work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT
git clone -q "$config_repo" "$work/config"
cd "$work/config/apps/$component/overlays/dev"
for ref in "${refs[@]}"; do kustomize edit set image "$ref"; done
git add kustomization.yaml
git commit -q -m "dev: bookstore-$component $version" -m "Source-Commit: $source_commit"
API bileşeni iki image demek: API'nin kendisi ve dördüncü yazıdaki migration Job'ının image'ı. İkisi aynı sürümle build edilip aynı commit'te değişiyor; aksi hâlde yeni API eski şemayla açılabilir. Kaynak commit'in SHA'sı (Git'in commit'e verdiği hash) üç yere gidiyor: image label'ı, uygulamanın /api/info cevabı ve config commit'inin mesajı. Böylece cluster'daki bir pod'dan geriye, hangi kod satırından build edildiğine kadar gidilebiliyor. Çalıştırınca:
CONFIG_REPO=../bookstore-config ci/build-and-push.sh api 1.2.0
==> build localhost:52612/bookstore/api:1.2.0 (source 033d7d3deb3a)
pushed localhost:52612/bookstore/api:1.2.0@sha256:<digest-api-1.2.0>
==> build localhost:52612/bookstore/migrator:1.2.0 (source 033d7d3deb3a)
pushed localhost:52612/bookstore/migrator:1.2.0@sha256:<digest-migrator-1.2.0>
==> config repo: b6d67c4 dev: bookstore-api 1.2.0
Config repo'suna düşen commit yalnız dev overlay'ine dokunuyor:
b6d67c4 Bookstore Lab dev: bookstore-api 1.2.0
Source-Commit: 033d7d3deb3a
--- a/apps/api/overlays/dev/kustomization.yaml
+++ b/apps/api/overlays/dev/kustomization.yaml
images:
-- digest: sha256:<digest-api-1.1.0>
+- digest: sha256:<digest-api-1.2.0>
name: bookstore/api
newName: localhost:52612/bookstore/api
- newTag: 1.1.0
-- digest: sha256:<digest-migrator-1.1.0>
+ newTag: 1.2.0
+- digest: sha256:<digest-migrator-1.2.0>
name: bookstore/migrator
newName: localhost:52612/bookstore/migrator
- newTag: 1.1.0
+ newTag: 1.2.0
Bu yazıda ApplicationSet'ten RollingSync'i de çıkardık ve template'e automated sync ile selfHeal ekledik. Promotion artık Git'te oluyor: dev'e giden şey bir commit, staging ve prod'a giden şey merge edilmiş bir pull request. Hangi ortamın neyi çalıştıracağına Git karar verdiği için her ortamın kendi overlay'i değişince kendiliğinden sync olması güvenli.

Tag mi Digest mi?
Base manifest'lerde image adı artık registry adresi taşımıyor: Deployment bookstore/api, migration Job'ı bookstore/migrator diyor. Gerçek registry adresi, tag ve digest her ortamın overlay'inde. Neden tag'e ek olarak digest? Çünkü tag değiştirilebilir bir etiket. Aynı sürüm numarasıyla, bir commit sonra bir build daha aldık:
PUSH_ONLY=1 ci/build-and-push.sh api 1.2.0
docker buildx imagetools inspect localhost:52612/bookstore/api:1.2.0 --format '{{json .Manifest.Digest}}'
kubectl -n bookstore-dev get deploy api -o jsonpath='{.spec.template.spec.containers[0].image}'
==> build localhost:52612/bookstore/api:1.2.0 (source 5321a1077cd3)
pushed localhost:52612/bookstore/api:1.2.0@sha256:<digest-B>
"sha256:<digest-B>"
localhost:52612/bookstore/api:1.2.0@sha256:<digest-A>
Registry itiraz etmedi; 1.2.0 artık başka bir image'ı gösteriyor. Dev etkilenmedi, çünkü pod spec'i digest'i taşıyor ve node digest'i çekiyor. Yalnız tag yazsaydık, bir pod yeniden başladığında (node değişimi, eviction, scale) aynı Deployment iki farklı kodu çalıştırabilirdi ve Git geçmişi bunu göstermezdi. Tag'i yine de yazıyoruz: insan okuyor, digest ise makinenin garantisi. Bazı registry'lerde tag'leri değiştirilemez yapma ayarı da var; digest ile birlikte kullanılabilir.
Environment Promotion: Dev'e Commit, Staging ve Prod'a Pull Request
Dev'de denenen image'ı staging'e taşımak yeni bir build değil, bir kopyalama işi: dev overlay'inin render ettiği tag ve digest staging overlay'ine yazılıyor. Bunu ci/promote.sh yapıyor; doğrudan main'e değil, bir branch'e yazıyor:
refs=$(kustomize build "apps/$component/overlays/$from" | grep -oE 'image: [^ ]+@sha256:[0-9a-f]+' | cut -d' ' -f2 | sort -u)
[ -n "$refs" ] || { echo "no digest-pinned image in $from" >&2; exit 1; }
version=$(echo "$refs" | head -1 | sed -E 's/.*:([^:@]+)@.*/\1/')
branch="promote/$to-$component-$version"
git switch -q -c "$branch"
cd "apps/$component/overlays/$to"
# localhost:52612/bookstore/api:1.2.0@sha256:... -> mantıksal ad bookstore/api
for ref in $refs; do kustomize edit set image "$(echo "$ref" | sed -E 's#^[^/]+/##; s/:[^:@]+@.*//')=$ref"; done
git add kustomization.yaml
git commit -q -m "$to: bookstore-$component $version" -m "Promoted-From: $from"
git push -q origin "$branch"
$ ci/promote.sh api dev prod
promotion order is dev -> staging -> prod, not dev -> prod
exit=2
$ ci/promote.sh api dev staging
==> pushed promote/staging-api-1.2.0; open a pull request: promote/staging-api-1.2.0 -> main
Script sıra dışı bir promotion'ı reddediyor. GitHub'da bu noktada gh pr create ile bir pull request açılır, branch protection kuralı onay ister ve merge sonrası Argo CD staging'i günceller; lab'da pull request'i merge eden reviewer biziz. Branch merge edilmeden staging hâlâ 1.1.0 çalıştırıyordu; merge'den sonra Application'ı argocd app get --refresh ile yenileyince staging 17 saniye içinde 1.2.0 ile Healthy oldu. Aynısını prod için yapınca config repo'sunun geçmişi promotion'ın kendisi oluyor:
* 6cdfda5 Merge promote/prod-api-1.2.0: bookstore-api 1.2.0 to prod
|\
| * 12f4458 prod: bookstore-api 1.2.0
|/
* c2867b8 Merge promote/staging-api-1.2.0: bookstore-api 1.2.0 to staging
|\
| * 08c075a staging: bookstore-api 1.2.0
|/
* b6d67c4 dev: bookstore-api 1.2.0
Kim, ne zaman, hangi onayla prod'a ne çıkardı sorusunun cevabı bu grafikte. Geri almak da aynı yoldan: prod merge commit'ini git revert etmek (Git & GitHub serisindeki revert yazısı). Dev'i otomatik, staging ve prod'u pull request ile yürütmek tek seçenek değil; küçük bir ekipte staging de otomatik olabilir, ama prod'a giden adımın bir insan onayından geçmesi çoğu ekibin istediği şey.
GitHub Actions'ta Aynı Pipeline
Script'in GitHub Actions karşılığını ci/github-actions/gitops-ci.yml olarak yazdık ve actionlint ile doğruladık; GitHub'da çalıştırmadık. Kritik kısım config repo'suna yazan job:
promote-dev:
needs: build
runs-on: ubuntu-latest
# Dev overlay'ine aynı anda tek yazan olsun; ikinci koşu yarışmak yerine bekler.
concurrency:
group: config-repo-dev
cancel-in-progress: false
steps:
# Yalnız config repo'suna kurulu, contents: write yetkili bir GitHub App. Kişisel token yok.
- id: token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
app-id: ${{ vars.CONFIG_BOT_APP_ID }}
private-key: ${{ secrets.CONFIG_BOT_PRIVATE_KEY }}
repositories: bookstore-config
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: ${{ github.repository_owner }}/bookstore-config
token: ${{ steps.token.outputs.token }}
Yetki burada en az olacak şekilde bölünüyor. Build job'ı GITHUB_TOKEN ile yalnız packages: write alıyor ve GHCR'a push ediyor. Config repo'suna yazan job, yalnız o repo'ya kurulmuş bir GitHub App'in kısa ömürlü token'ını kullanıyor; bir deploy key de olur. Hiçbir job'da kubeconfig yok. Uygulama repo'sunu ele geçiren biri en fazla config repo'suna commit atabilir; staging ve prod'da branch protection o commit'i yine bir onaya takar. Action'ların SHA ile sabitlenmesi GitHub Actions serisinin güvenlik yazısındaki gibi.
Aynı Dosyayı İki Pipeline Güncellerse
İki pipeline aynı anda config repo'sunu klonlar ve ikisi de commit atarsa, ikinci push reddedilir. Script bu durumda git pull --rebase ile yeni main'in üstüne geçip tekrar deniyor. Farklı dosyalara dokunan iki commit (api ve web) sorunsuz birleşiyor:
Rebasing (1/1)Successfully rebased and updated refs/heads/main.
c6f1525 dev: bookstore-web 1.0.1
4daa126 dev: bookstore-api 1.2.3
Aynı satıra dokunan iki commit ise (iki ayrı API build'i, 1.2.1 ve 1.2.2) birleşmiyor; bunu config repo'sunun bir kopyasında denedik:
Rebasing (1/1)Auto-merging apps/api/overlays/dev/kustomization.yaml
CONFLICT (content): Merge conflict in apps/api/overlays/dev/kustomization.yaml
error: could not apply 5195578... dev: bookstore-api 1.2.2
Script set -e ile burada duruyor ve pipeline kırmızıya dönüyor. Bu istenen davranış: çakışmayı otomatik çözmeye çalışan bir script, eski build'in yenisinin üstüne yazmasına yol açabilir. Daha iyisi çakışmayı hiç yaşamamak: GitHub Actions'ta concurrency grubu aynı overlay'e yazan koşuları sıraya koyar; her ortam ve uygulama kendi dosyasında durduğu için farklı uygulamalar birbirini beklemez.
Argo CD Image Updater
Image Updater ters yönden çalışıyor: CI yalnız image'ı push ediyor, cluster'da çalışan Image Updater registry'yi izleyip yeni bir tag gördüğünde config repo'suna commit atıyor. 1.0 sürümünden (Kasım 2025) beri ayarlar Application annotation'larında değil, bir ImageUpdater custom resource'unda (CR) duruyor. Resmi manifest'i Argo CD'nin namespace'ine kurduk ve registry'yi tanıttık:
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/v1.5.0/config/install.yaml
data:
# Image adları localhost:52612/...; cluster içinden registry gitopsseries-registry:5000 adresinde.
registries.conf: |
registries:
- name: Lab registry
prefix: localhost:52612
api_url: http://gitopsseries-registry:5000
insecure: yes
git.user: argocd-image-updater
git.email: image-updater@example.com
Hangi Application'ın hangi image'ı izleyeceği config repo'sunda, App of Apps root'unun altında:
apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
name: bookstore-dev
namespace: argocd
spec:
writeBackConfig:
# Argo CD'nin bu repo için tuttuğu credential ile commit atar ("repocreds").
method: git
gitConfig:
branch: main
# apps/api/overlays/dev/kustomization.yaml'ı `kustomize edit set image` gibi düzenler.
writeBackTarget: kustomization
applicationRefs:
- namePattern: bookstore-api-dev
images:
- alias: api
imageName: localhost:52612/bookstore/api:1.2.x
commonUpdateSettings:
updateStrategy: semver
manifestTargets:
kustomize:
# Base manifest'lerdeki mantıksal ad.
name: bookstore/api
- alias: migrator
imageName: localhost:52612/bookstore/migrator:1.2.x
commonUpdateSettings:
updateStrategy: semver
manifestTargets:
kustomize:
name: bookstore/migrator
Önce credential'ı ayrı bir Secret'tan vermeyi denedik (method: git:secret:git-creds). Lab'daki git sunucusu düz HTTP konuşuyor ve bu yol yalnız HTTPS ve SSH (Secure Shell; şifreli uzak bağlantı protokolü) adreslerini kabul ediyor:
level=error msg="Could not update application spec: could not get creds for repo 'http://git-server.git-server.svc.cluster.local/bookstore-config': unknown repository type"
Bu yüzden push yetkili kullanıcıyı Argo CD'ye bir repository Secret'ı olarak tanıttık ve varsayılan repocreds yöntemine döndük. Bunun bir bedeli var: Argo CD'nin repo-server'ı artık config repo'suna yazabilen bir credential tutuyor. GitHub'da bir HTTPS adresiyle git:secret: ve yalnız Image Updater'ın okuyabildiği bir GitHub App daha temiz.
Önce Tahmin Et'in cevabı, CR'ı uyguladıktan sonraki ilk tur. Registry'de yeni bir sürüm yoktu ama bir commit geldi:
c8f8ffc argocd-image-updater <image-updater@example.com>
bookstore-api-dev: update images
bookstore/api: sha256:<digest-A> -> 1.2.0
bookstore/migrator: sha256:<digest-migrator-1.2.0> -> 1.2.0
images:
-- digest: sha256:<digest-A>
- name: bookstore/api
+- name: bookstore/api
newName: localhost:52612/bookstore/api
newTag: 1.2.0
-- digest: sha256:<digest-migrator-1.2.0>
- name: bookstore/migrator
+- name: bookstore/migrator
newName: localhost:52612/bookstore/migrator
newTag: 1.2.0
Semver stratejisi Git'e yalnız tag yazıyor ve gördüğü digest'i siliyor. Sürüm aynı kaldı ama dev'in satırının anlamı değişti: artık "digest-A" değil, "registry'de 1.2.0 o an neyi gösteriyorsa". Az önce 1.2.0'ı yeniden build ettiğimiz için bu digest-B demek. Lab'da Argo CD bu commit'i sync etmeden bir sonraki commit geldi, o yüzden pod'ların yeni koda geçtiğini görmedik; ama bir sonraki pod yeniden başlatmasında (ya da başka bir node'da) hangi image'ın çalışacağı artık Git'ten okunamıyor. Bir önceki bölümde kaçındığımız durum bu. Ardından 1.2.1'i yalnız push ettik (PUSH_ONLY=1) ve Image Updater'ın turunu bir annotation ile hemen tetikledik; commit 15 saniye sonra geldi ve dev 1.2.1'e geçti:
cd399a5 argocd-image-updater
bookstore-api-dev: update images
bookstore/api: sha256:<digest-A> -> 1.2.1
bookstore/migrator: sha256:<digest-migrator-1.2.0> -> 1.2.1
{"version":"1.2.1","commit":"d3141c56b19d","environment":"Production","pod":"api-677dc85687-dfkxs","database":"Postgres"}
Varsayılan kontrol aralığı iki dakika; registry webhook'u ile anında tetiklenebiliyor. Image Updater ne zaman tercih edilmez? Promotion'ın bir pull request ve onay istediği ortamlarda (1.5'te pull request açma desteği GitHub, GitLab, Azure DevOps ve Gitea için var, ama karar mantığı yine "en yeni tag"), digest ile sabitlemenin şart olduğu yerlerde (digest stratejisi tek bir tag'i izler, semver aralığıyla birleşmez) ve build'i zaten config commit'i atabilen bir CI'ınız varsa. Dev gibi "son build hep çalışsın" denen bir ortamda işe yarıyor; prod'a giden yolu ona bırakmamak gerekir.
Rendered Manifest Pattern ve Source Hydrator
Bir commit'in Kustomize ya da Helm'den geçtikten sonra cluster'a tam olarak ne uygulayacağını görmek için Argo CD'nin diff'ine bakmak gerekiyor. Rendered manifest pattern bu adımı Git'e taşıyor: render edilmiş düz YAML (manifest'lerin yazıldığı metin formatı) ayrı bir branch'e commit edilir ve Argo CD onu uygular; review'da insan gerçek manifest'i görür. Argo CD'de bunun karşılığı Source Hydrator: Application'da spec.sourceHydrator altında drySource (Kustomize klasörü) ve syncSource (render edilmiş manifest'lerin yazılacağı branch) verilir, commit'i ayrı bir commit-server component'i atar. 3.5.0'dan beri beta, varsayılan olarak kapalı; 3.6'da render edilmiş commit'leri GPG (GNU Privacy Guard; açık anahtarla imzalama aracı) ile imzalama alpha olarak geldi. Bu seride ona geçmiyoruz ama promotion'ı daha sıkı denetlemek isteyen ekipler için izlenecek yön bu. Promotion'ı ayrı bir araçla yöneten Kargo gibi projeler de var; burada adını anmakla yetiniyoruz.
Üretimde Dikkat
Config repo'suna yazan kimliği sayılı tut: CI'ın GitHub App'i, gerekiyorsa Image Updater ve insanlar. Branch protection staging ve prod overlay'lerine dokunan her değişikliğe onay istemeli; CODEOWNERS ile apps/*/overlays/prod/ yolunu ayrı bir ekibe bağlamak bunu klasör bazında yapar. Config commit mesajında kaynak SHA'yı zorunlu tut; bir olayda "prod'daki bu image hangi koddan geldi" sorusu bir git log ile cevaplanmalı. Registry'de tag'leri değiştirilemez yap (Harbor, ECR (Amazon Elastic Container Registry; Amazon'un sunduğu registry) ve ACR (Azure Container Registry; Azure'un sunduğu registry) bu ayarı sunuyor), eski image'ları temizlerken prod'un çalıştırdığı digest'i silmeyecek bir saklama kuralı kur. Webhook kullanmıyorsan Argo CD varsayılan olarak repo'yu iki ile üç dakika arasında bir yokluyor (timeout.reconciliation 120 saniye ve 60 saniyeye kadar jitter); merge ile deploy arasındaki bu gecikme normal, Git sağlayıcısından Argo CD'ye bir webhook bunu saniyelere indirir.
Sık Düşülen Tuzaklar
CI'ın config repo'sunda doğrudan main'e prod commit'i atması. Promotion'ın tek onay noktası pull request'ti; CI'a main'e yazma yetkisi verip prod overlay'ine dokunmasına izin vermek bu noktayı ortadan kaldırır.
latest tag'i. Git'te hiçbir şey değişmediği için Argo CD hiçbir şey yapmaz, pod'lar ise yeniden başladıklarında ne bulurlarsa onu çalıştırır. Git'teki satır "ne çalışıyor" sorusuna artık cevap vermez.
Aynı overlay'e yazan paralel pipeline'lar. Push'u zorla (--force) yapmak eski build'in yenisini ezmesine yol açar; sıraya koy ya da çakışmada dur.
Registry adresini Kustomize image adı olarak kullanmak. İlk denemede base'de localhost:52612/bookstore/api yazıyordu ve Image Updater iki image'dan yalnız sonuncusunu dosyaya yazdı. Argo CD'nin Kustomize image eşleştirmesi, = içermeyen bir override'da adı ilk : karakterine kadar okuyor; port'lu bir registry'de iki image'ın adı da localhost görünüyor. Base'de mantıksal ad (bookstore/api) ve overlay'de newName bu sorunu ortadan kaldırdı.
Image Updater'ı digest ile sabitlenmiş bir ortama semver stratejisiyle bağlamak. Gördüğümüz gibi digest'i siler.
Küçük Bir Deneme
★☆☆ Web için ci/build-and-push.sh web 1.0.1 çalıştır, sonra ci/promote.sh web dev staging ile branch'i oluştur. Merge etmeden önce Argo CD'de bookstore-web-staging'in hâlâ Synced olduğunu doğrula; neden OutOfSync olmadığını açıkla.
★★☆ ci/promote.sh'a bir kontrol ekle: hedef ortama yazılacak digest kaynak ortamda en az on dakikadır Healthy değilse promotion'ı reddetsin. Bilgiyi argocd app get -o json çıktısındaki status.health ve status.operationState.finishedAt alanlarından al.
★★★ ImageUpdater CR'ında updateStrategy: digest ile imageName: localhost:52612/bookstore/api:1.2.1 kur, aynı tag'i yeniden build edip push et ve Image Updater'ın bu sefer Git'e ne yazdığına bak. Semver stratejisiyle farkını, "dev her zaman son build'i çalıştırsın" ihtiyacı açısından karşılaştır.
CI artık cluster'a dokunmadan image üretiyor ve promotion Git'te bir commit ya da pull request. Config repo'sunda hâlâ eksik olan bir şey var: veritabanı parolası. Lab script'iyle elle yarattığımız Secret Git'te yok. Bir sonraki yazıda secret'ları Git'e koymanın güvenli yollarına bakıyoruz: Sealed Secrets, SOPS (Secrets OPerationS; dosyadaki değerleri şifreleyen araç) ve secret'ı Git'e hiç koymamak.