Docker Compose bellek sınırıyla OOM nasıl önlenir
Docker Compose'ta deploy.resources ve mem_limit ile CPU ve RAM sınırı koyun. OOM sonrası görülen exit 137, swap ve VPS kapasitesine göre boyutlandırma açıklanıyor.
Docker Compose bellek sınırı ne yapar
Docker Compose bellek sınırı, Linux çekirdeğinin tek bir container'ın cgroup'una (işlem grubu; bir işlem kümesi için kaynakları ölçen çekirdek özelliği) uyguladığı kesin üst sınırdır. Bir serviste deploy.resources.limits.memory ayarlandığında bu container, belirtilen değerden daha fazla bellek kullanamaz. Kullanmayı denediğinde çekirdek, container içindeki bir işlemi sonlandırır ve container genellikle 137 çıkış koduyla kapanır.
Bu durum, RAM'in sabit olduğu ve kullanılabilecek ek host belleğinin bulunmadığı VPS ortamlarında özellikle önemlidir. Bellek sızıntısı olan veya hatalı bir sorgu çalıştıran tek bir container, 8GB kapasiteli bir sunucudaki tüm boş sayfaları tüketebilir. Ardından çekirdek, en sorunlu gördüğü işlemi sonlandırır. Bu işlem, soruna neden olan container yerine çoğu zaman bir veritabanı veya SSH oturumunuz olur. Sınırlar, tüm sunucunun kesintiye uğraması yerine yalnızca yeniden başlatılan tek bir servisin etkilenmesini sağlar.
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mUygulayın ve sınırın etkin olduğunu doğrulayın:
docker compose up -d
docker stats --no-streamMEM USAGE / LIMIT sütununda 142MiB / 1GiB benzeri bir değer görünmelidir. Sınır sütununda host'un toplam RAM'i görünüyorsa ayar uygulanmamıştır. Bu durumda, uygulanana kadar bu kılavuzun geri kalanındaki adımlar yardımcı olmaz. Compose dosyası sizin için yeniyse VPS için Docker Compose temelleri, burada kullanılan dosya düzenini açıklar.
deploy.resources.limits veya mem_limit: hangisi geçerlidir
Aynı kavram için iki farklı yazım kullanılır. Bu nedenle konu karıştırılır.
mem_limit, mem_reservation, memswap_limit, cpus ve cpu_shares, eski Compose dosyası biçimlerinden devralınan üst düzey hizmet anahtarlarıdır. deploy.resources, Swarm şemasından gelmiştir ve artık Compose Specification'ın parçasıdır. docker compose bugün bu biçimi okur.
Her ikisi de tek bir ana bilgisayarda çalışır. Compose V2, docker compose eklentisi, docker compose up komutunu Swarm kümesi olmadan çalıştırdığınızda deploy.resources.limits ve deploy.resources.reservations değerlerini uygular. deploy bloğunun yalnızca Swarm'a özgü bölümleri diğer anahtarlardır: mode, placement, update_config ve endpoint_mode, docker stack deploy için anlam taşır ve docker compose up tarafından yok sayılır. Bu nedenle "deploy için Swarm gerekir" şeklindeki yaygın öneri, resources alt bölümü için yanlıştır. Bu öneri izlenirse hizmetlere hiç sınır uygulanmaz.
Proje başına tek bir yazım biçimi seçin. Aynı hizmette mem_limit: 512m ve deploy.resources.limits.memory: 1g kullanılması, ilk bakışta anlaşılamayan bir dosya oluşturur. Hangi değerin geçerli olduğunu tahmin etmek yerine daemon'a sorun:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Bellek değerleri bayt cinsindendir. Bu nedenle 1g, 1073741824 olarak görüntülenir. CPU değeri nano CPU cinsindendir. Bu nedenle 1.5, 1500000000 olarak görüntülenir. Herhangi bir alandaki 0, sınır ayarlanmadığı anlamına gelir. Docker'ın kabul ettiği en düşük bellek sınırı 6m değeridir. Bunun altındaki değerlerde container başlatılamaz.
Bir container sınıra ulaştığında ne olur
Container yavaşlamaz. Sonlanır.
Bir işlem bir sayfa istediğinde ve cgroup zaten memory.max değerine ulaşmışsa kernel, önce o cgroup içinde mümkün olan kaynakları geri alır: temiz sayfa önbelleğini, ardından swap'e alınabilen sayfaları. Geri alma işlemi yeterli alan açmazsa cgroup OOM (bellek yetersizliği) katili, container içindeki bir işlemi seçer ve ona SIGKILL gönderir. Container'ın PID 1 işleminin sonlandırılması container'ı sonlandırır. Çıkış kodu 137, 128 artı sinyal 9'dur. Bu nedenle 137, herhangi bir SIGKILL işleminin göstergesidir; tek başına OOM kanıtı değildir.
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 bir OOM sonlandırmasıdır. false 137, SIGKILL'i başka bir bileşenin gönderdiği anlamına gelir. Bunun yaygın nedeni, uygulamanın SIGTERM'i yok sayması nedeniyle docker compose stop değerinin on saniyelik bekleme süresine ulaşmasıdır. Bu ayrım saatlerce sürecek incelemeyi önler, çünkü bu iki sorunun ortak bir nedeni yoktur.
Olay iki yerde daha kaydedilir. Daemon'u canlı olarak izleyin:
docker events --filter event=oomArdından yeniden başlatmadan sonra da kalan kayıt olan kernel günlüğünü okuyun:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'Bir cgroup sonlandırması, Memory cgroup out of memory: Killed process 24713 (node) ile başlayan bir satır yazdırır. Memory cgroup öneki olmayan bir satır host OOM'dur; bu, makinenin kendisinde RAM kalmadığı anlamına gelir. Limitlerin önlemesi gereken arıza budur. Böyle bir kayıt, limitlerinizin toplamının fazla olduğuna veya bazı servislerin hiç limiti olmadığına işaret eder.
restart: unless-stopped ile bir OOM döngüsü kolayca gizlenir, çünkü servis sonlandıktan bir saniye sonra docker compose ps içinde çalışıyor olarak görünür. Uptime sütununu ve yeniden başlatma sayacını kontrol edin. Ayrıca limiti, uygulamayı sağlıksız olarak bildiren bir healthcheck ile birlikte kullanın. Böylece sürekli sonlanan bir container, sizin izlemenize gerek kalmadan görünür olur.
Rezerve bir ipucudur, sınır ise kuraldır
reservations.memory (eski adıyla mem_reservation) yumuşak bir tabandır. Docker bunu, daemon ana bilgisayarda kaynak çekişmesi veya düşük bellek algıladığında etkinleştirilen yumuşak bir sınır olarak tanımlar. Bir container'ın bu değerin üzerine çıkmasını hiçbir zaman engellemez. Ayrıca container bellek istediğinde belleğin kullanılabilir olacağını da garanti etmez. Yalnızca kernel'i, önce rezerve değerinin üzerinde olan container'lardan bellek geri almaya yönlendirir.
Bu nedenle rezervasyon tek başına hiçbir şeyi korumaz. Baskı altında öncelikli işlem görmek istediğiniz bir servisi işaretlemek için kullanın ve güvenlik için sınıra güvenin. Rezervasyonu sınırın altında tutun. Aksi durumda container başlatılamaz: Docker yapılandırmayı Minimum memory limit can not be less than memory reservation limit ile reddeder.
Swap kullanımını doğru değerlendirme
Çoğu VPS imajında hiç swap dosyası bulunmaz. swapon --show ve free -h komutlarını çalıştırın. Toplam swap miktarı sıfırsa aşağıdaki swap ile ilgili ayarların hiçbir etkisi olmaz ve bellek sınırınız yalnızca RAM sınırı olarak uygulanır.
memswap_limit swap miktarını göstermez. RAM ile swap toplamını gösterir. mem_limit: 1g ve memswap_limit: 2g ile container 1GB RAM ve 1GB swap alır. İki değerin eşit ayarlanması container için hiç swap bırakmaz. mem_limit ayarlanır ve memswap_limit ayarı belirtilmezse container yeniden bellek sınırı kadar swap kullanabilir.
Ubuntu 24.04 ve Debian 13 varsayılan olarak cgroup v2 kullanır. Bu sürümde swap ayrı bir sayaçtır (memory.swap.max) ve bu yapılandırma ek kurulum gerektirmeden çalışır. Eski Your kernel does not support swap limit capabilities mesajı, swapaccount=1 olmadan başlatılan cgroup v1 ana makinelerinden kaynaklanır. Bu sistemlerde bellek sınırı uygulanmaya devam eder, ancak swap bölümü yok sayılır.
Swap kullanımının ne sağladığı konusunda gerçekçi olun. Swap, OOM sonlandırmasını daha az olası hale getirmez; yalnızca daha geç gerçekleşmesini sağlar. Bellek sızıntısı olan bir işlem RAM'i doldurduğu kadar kolay biçimde swap alanını da doldurur. Ayrıca paylaşımlı VPS depolamasında swap kullanan ve sürekli sayfalama yapan bir container, sunucudaki diğer tüm hizmetleri yavaşlatır. Gecikmeye duyarlı işlemler için swap olmadan doğru bir sınır belirlemek, işlemin daha hızlı ve daha öngörülebilir şekilde başarısız olmasını sağlar.
Bellek kullanımının olduğundan daha kötü görünmesinin nedeni
docker stats içindeki MEM USAGE değeri sayfa önbelleğini de içerir. Bu nedenle büyük dosyalar okuyan bir container bellek sınırına yaklaşır ve o seviyede kalır. Bu normaldir ve bellek sızıntısı değildir. Çünkü temiz önbellek, OOM killer çağrılmadan önce geri alınır. self-hosted Jellyfin medya sunucusu gibi bir hizmet, tam olarak bu nedenle sürekli sınırına yakın görünebilir.
Sayıyı container içinden önbellek ve gerçek çalışma kümesi olarak ayırın:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon bırakılabilir olmayan anonim bellektir ve çalışma kümesini oluşturur. file geri alınabilen sayfa önbelleğidir. Bellek sınırını toplam değer yerine anon değerine ve bir pay ekleyerek belirleyin. memory.events dosyası durumu kesin olarak gösterir: sıfırdan büyük bir oom_kill sayacı, container başlatıldığından beri kernel'in bu container içindeki bir işlemi sonlandırdığını belirtir. Artan bir max sayacı ise container'ın şu anda bellek sınırında tutulduğunu gösterir. Her iki komutun çalışması için image içinde bir shell ve coreutils bulunmalıdır. Bu nedenle distroless veya scratch image üzerinde başarısız olurlar.
8GB VPS üzerindeki boyutlandırma sınırları
Uygulamalardan değil, ana bilgisayardan başlayın. 8GB VPS üzerinde kernel, Docker daemon, sshd, journald ve kendi oturum kabuğunuz için yaklaşık 1GB bırakın. Böylece dağıtılabilecek yaklaşık 7GB kalır ve tüm container limitlerinin toplamı bu değerin altında tutulmalıdır. Aşırı tahsis, iki hizmetin aynı anda en yüksek kaynak kullanımına ulaştığı güne kadar çalışır.
8GB bir sistem için uygulanabilir bir dağılım:
- Reverse proxy: 128m limit. Küçük bir işlemdir. Bu kadar sıkı bir limit, kontrolden çıkan bir yapılandırma yeniden yüklemesini hemen yakalar.
- PostgreSQL: 2g limit. Veritabanı yapılandırmasında
shared_buffersyaklaşık 512MB olarak ayarlanmalıdır. - Application container: 1g limit.
- Background worker: 512m limit.
- Media veya file service: 2g limit. Bunun büyük bölümü page cache olarak kullanılır.
Bu sayıları kendi stack'inize doğrudan uygulamayın. Hizmetleri bir gün boyunca gerçek yük altında çalıştırın, docker stats değerini izleyin, her container için en yüksek anon değerini alın ve üst sınır olarak buna yaklaşık yarısı kadar ek kaynak bırakın. Çok düşük ayarlanan bir limit, limitsiz çalıştırmaktan daha kötüdür; normal bir network traffic artışı sırasında sağlıklı bir hizmeti sonlandırır.
Bir tuzağın ayrıca belirtilmesi gerekir. Limiti runtime'ların çoğu, kendilerine bildirilmediği sürece görmez. PostgreSQL, shared_buffers ve work_mem değerlerini container limitinin üzerine rahatlıkla ayarlar ve sonlandırılır. Bir JVM (Java virtual machine), heap boyutunu host RAM yerine cgroup limitine göre ayarlamak için -XX:MaxRAMPercentage=75 gerektirir. Node.js için --max-old-space-size megabayt cinsinden container limitinin altında ayarlanmalıdır; aksi halde garbage collector heap'in kernel müdahale edene kadar büyümesine izin verir. cgroup pazarlık yapmaz. Sonlandırır.
CPU sınırları tamamen farklı şekilde çalışır
cpus: "1.5", CFS (tamamen adil zamanlayıcı) kotası olarak uygulanan tek bir çekirdeğin %150'si anlamına gelir. Kapsayıcı, her 100ms'lik dönemde 150ms CPU süresi alır. Bu süre, tüm iş parçacıkları arasında paylaşılır. Süre dolduğunda çekirdek, sonraki döneme kadar bekletir.
Önemli fark budur. Bellek sınırını aşan kapsayıcı sonlandırılır. CPU sınırını aşan kapsayıcı ise kısıtlanır ve daha yavaş çalışmayı sürdürür. Bu nedenle CPU sınırı daha yüksek bir değerle güvenle ayarlanabilir. Bellek sınırında ise ek pay bırakılması gerekir.
cpu_shares farklı bir araçtır: Yalnızca CPU'lar gerçekten tamamen kullanıldığında önem taşıyan göreli bir ağırlıktır. 1024 ve 512 pay değerlerine sahip iki kapsayıcı, yoğun kullanılan bir çekirdeği yaklaşık ikiye bir oranında paylaşır. Boşta olan bir sistemde ise ikisi de kısıtlanmaz. Hizmetleri önem sırasına göre sıralamak için payları kullanın. Gerçek bir üst sınır gerektiğinde cpus kullanın. Örneğin, gece çalışan bir kod dönüştürme işinin web sunucunuzun kaynaklarını tüketmesini önleyebilirsiniz.
FAQ
deploy.resources.limits, Docker Swarm olmadan çalışır mı?
Evet. Compose V2, tek bir ana bilgisayarda docker compose up çalıştırıldığında deploy.resources.limits ve deploy.resources.reservations değerlerini uygular. Sınırı bayt cinsinden yazdıran ve herhangi bir sınır uygulanmadığında 0 yazdıran docker inspect --format '{{.HostConfig.Memory}}' <container> ile doğrulanabilir. deploy içindeki gerçekten Swarm gerektiren anahtarlar mode, placement, update_config ve endpoint_mode değerleridir.
Docker Compose'ta 137 çıkış kodu ne anlama gelir?
Ana işlem SIGKILL sinyali aldığı anlamına gelir; 137, 128 ile 9 sinyal numarasının toplamıdır. Yaygın neden kernel OOM killer'dır. Ancak bir uygulama SIGTERM sinyalini yok saydığında kapatma zaman aşımı da aynı kodu üretir. Bu iki durumu ayırt etmek için docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> çalıştırılmalıdır. true 137 bellek nedeniyle sonlandırmayı, false 137 ise başka bir nedeni gösterir.
mem_limit mi, yoksa deploy.resources.limits.memory mi ayarlanmalıdır?
docker compose ile her ikisi de çalışır. deploy.resources.limits.memory geçerli Compose Specification biçimidir ve yeni bir dosya için daha uygun varsayılandır. Dosyanın geri kalanında eski üst düzey anahtarlar kullanılıyorsa mem_limit korunmalıdır. Aynı hizmette her ikisini de ayarlamak dosyanın okunmasını zorlaştırır. Bu nedenle biri seçilmeli ve sonuç docker inspect ile doğrulanmalıdır.
Container, sonlandırılmadan tam bellek sınırında neden kalıyor?
docker stats içindeki kullanım değeri, kernelin bellek baskısı altında OOM sonlandırması başlatmak yerine serbest bıraktığı sayfa önbelleğini de içerir. docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat çalıştırılmalı ve yeniden kullanılamayan çalışma kümesini gösteren anon değeri okunmalıdır. Düşük anon değerinin yanında yüksek file değeri görülmesi, container'ın disk giriş ve çıkışı yaptığı anlamına gelir. Bu durum, container'ın sonlandırılmak üzere olduğu anlamına gelmez.
8GB VPS'te ne kadar RAM ayrılmadan bırakılmalıdır?
Kernel, Docker daemon, sshd, journald ve kullanılan shell için yaklaşık 1GB bırakılmalı, ardından tüm container sınırlarının toplamı kalan 7GB'ın altında tutulmalıdır. Değerler kesinleştirilmeden önce, gerçek yük altında her container için en yüksek anon değeri bir gün boyunca izlenmelidir. Toplam, doldurulması gereken bir hedef değil, bir bütçe olarak ele alınmalıdır.