Mert Özen Her Satırda Daha İleri
DevOps Kubernetes — #19

Kubernetes 1.33–1.37 Yenilikleri: Native Sidecar, In-Place Resize, preStop Sleep, Gateway API ve ingress-nginx Sonrası

Mert Özen 8 Kas 2026 10 dk 2 görüntülenme
Kubernetes 1.33–1.37 Yenilikleri: Native Sidecar, In-Place Resize, preStop Sleep, Gateway API ve ingress-nginx Sonrası

Kubernetes 1.33–1.37 arasında uygulama geliştiriciyi en çok etkileyenler native sidecar (1.33), preStop sleep (1.34), in-place resize (1.35), user namespace'ler (1.36) ve HPA tolerance (1.37). SIGTERM'ü yok sayan sidecar'ın Job'u nasıl 39 saniyeye uzattığını, externalIPs ve Endpoints deprecation'larını, KYAML'ı, ingress-nginx'in emekliliğini ve Gateway API v1.7'yi gerçek çıktılarla görüyoruz.

Sürüm notu: Bu yazıdaki örnekler kind 0.33 ile Kubernetes 1.37, kubectl 1.37 ve .NET 10 ile denenmiştir. Sürüm bilgileri kubernetes.io release yazılarından ve Gateway API'nin (API: Application Programming Interface) GitHub sürüm notlarından, Kasım 2026 itibarıyla.

Önce Tahmin Et

Bir Job'a native sidecar olarak küçük bir log shipper ekliyorsun: initContainers altında, restartPolicy: Always ile. Asıl container 6 saniyede işini bitiriyor. Native sidecar'ın vaadi, Job'un sidecar yüzünden sonsuza kadar asılı kalmaması. Job kaç saniyede biter: 6 mı, 7 mi, yoksa daha uzun mu? İpucu: sidecar'ın komutu sh -c "tail -f ... | sed ...". İlk denemenin sonucu, Kubernetes'in yeni bir özelliğinin eski bir Linux gerçeğiyle nasıl karşılaştığını gösteriyor.

Kubernetes'in Sürüm Ritmi ve Bu Yazının Kapsamı

Kubernetes yılda üç minor sürüm çıkarıyor: 1.33 (Nisan 2025), 1.34 (Ağustos 2025), 1.35 (Aralık 2025), 1.36 (Nisan 2026) ve 1.37 (Ağustos 2026). Her sürümde onlarca özellik alpha, beta ya da GA (General Availability) aşamasına geçiyor; çoğu cluster yöneticisini ilgilendiriyor. Bu yazı yalnız .NET uygulaması yazan ve manifest'ini kendisi tutan geliştiriciyi etkileyenleri topluyor, her birinin serinin hangi yazısında kullanıldığını gösteriyor. Bir özelliğin cluster'ında açık olup olmadığını tahmin etme, API server'a sor; kind'daki 1.37 cluster'ımızda:

$ kubectl get --raw /metrics | grep '^kubernetes_feature_enabled' | grep -E '...'
kubernetes_feature_enabled{name="ContainerRestartRules",stage="BETA"} 1
kubernetes_feature_enabled{name="HPAConfigurableTolerance",stage=""} 1
kubernetes_feature_enabled{name="HPAScaleToZero",stage="BETA"} 1
kubernetes_feature_enabled{name="ImageVolume",stage=""} 1
kubernetes_feature_enabled{name="InPlacePodVerticalScaling",stage=""} 1
kubernetes_feature_enabled{name="PodLevelResources",stage="BETA"} 1
kubernetes_feature_enabled{name="UserNamespacesSupport",stage=""} 1

Boş stage, özelliğin GA olduğu ve feature gate'in artık yalnız geriye uyumluluk için durduğu anlamına geliyor. Managed cluster'larda alpha özellikler genelde kapalıdır; beta olanların açık olup olmadığı sağlayıcıya göre değişir.

ÖzellikGA olduğu sürümSerideki yeri
Native sidecar container1.3303 ve bu yazı
Job: backoffLimitPerIndex, success policy1.3313'e ek
preStop sleep action (0 saniye dahil)1.3410 ve bu yazı
Job: podReplacementPolicy1.3413'e ek
In-place pod resize1.3511, 16
User namespace'ler (hostUsers: false)1.3615
Image volume (OCI image'ı volume olarak)1.36bu yazı
HPA (Horizontal Pod Autoscaler) başına tolerance1.3711
kubectl get -o kyaml1.37bu yazı

Native Sidecar: Job'larda Gerçekten Biten Yan Container'lar

1.33'te GA olan native sidecar'ı serinin üçüncü yazısında pod yaşam döngüsü içinde anlattık: initContainers içinde restartPolicy: Always olan bir container, ana container'lardan önce başlar, onlar boyunca çalışır, onlardan sonra kapanır. Eskiden sidecar'lar sıradan container olduğu için, işi biten bir Job'un pod'u sidecar yüzünden hiç bitmez, herkes "sidecar'a dosya üzerinden 'kapan' sinyali gönder" gibi yamalar yazardı. Önce Tahmin Et'teki Job:

apiVersion: batch/v1
kind: Job
metadata:
  name: export-orders
  namespace: whatsnew
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      initContainers:
        - name: log-shipper
          image: busybox:1.37
          restartPolicy: Always
          command: ["sh", "-c", "touch /logs/export.log; tail -n +1 -f /logs/export.log | sed 's/^/[shipped] /'"]
          volumeMounts:
            - name: logs
              mountPath: /logs
      containers:
        - name: export
          image: busybox:1.37
          command: ["sh", "-c", "for i in 1 2 3; do echo \"exported batch $i\" >> /logs/export.log; sleep 2; done"]
          volumeMounts:
            - name: logs
              mountPath: /logs
      volumes:
        - name: logs
          emptyDir: {}
NAME            STATUS     COMPLETIONS   DURATION   AGE
export-orders   Complete   1/1           39s        39s
log-shipper {"terminated":{...,"exitCode":137,"finishedAt":"2026-11-08T05:07:13Z","reason":"Error","startedAt":"2026-11-08T05:06:36Z"}}
export {"terminated":{...,"exitCode":0,"finishedAt":"2026-11-08T05:06:42Z","reason":"Completed","startedAt":"2026-11-08T05:06:36Z"}}

Job bitti, yani native sidecar işini yaptı; ama 6 saniyelik iş 39 saniye sürdü. Ana container 05:06:42'de bitti, Kubernetes sidecar'a SIGTERM (Unix'in sonlandırma sinyali) gönderdi, sidecar tepki vermedi ve 30 saniyelik grace period'un sonunda SIGKILL (yakalanamayan öldürme sinyali) ile öldürüldü (exit code 137). Sebep, on altıncı yazıda .NET process'inde gördüğümüzle aynı: container'da PID (Process ID) 1 olan sh, handler'ı olmayan sinyalleri almaz. Üstelik sed'in tamponundaki satırlar SIGKILL ile gittiği için shipper'ın log'u boş kaldı; log taşımak için eklediğimiz sidecar, log'u kaybetti. Sinyali yakalayan sürüm:

          command: ["sh", "-c", "trap 'exit 0' TERM; touch /logs/export.log; tail -n +1 -f /logs/export.log & wait"]
NAME               STATUS     COMPLETIONS   DURATION   AGE
export-orders-v2   Complete   1/1           10s        10s
exported batch 1
exported batch 2
exported batch 3
log-shipper 0 2026-11-08T05:07:28Z 2026-11-08T05:07:35Z
export 0 2026-11-08T05:07:28Z 2026-11-08T05:07:34Z

Sidecar, ana container'dan bir saniye sonra exit code 0 ile kapandı ve log'un tamamı yerinde. Native sidecar Kubernetes tarafındaki sıralamayı çözüyor; sidecar'ın SIGTERM'e düzgün cevap vermesi hâlâ senin işin. Sidecar'ların resource'ları da on birinci yazıdaki in-place resize ile değiştirilebiliyor (1.33'ten beri; sıradan init container'lar ve ephemeral container'lar hariç). 1.35'te beta olan ContainerRestartRules ise container başına restart politikası ve exit code'a göre kurallar getiriyor; GA olduğunda "exit code 42'de container'ı yeniden başlat ama pod'u başarısız sayma" gibi kuralları Job'larda yazabileceğiz.

preStop sleep: Shell'siz Image'da Graceful Shutdown

Onuncu yazının konusu olan sorun: pod silinirken endpoint'ten çıkarılması ile SIGTERM'in aynı anda gitmesi ve arada gelen isteklerin 502 alması. Eski çözüm preStop: exec: command: ["sleep", "5"] idi, ama chiseled image'da sleep binary'si yok. 1.34'te GA olan sleep action bunu kubelet'in kendisine yaptırıyor:

apiVersion: v1
kind: Pod
metadata:
  name: api-prestop
  namespace: whatsnew
spec:
  terminationGracePeriodSeconds: 30
  containers:
    - name: api
      image: bookstore-api:1.2.0
      lifecycle:
        preStop:
          sleep:
            seconds: 5
delete issued at 05:07:49
pod "api-prestop" deleted from whatsnew namespace
05:07:54.510 info: Bookstore.Api[0] ApplicationStopping: SIGTERM received, draining
05:07:54.520 info: Bookstore.Api[0] ApplicationStopped

Silme komutundan beş saniye sonra .NET uygulaması SIGTERM'ü aldı; bu beş saniyede kube-proxy ve Gateway pod'u endpoint listesinden çıkarmaya vakit buldu. Sleep süresi terminationGracePeriodSeconds'tan düşülür: bu örnekte uygulamanın kapanmak için 25 saniyesi kaldı ve HostOptions.ShutdownTimeout bunun altında olmalı. 1.34'le birlikte seconds: 0 da geçerli oldu; bir chart'ta preStop'u "kapatmak" için kullanılabilir.

In-Place Resize, User Namespace'ler ve HPA Tolerance

Üç GA'yı serinin önceki yazılarında gerçek çıktılarla gördük, burada yalnız sonuçlarını topluyoruz. In-place pod resize (1.35) on birinci yazıda pod'u restart etmeden büyüttü, ama .NET'in GC (Garbage Collector) heap limiti ve Environment.ProcessorCount değişmedi; memory için GC.RefreshMemoryLimit() ya da resizePolicy: RestartContainer gerekiyor. On altıncı yazıda aynı özellik, dump almak için pod'a bellek payı açmamızı sağladı. VPA (Vertical Pod Autoscaler) tarafında InPlaceOrRecreate modu VPA 1.6'da GA oldu. User namespace'ler (1.36) on beşinci yazıda container'daki UID (User ID) 0'ı node'da 1017053184'e eşledi. HPA'da HPA başına tolerance (1.37) ise büyük Deployment'larda %10'luk küresel eşiğin yerine kendi eşiğini yazmana izin veriyor. Ayrıca 1.37'de HPA'nın kendi scale-to-zero desteği varsayılan açık beta oldu; Object ve External metriklerle çalışıyor, queue senaryosunda KEDA (Kubernetes Event-driven Autoscaling) hâlâ en kısa yol.

Deprecation'lar: externalIPs ve Endpoints

Uygulama geliştiriciyi doğrudan etkileyen iki deprecation var. Birincisi 1.33'te v1 Endpoints API'si (Application Programming Interface): yerine EndpointSlice var ve on yedinci yazıda kubectl get endpoints'in uyarısını ve API server metriğine yansımasını gördük. Kendi kodunda Endpoints okuyan bir parça varsa (ör. KubernetesClient ile servis keşfi), EndpointSlice'a geçir. İkincisi 1.36'da Service.spec.externalIPs:

$ kubectl apply -f svc-externalip.yaml
Warning: spec.externalIPs is deprecated and may no longer be implemented in some clusters
service/api-external created

externalIPs, cluster'a erişimi olan herkesin herhangi bir IP (Internet Protocol) adresine gelen trafiği kendi Service'ine çekebilmesi gibi bir güvenlik sorunu taşıyordu. Kubernetes projesinin planına göre kube-proxy desteği en erken 1.40'ta varsayılan olarak kapanacak, alan en erken 1.43'te tamamen kaldırılacak. Dışarıdan erişim için doğru yol LoadBalancer Service ya da Gateway API.

KYAML ve Image Volume

1.37'de GA olan KYAML (YAML Ain't Markup Language biçiminin Kubernetes için tanımlanmış bir alt kümesi) şöyle çalışır: string'ler her zaman tırnaklı, yapılar süslü parantezle, girintiye bağlı belirsizlik yok. Norveç'in ülke kodu NO'nun false'a dönmesi gibi klasik YAML sürprizlerini önlüyor ve çıktı yine geçerli YAML:

$ kubectl -n whatsnew get svc api-external -o kyaml
---
{
  apiVersion: "v1",
  kind: "Service",
  metadata: {
    ...
    name: "api-external",
    namespace: "whatsnew",
  },
  spec: {
    clusterIP: "10.96.136.147",
    ...
    externalIPs: [
      "192.0.2.10",
    ],

Manifest'lerini KYAML'a çevirmen gerekmiyor; ama bir chart'ın ürettiği YAML'ın beklediğin tipte değer üretip üretmediğini kontrol ederken işe yarıyor. Image volume (1.36'da GA) ise bir OCI (Open Container Initiative) image'ını salt okunur bir volume olarak mount ediyor: büyük bir makine öğrenmesi modelini ya da statik bir veri setini uygulama image'ından ayırıp kendi sürümüyle dağıtmanın yolu. Node'da containerd 2.1 ve üstü gerekiyor.

ingress-nginx Sonrası ve Gateway API v1.7

Altıncı yazıda konuştuğumuz değişiklik artık tamamlandı: ingress-nginx projesi 24 Mart 2026'da emekliye ayrıldı, GitHub deposu salt okunur ve son sürümü controller-v1.15.1. Mevcut kurulumlar çalışmaya devam ediyor, ama artık güvenlik yaması yok. Ingress API'si GA ve kaldırılması planlanmıyor, ancak dondurulmuş durumda; yeni özellikler yalnız Gateway API'ye geliyor. Seçenekler kabaca üç grupta: Envoy Gateway, NGINX Gateway Fabric, Traefik, Istio gibi açık kaynak Gateway API implementation'ları (seride Envoy Gateway kullandık); sağlayıcıların managed Gateway'leri (GKE (Google Kubernetes Engine) Gateway controller, AKS (Azure Kubernetes Service) tarafında Application Gateway for Containers ve app routing eklentisinin Gateway API modu, EKS (Amazon Elastic Kubernetes Service) tarafında AWS (Amazon Web Services) Load Balancer Controller); ve geçiş süresince Ingress'i destekleyen başka bir controller'a geçmek. Mevcut Ingress kaynaklarını çevirmek için ingress2gateway 1.0 Mart 2026'da çıktı, Kasım 2026 itibarıyla son sürüm 1.2.0; otuzdan fazla ingress-nginx annotation'ını tanıyor.

Gateway API'nin kendisi de hızlı ilerliyor. v1.5 (Şubat 2026) ListenerSet'i, TLSRoute'u ve HTTPRoute CORS (Cross-Origin Resource Sharing) filtresini standart kanala aldı; v1.6 (Haziran 2026) TCPRoute ve UDPRoute'u standarda taşıdı; 29 Ekim 2026'da çıkan v1.7 ise HTTPRoute retry'larını ve BackendTLSPolicy'nin ek TLS (Transport Layer Security) seçeneklerini standart yaptı. Bookstore açısından anlamı: altıncı yazıdaki HTTPRoute'a artık standart kanalda, implementation'a özgü annotation olmadan retry politikası yazılabiliyor. Hangi özelliği kullanabileceğin implementation'ın hangi Gateway API sürümünü ve hangi kanalı desteklediğine bağlı; kurulumdan önce implementation'ın conformance raporuna bak.

Üretimde Dikkat

Yeni bir özelliği kullanmadan önce üç soruyu cevapla: cluster'ının sürümü onu destekliyor mu (managed cluster'lar genelde bir iki minor geride), özellik GA mı (beta özellikler sonraki sürümde değişebilir) ve manifest'in hâlâ eski sürümlü bir cluster'a da gidiyor mu (aynı manifest staging'de 1.37, prod'da 1.35 olabilir). Desteklenmeyen bir alan API server tarafından sessizce düşürülebilir; kubectl apply --dry-run=server -o yaml ile alanın gerçekten kaldığını kontrol et. Bu yazı bir anlık görüntü: Kubernetes 1.38 ve Gateway API'nin sonraki sürümleri çıktıkça tablo değişecek. Takip için kubernetes.io'daki release yazıları ve her sürümün "deprecations and removals" yazısı yeterli; upgrade öncesi on sekizinci yazıdaki pluto ve on yedinci yazıdaki deprecated API metriği hâlâ en güvenilir iki kontrol.

Sık Düşülen Tuzaklar

Native sidecar'ın SIGTERM'ü yok sayan bir shell komutuyla çalıştırılması ve her Job'a 30 saniye eklemesi. Sidecar'ın log'unu tamponda tutup SIGKILL ile kaybetmek. preStop sleep süresini terminationGracePeriodSeconds'tan düşmeyi unutup uygulamaya kapanış için zaman bırakmamak. In-place resize'ın .NET runtime'ına otomatik yansıdığını sanmak. Yeni bir alanı eski sürümlü bir cluster'a uygulayıp sessizce düşmesini fark etmemek. externalIPs ve Endpoints uyarılarını görmezden gelmek. Emekli ingress-nginx'i "çalışıyor" diye yerinde bırakmak; çalışıyor olması yamalandığı anlamına gelmiyor. Ve bir feature'ın cluster'da açık olduğunu dokümandan tahmin etmek, oysa API server'ın metriği kesin cevabı veriyor.

Küçük Bir Deneme

★☆☆ Kendi cluster'ında kubernetes_feature_enabled metriğini oku ve bu yazıdaki tablodaki özelliklerin durumunu çıkar. Managed bir cluster'ın varsa aynı listeyi orada da çıkarıp karşılaştır.

★★☆ Bookstore API'sine native sidecar olarak bir log shipper ekle ve pod silinirken kapanış sırasını gözle: preStop sleep, uygulamanın ApplicationStopping log'u ve sidecar'ın exit'i hangi sırayla ve hangi saniyelerde oluyor?

★★★ Altıncı yazıdaki Gateway kurulumunda HTTPRoute'a bir retry politikası ekle ve Bookstore API'sinin /api/diag/break-liveness gibi hata üreten bir endpoint'iyle denemeye çalış. Kullandığın Gateway implementation'ı Gateway API v1.7'nin hangi özelliklerini destekliyor? Conformance raporundan doğrula.

Bu yazıyla Kubernetes serisi tamamlandı. Pod'un ne olduğundan başlayıp Deployment, Service, Gateway API, configuration, health probe'ları, resource'lar ve .NET GC, graceful shutdown, scaling, storage, Job'lar, Kustomize, güvenlik, debugging ve managed cluster'a çıkışa kadar Bookstore'u adım adım cluster'a taşıdık; son iki yazıda da geçmişin ve bugünün sürümlerini okuduk. Buradan sonraki doğal duraklar ilerideki Helm, GitOps ve Argo CD (CD: Continuous Delivery) ile Observability serileri: uygulamayı paketlemek, cluster'a Git'ten dağıtmak ve production'da ne olduğunu görmek.