Container'larım sürekli OOMKilled oluyor, memory limit'lerini PHP-FPM ve Go için nasıl doğru ayarlarım?
Soru
Kubernetes'te hem PHP-FPM (Laravel) hem de Go servislerimiz var. Lokalde limit koymadan çalıştırınca her şey yolunda görünüyor ama production'da yük altında pod'lar sürekli OOMKilled olup yeniden başlıyor. Memory request/limit değerlerini PHP-FPM ve Go container'ları için nasıl doğru ayarlarım? Sürekli limiti yükseltmek dışında hangi uygulama seviyesi ayarlara bakmalıyım?
Cevap
Kısa cevap: OOMKilled bir uygulama hatası değil, container’ın cgroup memory limitini aştığının sinyalidir.
Kısa cevap
Lokalde limit yok, kernel büyümeye izin veriyor; production’da limit + yük altında tavana çarpıp öldürülüyor. Çözüm limiti körlemesine yükseltmek değil, gerçek peak’i ölçüp uygulama knob’larını ona göre ayarlamak.
Neden
-
Önce ölçün: working set ne kadar?
container_memory_working_set_bytesmetriğine bakın. Limiti gözlenen peak’in üzerine, makul bir pay bırakarak koyun. “Lokalde sorun yok” yanıltıcıdır çünkü orada limit yoktur; gerçek soru yük altındaki tepe kullanımdır. -
PHP-FPM’de katil pm.max_children’dır. Toplam bellek ≈
max_children × istek başına ortalama bellek. Tutorial’dan kopyalanan devasa birmax_childrenyük altında tüm worker’ları açar ve limiti patlatır. Formül:max_children = (container limit − pay) / worker peak. Worker peak’i tek bir process’in gerçek RSS’inden ölçün. -
PHP
memory_limitcontainer’ı korumaz. O ayar per-process’tir; container geneline dair hiçbir garanti vermez, hatta tek bir worker’ınmemory_limit’e kadar büyümesine izin verir. Container’a karşı asıl kalkanınızmax_children’dır;memory_limit’i makul tutun ama container koruması sanmayın. -
Go runtime cgroup limitini varsayılan olarak bilmez. GC’yi heap büyümesine (
GOGC) göre tempolar, container limitine göre değil; limitli bir container’da bu, fazla allocate edip OOM olmak demektir. Çözüm:GOMEMLIMIT’i (Go 1.19+) limitin biraz altına ayarlayın — bu soft limit, belleğe yaklaştıkça GC’yi agresifleştirir. AyrıcaGOMAXPROCS’u CPU limitine eşitleyin (automaxprocs); Go 1.25 öncesinde bunu yapmazsanız scheduler node’un tüm çekirdeklerine göre çalışırdı, Go 1.25’ten (Ağustos 2025) itibaren runtime bunu cgroup CPU limitine göre otomatik ayarlıyor.# Go container resources: requests: { memory: "256Mi" } limits: { memory: "512Mi" } env: - { name: GOMEMLIMIT, value: "450MiB" } # limit − pay (soft limit) - { name: GOMAXPROCS, value: "1" } # CPU limitine eşitle
Ne yapmalı
-
K8s’te requests = tipik, limits = peak + pay. Heap dışı kullanım (stack, buffer, off-heap, PHP extension’ları) için mutlaka pay bırakın; limiti çok sıkarsanız normal bir burst bile pod’u öldürür. Sürprizsiz davranış istiyorsanız request = limit ile Guaranteed QoS düşünün.
-
Leak mi, yüksek peak mi ayırın.
working_setyük altında yüksek bir seviyede plato yapıyorsa bu bir boyutlandırma problemidir; limiti ve knob’ları ona göre ayarlayın. Monoton tırmanıp hiç plato yapmıyorsa bu bir memory leak’tir; limiti yükseltmek onu yalnızca geciktirir, kodu düzeltmeniz gerekir.
Sonuç: Ben olsam önce working set’i ölçer, limiti gözlenen peak + pay olarak koyardım. PHP-FPM’de max_children’ı limite karşı formülle sabitler, Go’da GOMEMLIMIT + GOMAXPROCS ayarlardım. Heap dışı için her zaman pay bırakır ve grafiği izleyerek bunun bir leak mı yoksa yüksek peak mi olduğunu ayırt ederdim. Limiti sonsuza dek yükseltmek bir çözüm değil, gerçek problemi erteleme biçimidir.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.