Kubernetes Pod Kaynaklarını Restart Etmeden Güncellemek

Kubernetes 1.27+ ile gelen "In-Place Pod Resizing" özelliği sayesinde artık CPU ve Memory değerlerini değiştirmek için podları öldürüp yeniden başlatmaya son veriyoruz.

Kubernetes Pod Kaynaklarını Restart Etmeden Güncellemek

Kubernetes dünyasında yıllardır süregelen bir "best practice" vardır: Pod immutable olmalıdır. Bir şey değişecekse, eski pod ölür, yenisi doğar. Bu kural çoğu zaman hayat kurtarıcı olsa da, özellikle trafiğin aniden yükseldiği anlarda veya JVM tabanlı warm-up süresi uzun süren uygulamalarda, sırf CPU limitini artırmak için podu yeniden başlatmak operasyonel bir kabustu.

In-Place Pod Vertical Scaling özelliği ile artık podları restart etmeden ve poda gelen trafikte kesinti yaşatmadan resourcelarını güncelleyebiliyoruz.

Bu yazıda, bu özelliğin nasıl çalıştığını, resizePolicy kavramını ve production ortamında nelere dikkat etmeniz gerektiğini inceleyeceğiz.

1. Gereksinimler

Bu özelliği kullanmaya başlamadan önce cluster ve client sürümlerinizin uyumlu olduğundan emin olmanız gerekiyor:

  • Kubernetes Server: Sunucunuz v1.33 veya daha yeni bir sürümde olmalıdır.
  • Kubectl Client: --subresource=resize parametresini kullanabilmek için local makinenizdeki kubectl istemcisi en az v1.32 sürümünde olmalıdır.
  • Feature Gate: InPlacePodVerticalScaling feature gate'i hem Control Plane hem de cluster'daki tüm Node'lar için etkinleştirilmiş olmalıdır.
💡
Kubernetes 1.33 sürümünden itibaren InPlacePodVerticalScaling değeri default true olarak geliyor. https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/

2. Neden İhtiyacımız Var?

Bu özelliğe neden ihtiyacımız olduğunu birkaç örnek üzerinden inceleyelim.

✳️
Aşağıdaki senaryoları scale edemeyeceğiniz, replica sayısını artıramayacağınız veya resourceları güncellemenin scalingten daha mantıklı olduğu stateful, legacy singleton vb. uygulamalardaki durumlar için düşünebilirsiniz.
  1. Bir uygulamanın memory tüketimi beklenmedik şekilde arttığında ve OOMKilled hatası kapıya dayandığında:
  • Eski Yöntemde: Panik içinde Deployment manifestini güncellerdiniz. Kubernetes, değişikliği algılayıp mevcut podları terminate eder ve yeni limitlerle yenilerini oluştururdu. Bu süreçte; poda gelen trafik kesilir, in-flight istekler başarısız olabilir ve en kötüsü pod yeniden ayağa kalkarken "cold start" veya cache loading gibi bir başlangıç süresini beklemek zorunda kalırdınız.
  • Yeni Yöntemde: Pod çalışmaya devam ederken spec dosyasındaki memory limitini artırırsınız. Kubelet, Linux Cgroups seviyesinde container'ın memory limitini anında yükseltir. Uygulama kesintiye uğramaz, cache silinmez ve uygulama kaldığı yerden çalışmaya devam eder.
  1. Java (Spring Boot vb.) veya büyük monolitik bir uygulamada CPU throttling gözlemliyorsunuz.
  • Eski Yöntemde: CPU limitini artırdığınızda, yukarıdaki örnekteki gibi yine podlar restart edilecek ve podlar ilk açıldıklarında warm-up, JIT (Just-In-Time) compilerın devreye girmesi, class'ların yüklenmesi ve db pool dolması dakikalar sürebilir.
  • Yeni Yöntemde: In-place resize ile CPU limitini güncellediğiniz anda, JVM duraksamadan bu yeni işlemci gücünü kullanmaya başlar.
  1. Scaling sadece "artırmak" için değildir; maliyet düşürmek için "azaltmak" da gerekir. Ancak kimse sırf kaynak kısılacak diye çalışan stabil bir sistemi gece yarısı restart etmek istemez.
  • Eski Yöntemde: Resourceları azaltmak, yine podun yeniden başlamasına neden olurdu. Bu riskli bir işlemdir; gece vakti podun tekrar ayağa kalkamama ve uykunuzun zehir olma ihtimali her zaman var.
  • Yeni yöntemde: Trafiğin azaldığı saatlerde, podları öldürmeden CPU ve RAM resourcelarını dinamik olarak aşağı çekebilirsiniz. Böylece Cluster Autoscaler, boşa çıkan node'ları kapatabilir ve cloud faturanızı, herhangi bir kesinti ve sorun yaşamadan düşürebilirsiniz.

3. Nasıl Çalışır: resizePolicy

Bu özellik varsayılan olarak resource değişimlerinde restart yapmamaya çalışır. Şimdiye kadar ki örnekleri de bunu baz alarak yazdım ancak isterseniz bu davranışı değiştirebilirsiniz.

Kubernetes, pod spec'ine resizePolicy adında yeni bir field ekliyoruz.

Bu field'ın alabileceği iki seçeneğimiz var:

  1. NotRequired: (Varsayılan) Değişikliği pod çalışmaya devam ederken uygular, podu restart etmez.
  2. RestartContainer: Resource değiştiğinde konteyneri yeniden başlatır. (Örneğin; dinamik bellek yönetimini desteklemeyen eski tip bir Java uygulaması kullanıyorsanız bu gerekli olabilir).
apiVersion: v1
kind: Pod
metadata:
  name: resize-demo
  labels:
    app: kerteriz-test
spec:
  containers:
  - name: pause
    image: registry.k8s.io/pause:3.8
    # Burada cpu ve memory için politika belirliyoruz
    resizePolicy:
    - resourceName: cpu
      restartPolicy: NotRequired
    - resourceName: memory
      restartPolicy: RestartContainer
    resources:
      limits:
        cpu: "500m"
        memory: "512Mi"
      requests:
        cpu: "500m"
        memory: "512Mi"

Yukarıdaki örnekte, CPU değişimi için restart gerektirmeyen ama Memory değişikliği için restartı zorunlu kılan bir Pod oluşturuyoruz.

Öncelikle kubectl get pod resize-demo --output=yaml çıktısına bakalım.

  • spec.containers[0].resources ve status.containerStatuses[0].resources değerlerini kontrol edin. Bu değerler manifest ile eşleşmelidir (500m CPU, 512Mi memory).
  • status.containerStatuses[0].restartCount değerine de dikkat edin (0 olmalıdır).

Şimdi, diyelim ki CPU yetmedi ve CPU request ve limitini 800m'ye yükseltmek istiyoruz. Bunun için --subresource resize argümanıyla kubectl patch komutunu kullanalım:

kubectl patch pod resize-demo --subresource resize --patch \
  '{"spec":{"containers":[{"name":"pause", "resources":{"requests":{"cpu":"800m"}, "limits":{"cpu":"800m"}}}]}}'

veya alternatif olarak,

kubectl edit pod resize-demo --subresource resize

komutuyla açılan editörde spec.containers[0].resources kısmında CPU değerlerini güncelleyebilirsiniz.

veya başka bir alternatif olarak,

kubectl apply -f <updated-manifest> --subresource resize --server-side

pod manifestinde güncellemeleri yaparak yukarıdaki komutla tekrar manifesti apply edebilirsiniz.

Güncelleme sonrası podu describe ettiğinizde:

  • spec.containers[0].resources artık cpu: 800m gösteriyor.
  • status.containerStatuses[0].resources da cpu: 800m gösteriyor, bu da node üzerindeki pod resize işleminin başarılı olduğunu gösterir.
  • status.containerStatuses[0].restartCount 0 olarak kalır, çünkü CPU resize politikasını NotRequired yapmıştık, yani pod restart olmadı.
Restart olmadan podun cpu request ve limitini artırdık

4. Pod Resize Durumları (Status Conditions)

Bir resize isteği gönderdiğinizde, Kubelet bu isteğin akıbetini Pod'un status alanına yazar. kubectl describe pod <pod-adı> komutunu çalıştırdığınızda, Conditions bölümünde karşılaşabileceğiniz durumlar ve anlamları şöyledir:

1. type: PodResizePending

Kubelet isteğinizi aldı ancak şu an yerine getiremiyor. Detaylı sebebi message alanında bulabilirsiniz. Genellikle iki ana nedeni vardır:

  • reason: Infeasible: İstenen değişiklik mevcut Node üzerinde fiziksel olarak imkansız. (Örneğin; Node toplamda 4 CPU'ya sahipken siz 5 CPU talep etmişsinizdir). Bu durumda işlem gerçekleşmez.
  • reason: Deferred: Şu an için kaynak yetersiz ama ileride mümkün olabilir. (Örneğin; Node dolu ama başka bir pod silinirse yer açılabilir). Bu durumda Kubelet isteği reddetmez, sıraya alır ve denemeye devam eder.

Örneğin aynı patch komutunu cpu request ve limiti 80 core yapacak şekilde gönderirsem ve kubectl get pod <pod-adı> -o jsonpath='{.status.conditions}' ile bakarsam pod statusunda aşağıdaki komutu görürüm:

Açıkça Infeasible, çünkü bende böyle bir kaynak yok :)

2. type: PodResizeInProgress

İşler yolunda! Kubelet resize isteğini kabul etti ve gerekli kaynağı ayırdı (allocated). Şu an değişiklikler container seviyesinde uygulanıyor.

  • Bu durum genellikle çok kısa sürer ancak kaynak türüne ve runtime davranışına göre uzayabilir.
  • Eğer uygulama aşamasında teknik bir aksaklık olursa, hata mesajını reason: Error etiketiyle birlikte burada görürsünüz.

4.1. Kubelet 'Deferred' İstekleri Nasıl Yönetir? (Retry Mantığı)

Eğer kaynak yetersizliği nedeniyle resize isteğiniz Deferred (ertelenmiş) durumuna düşerse, Kubelet pes etmez. Belirli aralıklarla (örneğin node üzerinden başka bir pod silindiğinde veya scale down olduğunda) şansını tekrar dener.

Ancak birden fazla bekleyen (Deferred) resize isteği varsa, bu bir kaos ortamı yaratmaz. Kubelet belirli bir hiyerarşiye göre önceliklendirme yapar:

  1. PriorityClass: İlk kural önceliktir. Daha yüksek Priority değerine (PriorityClass baz alınarak) sahip podların resize istekleri, düşük olanlardan önce denenir.
  2. QoS Sınıfı: Eğer iki podun Priority değerleri eşitse, Guaranteed sınıfındaki podların resize işlemi, Burstable olanlardan önce denenir.
  3. Bekleme Süresi: Her şey eşitse, "erken gelen alır" mantığı işler. Deferred durumunda daha uzun süredir bekleyen pod öncelik kazanır.

Önemli Bir Detay: Yüksek öncelikli bir resize isteğinin hala pending durumunda olması, kuyruğun arkasındaki diğer istekleri bloklamaz. Kubelet, yüksek öncelikli işi yapamasa bile, diğer pending resize işlemlerini denemeye devam eder. Yani büyük bir pod sığmıyor diye küçük podların büyümesi engellenmez.

4.2. Versiyon Takibi: observedGeneration Kullanımı

Bir pod üzerinde arka arkaya birden fazla değişiklik yaptığınızda, Kubelet'in hangi değişiklikle uğraştığını anlamak karmaşıklaşabilir. İşte burada generation alanları devreye girer:

  • status.observedGeneration (Top-level): Bu alan, Kubelet'in görüp kabul ettiği (acknowledged) en son metadata.generation değerini gösterir. Kubelet'in sizin son gönderdiğiniz pod spesifikasyonundan haberdar olup olmadığını buradan anlarsınız.

Daha spesifik durum takibi için ise conditions altındaki alanlara bakmalısınız:

  • PodResizeInProgress durumunda: conditions[].observedGeneration alanı, şu an halihazırda uygulanmakta olan (in-progress) resize işleminin hangi podSpec versiyonuna ait olduğunu gösterir.
  • PodResizePending durumunda: conditions[].observedGeneration alanı, tahsis edilmeye (allocation) çalışılan ama beklemeye alınan o resize isteğinin hangi podSpec versiyonuna ait olduğunu gösterir.

Bu alanlar, özellikle CI/CD pipeline'larında veya otomasyon script'lerinde, "Gönderdiğim son güncelleme işlendi mi, yoksa hala eski isteği mi deniyor?" sorusunun cevabını programatik olarak bulmanızı sağlar.

5. Kısıtlamalar

Kubernetes 1.35 itibarıyla, In-Place Resize özelliği henüz her senaryoyu desteklemiyor. Production ortamında sürprizlerle karşılaşmamak için aşağıdaki sınırları bilmenizde fayda var:

  • Kaynak Türleri: Şimdilik sadece CPU ve Memory kaynakları resize edilebilir. (Ephemeral storage vb. desteklenmez).
  • Memory Düşürme: Eğer memory için resizePolicy olarak NotRequired (veya belirtilmemişseniz) kullanıyorsanız, Kubelet memory limitini düşürürken OOM-kill yaşanmaması için "best-effort" bir çaba gösterir, ancak garanti vermez.
    • Eğer düşürmek istediğiniz yeni limit, o anki bellek kullanımından azsa; resize işlemi atlanır (skipped) ve durum "In Progress" olarak kalır.
    • Buna "best-effort" denmesinin sebebi; kontrol yapıldığı an ile limitin uygulandığı an arasında bir "race condition" oluşabilir ve memory kullanımı o milisaniyede aniden fırlayabilir.
  • QoS Sınıfı: Pod oluşturulurken belirlenen Quality of Service (QoS) sınıfı (Guaranteed, Burstable veya BestEffort) resize işlemiyle değiştirilemez. Yeni değerleriniz orijinal sınıfa sadık kalmalıdır:
    • Guaranteed: Resize sonrası da CPU ve Memory için Requests ve Limits değerleri birbirine eşit olmaya devam etmelidir.
    • Burstable: Hem CPU hem de Memory için Requests ve Limits değerlerini aynı anda eşitleyemezsiniz (Çünkü bu onu Guaranteed yapar).
    • BestEffort: Kaynak gereksinimi (request veya limit) ekleyemezsiniz (Çünkü bu onu Burstable veya Guaranteed yapar).
  • Container Türleri: Restart edilemeyen init container ve ephemeral containerlar resize edilemez. Ancak Sidecar containerlar resize edilebilir.
  • Kaynak Kaldırma: Bir kez set edilen requests veya limits değerleri tamamen silinemez; sadece değerleri değiştirilebilir.
  • İşletim Sistemi: Windows podları in-place resize özelliğini desteklemez.
  • Node Politikaları: Static CPU veya Memory manager politikalarıyla yönetilen podlar in-place resize edilemez.
  • Swap: Swap memory kullanan podlarda, memory resize işlemi yapabilmek için resizePolicy mutlaka RestartContainer olarak ayarlanmalıdır.

Not: Bu kısıtlamaların bir kısmı gelecekteki Kubernetes versiyonlarında gevşetilebilir.

6. Özet

Kubernetes In-Place Resize, yıllardır süregelen "Pod'lar immutable'dır" dogmasını yıkarak operasyonel esnekliğimizi bir üst seviyeye taşıyor. Artık sırf CPU limitini artırmak için podları öldürmeye, cache'leri boşaltmaya veya dakikalarca süren "cold-start" sancılarını çekmeye gerek yok.