Docker'da bir container'ın kullanabileceği belleği --memory (kısaca -m), işlemci payını ise --cpus bayrağıyla sınırlarsınız. Örneğin docker run -m 512m --cpus="1.5" ... komutu container'a en fazla 512 MB bellek ve bir buçuk CPU'luk işlem süresi verir. Bu bayrakları vermezseniz container'ın hiçbir kaynak sınırı olmaz ve çekirdeğin zamanlayıcısının izin verdiği kadar bellek ve CPU kullanabilir. Paylaşılan bir VPS'te tek bir container'ın belleği tüketip tüm sunucuyu kilitlemesini önlemenin yolu bu limitlerdir.

Bu yazıda resmî belgelerdeki bellek seçeneklerini (--memory, --memory-swap, --memory-swappiness, --memory-reservation, --oom-kill-disable), --memory-swap değerinin kafa karıştıran kombinasyonlarını, OOM (Out Of Memory) riskini, CPU tarafındaki CFS seçeneklerini ve gerçek zamanlı zamanlayıcıyı ele alıyoruz. Compose'daki servis düzeyi karşılıkları da örnekle gösteriliyor. Limitlerin çalışma anındaki etkisini izlemek için docker stats yazısına, Compose'un deploy.resources bloğu için Compose deploy ve kaynak limitleri yazısına, serinin geri kalanı için Docker öğrenme rehberine bakabilirsiniz.

Varsayılan durum ve çekirdek desteği

Belgelerin ilk cümlesi önemlidir: varsayılan olarak bir container'ın kaynak kısıtı yoktur. Docker kaynak sınırlarını docker run komutunun çalışma zamanı bayraklarıyla uygular ve bu bayrakların çoğu, Linux çekirdeğinin ilgili yetenekleri desteklemesini gerektirir. Desteği kontrol etmek için docker info komutunu çalıştırırsınız; çekirdekte bir yetenek kapalıysa çıktının sonunda belgelerde gösterilen türden bir uyarı görürsünüz:

bash
WARNING: No swap limit support

Ubuntu veya Debian sunucularda bir imajla çalışırken "Your kernel does not support swap limit capabilities. Limitation discarded." benzeri bir mesaj da görebilirsiniz. Belgelere göre bu yeteneklere ihtiyacınız yoksa uyarıyı yok sayabilirsiniz. Açmak isterseniz /etc/default/grub dosyasındaki GRUB_CMDLINE_LINUX satırına iki anahtar eklenir, ardından önyükleyici güncellenir ve sistem yeniden başlatılır:

bash
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
bash
sudo update-grub

Bellek tükenmesi (OOM) neden tehlikelidir?

Bellek tükenmesi (OOM) neden tehlikelidir? Uygulamanın bellek ihtiyacını üretime almadan önce testlerle öğrenin., Uygulamayı yalnızca yeterli kaynağa sahip sunucularda...

Linux sunucuda çekirdek önemli sistem işlevleri için yeterli bellek kalmadığını fark ettiğinde bir OOME (Out Of Memory Exception) üretir ve bellek açmak için süreçleri öldürmeye başlar. Öldürülebilecek süreçler arasında Docker'ın kendisi ve sunucudaki diğer kritik uygulamalar da vardır; yanlış süreç öldürülürse tüm sistem fiilen çökebilir.

Docker bu riski azaltmak için daemon'un OOM önceliğini ayarlar, böylece daemon diğer süreçlere göre daha zor öldürülür. Container'ların OOM önceliği ise ayarlanmaz. Sonuç olarak bellek sıkıştığında tek bir container'ın öldürülmesi, Docker daemon'un ya da sistem süreçlerinin öldürülmesinden daha olasıdır. Belgeler bu korumayı aşmaya çalışmamanızı söyler: daemon veya container için --oom-score-adj değerini aşırı negatif bir sayıya çekmek ya da container'da --oom-kill-disable kullanmak bu güvenceleri devre dışı bırakır.

Belgelerin önerdiği önlemler şunlardır:

  • Uygulamanın bellek ihtiyacını üretime almadan önce testlerle öğrenin.
  • Uygulamayı yalnızca yeterli kaynağa sahip sunucularda çalıştırın.
  • Container'ın kullanabileceği belleği aşağıda anlatılan seçeneklerle sınırlayın.
  • Swap yapılandırırken dikkatli olun; swap bellekten yavaştır ama bellek tükenmesine karşı bir tampon sağlayabilir.
  • Gerekirse container'ı bir Swarm servisine dönüştürüp servis düzeyi kısıtlar ve node etiketleriyle yeterli belleği olan sunuculara yerleştirin.

Sert ve yumuşak bellek limitleri

Sert ve yumuşak bellek limitleri: -m veya --memory, --memory-swap, --memory-swappiness, --memory-reservation

Docker iki tür bellek limiti uygulayabilir. Sert limit, container'ın sabit bir miktardan fazla bellek kullanmasına izin vermez. Yumuşak limit ise container'ın ihtiyaç duyduğu kadar bellek kullanmasına izin verir, ta ki çekirdek düşük bellek ya da sunucuda çekişme tespit edene kadar. Seçeneklerin çoğu pozitif bir tamsayı ve ardından b, k, m veya g soneki alır; bunlar bayt, kilobayt, megabayt ve gigabaytı ifade eder.

SeçenekNe yapar
-m veya --memoryContainer'ın kullanabileceği en fazla bellek. Verilirse en küçük değer 6m (6 megabayt) olabilir.
--memory-swapContainer'ın diske swap yapabileceği miktarı belirleyen değiştirici; yalnızca --memory ile anlamlıdır.
--memory-swappinessÇekirdeğin container'ın anonim sayfalarından swap'a atabileceği yüzde; 0 ile 100 arası.
--memory-reservation--memory değerinden küçük bir yumuşak limit; çekişme veya düşük bellek durumunda devreye girer.
--oom-kill-disableOOM durumunda çekirdeğin container süreçlerini öldürmesini engeller; yalnızca -m ile birlikte kullanılmalı.

--memory-reservation değerinin işe yaraması için --memory değerinden küçük olması gerekir. Yumuşak bir limit olduğu için container'ın bu değeri aşmayacağını garanti etmez. --oom-kill-disable için belgelerin uyarısı nettir: bu seçeneği yalnızca -m limiti de verdiğiniz container'larda kullanın. Limit yoksa sunucunun belleği tükenebilir ve çekirdek belleği açmak için sunucunun kendi süreçlerini öldürmek zorunda kalabilir.

--memory-swap kombinasyonları

--memory-swap tek başına bir anlam taşımaz; yalnızca --memory da verildiğinde işe yarar. Swap, container kendisine ayrılan RAM'i tükettiğinde fazla bellek ihtiyacını diske yazmasını sağlar; sık sık swap'a yazan uygulamalar bunun performans bedelini öder. Asıl kafa karışıklığı, değerin toplam miktarı ifade etmesinden doğar: --memory-swap bellek ve swap'ın toplamıdır, --memory ise yalnızca swap dışı bellektir.

--memory--memory-swapSonuç
300m1g300m bellek ve 700m swap (1g - 300m)
300mverilmediSunucuda swap varsa toplam 600m bellek ve swap; swap miktarı --memory kadar
300m300mContainer swap kullanamaz
300m0Ayar yok sayılır, verilmemiş gibi davranılır
300m-1Sunucudaki swap miktarına kadar sınırsız swap

Container'ın hiç swap kullanmamasını istiyorsanız iki değeri aynı yaparsınız. Bunun sebebi yine tanımdır: toplam ile fiziksel bellek eşitse swap için pay kalmaz. Gecikmeye duyarlı bir veritabanı veya önbellek servisi için tipik bir komut şöyle görünür:

bash
docker run -d --name cache -m 512m --memory-swap 512m redis

--memory-swappiness

Varsayılan olarak çekirdek, container'ın kullandığı anonim sayfaların bir yüzdesini swap'a atabilir. --memory-swappiness bu yüzdeyi 0 ile 100 arasında ayarlar: 0 anonim sayfa swap'ını kapatır, 100 tüm anonim sayfaları swap'a atılabilir yapar. Değeri hiç vermezseniz sunucunun ayarı devralınır.

CPU limitleri: CFS zamanlayıcısı

CPU limitleri: CFS zamanlayıcısı: --cpus, --cpu-period, --cpu-quota, --cpuset-cpus

Bellekte olduğu gibi CPU'da da varsayılan sınırsızdır; her container sunucunun CPU döngülerine sınırsız erişir. Kullanıcıların çoğu Linux'un normal süreçler için kullandığı CFS (Completely Fair Scheduler) zamanlayıcısını yapılandırır. Bu bayrakları verdiğinizde Docker, sunucudaki container cgroup'unun ayarlarını değiştirir.

SeçenekNe yapar
--cpusContainer'ın kullanabileceği CPU miktarı. İki CPU'lu sunucuda 1.5 değeri en fazla bir buçuk CPU demektir.
--cpu-periodCFS periyodu; varsayılan 100000 mikrosaniye (100 ms). Çoğu kullanıcı değiştirmez.
--cpu-quotaHer periyotta container'ın kısılmadan önce kullanabileceği mikrosaniye; etkin tavan.
--cpuset-cpusContainer'ın kullanabileceği belirli çekirdekler; 0-3 veya 1,3 gibi. İlk CPU 0 numaralıdır.
--cpu-sharesVarsayılan 1024 ağırlığı artırır veya azaltır; yalnızca CPU sıkıştığında uygulanan yumuşak limit.

--cpus aslında periyot ve kota ikilisinin kısayoludur. Belgelerdeki örneğe göre tek CPU'lu bir sunucuda aşağıdaki iki komut aynı şeyi yapar ve container'a her saniye CPU'nun en fazla yüzde 50'sini garanti eder:

bash
docker run -it --cpus=".5" ubuntu /bin/bash

docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash

Aynı mantıkla --cpus="1.5", --cpu-period="100000" ve --cpu-quota="150000" ayarına eşittir. Çoğu durumda --cpus daha pratiktir; belgeler de periyot ve kota için bu öneriyi yapar.

--cpu-shares farklı çalışır. Bir tavan koymaz, yalnızca CPU döngüleri kısıtlıyken container'ın payını belirler. Bol CPU varsa tüm container'lar ihtiyaç duydukları kadar kullanır. Yani bu bir öncelik ayarıdır; belirli bir CPU erişimini garanti etmez veya ayırmaz. Belgelere göre Swarm modunda container'ların zamanlanmasını da engellemez. --cpuset-cpus ise container'ı belirli çekirdeklere bağlar; örneğin bir arka plan işini yalnızca 1,3 çekirdeklerinde çalıştırıp diğerlerini web sunucusuna bırakabilirsiniz.

bash
docker run -d --name arkaplan --cpuset-cpus="1,3" --cpu-shares=512 is-imaji

Gerçek zamanlı zamanlayıcı

CFS ile çalışamayan işler için gerçek zamanlı zamanlayıcı da yapılandırılabilir, ama belgeler bunu ileri düzey bir çekirdek özelliği olarak tanımlar ve çoğu kullanıcının bu değerlere dokunmasına gerek olmadığını, yanlış ayarın sunucuyu kararsız hatta kullanılamaz hale getirebileceğini vurgular. Üç adım gerekir: çekirdekte CONFIG_RT_GROUP_SCHED açık olmalı, daemon --cpu-rt-runtime ile başlatılmalı (örneğin 1 saniyelik varsayılan periyotta 950000 değeri gerçek zamanlı olmayan işlere en az 50000 mikrosaniye bırakır) ve container'a --cap-add=sys_nice, --cpu-rt-runtime ve --ulimit rtprio verilmelidir. Çekirdek ya da daemon doğru yapılandırılmamışsa container başlatılırken hata oluşur.

bash
docker run -it \
    --cpu-rt-runtime=950000 \
    --ulimit rtprio=99 \
    --cap-add=sys_nice \
    debian:jessie

Compose'da bellek ve CPU limitleri

Compose dosyasında aynı limitler servis düzeyindeki anahtarlarla verilir: mem_limit, mem_reservation, memswap_limit, mem_swappiness, cpus, cpu_shares, cpuset, pids_limit ve oom_kill_disable bunlardan bazılarıdır. Bayt değerleri 300m, 1gb gibi bir miktar ve birim olarak yazılır; desteklenen birimler b, k veya kb, m veya mb, g veya gb'dir. memswap_limit kuralları --memory-swap ile aynıdır.

yaml
services:
  api:
    image: nginx:alpine
    mem_limit: 512m
    memswap_limit: 512m
    mem_reservation: 256m
    cpus: 1.5
    pids_limit: 200

docker compose config çıktısında bayt değerleri bayta çevrilmiş olarak görünür; böylece Compose'un değeri nasıl yorumladığını kontrol edebilirsiniz:

bash
docker compose -f compose.yaml config
name: kaynak
services:
  api:
    cpus: 1.5
    image: nginx:alpine
    mem_limit: "536870912"
    mem_reservation: "268435456"
    memswap_limit: "536870912"
    networks:
      default: null
    pids_limit: 200

Servis düzeyi anahtarlar deploy.resources içindeki karşılıklarıyla tutarlı olmak zorundadır. Belge, mem_limit verildiğinde limits.memory ile tutarlı olması gerektiğini söyler. İki yerde farklı değer yazdığımız bir dosyada doğrulama gerçekten başarısız oldu:

yaml
services:
  api:
    image: nginx:alpine
    mem_limit: 512m
    deploy:
      resources:
        limits:
          memory: 1g
bash
docker compose -f compose.yaml config --quiet
services.api: can't set distinct values on 'mem_limit' and 'deploy.resources.limits.memory': invalid compose project

Hangisini kullanacağınızı seçip tek yerde tutun. deploy bloğunun ayrıntıları, rezervasyonlar ve GPU tanımları Compose deploy yazısında anlatılıyor. Çok servisli bir Compose dosyasının genel düzeni için Docker Compose rehberine bakabilirsiniz.

Sık yapılan hatalar

  • Limit vermeden üretime çıkmak. Varsayılan sınırsızdır ve belleği tüketen tek bir container tüm sunucuyu etkileyebilir.
  • --memory-swap değerini yalnızca swap miktarı sanmak. Değer bellek ve swap'ın toplamıdır; -m 300m --memory-swap 1g 700m swap demektir.
  • Swap kapatmak isterken --memory-swap 0 yazmak. 0 değeri yok sayılır; swap'ı kapatmak için iki değeri eşit yapın.
  • -m olmadan --oom-kill-disable kullanmak. Bu, çekirdeği sunucunun kendi süreçlerini öldürmeye itebilir; cgroup v2'de ise bayrak zaten dikkate alınmaz.
  • --cpu-shares değerinin bir tavan koyduğunu sanmak. Bu yalnızca CPU sıkıştığında devreye giren göreli bir ağırlıktır; tavan için --cpus kullanın.
  • Container içindeki free çıktısına bakarak swap limitini doğrulamaya çalışmak.
  • Compose'da aynı limiti hem servis düzeyinde hem deploy.resources içinde farklı değerlerle yazmak.

Sık Sorulan Sorular

Container'a en az ne kadar bellek limiti verebilirim?

-m veya --memory verdiğinizde izin verilen en küçük değer 6m, yani 6 megabayttır.

--cpus ile --cpu-shares arasındaki fark nedir?

--cpus container'ın kullanabileceği CPU miktarına bir tavan koyar ve her zaman uygulanır. --cpu-shares ise yalnızca CPU döngüleri kısıtlıyken hangi container'ın daha büyük pay alacağını belirler; boş bir sunucuda etkisi olmaz.

docker info "No swap limit support" uyarısı veriyor, sorun mu?

Swap limitlerine ihtiyacınız yoksa hayır; belgeler bu uyarıyı yok sayabileceğinizi söyler. İhtiyacınız varsa GRUB satırına cgroup_enable=memory swapaccount=1 ekleyip sudo update-grub çalıştırır ve sunucuyu yeniden başlatırsınız; bunun bellek ve performans maliyetini göz önünde bulundurun.

Limitin işe yarayıp yaramadığını nasıl görürüm?

docker stats çıktısındaki bellek kullanımı ve limit sütunları bunu gösterir. Daha ayrıntılı cgroup metrikleri ve Prometheus entegrasyonu için container metrikleri yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Docker öğrenme rehberi · docker stats ve metrikler · Compose deploy limitleri · Docker restart policy · VPS güvenlik sertleştirme