Docker Compose मध्ये OOM थांबवण्यासाठी memory limit
Docker Compose मध्ये deploy.resources आणि mem_limit वापरून memory व CPU मर्यादा ठरवा. exit 137, swap आणि VPS RAM नुसार योग्य sizing कशी करावी ते जाणून घ्या.
Docker Compose memory limit काय करते
Docker Compose memory limit ही Linux kernel एका कंटेनरच्या cgroup वर लावलेली कमाल मर्यादा आहे. cgroup म्हणजे प्रक्रियांच्या संचासाठी संसाधनांचे मोजमाप करणारे kernel feature. सेवेवर deploy.resources.limits.memory सेट केल्यास त्या कंटेनरला तुम्ही लिहिलेल्या मूल्यापेक्षा जास्त memory कधीही वापरता येत नाही. कंटेनरने ही मर्यादा ओलांडण्याचा प्रयत्न केल्यास kernel कंटेनरमधील एखादी process बंद करते. त्यानंतर कंटेनर साधारणपणे code 137 सह बंद होतो.
VPS वर याचे महत्त्व सर्वाधिक असते. तेथे RAM निश्चित असते आणि वापरण्यासाठी अतिरिक्त host memory उपलब्ध नसते. memory leak किंवा चुकीची query असलेला एक कंटेनर 8GB मशीनवरील प्रत्येक मोकळे page वापरू शकतो. त्यानंतर kernel सर्वाधिक समस्या निर्माण करणारी process बंद करते. ती process अनेकदा समस्या निर्माण करणाऱ्या कंटेनरऐवजी database किंवा तुमचे SSH session असते. Limits मुळे संपूर्ण server बंद पडण्याऐवजी केवळ एक सेवा restart होते.
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mते लागू करा आणि limit कार्यरत आहे याची खात्री करा:
docker compose up -d
docker stats --no-streamMEM USAGE / LIMIT column मध्ये 142MiB / 1GiB सारखे मूल्य दिसले पाहिजे. limit column मध्ये पूर्ण host RAM दिसत असल्यास setting लागू झालेली नाही. ती लागू होईपर्यंत या मार्गदर्शकातील पुढील माहिती उपयोगी ठरणार नाही. compose file तुमच्यासाठी नवीन असल्यास, VPS साठी Docker Compose ची मूलभूत माहिती या file layout चे स्पष्टीकरण देते.
deploy.resources.limits किंवा mem_limit: कोणते लागू होते
एकाच संकल्पनेसाठी दोन मांडणी वापरल्या जातात. त्यामुळे हा विषय गोंधळात टाकतो.
mem_limit, mem_reservation, memswap_limit, cpus आणि cpu_shares या जुन्या Compose file formats मधून आलेल्या top-level service keys आहेत. deploy.resources हे Swarm schema मधून आले असून आता Compose Specification चा भाग आहे. आज docker compose हेच format वाचते.
दोन्ही एकाच host वर काम करतात. कोणताही Swarm cluster नसतानाही, docker compose plugin तुम्ही docker compose up चालवल्यावर deploy.resources.limits आणि deploy.resources.reservations लागू करते. तपासणीच्या वेळी, 2026-10-10 रोजी, plugin ची v5 line सुरू होती आणि v5.6.0 ही latest stable release होती. जुन्या Compose file format versions सोबत गोंधळ होऊ नये म्हणून ती v2 वरून थेट v5 वर गेली आणि 3 व 4 वगळले. कोणत्याही v5 release ने resource limits हाताळण्याची पद्धत बदललेली नाही. deploy block मधील Swarm-केंद्रित भाग म्हणजे इतर keys, जसे placement, update_config आणि endpoint_mode. हे docker stack deploy साठी nodes च्या cluster मध्ये scheduling आणि rollouts कसे करायचे ते सांगतात. त्यामुळे resources subsection साठी “deploy ला Swarm आवश्यक आहे” हा सामान्य सल्ला चुकीचा आहे. तो पाळल्यास तुमच्या services वर कोणतीही limit लागू होत नाही.
प्रत्येक project साठी एकच मांडणी निवडा. एकाच service वर mem_limit: 512m आणि deploy.resources.limits.memory: 1g दोन्ही लिहिणे केवळ वाचण्यास अवघड नाही. Compose Specification नुसार mem_limit सेट केले असल्यास ते limits.memory शी सुसंगत असणे आवश्यक आहे. दोन्ही values वेगळ्या असल्यास कोणती value लागू होईल, हे Specification सांगत नाही. कोणतीही मांडणी वापरली तरी daemon ने प्रत्यक्षात काय लागू केले ते पडताळा:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Memory values bytes मध्ये असतात. त्यामुळे 1g चे output 1073741824 असे दिसते. CPU चे मोजमाप nano CPUs मध्ये असते. त्यामुळे 1.5 चे output 1500000000 असे दिसते. कोणत्याही field मध्ये 0 असल्यास कोणतीही limit सेट केलेली नाही. Docker स्वीकारणारी सर्वात लहान memory limit 6m आहे. त्यापेक्षा कमी value दिल्यास container सुरू होत नाही.
कंटेनर मर्यादेपर्यंत पोहोचल्यावर काय होते
कंटेनरचा वेग कमी होत नाही. तो बंद पडतो.
एखादी प्रक्रिया एखादे page मागते आणि cgroup आधीच त्याच्या memory.max वर पोहोचलेला असतो, तेव्हा kernel प्रथम त्या cgroup मध्ये शक्य तेवढी memory reclaim करतो: आधी clean page cache, त्यानंतर swap करता येणारी pages. Reclaim करूनही पुरेशी memory मोकळी झाली नाही, तर cgroup OOM (out of memory) killer कंटेनरमधील एखादी प्रक्रिया निवडतो आणि तिला SIGKILL पाठवतो. कंटेनरचा PID 1 बंद केल्यावर कंटेनर समाप्त होतो. Exit code 137 म्हणजे फक्त 128 अधिक signal 9. त्यामुळे 137 हा कोणत्याही SIGKILL चा संकेत आहे; तो स्वतःहून OOM झाल्याचा पुरावा नाही.
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 म्हणजे OOM kill. false 137 चा अर्थ सहसा दुसऱ्या घटकाने SIGKILL पाठवले असा असतो. एखादा program मुद्दाम 137 exit code देऊ शकतो, पण असे क्वचितच होते. नेहमीचे कारण म्हणजे docker compose stop ची दहा सेकंदांची grace period संपणे, कारण अॅपने SIGTERM कडे दुर्लक्ष केले. हा फरक समजल्यास अनेक तास वाचतात, कारण या दोन समस्यांचा एकमेकांशी संबंध नसतो.
ही घटना आणखी दोन ठिकाणी नोंदवली जाते. daemon थेट पाहण्यासाठी:
docker events --filter event=oomत्यानंतर kernel log वाचा. Restart नंतरही हा log नोंद ठेवतो:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'cgroup kill झाल्यावर Memory cgroup out of memory: Killed process 24713 (node) ने सुरू होणारी ओळ दिसते. या ओळीचा अर्थ असा की एका कंटेनरने स्वतःची मर्यादा गाठली. Host वर अजून भरपूर मोकळी RAM असतानाही ही ओळ दिसू शकते. Memory cgroup prefix नसलेली ओळ host OOM दर्शवते. याचा अर्थ मशीनची RAM संपली. मर्यादा याच प्रकारचे अपयश टाळण्यासाठी असतात. त्यामुळे अशी ओळ दिसल्यास तुमच्या मर्यादांची एकूण बेरीज खूप जास्त असू शकते, काही services वर कोणतीही मर्यादा नसू शकते किंवा Docker च्या बाहेरील एखादी गोष्ट तुम्ही ठेवलेल्या headroom पेक्षा जास्त memory वापरत असू शकते.
restart: unless-stopped मुळे OOM loop सहज लक्षात येत नाही, कारण सेवा बंद पडल्यानंतर एका सेकंदाने ती docker compose ps मध्ये पुन्हा चालू दिसते. uptime column आणि restart count तपासा. तसेच मर्यादेसोबत अॅप unhealthy असल्याचे दाखवणारा healthcheck वापरा. त्यामुळे वारंवार बंद पडणारा कंटेनर तुम्ही सतत पाहत नसतानाही लक्षात येतो.
Reservation हा संकेत आहे; limit हा नियम आहे
reservations.memory (जुने mem_reservation) हे soft floor आहे. Docker त्याचे वर्णन soft limit म्हणून करते. Host वर contention किंवा memory कमी असल्याचे daemon ला आढळल्यावर ते सक्रिय होते. यामुळे container त्या मर्यादेपेक्षा जास्त memory वापरणे कधीही थांबत नाही. तसेच container ने memory मागितल्यावर ती उपलब्ध असेल, याची हमीही मिळत नाही. याचा एकमेव परिणाम असा होतो की, limit पेक्षा जास्त memory वापरणाऱ्या containers मधून kernel ने memory reclaim करावी, याला प्राधान्य मिळते.
म्हणून reservation स्वतःहून कशाचेही संरक्षण करत नाही. दबावाच्या स्थितीत एखाद्या service शी अनुकूल पद्धतीने व्यवहार करावा असे दर्शवण्यासाठी ते वापरा. सुरक्षिततेसाठी limit वर अवलंबून रहा. Reservation ही limit पेक्षा कमी ठेवा. अन्यथा container सुरू होणार नाही: Docker Minimum memory limit can not be less than memory reservation limit सह configuration नाकारते.
Swap accounting, प्रामाणिकपणे
बहुतेक VPS images मध्ये कोणतीही swap file नसते. swapon --show आणि free -h चालवा. Swap चे एकूण प्रमाण शून्य असल्यास, खालील swap-संबंधित कोणतीही setting लागू होत नाही आणि तुमची memory limit ही फक्त RAM मर्यादा राहते.
memswap_limit हे swap चे प्रमाण नाही. ते memory आणि swap यांची एकूण मर्यादा आहे. mem_limit: 1g आणि memswap_limit: 2g वापरल्यास container ला 1GB RAM आणि 1GB swap मिळतो. दोन्ही values समान ठेवल्यास container ला swap मिळत नाही. mem_limit सेट करून memswap_limit unset ठेवल्यास container पुन्हा त्याच्या memory limit इतका swap वापरू शकतो.
Ubuntu 24.04 आणि Debian 13 मध्ये default म्हणून cgroup v2 वापरले जाते. येथे swap हा स्वतंत्र counter (memory.swap.max) असतो आणि अतिरिक्त setup शिवाय ही रचना कार्य करते. जुना message Your kernel does not support swap limit capabilities हा swapaccount=1 शिवाय boot केलेल्या cgroup v1 hosts वर दिसतो. अशा hosts वर memory limit लागू राहते; मात्र swap भागाकडे दुर्लक्ष केले जाते.
Swap मुळे नेमका काय फायदा होतो, याबाबत स्पष्ट रहा. त्यामुळे OOM kill होण्याची शक्यता कमी होत नाही; तो फक्त उशिरा होतो. कारण memory leak असलेली process RAM भरते त्याचप्रमाणे swap देखील भरते. दरम्यान, shared VPS storage वर swap मुळे thrashing करणारा container त्या server वरील इतर प्रत्येक सेवेचा वेग कमी करतो. Latency-संवेदनशील सेवांसाठी, swap शिवाय योग्य limit वापरणे अधिक जलद आणि अधिक अंदाजे पद्धतीने failure घडवते.
प्रत्यक्ष वापरापेक्षा memory usage अधिक असल्यासारखा का दिसतो
Linux मध्ये docker stats मधील MEM USAGE आकड्यातून फक्त inactive page cache वजा केला जातो (cgroup v2 मध्ये inactive_file). त्यामुळे अलीकडे वापरलेला cache अजूनही मोजला जातो. मोठ्या फाइल्स वाचणारा container त्याच्या limit जवळ पोहोचतो आणि तिथेच राहतो. हे सामान्य आहे आणि memory leak नाही, कारण OOM killer ला call करण्यापूर्वी clean cache reclaim केला जातो. self-hosted Jellyfin media server सारखी service याच कारणामुळे कायमच्या मर्यादेजवळ असल्यासारखी दिसते.
Container च्या आतून cache आणि प्रत्यक्ष working set यांत हा आकडा विभागा:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon म्हणजे anonymous memory. ही अशी working set आहे जी drop करता येत नाही. file म्हणजे page cache. तो reclaim करता येतो. तुमची limit एकूण memory वर नव्हे, तर anon अधिक योग्य margin यावर आधारित ठरवा. memory.events फाइल याचे स्पष्ट उत्तर देते: oom_kill counter शून्यापेक्षा अधिक असल्यास container सुरू झाल्यापासून kernel ने त्यातील एखादी प्रक्रिया kill केली आहे. max counter वाढत असल्यास container सध्या आपल्या ceiling वर अडवला जात आहे. दोन्ही commands साठी image मध्ये shell आणि coreutils आवश्यक आहेत. त्यामुळे distroless किंवा scratch image वर त्या अपयशी ठरतात.
8GB VPS वरील आकारमानाच्या मर्यादा
अॅप्सपासून नव्हे, तर host पासून सुरुवात करा. 8GB VPS वर kernel, Docker daemon, sshd, journald आणि तुमच्या login shell साठी सुमारे 1GB मोकळी ठेवा. त्यामुळे वाटप करण्यासाठी साधारण 7GB उरतात. प्रत्येक container च्या मर्यादांची बेरीज या मर्यादेपेक्षा कमी राहिली पाहिजे. दोन सेवा एकाच वेळी कमाल वापरावर पोहोचतात त्या दिवसापर्यंत overcommitting चालते.
8GB मशीनसाठी एक व्यवहार्य विभागणी:
- Reverse proxy: 128m limit. ही लहान प्रक्रिया असते. इतकी कडक मर्यादा runaway configuration reload त्वरित पकडते.
- PostgreSQL: 2g limit. Database configuration मध्ये
shared_buffersसुमारे 512MB वर सेट करा. - Application container: 1g limit.
- Background worker: 512m limit.
- Media किंवा file service: 2g limit. यातील बहुतांश memory page cache साठी वापरली जाईल.
ही आकडेवारी तुमच्या स्वतःच्या stack मध्ये तशीच वापरू नका. सेवा एक दिवस प्रत्यक्ष load खाली चालवा, docker stats वर लक्ष ठेवा, प्रत्येक container साठी कमाल anon मूल्य नोंदवा आणि त्यावर headroom म्हणून साधारण निम्मी अतिरिक्त मर्यादा जोडा. खूप कडक limit ठेवणे limit न ठेवण्यापेक्षा वाईट असते, कारण सामान्य traffic spike दरम्यान निरोगी सेवा बंद होऊ शकते.
एका समस्येची स्वतंत्र नोंद आवश्यक आहे. काही runtimes तुम्ही त्यांना limit सांगितल्याशिवाय ती मर्यादा दुर्लक्षित करतात. मर्यादा वाचणारे runtimes देखील तुमच्या अपेक्षेप्रमाणे स्वतःचा आकार ठरवतीलच असे नाही. PostgreSQL shared_buffers आणि work_mem यांचा आकार container limit पेक्षा जास्त ठेवू शकते आणि नंतर प्रक्रिया बंद होऊ शकते. आधुनिक JVM (Java virtual machine) default नुसार cgroup limit ओळखते. मात्र OpenJDK सारख्या HotSpot builds मध्ये heap साठी त्या मर्यादेच्या केवळ 25 percent इतकी जागा दिली जाते. त्यामुळे heap ला limit मधील अधिक memory वापरू देताना heap बाहेरील memory साठी जागा ठेवण्यासाठी -XX:MaxRAMPercentage=75 हा नेहमीचा पर्याय आहे. Node.js version 12 पासून cgroup limit वरून default heap चा आकार ठरवते. तो container limit च्या साधारण निम्मा असतो आणि सुमारे 2GB वर स्थिरावतो. वेगळी कमाल मर्यादा हवी असल्यासच --max-old-space-size megabytes मध्ये सेट करा. ती container limit पेक्षा कमी ठेवा, जेणेकरून kernel हस्तक्षेप करण्यापूर्वी garbage collector कार्य करू शकेल. Ollama मध्येही तत्त्व तेच आहे, पण knob वेगळा आहे. num_ctx वाढवल्याने KV cache वाढतो आणि त्यामुळे शेकडो megabytes memory वापरली जाऊ शकते. परिणामी दीर्घ prompt च्या मध्यात container बंद होतो. cgroup वाटाघाटी करत नाही. ते प्रक्रिया बंद करते.
CPU मर्यादा पूर्णपणे वेगळ्या पद्धतीने कार्य करतात
cpus: "1.5" म्हणजे एका core च्या 150% क्षमतेइतका CPU वापर. ही मर्यादा CFS (completely fair scheduler) quota म्हणून लागू केली जाते. प्रत्येक 100ms कालावधीत container ला 150ms CPU time मिळतो. हा वेळ त्याच्या सर्व threads मध्ये सामायिक केला जातो. हा वेळ संपल्यावर kernel पुढील कालावधीपर्यंत त्याला प्रतीक्षा करण्यास भाग पाडतो.
हा महत्त्वाचा फरक आहे. Memory limit ओलांडल्यास container बंद केला जातो. CPU limit ओलांडल्यास container throttled होतो आणि कमी वेगाने कार्यरत राहतो. त्यामुळे CPU limit तुलनेने आक्रमकपणे सेट करणे सुरक्षित असते. मात्र memory limit साठी अतिरिक्त headroom आवश्यक असतो.
cpu_shares हे वेगळे साधन आहे. हे relative weight असते आणि CPUs प्रत्यक्षात पूर्ण क्षमतेने वापरले जात असतील तेव्हाच त्याचा परिणाम होतो. 1024 आणि 512 shares असलेले दोन containers व्यस्त core साधारणपणे दोन-एक प्रमाणात वाटून घेतात. मात्र idle box वर दोन्हीपैकी कोणत्याही container वर मर्यादा लागू होत नाही. सेवांचे महत्त्वानुसार क्रम ठरवण्यासाठी shares वापरा. वास्तविक कमाल मर्यादा आवश्यक असल्यास cpus वापरा. उदाहरणार्थ, nightly transcode job मुळे web server च्या कार्यक्षमतेवर परिणाम होऊ नये यासाठी ते वापरा.
FAQ
Docker Swarm शिवाय deploy.resources.limits काम करते का?
होय. Docker Compose मधील docker compose plugin (आता त्याची v5 line उपलब्ध आहे) एकाच host वर docker compose up चालवताना deploy.resources.limits आणि deploy.resources.reservations लागू करतो. docker inspect --format '{{.HostConfig.Memory}}' <container> वापरून याची खात्री करा. हा command मर्यादा bytes मध्ये दाखवतो आणि कोणतीही मर्यादा लागू नसेल तर 0 दाखवतो. Swarm लक्षात घेऊन deploy मध्ये लिहिलेल्या keys या cluster साठीच्या keys आहेत. त्यांमध्ये placement, update_config आणि endpoint_mode यांचा समावेश होतो.
Docker Compose मध्ये exit code 137 चा अर्थ काय?
याचा जवळजवळ नेहमी अर्थ असा होतो की मुख्य process ला SIGKILL मिळाला. 137 म्हणजे 128 अधिक signal 9. एखादा program जाणीवपूर्वक 137 सह exit होऊ शकतो, पण असे क्वचित होते. Kernel OOM killer हे सर्वसाधारण कारण आहे. मात्र एखादे app SIGTERM कडे दुर्लक्ष करते तेव्हा shutdown timeout मुळेही हाच code मिळतो. दोन्ही कारणांमध्ये फरक करण्यासाठी docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> चालवा. true 137 memory kill दर्शवतो, तर false 137 तसे दर्शवत नाही.
mem_limit किंवा deploy.resources.limits.memory यापैकी कोणते सेट करावे?
docker compose सोबत दोन्हीपैकी कोणतेही वापरता येते. नवीन file साठी deploy.resources.limits.memory हा सध्याचा Compose Specification प्रकार असून तो अधिक योग्य default आहे. तुमच्या file मध्ये आधीपासून जुन्या top-level keys वापरल्या असतील, तर mem_limit ठेवा. दोन्ही सेट केल्यास ते सुसंगत असणे Compose Specification ला आवश्यक आहे. त्यामुळे एकच पर्याय निवडा आणि docker inspect वापरून परिणामी configuration पडताळा.
माझा container पूर्ण memory limit इतकी memory वापरत असूनही kill का होत नाही?
docker stats मधील usage figure मध्ये अलीकडे वापरलेला page cache अजूनही मोजला जातो. Memory pressure आल्यावर kernel हा cache काढून टाकतो आणि OOM kill सुरू करत नाही. docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat चालवा आणि anon value वाचा. ही reclaim करता न येणारी working set आहे. कमी anon value च्या शेजारी उच्च file value दिसत असेल, तर container disk input आणि output करत आहे. तो लगेच बंद होण्याच्या स्थितीत नाही.
8GB VPS वर किती RAM unallocated ठेवावी?
Kernel, Docker daemon, sshd, journald आणि तुमच्या स्वतःच्या shell साठी सुमारे 1GB ठेवा. त्यानंतर सर्व container limits ची बेरीज उरलेल्या 7GB पेक्षा कमी ठेवा. प्रत्यक्ष load असताना प्रत्येक container मधील कमाल anon value एक दिवस monitor करा आणि त्यानंतरच आकडे निश्चित करा. एकूण रकमेचा वापरण्याची मर्यादा म्हणून विचार करा. ती पूर्ण भरण्याचे लक्ष्य ठेवू नका.