Docker Compose मध्ये OOM थांबवण्यासाठी मेमरी मर्यादा
VPS वरील एक container संपूर्ण server बंद पाडू नये म्हणून Docker Compose मध्ये deploy.resources आणि mem_limit वापरा; OOM नंतर exit 137, swap व योग्य sizing समजा.
Docker Compose मेमरी मर्यादा काय करते
Docker Compose मेमरी मर्यादा म्हणजे Linux kernel एका container च्या cgroup वर लावलेली कठोर कमाल मर्यादा. cgroup म्हणजे प्रक्रियांच्या संचासाठी संसाधनांचे मोजमाप करणारे kernel वैशिष्ट्य. सेवेसाठी deploy.resources.limits.memory सेट केल्यावर तो container तुम्ही लिहिलेल्या संख्येपेक्षा जास्त मेमरी वापरू शकत नाही. त्याने मर्यादा ओलांडण्याचा प्रयत्न केल्यास kernel container मधील एखादी प्रक्रिया बंद करते. त्यानंतर container सहसा 137 कोडसह बंद होतो.
VPS वर याचे महत्त्व सर्वाधिक असते. तेथे RAM निश्चित असते आणि वापरण्यासाठी अतिरिक्त host मेमरी उपलब्ध नसते. मेमरी लीक असलेला किंवा चुकीची query करणारा एक container 8GB मशीनवरील प्रत्येक मोकळे page वापरू शकतो. त्यानंतर kernel ला सर्वांत गंभीर वाटणारी प्रक्रिया बंद करतो. ही प्रक्रिया समस्या निर्माण करणाऱ्या container ऐवजी database किंवा तुमचे SSH session असण्याची शक्यता असते. मर्यादांमुळे संपूर्ण server बंद पडण्याऐवजी केवळ एक service restart होते.
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mमर्यादा लागू करा आणि ती कार्यरत असल्याची खात्री करा:
docker compose up -d
docker stats --no-streamMEM USAGE / LIMIT column मध्ये 142MiB / 1GiB सारखे मूल्य दिसले पाहिजे. मर्यादा 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 फाइल स्वरूपांमधून वारशाने आलेल्या, सेवेच्या उच्च-स्तरीय keys आहेत. deploy.resources हे Swarm schema मधून आले असून आता Compose Specification चा भाग आहे. आज docker compose हेच स्वरूप वाचते.
दोन्ही एका host वर कार्य करतात. Swarm cluster कुठेही नसतानाही, docker compose plugin असलेले Compose V2, docker compose up चालविल्यावर deploy.resources.limits आणि deploy.resources.reservations लागू करते. deploy block मधील Swarm-पुरते भाग म्हणजे इतर keys: mode, placement, update_config आणि endpoint_mode यांचा अर्थ docker stack deploy साठी असतो आणि docker compose up त्यांच्याकडे दुर्लक्ष करते. त्यामुळे "resources subsection साठी deploy ला Swarm आवश्यक आहे" हा सर्वसाधारण सल्ला चुकीचा आहे. तो पाळल्यास तुमच्या services वर कोणतीही मर्यादा लागू होत नाही.
प्रत्येक project साठी एकच लेखनप्रकार निवडा. एकाच service वर mem_limit: 512m आणि deploy.resources.limits.memory: 1g लिहिल्यास, फाइल पटकन समजणे कठीण होते. कोणती value लागू झाली याचा अंदाज घेण्याऐवजी daemon ला विचारा:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Memory values bytes मध्ये असतात. त्यामुळे 1g हे 1073741824 म्हणून दाखवते. CPU चे एकक nano CPUs आहे. त्यामुळे 1.5 हे 1500000000 म्हणून दाखवते. कोणत्याही field मध्ये 0 असल्यास कोणतीही मर्यादा सेट केलेली नाही. Docker स्वीकारत असलेली सर्वात कमी memory limit 6m आहे. त्यापेक्षा कमी मूल्य दिल्यास container सुरू होण्यास नकार देतो.
कंटेनर मर्यादेपर्यंत पोहोचल्यावर काय होते
कंटेनरचा वेग कमी होत नाही. तो बंद पडतो.
जेव्हा एखादी प्रक्रिया page मागते आणि cgroup आधीच त्याच्या memory.max वर असते, तेव्हा kernel प्रथम त्या cgroup मध्ये शक्य तितकी साधने पुन्हा उपलब्ध करतो: स्वच्छ page cache, त्यानंतर swap करता येणारी pages. Reclaim करून पुरेशी जागा उपलब्ध न झाल्यास, 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 पाठवले आहे. याचे नेहमीचे कारण म्हणजे docker compose stop ची दहा सेकंदांची grace period संपणे, कारण app ने SIGTERM कडे दुर्लक्ष केले. हा फरक समजल्यास अनेक तास वाचतात, कारण या दोन समस्यांचा परस्पर काहीही संबंध नाही.
ही घटना आणखी दोन ठिकाणी नोंदवली जाते. daemon चे थेट निरीक्षण करा:
docker events --filter event=oomत्यानंतर kernel log वाचा. Restart नंतरही टिकून राहणारी नोंद हीच असते:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'cgroup kill झाल्यास Memory cgroup out of memory: Killed process 24713 (node) ने सुरू होणारी ओळ दिसते. Memory cgroup prefix नसलेली ओळ host OOM दर्शवते. याचा अर्थ मशीनमधील RAM संपली आहे. मर्यादा याच अपयशाला रोखण्यासाठी असतात. त्यामुळे अशी ओळ दिसल्यास तुमच्या मर्यादांची एकूण बेरीज खूप जास्त आहे किंवा काही services ना कोणतीही मर्यादा दिलेली नाही.
restart: unless-stopped वापरल्यास OOM loop सहज लपतो, कारण सेवा बंद पडल्यानंतर एका सेकंदाने docker compose ps मध्ये पुन्हा सुरू झालेली दिसते. uptime column आणि restart count तपासा. तसेच मर्यादेसोबत app ला unhealthy म्हणून नोंदवणारा healthcheck वापरा, जेणेकरून सतत बंद पडणारा कंटेनर तुमच्या निरीक्षणाशिवायही दिसून येईल.
आरक्षण हा केवळ संकेत आहे; मर्यादाच नियम आहे
reservations.memory (जुने mem_reservation) ही सौम्य किमान मर्यादा आहे. Docker तिचे वर्णन soft limit म्हणून करते. डेमनला होस्टवर संसाधनांसाठी स्पर्धा किंवा कमी मेमरी आढळल्यावर ती सक्रिय होते. कंटेनरने ही मर्यादा ओलांडू नये, असे ती कधीही थांबवत नाही. तसेच कंटेनरने मेमरी मागितल्यावर ती उपलब्ध असेल, याची हमीही देत नाही. आरक्षणापेक्षा जास्त मेमरी वापरणाऱ्या कंटेनरमधून मेमरी आधी परत मिळवण्याकडे ते केवळ kernel ला प्रवृत्त करते.
म्हणून आरक्षण स्वतःहून कशाचेही संरक्षण करत नाही. ताणाच्या परिस्थितीत ज्या सेवेला प्राधान्याने हाताळायचे आहे, ती सेवा दर्शवण्यासाठी त्याचा वापर करा आणि सुरक्षिततेसाठी मर्यादेवर अवलंबून रहा. आरक्षण मर्यादेपेक्षा कमी ठेवा. अन्यथा कंटेनर सुरू होणार नाही: Docker Minimum memory limit can not be less than memory reservation limit सह कॉन्फिगरेशन नाकारते.
स्वॅपचे अचूक लेखांकन
बहुतेक VPS प्रतिमांमध्ये स्वॅप फाइल मुळीच नसते. swapon --show आणि free -h चालवा. स्वॅपची एकूण मात्रा शून्य असल्यास, खालील स्वॅप-संबंधित प्रत्येक सेटिंगचा काहीही परिणाम होत नाही आणि तुमची मेमरी मर्यादा ही केवळ RAM मर्यादा असते.
memswap_limit ही स्वॅपची मात्रा नाही. ती मेमरी आणि स्वॅप यांची एकूण मात्रा आहे. mem_limit: 1g आणि memswap_limit: 2g वापरल्यास, container ला 1GB RAM आणि 1GB स्वॅप मिळतो. दोन्ही मूल्ये समान ठेवल्यास container ला स्वॅप मिळत नाही. mem_limit सेट करून memswap_limit unset ठेवल्यास, container पुन्हा त्याच्या मेमरी मर्यादेइतका स्वॅप वापरू शकतो.
Ubuntu 24.04 आणि Debian 13 मध्ये, डिफॉल्टनुसार cgroup v2 वापरले जाते. त्यामध्ये स्वॅपसाठी स्वतंत्र काउंटर (memory.swap.max) असतो आणि यासाठी अतिरिक्त सेटअप आवश्यक नसतो. जुना संदेश Your kernel does not support swap limit capabilities हा swapaccount=1 शिवाय बूट केलेल्या cgroup v1 hosts कडून येतो. अशा hosts वर मेमरी मर्यादा लागू राहते, परंतु स्वॅपचा भाग दुर्लक्षित केला जातो.
स्वॅपमुळे नेमका काय फायदा होतो, याबाबत स्पष्ट रहा. OOM kill होण्याची शक्यता कमी होत नाही; तो उशिरा होतो. कारण मेमरी गळती करणारी प्रक्रिया RAM भरते तितक्याच सहजतेने स्वॅपही भरते. दरम्यान, shared VPS storage वर स्वॅपमध्ये सतत हालचाल करणारा container त्या host वरील इतर प्रत्येक सेवेचा वेग कमी करतो. Latency संवेदनशील कोणत्याही सेवेसाठी, स्वॅपशिवाय योग्य मर्यादा ठेवल्यास बिघाड अधिक लवकर आणि अधिक अंदाजे होतो.
स्मृतीचा वापर प्रत्यक्षापेक्षा जास्त का दिसतो
docker stats मधील MEM USAGE आकृतीत page cache समाविष्ट असतो. त्यामुळे मोठ्या फाइल्स वाचणारा container त्याच्या मर्यादेपर्यंत वाढतो आणि तिथेच राहतो. हे सामान्य आहे. हा memory leak नाही, कारण OOM killer ला कधीही कॉल करण्यापूर्वी clean cache पुन्हा मिळवला जातो. स्वतः होस्ट केलेला 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 आहे जो काढून टाकता येत नाही. file हा page cache आहे. तो काढून टाकता येतो. तुमची मर्यादा एकूण memory वर नव्हे, तर anon अधिक योग्य राखीव क्षमतेवर आधारित ठेवा. memory.events फाइलमुळे हे निश्चितपणे स्पष्ट होते: oom_kill counter शून्यापेक्षा जास्त असल्यास, container सुरू झाल्यापासून kernel ने या container मधील काहीतरी बंद केले आहे. max counter वाढत असल्यास, container सध्या त्याच्या कमाल मर्यादेवर रोखला जात आहे. दोन्ही commands साठी image मध्ये shell आणि coreutils आवश्यक आहेत. त्यामुळे distroless किंवा scratch image वर त्या अयशस्वी होतात.
8GB VPS वरील आकारमान मर्यादा
अॅप्लिकेशन्सपासून नव्हे, तर होस्टपासून सुरुवात करा. 8GB VPS वर kernel, Docker daemon, sshd, journald आणि तुमच्या login shell साठी सुमारे 1GB राखून ठेवा. त्यामुळे वापरासाठी अंदाजे 7GB उरतात. सर्व container limits ची बेरीज या मर्यादेपेक्षा कमी असावी. दोन सेवा एकाच वेळी कमाल वापरापर्यंत पोहोचेपर्यंत overcommitting चालते.
8GB मशीनसाठी एक व्यवहार्य विभागणी:
- Reverse proxy: 128m limit. ही लहान process असते. त्यामुळे एवढी कडक मर्यादा runaway config reload त्वरित पकडते.
- PostgreSQL: 2g limit आणि database config मध्ये
shared_buffersसुमारे 512MB वर सेट करा. - Application container: 1g limit.
- Background worker: 512m limit.
- Media किंवा file service: 2g limit. यातील बहुतांश भाग page cache साठी वापरला जाईल.
ही संख्या तुमच्या स्वतःच्या stack मध्ये तशीच वापरू नका. सेवा प्रत्यक्ष भाराखाली एक दिवस चालवा. docker stats वर लक्ष ठेवा. प्रत्येक container साठी कमाल anon मूल्य नोंदवा आणि त्यावर सुमारे निम्मी अतिरिक्त headroom जोडा. खूप कमी ठेवलेली मर्यादा मर्यादा नसण्यापेक्षा अधिक धोकादायक असते, कारण सामान्य network traffic वाढीदरम्यान ती निरोगी सेवेला बंद करू शकते.
एका जोखमीसाठी स्वतंत्र सूचना आवश्यक आहे. मर्यादेबद्दल runtime ला सांगितले नाही, तर ती बहुतांश runtimes ना दिसत नाही. PostgreSQL shared_buffers आणि work_mem यांचा आकार container limit पेक्षा जास्त ठेवू शकते आणि process बंद होऊ शकते. JVM (Java virtual machine) ला host RAM ऐवजी cgroup limit वरून heap चा आकार ठरवण्यासाठी -XX:MaxRAMPercentage=75 आवश्यक आहे. Node.js मध्ये --max-old-space-size megabytes मध्ये सेट करा आणि ते container limit पेक्षा कमी ठेवा. अन्यथा त्याचा garbage collector heap वाढवत राहतो, जोपर्यंत kernel हस्तक्षेप करत नाही. cgroup वाटाघाटी करत नाही. ते process बंद करते.
CPU मर्यादा पूर्णपणे वेगळ्या प्रकारे कार्य करतात
cpus: "1.5" म्हणजे एका core च्या 150% क्षमतेइतका CPU वापर. ही मर्यादा CFS (completely fair scheduler) quota म्हणून लागू केली जाते. प्रत्येक 100ms कालावधीत container ला 150ms CPU वेळ मिळतो. हा वेळ त्याच्या सर्व threads मध्ये सामायिक केला जातो. हा वेळ संपल्यावर kernel पुढील कालावधीपर्यंत त्याला प्रतीक्षा करण्यास भाग पाडतो.
हा महत्त्वाचा फरक आहे. Container ने memory मर्यादा ओलांडल्यास तो बंद केला जातो. Container ने CPU मर्यादा ओलांडल्यास त्याचा CPU वापर नियंत्रित केला जातो आणि तो कमी वेगाने कार्यरत राहतो. त्यामुळे CPU मर्यादा तुलनेने आक्रमकपणे निश्चित करणे सुरक्षित असते. मात्र memory मर्यादेसाठी अतिरिक्त मोकळी क्षमता ठेवणे आवश्यक असते.
cpu_shares हे वेगळे साधन आहे. हे सापेक्ष weight आहे आणि CPU प्रत्यक्षात पूर्ण क्षमतेने वापरले जात असतानाच त्याचा परिणाम होतो. 1024 आणि 512 shares असलेले दोन containers व्यस्त core चे काम साधारणपणे दोनास एक या प्रमाणात वाटून घेतात. निष्क्रिय system वर मात्र त्यांपैकी कोणावरही मर्यादा लागू होत नाही. सेवांचे महत्त्वानुसार क्रम ठरवण्यासाठी shares वापरा. प्रत्यक्ष कमाल मर्यादा आवश्यक असल्यास cpus वापरा. उदाहरणार्थ, रात्री चालणारे transcode काम तुमच्या web server च्या कार्यक्षमतेवर परिणाम करू नये यासाठी ते वापरा.
FAQ
deploy.resources.limits हे Docker Swarm शिवाय कार्य करते का?
होय. एका host वर docker compose up चालवल्यावर Compose V2 deploy.resources.limits आणि deploy.resources.reservations लागू करते. docker inspect --format '{{.HostConfig.Memory}}' <container> वापरून याची पुष्टी करा. हे मर्यादा bytes मध्ये दाखवते आणि कोणतीही मर्यादा लागू न झाल्यास 0 दाखवते. deploy मधील Swarm आवश्यक असलेल्या keys म्हणजे mode, placement, update_config आणि endpoint_mode.
Docker Compose मध्ये exit code 137 चा अर्थ काय?
याचा अर्थ मुख्य process ला SIGKILL प्राप्त झाले आहे, कारण 137 म्हणजे 128 अधिक signal 9. 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 सोबत दोन्हीपैकी कोणतेही कार्य करते. deploy.resources.limits.memory हा सध्याच्या Compose Specification मधील प्रकार आहे आणि नवीन file साठी अधिक योग्य default आहे. तुमच्या file मधील उर्वरित भागात जुने top-level keys आधीच वापरले असल्यास mem_limit ठेवा. एका service वर दोन्ही सेट केल्याने file वाचणे अधिक कठीण होते. त्यामुळे एकच निवडा आणि docker inspect वापरून परिणामाची पडताळणी करा.
माझा container पूर्ण memory limit इतकी memory वापरत असूनही kill का होत नाही?
docker stats मधील usage figure मध्ये page cache समाविष्ट असतो. OOM kill सुरू करण्याऐवजी kernel दबावाखाली तो cache काढून टाकतो. docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat चालवा आणि anon value वाचा. ही value reclaim करता न येणारा working set दर्शवते. कमी anon value च्या शेजारी जास्त file value असल्यास container disk input आणि output करत आहे. तो बंद होण्याच्या स्थितीत नाही.
8GB VPS वर किती RAM वाटप न करता ठेवावी?
Kernel, Docker daemon, sshd, journald आणि तुमच्या स्वतःच्या shell साठी सुमारे 1GB ठेवा. त्यानंतर सर्व container limits ची बेरीज उरलेल्या 7GB पेक्षा कमी ठेवा. अंतिम आकडे ठरवण्यापूर्वी प्रत्यक्ष load अंतर्गत एका दिवसासाठी प्रत्येक container मधील peak anon value वर लक्ष ठेवा. एकूण मर्यादेला भरून काढायचे लक्ष्य न मानता budget म्हणून वापरा.