Mert Özen Her Satırda Daha İleri
DevOps Configuration ve Secret Management — #06

Container ve Kubernetes'te Secret: External Secrets Operator, Secrets Store CSI Driver ve Workload Identity

Mert Özen 14 Kas 2026 12 dk 10 görüntülenme
Container ve Kubernetes'te Secret: External Secrets Operator, Secrets Store CSI Driver ve Workload Identity

Kubernetes'te secret'ın kaynağı Vault ya da Key Vault olmalı; External Secrets Operator onu Kubernetes Secret'ına, Secrets Store CSI Driver doğrudan pod'un volume'una taşır. kind'de ölçtük: Vault'taki değişiklik volume'u okuyan pod'a yaklaşık 56 saniyede yansıdı, environment variable'lı pod restart'a kadar eski değeri gördü. CSI'da rotation'ın varsayılan kapalı olduğunu, ARG ile verilen token'ın docker history'de kaldığını ve BuildKit secret mount ile Compose secrets'ı da görüyoruz.

Sürüm notu: Bu yazıdaki örnekler kind 0.33 ile Kubernetes 1.37, Helm 4.3, External Secrets Operator 2.15, Secrets Store CSI (Container Storage Interface; Kubernetes'in depolama eklenti arayüzü) Driver 1.6.2, Vault CSI provider (Vault Helm chart 0.34.1), HashiCorp Vault 2.2.2, Docker 29.2 (BuildKit ve Compose) ve .NET 10 ile denenmiştir. AKS (Azure Kubernetes Service; Azure'un yönetilen Kubernetes servisi) Workload Identity adımları Microsoft Learn ve External Secrets dokümanına göre yazıldı, bir AKS cluster'ında çalıştırılmadı.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) API'si Kubernetes'te iki şekilde çalışıyor: bir pod JWT (JSON Web Token; imzalı kimlik token'ı) key'ini environment variable olarak, diğeri aynı Secret'ı volume'daki bir dosya olarak alıyor. Secret'ın kaynağı Vault; External Secrets Operator onu 15 saniyede bir senkronize ediyor. Vault'ta key'i değiştiriyorsun. Hangi pod yeni key'i görür ve ne kadar sürede? Bir de şu: Dockerfile'da bir NuGet token'ını ARG ile verip kullandıktan sonra dosyayı aynı RUN satırında silersen, token image'da kalır mı?

Kubernetes Secret'ın Sınırları

Kubernetes Secret object'inin temelini Kubernetes serisinin ConfigMap ve Secret yazısında gördük: değerler base64 ile kodlanır, şifrelenmez; etcd'de şifreli tutulması için encryption at rest ayrıca açılmalıdır ve namespace'te Secret okuma yetkisi olan herkes değeri görür. Bu yazıda o temelin üzerine üç soru ekliyoruz. Secret'ın asıl kaynağı Vault ya da Key Vault iken Kubernetes'e nasıl gelecek? Değer değişince pod'a nasıl yansıyacak? Pod'un cloud'a erişmesi için bir secret gerekiyor mu?

Kubernetes Secret'ı manifest olarak Git'e koymak bu soruların hiçbirini çözmez. Şifreli secret'ları Git'te tutmanın yolları, yani Sealed Secrets ve SOPS (Secrets OPerationS; dosyadaki değerleri şifreleyen araç), GitOps ve Argo CD serisinde anlatılıyor. Bu yazıdaki yaklaşım farklı: Git'te secret'ın değeri değil, yalnızca nereden alınacağının tarifi duruyor.

External Secrets Operator: Vault'tan Kubernetes Secret'ına

External Secrets Operator (ESO), dış bir secret store'dan değer okuyup Kubernetes Secret'ı üreten bir controller. İki temel resource'u var: SecretStore (ya da cluster genelinde ClusterSecretStore) store'a nasıl bağlanılacağını, ExternalSecret hangi değerin hangi Secret'a yazılacağını söyler. Denemede beşinci yazıdaki Vault'u kind cluster'ının içinde dev mode'da çalıştırdım ve ESO'yu Helm ile kurdum. ESO, Vault'a Kubernetes auth method'uyla giriş yapıyor; yani ESO'ya da saklanan bir token verilmiyor:

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: vault
  namespace: bookstore
spec:
  provider:
    vault:
      server: http://vault.vault.svc:8200
      path: secret
      version: v2
      auth:
        # Kubernetes auth: service account token ile, saklanan bir secret olmadan
        kubernetes:
          mountPath: kubernetes
          role: bookstore-eso
          serviceAccountRef:
            name: bookstore-eso
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: bookstore-api
  namespace: bookstore
spec:
  refreshInterval: 15s
  secretStoreRef:
    name: vault
    kind: SecretStore
  target:
    name: bookstore-api-secrets
    creationPolicy: Owner
  data:
    # Key adları .NET configuration'ın beklediği biçimde: "__" = ":"
    - secretKey: Jwt__SigningKey
      remoteRef:
        key: bookstore/api
        property: jwt_signing_key
    - secretKey: Email__ApiKey
      remoteRef:
        key: bookstore/api
        property: email_api_key
$ helm install external-secrets external-secrets/external-secrets --version 2.15.0 -n external-secrets --create-namespace --wait
$ kubectl apply -f namespace.yaml -f secretstore.yaml -f externalsecret.yaml
$ kubectl -n bookstore get secretstore,externalsecret
NAME                                    AGE   STATUS   CAPABILITIES   READY
secretstore.external-secrets.io/vault   8s    Valid    ReadWrite      True

NAME                                               STORETYPE     STORE   REFRESH INTERVAL   STATUS         READY   LAST SYNC
externalsecret.external-secrets.io/bookstore-api   SecretStore   vault   15s                SecretSynced   True    7s

ESO 2.x'te API sürümü external-secrets.io/v1; eski örneklerde gördüğün v1beta1 artık sunulmuyor, cluster'daki CRD (Custom Resource Definition; Kubernetes'e eklenen özel resource tipi) tanımında yalnızca v1 açık. Secret'ın key'lerini doğrudan .NET'in beklediği adlarla (Jwt__SigningKey) yazdırmak, uygulama tarafında hiçbir eşleme kodu gerektirmiyor. creationPolicy: Owner, ExternalSecret silinince Secret'ın da silinmesini sağlar. Bir ayrıntı: Kubernetes'in Secret'ı okuma yetkisi uygulamanın service account'unda yok, ESO'nun controller'ında var. Secret'lara kimin erişebildiğini sorarken bu controller'ı da saymalısın:

$ kubectl auth can-i get secrets -n bookstore --as=system:serviceaccount:bookstore:bookstore-api
no
$ kubectl auth can-i get secrets -n bookstore --as=system:serviceaccount:external-secrets:external-secrets
yes

Rotation Pod'a Nasıl Yansır? Environment Variable ve Volume

ESO'nun ürettiği Secret'ı iki Deployment'a farklı şekilde verdim. api-env bütün key'leri envFrom ile environment variable olarak alıyor. api-volume Secret'ı /app/secrets'a mount ediyor ve uygulama klasörü key-per-file provider ile okuyor: dosya adı key, içeriği value.

          # Secret'ın bütün key'leri environment variable olarak
          envFrom:
            - secretRef:
                name: bookstore-api-secrets
          env:
            - name: Secrets__Directory
              value: /app/secrets
            # Kubernetes symlink'le güncellediği dosyaları güvenilir görmek için
            - name: DOTNET_USE_POLLING_FILE_WATCHER
              value: "true"
          volumeMounts:
            - name: secrets
              mountPath: /app/secrets
              readOnly: true
      volumes:
        - name: secrets
          secret:
            secretName: bookstore-api-secrets
// Docker/Kubernetes'te dosya olarak mount edilen secret'lar: dosya adı key, içerik value
if (builder.Configuration["Secrets:Directory"] is { Length: > 0 } secretsDirectory)
{
    builder.Configuration.AddKeyPerFile(secretsDirectory, optional: true, reloadOnChange: true);
}

Polling watcher'ı açmamın nedeni Kubernetes serisinde ölçtüğümüz davranış: kubelet volume'daki dosyaları ..data symlink'ini çevirerek günceller ve .NET'in varsayılan file watcher'ı bu değişikliği her zaman görmez. Sonra Vault'ta JWT key'ini değiştirdim ve beş saniyede bir Secret'taki değerin ve iki pod'un gördüğü key'in parmak izini yazdırdım:

09:51:39 vault kv patch secret/bookstore/api jwt_signing_key=...
+0s secret=c425ba68 env-pod=c425ba68 volume-pod=c425ba68
+5s secret=ed300634 env-pod=c425ba68 volume-pod=c425ba68
+10s secret=ed300634 env-pod=c425ba68 volume-pod=c425ba68
...
+51s secret=ed300634 env-pod=c425ba68 volume-pod=c425ba68
+56s secret=ed300634 env-pod=c425ba68 volume-pod=ed300634

ESO Secret'ı beş saniye içinde güncelledi (refresh aralığı 15 saniye). Volume'daki dosya yaklaşık 56 saniye sonra değişti; bu gecikme kubelet'in senkronizasyon döngüsünden ve Secret cache'inden geliyor, ESO'dan değil. Environment variable'lı pod ise yeni değeri hiç görmedi: environment process başlarken bir kez okunur. Onu güncellemenin tek yolu yeni bir pod:

$ kubectl -n bookstore rollout restart deploy/api-env
deployment.apps/api-env restarted
$ curl -s localhost:18087/api/info | grep -o '"jwtKeyFingerprint":"[a-f0-9]*"'
"jwtKeyFingerprint":"ed300634"

Baştaki sorunun cevabı bu: volume'u okuyan pod yaklaşık bir dakikada, environment variable'lı pod ancak restart ile. Rotation planı yaparken bu bir dakikalık gecikmeyi ve restart ihtiyacını hesaba katmalısın. Environment variable kullanmak zorundaysan, Secret değişince rollout'u otomatik tetikleyen bir controller (Stakater Reloader gibi) ya da pod template'ine Secret'ın özetini annotation olarak yazan bir deploy adımı kullanılabilir; bu yazıda ikisini de çalıştırmadım.

Secrets Store CSI Driver: Secret'ı Doğrudan Volume Olarak

İkinci yaklaşım Kubernetes Secret'ını aradan çıkarmak. Secrets Store CSI Driver, pod başlarken bir provider (Vault, Azure Key Vault, AWS (Amazon Web Services; Amazon'un sunduğu bulut platformu), GCP (Google Cloud Platform; Google'ın bulut platformu)) üzerinden değerleri okuyup doğrudan pod'un volume'una yazar. Pod, Vault'a kendi service account'uyla giriş yapar; etcd'de bir kopya oluşmaz:

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: bookstore-api-vault
  namespace: bookstore
spec:
  provider: vault
  parameters:
    vaultAddress: http://vault.vault.svc:8200
    # Pod'un kendi service account'u ile Vault'a giriş
    roleName: bookstore-api
    objects: |
      - objectName: "Jwt__SigningKey"
        secretPath: "secret/data/bookstore/api"
        secretKey: "jwt_signing_key"
      - objectName: "Email__ApiKey"
        secretPath: "secret/data/bookstore/api"
        secretKey: "email_api_key"
      volumes:
        - name: secrets
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: bookstore-api-vault
$ helm install csi secrets-store-csi-driver/secrets-store-csi-driver --version 1.6.2 -n kube-system --set syncSecret.enabled=true
$ helm install vault-csi hashicorp/vault --version 0.34.1 -n vault \
    --set server.enabled=false --set injector.enabled=false --set csi.enabled=true

$ kubectl -n bookstore exec deploy/api-csi -- ls -l /app/secrets
lrwxrwxrwx 1 root root 20 Nov 14 09:54 Email__ApiKey -> ..data/Email__ApiKey
lrwxrwxrwx 1 root root 22 Nov 14 09:54 Jwt__SigningKey -> ..data/Jwt__SigningKey
$ curl -s localhost:18089/api/info
{"environment":"Staging","freeShippingThreshold":750,"jwtKeyFingerprint":"ed300634","secrets":{"ConnectionStrings:Bookstore":false,"Jwt:SigningKey":true,"Email:ApiKey":true,"Payments:Webhook:Secret":false}}
$ kubectl get secret -n bookstore
NAME                    TYPE     DATA   AGE
bookstore-api-secrets   Opaque   2      3m19s

Uygulama tarafında değişen hiçbir şey yok; aynı key-per-file provider aynı klasörü okuyor. Namespace'teki tek Secret, ESO'nun ürettiği; CSI ile gelen değerler Kubernetes API'sinde hiç görünmüyor. İki önemli sınır var. Birincisi rotation: chart'ın varsayılan değerlerinde enableSecretRotation: false ve özellik alpha olarak işaretli; açmadığın sürece pod'daki dosya, pod yaşadıkça ilk okunan değerde kalır. İkincisi, değeri environment variable olarak isteyen bir uygulama için driver'ın secretObjects ile ayrıca bir Kubernetes Secret'ı senkronize etmesi gerekir; bu da CSI'ın "etcd'de kopya yok" avantajını ortadan kaldırır.

ESO mu, CSI Driver mı?

İkisi aynı problemi farklı yerlerden çözüyor. Bookstore için karar tablosu şu:

SoruExternal Secrets OperatorSecrets Store CSI Driver
Değer etcd'ye yazılır mı?Evet, Kubernetes Secret olarakHayır (secretObjects kullanılmadıkça)
Store'a kim bağlanır?Tek bir controllerHer pod kendi kimliğiyle
Environment variable ile kullanımDoğrudan (envFrom)Ek bir Secret senkronizasyonu gerekir
RotationrefreshInterval ile; volume'a kubelet gecikmesiyle yansırVarsayılan kapalı, alpha bir özellik
Store erişilemezseMevcut Secret kalır, pod'lar açılırPod'un volume'u mount edilemez, pod başlamaz
Store'a yükSecret başına, refresh aralığında bir istekPod başına, her başlangıçta (ve rotation açıksa periyodik)

Bookstore'da varsayılan ESO: uygulamalar Secret'ı volume olarak okuyor, store kısa bir kesintide bile pod'ların açılmasını engellemiyor ve Vault'a tek bir kimlik bağlanıyor. CSI'ı, değerin etcd'de hiç durmaması gereken birkaç yüksek değerli secret için düşünüyoruz.

AKS Workload Identity: Pod'a Secret'sız Cloud Kimliği

Üçüncü yazıda App Service'teki managed identity'yi gördük. Kubernetes'te pod'un karşılığı AKS Workload Identity: cluster bir OIDC (OpenID Connect; token tabanlı kimlik protokolü) issuer yayınlar, Entra ID'de bir user-assigned managed identity'ye federated credential eklenir ve bu credential belirli bir service account'a bağlanır. Pod'a hiçbir secret verilmez; Kubernetes'in pod'a yazdığı kısa ömürlü service account token'ı Entra ID token'ıyla değiştirilir. ESO'yu Key Vault'a böyle bağlayan bir SecretStore (AKS'te çalıştırılmadı; şemayı kind cluster'ında kubectl apply --dry-run=server ile doğruladım):

# AKS: ESO, Key Vault'a workload identity ile bağlanır (bu yazıda çalıştırılmadı)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookstore-eso
  namespace: bookstore
  annotations:
    azure.workload.identity/client-id: "<user-assigned-identity-client-id>"
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: azure-kv
  namespace: bookstore
spec:
  provider:
    azurekv:
      authType: WorkloadIdentity
      vaultUrl: https://bookstore-kv-prod.vault.azure.net
      serviceAccountRef:
        name: bookstore-eso

Uygulamanın kendisi Key Vault'a doğrudan bağlanacaksa pod template'ine azure.workload.identity/use: "true" label'ı eklenir; webhook pod'a AZURE_CLIENT_ID, AZURE_TENANT_ID ve AZURE_FEDERATED_TOKEN_FILE değişkenlerini yazar. Bu durumda ManagedIdentityCredential yerine WorkloadIdentityCredential gerekiyor; Bookstore'un credential seçimine bu dalı ekledim ve seçimi unit testlerle sabitledim:

// AKS Workload Identity webhook'u bu değişkeni pod'a ekler
if (!string.IsNullOrEmpty(configuration["AZURE_FEDERATED_TOKEN_FILE"]))
{
    return new WorkloadIdentityCredential();
}
$ dotnet test
Passed!  - Failed:     0, Passed:    14, Skipped:     0, Total:    14

Container'da Secret: Build-Time ve Docker Compose

Secret'ların bir kısmı runtime'da değil build sırasında gerekir: özel bir NuGet feed'inin ya da npm registry'sinin token'ı gibi. Baştaki ikinci sorunun cevabını denedim. Token'ı ARG ile verip dosyayı aynı satırda silen bir Dockerfile:

FROM alpine:3
ARG NUGET_TOKEN
# Token yalnız bu adımda kullanılıyor ve dosya hemen siliniyor... ama
RUN printf 'feed=https://pkgs.example.com/bookstore\ntoken=%s\n' "$NUGET_TOKEN" > /tmp/feed.conf \
 && echo "restoring with $(wc -c < /tmp/feed.conf) bytes of config" && rm /tmp/feed.conf
$ docker buildx build --load -t secretseries-build:arg --build-arg NUGET_TOKEN=$NUGET_TOKEN -f Dockerfile.arg .
$ docker history --no-trunc --format "{{.CreatedBy}}" secretseries-build:arg | head -1
RUN |1 NUGET_TOKEN=7df0abb391cb75d278e16e3801b3c3ec8c8dccba /bin/sh -c printf 'feed=https://pkgs.example.com/bookstore\ntoken=%s\n' "$NUGET_TOKEN" > /tmp/feed.conf  && echo "restoring with $(wc -c < /tmp/feed.conf) bytes of config" && rm /tmp/feed.conf # build

Dosya silindi ama token image'ın history'sinde düz metin duruyor; image'ı registry'den çekebilen herkes görür. Multi-stage build'de ARG'ı yalnızca build stage'inde kullanırsan son image'ın history'sine girmez, ama ENV ile verilen ya da son stage'de kullanılan her değer girer. Doğru yol BuildKit'in secret mount'u: değer yalnızca o RUN adımı boyunca /run/secrets altında görünür ve hiçbir layer'a yazılmaz:

# syntax=docker/dockerfile:1
FROM alpine:3
# Secret yalnız bu RUN adımında /run/secrets altında görünür, layer'a yazılmaz
RUN --mount=type=secret,id=nuget_token \
    printf 'feed=https://pkgs.example.com/bookstore\ntoken=%s\n' "$(cat /run/secrets/nuget_token)" > /tmp/feed.conf \
 && echo "restoring with $(wc -c < /tmp/feed.conf) bytes of config" && rm /tmp/feed.conf
$ docker buildx build --load -t secretseries-build:secret --secret id=nuget_token,src=nuget_token.txt -f Dockerfile.secret .
#7 0.089 restoring with 87 bytes of config
$ docker history --no-trunc --format "{{.CreatedBy}}" secretseries-build:secret | head -1
RUN /bin/sh -c printf 'feed=https://pkgs.example.com/bookstore\ntoken=%s\n' "$(cat /run/secrets/nuget_token)" > /tmp/feed.conf  && echo "restoring with $(wc -c < /tmp/feed.conf) bytes of config" && rm /tmp/feed.conf # buildkit

87 bayt, token'ın build sırasında okunduğunu gösteriyor; history'de yalnızca komutun kendisi var. CI'da aynı token pipeline secret'ından bir dosyaya yazılıp --secret ile verilir; bunun pipeline tarafı GitHub Actions serisinin secret yazısında anlatıldı.

Kubernetes kullanmayan ortamlarda Docker Compose'un secrets alanı aynı key-per-file düzenini sağlar. Secret, container'a /run/secrets altında bir dosya olarak bağlanır; environment'ta görünmez:

name: secretseries

services:
  api:
    image: secretseries-api:06
    init: true
    ports:
      - "18086:8080"
    environment:
      ASPNETCORE_ENVIRONMENT: Staging
      # Secret değil, secret'ların bulunduğu klasör
      Secrets__Directory: /run/secrets
    secrets:
      - source: jwt_signing_key
        target: Jwt__SigningKey
      - source: email_api_key
        target: Email__ApiKey

secrets:
  jwt_signing_key:
    file: ./secrets/jwt_signing_key.txt
  email_api_key:
    file: ./secrets/email_api_key.txt
$ mkdir secrets
$ openssl rand -base64 32 | tr -d '\n' > secrets/jwt_signing_key.txt
$ openssl rand -hex 16 | tr -d '\n' > secrets/email_api_key.txt
$ docker compose up -d
$ curl -s localhost:18086/api/info
{"environment":"Staging","freeShippingThreshold":750,"jwtKeyFingerprint":"aa0f5a7a","secrets":{"ConnectionStrings:Bookstore":false,"Jwt:SigningKey":true,"Email:ApiKey":true,"Payments:Webhook:Secret":false}}
$ docker exec secretseries-api-1 ls -l /run/secrets
-rw-r--r-- 1 app app 32 Nov 14 09:47 Email__ApiKey
-rw-r--r-- 1 app app 44 Nov 14 09:47 Jwt__SigningKey
$ docker inspect secretseries-api-1 --format '{{range .Mounts}}{{.Type}} {{.Destination}}{{"\n"}}{{end}}'
bind /run/secrets/Email__ApiKey
bind /run/secrets/Jwt__SigningKey

Compose'ta (Swarm dışında) secret aslında bir bind mount: dosya host'ta düz metin duruyor ve secrets/ klasörü .gitignore'da. Kazanç, ilk yazıda gördüğümüz docker inspect ve /proc/<pid>/environ kanallarının kapanması.

Üretimde Dikkat

ESO'nun her ExternalSecret için store'a refresh aralığında bir istek attığını unutma. 200 ExternalSecret ve 15 saniyelik aralık, Vault'a dakikada 800 okuma demek; çoğu secret için bir saatlik aralık yeterli, hızlı yayılması gereken birkaç secret için daha kısa bir aralık seç. ESO'nun controller'ı bütün namespace'lerdeki Secret'ları yazabildiği için cluster'daki en yetkili bileşenlerden biri; ClusterSecretStore kullanıyorsan hangi namespace'lerin onu kullanabileceğini conditions ile sınırla. Kurulumdan hemen sonra ExternalSecret'ın event'lerinde bir kez could not get secret data from provider uyarısı gördüm; Vault'un Kubernetes auth ayarı tamamlanmadan yapılan ilk deneme başarısız oldu ve ESO bir sonraki denemede Secret'ı oluşturdu. Bu tür geçici hatalar normal, ama SecretSynced olmayan ExternalSecret'lar için bir alarm kur; aksi hâlde rotation sessizce durur.

Etcd encryption at rest'i açmayı ve Secret okuma yetkisini (get, list, watch) gözden geçirmeyi atlama: list yetkisi olan biri namespace'teki bütün Secret'ların değerlerini görür. kubectl exec yetkisi de bir secret okuma yetkisidir; volume'daki dosyayı da environment'ı da okuyabilir.

Sık Düşülen Tuzaklar

Secret'ı environment variable olarak verip rotation'da pod'u yeniden başlatmamak. Volume'u subPath ile mount etmek; subPath mount'ları hiç güncellenmez. .NET'in varsayılan file watcher'ının symlink değişikliğini kaçırabileceğini bilmeden reloadOnChange'e güvenmek. CSI driver'ın rotation'ının varsayılan kapalı olduğunu bilmemek. Dockerfile'da token'ı ARG ya da ENV ile verip docker history'de bırakmak. ESO'nun v1beta1 API'sini kullanan eski örnekleri kopyalamak. Workload identity yerine bir service principal secret'ını Kubernetes Secret'ı olarak saklamak; döngüyü yeniden başlatmış olursun.

Küçük Bir Deneme

★☆☆ Kendi image'larında docker history --no-trunc çalıştır ve ARG, ENV ya da komut satırında kalmış bir token olup olmadığına bak. Bulursan BuildKit secret mount'a çevir.

★★☆ kind'de ESO kur, bir secret'ı Vault'tan ya da sahte bir provider'dan senkronize et ve onu bir pod'a environment variable, bir başkasına volume olarak ver. Değeri değiştirip iki pod'un yeni değeri ne zaman gördüğünü ölç; ölçümü DOTNET_USE_POLLING_FILE_WATCHER açık ve kapalıyken tekrarla.

★★★ Aynı secret'ı Secrets Store CSI Driver ile mount et, enableSecretRotation'ı açıp rotationPollInterval'ı kısalt ve dosyanın ne zaman değiştiğini ESO ile karşılaştır. Ardından store'u kapatıp yeni bir pod başlat ve iki yaklaşımın bu durumdaki davranışını not et.

Secret'lar artık store'dan pod'a ulaşıyor ve değiştiklerinde ne zaman yansıdıklarını biliyoruz. Serinin son yazısında bu bilgiyi kullanıyoruz: iki aktif secret ile kesintisiz rotation, sızan bir secret'ta ilk 60 dakikada yapılacaklar ve Bookstore'daki secret sayısını sıfıra yaklaştıran secret'sız mimari.