Docker Compose memory limit తో OOM సమస్యను ఆపడం
ఒక container VPS మొత్తం RAMను తినకుండా Docker Composeలో deploy.resources లేదా mem_limit సెట్ చేయండి. OOM తర్వాత కనిపించే exit 137, swap, సరైన పరిమాణాన్ని తెలుసుకోండి.
Docker Compose memory limit ఏమి చేస్తుంది
Docker Compose memory limit అనేది ఒక container యొక్క cgroup పై Linux kernel విధించే గరిష్ఠ పరిమితి. cgroup అనేది ఒక సమూహం processes కోసం వనరులను కొలిచే kernel feature. ఒక service పై deploy.resources.limits.memory సెట్ చేస్తే, ఆ container మీరు పేర్కొన్న పరిమాణం కంటే ఎక్కువ memory ఎప్పటికీ ఉపయోగించదు. అది ఆ పరిమితిని దాటడానికి ప్రయత్నించినప్పుడు, kernel container లోని ఒక process ను terminate చేస్తుంది. సాధారణంగా container code 137 తో exit అవుతుంది.
ఇది ముఖ్యంగా VPS లో కీలకం. అక్కడ RAM స్థిరంగా ఉంటుంది. ఉపయోగించడానికి అదనపు host memory ఉండదు. memory leak ఉన్న లేదా తప్పు query నడిపే ఒక container 8GB server లోని మొత్తం ఖాళీ pages ను వినియోగించవచ్చు. అప్పుడు kernel సమస్యకు అత్యంత కారణమైనదిగా భావించిన process ను terminate చేస్తుంది. అది సమస్య కలిగించిన container కాకుండా database లేదా మీ SSH session కావచ్చు. Limits అమలు చేస్తే మొత్తం server నిలిచిపోవడానికి బదులుగా ఒక service మాత్రమే 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 వర్తించలేదు. అది వర్తించే వరకు ఈ guide లోని మిగతా దశలు సహాయపడవు. 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 లో భాగంగా ఉంది. ఈ format ను 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 వీటిని పట్టించుకోదు. కాబట్టి "deploy needs Swarm" అనే సాధారణ సలహా resources subsection విషయంలో తప్పు. దాన్ని అనుసరిస్తే మీ services కు ఎలాంటి limit ఉండదు.
ప్రతి project కు ఒకే spelling ఎంచుకోండి. ఒకే service పై mem_limit: 512m మరియు deploy.resources.limits.memory: 1g రెండింటినీ రాస్తే, file ను ఒక్క చూపులో అర్థం చేసుకోవడం కష్టం అవుతుంది. ఏ విలువ వర్తించిందో ఊహించకుండా daemon ను అడగండి:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Memory విలువలు bytes లో ఉంటాయి. అందువల్ల 1g, 1073741824 గా చూపిస్తుంది. CPU విలువలు nano CPUs లో ఉంటాయి. అందువల్ల 1.5, 1500000000 గా చూపిస్తుంది. ఏ field లోనైనా 0 కనిపిస్తే limit సెట్ కాలేదని అర్థం. Docker అంగీకరించే కనిష్ఠ memory limit 6m. దానికంటే తక్కువ limit ఇస్తే container ప్రారంభం కాదు.
కంటైనర్ పరిమితిని చేరుకున్నప్పుడు ఏమి జరుగుతుంది
కంటైనర్ నెమ్మదించదు. అది ఆగిపోతుంది.
ఒక ప్రక్రియ ఒక page ను కోరినప్పుడు, cgroup ఇప్పటికే దాని memory.max వద్ద ఉంటే, kernel ముందుగా ఆ cgroup లో సాధ్యమైన వాటిని reclaim చేస్తుంది: clean page cache ను, తరువాత swap చేయగల pages ను. Reclaim ద్వారా తగినంత memory విడుదల కాకపోతే, cgroup OOM (out of memory) killer కంటైనర్లోని ఒక ప్రక్రియను ఎంచుకుని దానికి SIGKILL పంపుతుంది. కంటైనర్కు చెందిన PID 1 ను terminate చేస్తే కంటైనర్ ముగుస్తుంది. Exit code 137 అంటే కేవలం 128 plus signal 9. అందువల్ల 137 అనేది ఏదైనా SIGKILL కు గుర్తు మాత్రమే; అది స్వయంగా OOM జరిగిందని నిరూపించదు.
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 అంటే OOM kill. false 137 అంటే మరేదో SIGKILL పంపింది. సాధారణ కారణం, యాప్ SIGTERM ను పట్టించుకోకపోవడంతో docker compose stop దాని పది సెకన్ల grace period ను చేరుకోవడం. ఈ తేడా గంటల సమయాన్ని ఆదా చేస్తుంది, ఎందుకంటే ఈ రెండు సమస్యలకు పరస్పర సంబంధం లేదు.
ఈ సంఘటనను మరో రెండు ప్రదేశాలు నమోదు చేస్తాయి. daemon ను ప్రత్యక్షంగా monitor చేయండి:
docker events --filter event=oomతర్వాత kernel log ను చదవండి. restart తర్వాత కూడా మిగిలేది ఇదే record:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'cgroup kill జరిగినప్పుడు Memory cgroup out of memory: Killed process 24713 (node) తో ప్రారంభమయ్యే line కనిపిస్తుంది. Memory cgroup prefix లేని line host OOM ను సూచిస్తుంది. అంటే machine లోని RAM మొత్తం అయిపోయిందని అర్థం. Limits నివారించాల్సింది ఇదే failure. కాబట్టి ఈ line కనిపిస్తే, మీ limits మొత్తం చాలా ఎక్కువగా ఉన్నాయని లేదా కొన్ని services కు limit ఏదీ లేదని అర్థం.
restart: unless-stopped తో OOM loop సులభంగా దాగిపోతుంది, ఎందుకంటే service చనిపోయిన ఒక సెకను తర్వాత docker compose ps లో అది మళ్లీ up గా కనిపిస్తుంది. uptime column మరియు restart count ను తనిఖీ చేయండి. అలాగే యాప్ను unhealthy గా నివేదించే healthcheck తో limit ను కలిపి ఉపయోగించండి. అప్పుడు నిరంతరం చనిపోతున్న కంటైనర్ను మీరు స్వయంగా గమనించకపోయినా గుర్తించవచ్చు.
Reservation ఒక సూచన మాత్రమే; limit ఒక నియమం
reservations.memory (పాత mem_reservation) ఒక soft floor. Hostలో contention లేదా తక్కువ memory ఉన్నట్లు daemon గుర్తించినప్పుడు ఇది సక్రియమయ్యే soft limit అని Docker వివరిస్తుంది. దీని కంటే container ఎక్కువ memory ఉపయోగించడాన్ని ఇది ఎప్పుడూ ఆపదు. Container memory కోరిన సమయంలో memory తప్పనిసరిగా అందుబాటులో ఉంటుందని కూడా ఇది హామీ ఇవ్వదు. Reservation కంటే ఎక్కువ memory ఉపయోగిస్తున్న containers నుంచి ముందుగా memory reclaim చేయాలని ఇది kernelకు మాత్రమే ప్రాధాన్యతను సూచిస్తుంది.
అందువల్ల reservation తనంతట తానే ఏదీ రక్షించదు. ఒత్తిడి ఉన్నప్పుడు అనుకూలంగా నిర్వహించాలనుకునే serviceను గుర్తించడానికి దీన్ని ఉపయోగించండి. భద్రత కోసం limitపై ఆధారపడండి. Reservationను limit కంటే తక్కువగా ఉంచండి. లేకపోతే container ప్రారంభం కాదు. Docker configను Minimum memory limit can not be less than memory reservation limitతో తిరస్కరిస్తుంది.
Swap accounting, నిజాయితీగా
చాలా VPS images లో ఎలాంటి swap file కూడా ఉండదు. swapon --show మరియు free -h అమలు చేయండి. మొత్తం swap విలువ zero అయితే, దిగువన ఉన్న swap సంబంధిత ప్రతి setting పనిచేయదు. మీ 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) గా ఉంటుంది. అదనపు setup లేకుండానే ఇది పనిచేస్తుంది. Your kernel does not support swap limit capabilities అనే పాత message, swapaccount=1 లేకుండా boot చేసిన cgroup v1 hosts నుంచి వస్తుంది. అలాంటి hosts లో memory limit వర్తిస్తుంది. కానీ swap భాగం పరిగణనలోకి తీసుకోబడదు.
swap వల్ల నిజంగా లభించేది ఏమిటో స్పష్టంగా అర్థం చేసుకోండి. ఇది OOM kill ను తక్కువగా సంభవించేలా చేయదు; అది మరింత ఆలస్యంగా జరిగేలా మాత్రమే చేస్తుంది. ఎందుకంటే memory leak ఉన్న process RAM ను నింపినంత సులభంగా swap ను కూడా నింపుతుంది. అదే సమయంలో, shared VPS storage పై swap ను అధికంగా ఉపయోగిస్తున్న container ఆ machine లోని ప్రతి ఇతర సేవను నెమ్మదిస్తుంది. latency కు సున్నితమైన పనుల కోసం, swap లేకుండా సరైన limit పెట్టడం వల్ల వైఫల్యం వేగంగా మరియు మరింత ఊహించదగిన విధంగా జరుగుతుంది.
మెమరీ వినియోగం వాస్తవం కంటే ఎక్కువగా ఎందుకు కనిపిస్తుంది
docker stats లోని MEM USAGE గణాంకంలో page cache కూడా ఉంటుంది. అందువల్ల పెద్ద ఫైళ్లను చదివే container దాని పరిమితి వరకు పెరిగి అక్కడే ఉంటుంది. ఇది సాధారణమే. ఇది leak కాదు, ఎందుకంటే OOM killer ను పిలిచేలోపే clean cache ను తిరిగి వినియోగించుకోవచ్చు. self-hosted Jellyfin media server వంటి సేవ ఈ కారణంగానే ఎల్లప్పుడూ తన పరిమితికి దగ్గరగా ఉన్నట్లు కనిపిస్తుంది.
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 అనేది drop చేయలేని anonymous memory, అంటే working set. file అనేది తిరిగి వినియోగించుకోగల page cache. మీ limit ను మొత్తం విలువ ఆధారంగా కాకుండా anon తో పాటు కొంత margin కలిపి నిర్ణయించండి. memory.events ఫైల్ దీనిని స్పష్టంగా నిర్ధారిస్తుంది: oom_kill counter సున్నా కంటే ఎక్కువగా ఉంటే, ఈ container ప్రారంభమైన తర్వాత kernel ఏదో ఒకదాన్ని terminate చేసింది. max counter పెరుగుతుంటే, container ప్రస్తుతం తన పరిమితి వద్ద నిలిచిపోయిందని అర్థం. రెండు commands నడవడానికి image లో shell మరియు coreutils ఉండాలి. అందువల్ల distroless లేదా scratch image లో అవి విఫలమవుతాయి.
8GB VPSలో పరిమాణ పరిమితులు
ముందుగా apps ను కాకుండా host ను పరిగణించండి. 8GB VPSలో kernel, Docker daemon, sshd, journald మరియు మీ login shell కోసం సుమారు 1GB ఖాళీగా ఉంచండి. దీంతో పంచిపెట్టడానికి దాదాపు 7GB మిగులుతుంది. ప్రతి container limitల మొత్తం దీనికంటే తక్కువగా ఉండాలి. రెండు services ఒకేసారి peak కు చేరే రోజు వరకు overcommitting సరిగానే పనిచేయవచ్చు.
8GB box కోసం ఒక ఆచరణాత్మక విభజన:
- Reverse proxy: 128m limit. ఇది చిన్న process. ఇంత కఠినమైన limit 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లో నేరుగా ఉపయోగించవద్దు. Servicesను ఒక రోజు నిజమైన loadలో నడపండి. docker stats ను monitor చేయండి. ప్రతి containerలో గరిష్ఠంగా నమోదైన anon విలువను తీసుకుని, దానికి సుమారు సగం అదనంగా headroomగా జోడించండి. చాలా తక్కువగా సెట్ చేసిన limit, limit లేకపోవడం కంటే ప్రమాదకరం. సాధారణ network traffic spike సమయంలో అది ఆరోగ్యంగా ఉన్న serviceను terminate చేస్తుంది.
ఒక ముఖ్యమైన తప్పుదారి ఉంది. Limit గురించి runtimesకు తెలియజేయకపోతే, వాటిలో చాలా runtimes దాన్ని గుర్తించవు. PostgreSQL shared_buffers మరియు work_mem పరిమాణాలను తన container limit కంటే ఎక్కువగా సెట్ చేసి, చివరకు terminate కావచ్చు. 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 జోక్యం చేసుకుంటుంది. Ollamaలో కూడా ఇదే సమస్య ఉంటుంది, కానీ knob వేరుగా ఉంటుంది. num_ctx పెంచితే KV cache పరిమాణం పెరుగుతుంది మరియు వందల megabytes వరకు పెరిగిన ఆ cache కారణంగా దీర్ఘమైన prompt మధ్యలో container terminate అవుతుంది. cgroup చర్చలు జరపదు. అది terminate చేస్తుంది.
CPU పరిమితులు పూర్తిగా భిన్నంగా పనిచేస్తాయి
cpus: "1.5" అంటే ఒక core సామర్థ్యంలో 150%. ఇది CFS (completely fair scheduler) quotaగా అమలు చేయబడుతుంది. ప్రతి 100ms వ్యవధిలో containerకు 150ms CPU సమయం లభిస్తుంది. ఈ సమయం దాని అన్ని threads మధ్య పంచబడుతుంది. ఆ సమయం పూర్తయినప్పుడు, తదుపరి వ్యవధి వరకు kernel containerను వేచి ఉండేలా చేస్తుంది.
ఇదే ముఖ్యమైన తేడా. ఒక container తన memory limit దాటితే, అది kill చేయబడుతుంది. ఒక container తన CPU limit దాటితే, అది throttle చేయబడుతుంది మరియు తక్కువ వేగంతో కొనసాగుతుంది. అందువల్ల CPU limitను కొంత కఠినంగా సెట్ చేయడం సురక్షితం. అయితే memory limitకు అదనపు headroom అవసరం.
cpu_shares వేరే సాధనం. ఇది relative weight. CPUs పూర్తిగా వినియోగంలో ఉన్నప్పుడు మాత్రమే దీనికి ప్రభావం ఉంటుంది. 1024 మరియు 512 shares కలిగిన రెండు containers, busy coreను సుమారు రెండు-ఒకటి నిష్పత్తిలో పంచుకుంటాయి. idle serverలో అయితే ఏ containerకూ పరిమితి ఉండదు. సేవల ప్రాధాన్యతను క్రమబద్ధీకరించడానికి sharesను ఉపయోగించండి. వాస్తవ ceiling అవసరమైనప్పుడు cpus ను ఉపయోగించండి. ఉదాహరణకు, ప్రతి రాత్రి నడిచే transcode job మీ web serverకు CPU కొరత కలిగించకుండా ఆపడానికి ఇది ఉపయోగపడుతుంది.
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 లోని 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 kill కాకుండా పూర్తి memory limit వద్ద ఎందుకు ఉంది?
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 ను చూడండి. ఇది తిరిగి reclaim చేయలేని working set. తక్కువ anon value పక్కన ఎక్కువ file value ఉంటే, అది disk input and output చేస్తున్న container అని అర్థం. అది వెంటనే ఆగిపోబోతున్న container అని కాదు.
8GB VPS పై ఎంత RAM ను కేటాయించకుండా ఉంచాలి?
kernel, Docker daemon, sshd, journald మరియు మీ స్వంత shell కోసం సుమారు 1GB ఉంచండి. తరువాత అన్ని containers limits మొత్తం మిగిలిన 7GB కంటే తక్కువగా ఉండేలా చూడండి. వాస్తవ load లో ఒక రోజు పాటు ప్రతి container యొక్క గరిష్ఠ anon value ను monitor చేసిన తరువాతే values ను నిర్ణయించండి. మొత్తం మొత్తాన్ని నింపాల్సిన target గా కాకుండా budget గా పరిగణించండి.