Docker Compose में memory limit कैसे सेट करें
Docker Compose में deploy.resources का उपयोग करके CPU और RAM की सीमा तय करें। यह गाइड आपको exit 137 एरर से बचने और एक greedy container को पूरे VPS को क्रैश करने से रोकने में मदद करेगी।
Docker Compose memory limit क्या करता है
Docker Compose memory limit एक hard cap है जिसे Linux kernel किसी container के cgroup (control group, वह kernel feature जो प्रक्रियाओं के समूह के लिए संसाधनों को मापता है) पर लगाता है। किसी service पर deploy.resources.limits.memory सेट करें और वह container आपके द्वारा लिखी गई संख्या से अधिक RAM का उपयोग कभी नहीं कर पाएगा। जब वह ऐसा करने का प्रयास करता है, तो kernel container के अंदर की किसी प्रक्रिया को समाप्त (kill) कर देता है, और container आमतौर पर code 137 के साथ exit हो जाता है।
यह VPS पर सबसे अधिक मायने रखता है, जहाँ RAM निश्चित होती है और उधार लेने के लिए कोई अतिरिक्त host memory नहीं होती। memory leak या खराब query वाला एक container 8GB के box पर मौजूद हर free page को भर देगा। इसके बाद kernel उस प्रक्रिया को मार देता है जिसे वह सबसे खराब मानता है, जो अक्सर समस्या पैदा करने वाले container के बजाय database या आपका SSH session होता है। Limits पूरे सर्वर के बंद होने की स्थिति को केवल एक service के restart होने तक सीमित कर देती हैं।
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mइसे लागू करें और पुष्टि करें कि limit live है:
docker compose up -d
docker stats --no-streamMEM USAGE / LIMIT column में 142MiB / 1GiB जैसा कुछ लिखा होना चाहिए। यदि limit column में host की पूरी RAM दिखाई दे रही है, तो setting लागू नहीं हुई है, और जब तक यह लागू नहीं होती, तब तक इस guide का बाकी हिस्सा आपकी मदद नहीं करेगा। यदि compose file आपके लिए नई है, तो the Docker Compose basics for a VPS उस file layout को कवर करती है जिस पर यह आधारित है।
deploy.resources.limits या mem_limit: कौन सा लागू होता है
एक ही विचार के लिए दो वर्तनी मौजूद हैं, और यही कारण है कि यह भ्रमित करने वाला है।
mem_limit, mem_reservation, memswap_limit, cpus और cpu_shares पुराने Compose फ़ाइल स्वरूपों से विरासत में मिली टॉप-लेवल सर्विस कुंजियाँ हैं। deploy.resources Swarm स्कीमा से आया था और अब यह Compose Specification का हिस्सा है, जो कि वह स्वरूप है जिसे docker compose आज पढ़ता है।
दोनों एक ही होस्ट पर काम करते हैं। Compose V2, जो कि docker compose प्लगइन है, deploy.resources.limits और deploy.resources.reservations को लागू करता है जब आप docker compose up चलाते हैं, भले ही वहां कोई Swarm क्लस्टर न हो। deploy ब्लॉक के केवल-Swarm वाले हिस्से अन्य कुंजियाँ हैं: mode, placement, update_config और endpoint_mode का अर्थ docker stack deploy के लिए कुछ होता है और इन्हें docker compose up द्वारा अनदेखा कर दिया जाता है। इसलिए यह आम सलाह कि "deploy के लिए Swarm की आवश्यकता होती है" resources सब-सेक्शन के लिए गलत है, और इसका पालन करने से आपकी सेवाओं पर कोई सीमा (limit) नहीं रहती है।
प्रति प्रोजेक्ट एक वर्तनी चुनें। एक ही सर्विस पर mem_limit: 512m और deploy.resources.limits.memory: 1g लिखना ऐसी फ़ाइल बनाता है जिसे कोई भी एक नज़र में नहीं पढ़ सकता। यह अनुमान लगाने के बजाय कि कौन सी संख्या प्रभावी रही, daemon से पूछें:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1मेमोरी मान बाइट्स में होते हैं, इसलिए 1g को 1073741824 के रूप में प्रिंट किया जाता है। CPU नैनो-CPUs में होता है, इसलिए 1.5 को 1500000000 के रूप में प्रिंट किया जाता है। किसी भी फ़ील्ड में 0 का अर्थ है कि कोई सीमा निर्धारित नहीं की गई थी। Docker द्वारा स्वीकार की जाने वाली सबसे छोटी मेमोरी सीमा 6m है, और इससे कम होने पर कंटेनर स्टार्ट होने से मना कर देता है।
जब कोई container अपनी सीमा (limit) तक पहुँच जाता है तो क्या होता है
Container की गति धीमी नहीं होती है। वह बंद हो जाता है।
जब कोई process page की मांग करती है और cgroup पहले से ही अपनी memory.max पर होता है, तो kernel सबसे पहले उस cgroup के भीतर जो कुछ भी संभव हो उसे reclaim करता है: clean page cache, और फिर वे pages जिन्हें swap किया जा सकता है। यदि reclaim करने से पर्याप्त memory खाली नहीं होती है, तो cgroup OOM (out of memory) killer container के भीतर एक process को चुनता है और उसे SIGKILL भेजता है। Container के PID 1 को kill करने से container समाप्त हो जाता है। 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 को live monitor करें:
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 है, जिसका अर्थ है कि machine की RAM समाप्त हो गई है। यह वह विफलता है जिसे रोकने के लिए limits बनाई गई हैं, इसलिए इसे देखना इस बात का संकेत है कि आपकी limits का योग बहुत अधिक है, या कुछ services पर कोई limit ही नहीं है।
restart: unless-stopped के साथ, एक OOM loop आसानी से छिप जाता है, क्योंकि service मरने के एक सेकंड बाद ही docker compose ps में up दिखाई देती है। Uptime column और restart count की जाँच करें, और limit को एक healthcheck जो app को unhealthy रिपोर्ट करे के साथ जोड़ें ताकि बार-बार मरने वाला container आपके देखे बिना भी दिखाई दे सके।
Reservation एक संकेत है, limit ही नियम है
reservations.memory (पुराना mem_reservation) एक soft floor है। Docker इसे एक soft limit के रूप में वर्णित करता है जो तब सक्रिय होती है जब daemon host पर contention या कम memory का पता लगाता है। यह container को इससे ऊपर जाने से कभी नहीं रोकता है, और यह इस बात की गारंटी भी नहीं देता है कि जब container memory मांगेगा तो वह उपलब्ध होगी। यह केवल kernel को उन containers से memory reclaim करने के लिए प्रेरित करता है जो अपनी reservation से ऊपर हैं।
इसलिए reservation अपने आप में किसी चीज़ की सुरक्षा नहीं करता है। इसका उपयोग उन services को चिह्नित करने के लिए करें जिन्हें आप दबाव की स्थिति में प्राथमिकता देना चाहते हैं, और सुरक्षा के लिए limit पर निर्भर रहें। Reservation को limit से कम रखें, अन्यथा container start नहीं होगा: Docker इस config को Minimum memory limit can not be less than memory reservation limit के साथ अस्वीकार कर देता है।
Swap accounting, ईमानदारी से
अधिकांश VPS images में कोई swap file नहीं होती है। swapon --show और free -h चलाएँ। यदि swap total शून्य है, तो नीचे दी गई swap-संबंधित कोई भी सेटिंग काम नहीं करेगी, और आपकी memory limit केवल RAM तक सीमित रहेगी।
memswap_limit swap की मात्रा नहीं है। यह memory और swap का कुल योग है। mem_limit: 1g और memswap_limit: 2g के साथ, container को 1GB RAM और 1GB swap मिलता है। दोनों मानों को बराबर सेट करने पर container को बिल्कुल भी swap नहीं मिलता है। mem_limit को सेट करने और memswap_limit को unset छोड़ने से container अपनी memory limit के बराबर swap का उपयोग कर सकता है।
Ubuntu 24.04 और Debian 13 डिफ़ॉल्ट रूप से cgroup v2 का उपयोग करते हैं, जहाँ swap एक अलग counter (memory.swap.max) है और यह बिना किसी अतिरिक्त सेटअप के काम करता है। पुराना संदेश Your kernel does not support swap limit capabilities उन cgroup v1 hosts से आता है जो बिना swapaccount=1 के बूट हुए हैं। उन पर memory limit लागू रहती है जबकि swap वाले हिस्से को अनदेखा कर दिया जाता है।
Swap आपको क्या लाभ देता है, इस बारे में ईमानदार रहें। यह OOM kill को धीमा बनाता है, न कि कम संभावित, क्योंकि एक leaking process RAM की तरह ही swap को भी भर देती है। इस बीच, shared VPS storage पर swap का उपयोग करने वाला container उस box पर चल रही अन्य सभी services को धीमा कर देता है। latency के प्रति संवेदनशील किसी भी चीज़ के लिए, बिना swap के एक सही limit सेट करना बेहतर है, क्योंकि यह अधिक तेज़ी से और पूर्वानुमानित तरीके से विफल (fail) होता है।
मेमोरी का उपयोग जितना दिखता है, उससे अधिक खराब क्यों नहीं है
MEM USAGE का आंकड़ा docker stats में पेज कैश को शामिल करता है, इसलिए जो कंटेनर बड़ी फाइलें पढ़ता है, उसका उपयोग अपनी सीमा तक पहुँच जाता है और वहीं बना रहता है। यह सामान्य है और यह कोई लीक नहीं है, क्योंकि OOM killer के सक्रिय होने से पहले क्लीन कैश को वापस ले लिया जाता है। Jellyfin मीडिया सर्वर जैसी सर्विस इसी कारण से हमेशा अपनी सीमा के करीब दिखाई देगी।
कंटेनर के अंदर से संख्या को कैश और वास्तविक वर्किंग सेट में विभाजित करें:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon एनोनिमस मेमोरी है, वह वर्किंग सेट जिसे हटाया नहीं जा सकता। file पेज कैश है, जिसे हटाया जा सकता है। अपनी सीमा को anon और थोड़े अतिरिक्त मार्जिन के आधार पर निर्धारित करें, न कि कुल योग के आधार पर। memory.events फाइल इस बहस को पूरी तरह समाप्त कर देती है: यदि oom_kill काउंटर शून्य से ऊपर है, तो इसका मतलब है कि कर्नल ने कंटेनर शुरू होने के बाद से कुछ न कुछ किल किया है, और यदि max काउंटर बढ़ रहा है, तो इसका मतलब है कि कंटेनर को अभी उसकी सीमा पर रोका जा रहा है। दोनों कमांड्स के लिए इमेज के अंदर शेल और coreutils की आवश्यकता होती है, इसलिए वे distroless या scratch इमेज पर काम नहीं करेंगे।
8GB VPS पर साइजिंग सीमाएं
होस्ट से शुरुआत करें, न कि ऐप्स से। 8GB VPS पर, kernel, Docker daemon, sshd, journald और अपने login shell के लिए लगभग 1GB जगह छोड़ दें। इससे लगभग 7GB मेमोरी उपलब्ध रहती है, और सभी containers की कुल सीमा इससे कम होनी चाहिए। Overcommitting तब तक काम करती है जब तक कि दो सेवाएं एक साथ peak पर न पहुँच जाएं।
8GB बॉक्स पर एक व्यावहारिक विभाजन:
- Reverse proxy: 128m की सीमा। यह एक छोटी प्रक्रिया है, और इतनी सख्त सीमा एक खराब config reload को तुरंत पकड़ लेती है।
- PostgreSQL: 2g की सीमा, जिसमें database config में
shared_buffersको लगभग 512MB पर सेट किया गया हो। - Application container: 1g की सीमा।
- Background worker: 512m की सीमा।
- Media या file service: 2g की सीमा, जिसका अधिकांश हिस्सा page cache होगा।
इन संख्याओं को अपने stack में कॉपी न करें। सेवाओं को एक दिन के लिए वास्तविक load के तहत चलाएं, docker stats पर नज़र रखें, प्रति container अधिकतम anon मान लें और उसमें लगभग आधा हिस्सा headroom के रूप में जोड़ें। बहुत सख्त सीमा तय करना बिना सीमा के होने से भी बदतर है, क्योंकि यह सामान्य traffic spike के दौरान एक स्वस्थ सेवा को बंद कर देती है।
एक समस्या पर विशेष ध्यान दें। अधिकांश runtimes को सीमा का पता नहीं चलता जब तक आप उन्हें इसके बारे में न बताएं। PostgreSQL खुशी-खुशी shared_buffers और work_mem को अपनी container सीमा से ऊपर ले जाएगा और बंद हो जाएगा। JVM (Java virtual machine) को cgroup सीमा से अपना heap साइज करने के लिए -XX:MaxRAMPercentage=75 की आवश्यकता होती है, न कि host RAM से। Node.js को मेगाबाइट में --max-old-space-size की आवश्यकता होती है, जिसे container सीमा से नीचे सेट किया जाना चाहिए, अन्यथा इसका garbage collector heap को तब तक बढ़ने देता है जब तक kernel हस्तक्षेप न करे। Ollama के साथ भी यही स्थिति है, बस उसका knob अलग है, क्योंकि num_ctx बढ़ाने से KV cache सैकड़ों मेगाबाइट बढ़ जाता है और container एक लंबे prompt के बीच में ही बंद हो जाता है। Cgroup समझौता नहीं करता है। यह सीधे बंद कर देता है।
CPU limits का व्यवहार पूरी तरह अलग होता है
cpus: "1.5" का अर्थ है एक core का 150%, जिसे CFS (completely fair scheduler) quota के रूप में लागू किया जाता है। container को हर 100ms की अवधि में 150ms का CPU समय मिलता है, जो उसके सभी threads में साझा होता है। जब यह समय समाप्त हो जाता है, तो kernel उसे अगली अवधि तक प्रतीक्षा करने के लिए मजबूर करता है।
यह एक महत्वपूर्ण अंतर है। यदि कोई container अपनी memory limit से ऊपर जाता है, तो उसे kill कर दिया जाता है। यदि कोई container अपनी CPU limit से ऊपर जाता है, तो उसे throttled कर दिया जाता है और वह धीमी गति से काम करना जारी रखता है। इसलिए CPU limit को आक्रामक रूप से सेट करना सुरक्षित है, जबकि memory limit के लिए अतिरिक्त headroom की आवश्यकता होती है।
cpu_shares एक अलग tool है: यह एक सापेक्ष भार (relative weight) है जो केवल तभी मायने रखता है जब CPU पूरी तरह से व्यस्त (saturated) हों। 1024 और 512 के shares वाले दो containers एक व्यस्त core को लगभग दो-एक के अनुपात में विभाजित करते हैं, और idle box पर इनमें से किसी को भी प्रतिबंधित नहीं किया जाता है। services को उनके महत्व के आधार पर रैंक करने के लिए shares का उपयोग करें, और जब आपको वास्तविक सीमा (ceiling) की आवश्यकता हो तो cpus का उपयोग करें, उदाहरण के लिए किसी nightly transcode job को अपने 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> से करें, जो limit को bytes में दिखाता है और यदि कोई limit लागू नहीं की गई है, तो 0 प्रिंट करता है। deploy के भीतर वे keys जिन्हें वास्तव में Swarm की आवश्यकता होती है, वे 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 के लिए बेहतर विकल्प है। यदि आपकी file के बाकी हिस्से पहले से ही पुराने top-level keys का उपयोग कर रहे हैं, तो mem_limit को बनाए रखें। एक ही service पर दोनों को सेट करने से file को पढ़ना कठिन हो जाता है, इसलिए किसी एक को चुनें और docker inspect के साथ परिणाम की पुष्टि करें।
मेरा container अपनी पूरी memory limit पर क्यों है, जबकि वह kill नहीं हो रहा है?
docker stats में उपयोग का आंकड़ा page cache को शामिल करता है, जिसे kernel OOM kill ट्रिगर करने के बजाय दबाव पड़ने पर हटा देता है। docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat चलाएँ और anon मान को पढ़ें, जो कि working set है जिसे reclaim नहीं किया जा सकता। कम anon मान के साथ उच्च file मान का अर्थ है कि container disk input और output कर रहा है, न कि यह कि वह बंद होने वाला है।
8GB RAM वाले VPS पर मुझे कितनी RAM unallocated छोड़नी चाहिए?
kernel, Docker daemon, sshd, journald और अपने shell के लिए लगभग 1GB छोड़ दें, फिर सभी container limits का योग शेष 7GB के भीतर रखें। commit करने से पहले एक दिन तक वास्तविक load के तहत प्रति container अधिकतम anon मान पर नज़र रखें, और कुल योग को भरने के लक्ष्य के बजाय एक बजट के रूप में मानें।