Docker Compose memory limit: OOM மற்றும் code 137 தடுப்பு
Docker Compose-ல் memory, CPU வரம்புகளை அமைத்து, ஒரு container VPS-ஐ முடக்காமல் தடுக்கவும். deploy.resources, mem_limit, swap மற்றும் code 137 பற்றி அறிக.
Docker Compose memory limit என்ன செய்கிறது
Docker Compose memory limit என்பது Linux kernel ஒரு container-ன் cgroup மீது விதிக்கும் கடுமையான உச்சவரம்பாகும். cgroup என்பது செயல்முறைகளின் தொகுப்புக்கான வளப் பயன்பாட்டை அளக்கும் kernel அம்சமாகும். ஒரு service-க்கு deploy.resources.limits.memory அமைத்தால், அந்த container நீங்கள் குறிப்பிட்ட எண்ணிக்கையைவிட அதிக memory-ஐ ஒருபோதும் பயன்படுத்த முடியாது. அது அந்த வரம்பை மீற முயன்றால், kernel container-க்குள் உள்ள ஒரு process-ஐ நிறுத்தும். Container பொதுவாக code 137 உடன் வெளியேறும்.
RAM அளவு நிர்ணயிக்கப்பட்டு, host memory-யிலிருந்து கூடுதல் memory பெற முடியாத VPS-ல் இது மிகவும் முக்கியமானது. Memory leak அல்லது தவறான query கொண்ட ஒரு container, 8GB server-ல் உள்ள அனைத்து free page-களையும் பயன்படுத்திவிடும். பின்னர் kernel மிகவும் பாதிக்கப்பட்டதாகக் கருதும் process-ஐ நிறுத்தும். அது பெரும்பாலும் சிக்கலை ஏற்படுத்திய 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 செயல்படுத்தப்படவில்லை. அது செயல்படும் வரை இந்த வழிகாட்டியின் மீதமுள்ள பகுதிகள் உதவாது. compose file உங்களுக்கு புதிதாக இருந்தால், VPS-க்கான Docker Compose அடிப்படைகள் இந்த அமைப்பு சார்ந்திருக்கும் file layout-ஐ விளக்குகின்றன.
deploy.resources.limits அல்லது mem_limit: எது பொருந்தும்
ஒரே கருத்துக்கு இரண்டு எழுத்து வடிவங்கள் உள்ளன. அதனால் இது குழப்பமாக உள்ளது.
mem_limit, mem_reservation, memswap_limit, cpus மற்றும் cpu_shares ஆகியவை பழைய Compose file வடிவங்களில் இருந்து பெறப்பட்ட top-level service keys ஆகும். deploy.resources என்பது Swarm schema-வில் இருந்து வந்தது. இப்போது அது Compose Specification-ன் ஒரு பகுதியாக உள்ளது. இதுவே docker compose தற்போது படிக்கும் வடிவமாகும்.
இரண்டும் ஒரே host-ல் செயல்படும். Swarm cluster எதுவும் இல்லாமல் docker compose up இயக்கும்போது, Compose V2-ன் docker compose plugin, deploy.resources.limits மற்றும் deploy.resources.reservations ஆகியவற்றைப் பயன்படுத்துகிறது. deploy block-ன் Swarm-க்கு மட்டும் உரியவை மற்ற keys ஆகும்: mode, placement, update_config மற்றும் endpoint_mode ஆகியவை docker stack deploy-க்கு பொருள் தரும்; அவை docker compose up-ல் புறக்கணிக்கப்படும். எனவே, "deploy-க்கு Swarm தேவை" என்ற பொதுவான அறிவுரை resources subsection-க்கு தவறானது. அதை பின்பற்றினால், உங்கள் services-க்கு எந்த limit-மும் இருக்காது.
ஒவ்வொரு project-க்கும் ஒரு எழுத்து வடிவத்தை மட்டும் தேர்ந்தெடுக்கவும். ஒரே service-ல் mem_limit: 512m மற்றும் deploy.resources.limits.memory: 1g ஆகியவற்றை எழுதினால், ஒரே பார்வையில் புரிந்துகொள்ள முடியாத file உருவாகும். எந்த மதிப்பு பயன்படுத்தப்பட்டது என்று ஊகிப்பதற்குப் பதிலாக, daemon-ஐக் கேட்கவும்:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Memory values bytes-ல் இருக்கும். ஆகவே, 1g என்பது 1073741824 எனக் காட்டப்படும். CPU nano CPUs-ல் அளவிடப்படும். ஆகவே, 1.5 என்பது 1500000000 எனக் காட்டப்படும். எந்த field-லும் 0 இருந்தால், எந்த limit-மும் அமைக்கப்படவில்லை என்று பொருள். Docker ஏற்கும் குறைந்தபட்ச memory limit 6m ஆகும். அதற்குக் கீழே இருந்தால், container தொடங்காது.
ஒரு container வரம்பை எட்டும்போது என்ன நடக்கும்
container மெதுவாகாது. அது நிறுத்தப்படும்.
ஒரு process ஒரு page-ஐக் கோரும்போது, cgroup ஏற்கனவே அதன் memory.max வரம்பை எட்டியிருந்தால், kernel முதலில் அந்த cgroup-க்குள் மீட்டெடுக்கக்கூடியவற்றை மீட்டெடுக்கும்: முதலில் clean page cache, பின்னர் swap செய்யக்கூடிய pages. Reclaim போதுமான memory-ஐ விடுவிக்கவில்லை என்றால், cgroup OOM (out of memory) killer, container-க்குள் உள்ள ஒரு process-ஐத் தேர்ந்தெடுத்து அதற்கு SIGKILL அனுப்பும். Container-ன் PID 1 process-ஐ நிறுத்தினால் 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 அனுப்பியுள்ளது. இதற்கான வழக்கமான காரணம், app SIGTERM-ஐப் புறக்கணித்ததால் docker compose stop அதன் பத்து வினாடி grace period-ஐ எட்டியதாகும். இந்த வேறுபாடு பல மணி நேரங்களைச் சேமிக்கும், ஏனெனில் இந்த இரண்டு பிரச்சினைகளுக்கும் தொடர்பில்லை.
இந்த நிகழ்வை மேலும் இரண்டு இடங்கள் பதிவு செய்கின்றன. 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-ஐக் குறிக்கும். அதாவது machine-ல் உள்ள RAM முழுவதும் பயன்படுத்தப்பட்டுவிட்டது. இதைத் தடுப்பதற்காகவே limits பயன்படுத்தப்படுகின்றன. எனவே இதைக் காண்பது, உங்கள் limits-ன் மொத்தம் மிக அதிகமாக இருப்பதற்கான அல்லது சில services-க்கு limit எதுவும் அமைக்கப்படாததற்கான அறிகுறியாகும்.
restart: unless-stopped பயன்படுத்தும்போது, OOM loop எளிதில் மறைந்துவிடும், ஏனெனில் service இறந்த ஒரு வினாடிக்குப் பிறகு docker compose ps-ல் மீண்டும் இயங்குவதாகத் தோன்றும். uptime column மற்றும் restart count-ஐச் சரிபார்க்கவும். மேலும், container தொடர்ந்து நிறுத்தப்படுவதை நீங்கள் நேரடியாகக் கண்காணிக்காமல் அறிய, app-ஐ unhealthy எனக் காட்டும் healthcheck உடன் அந்த limit-ஐ இணைக்கவும்.
Reservation ஒரு குறிப்பு; limit ஒரு விதி
reservations.memory (பழைய mem_reservation) என்பது ஒரு soft floor ஆகும். Host-ல் contention அல்லது குறைந்த memory இருப்பதை daemon கண்டறியும் போது செயல்படுத்தப்படும் soft limit ஆக இதை Docker விவரிக்கிறது. Container இதை மீறிச் செல்லாது என்று இது ஒருபோதும் தடுக்காது. Container memory கேட்கும் போது அது கிடைக்கும் என்று இது ஒருபோதும் உத்தரவாதம் அளிக்காது. Reservation-ஐ மீறிய containers-லிருந்து முதலில் memory-ஐ reclaim செய்யும்படி kernel-க்கு இது முன்னுரிமை அளிக்கும்.
எனவே, reservation மட்டும் எந்தப் பாதுகாப்பையும் வழங்காது. அழுத்தநிலையில் சாதகமாகக் கையாளப்பட வேண்டிய service-ஐக் குறிக்க இதைப் பயன்படுத்தவும். பாதுகாப்புக்காக limit-ஐ நம்பவும். Reservation-ஐ limit-க்கு குறைவாக வைத்திருக்கவும். இல்லையெனில் container start ஆகாது: Docker config-ஐ Minimum memory limit can not be less than memory reservation limit மூலம் நிராகரிக்கும்.
Swap கணக்கீடு, சரியாக
பெரும்பாலான VPS images-களில் swap file எதுவும் இருக்காது. swapon --show மற்றும் free -h ஐ இயக்கவும். swap total zero ஆக இருந்தால், கீழே உள்ள swap தொடர்பான அமைப்புகள் எதுவும் செயல்படாது. உங்கள் memory limit, RAM cap ஆக மட்டுமே இருக்கும்.
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-லிருந்து வருகிறது. அவற்றில் memory limit தொடர்ந்து செயல்படும். ஆனால் swap பகுதி புறக்கணிக்கப்படும்.
swap உங்களுக்கு உண்மையில் என்ன வழங்குகிறது என்பதைத் தெளிவாகப் புரிந்துகொள்ளுங்கள். இது OOM kill நிகழ்வை மெதுவாக்கும். ஆனால் அது நிகழும் வாய்ப்பைக் குறைக்காது. ஏனெனில் memory leak உள்ள process, RAM-ஐ நிரப்புவது போலவே swap-ஐயும் நிரப்பும். அதே நேரத்தில், shared VPS storage-ல் swap-ஐ அதிகமாகப் பயன்படுத்தும் container, அந்த server-ல் இயங்கும் பிற service-களையும் மெதுவாக்கும். latency முக்கியமான எந்த workload-க்கும், சரியான limit-ஐ swap இல்லாமல் அமைப்பது வேகமாகவும் கணிக்கக்கூடிய வகையிலும் failure-ஐ ஏற்படுத்தும்.
நினைவகப் பயன்பாடு உண்மையைவிட மோசமாகத் தோன்றுவதற்கான காரணம்
docker stats இல் உள்ள MEM USAGE எண்ணிக்கையில் page cache சேர்க்கப்பட்டுள்ளது. ஆகவே பெரிய கோப்புகளைப் படிக்கும் container அதன் limit-ஐ நோக்கி memory பயன்பாட்டை உயர்த்தி, அங்கேயே நிலைத்திருக்கும். இது இயல்பானது; memory leak அல்ல. காரணம், OOM killer அழைக்கப்படுவதற்கு முன்பே clean cache மீட்டெடுக்கப்படும். தானே host செய்யும் Jellyfin media server போன்ற service-கள் இதே காரணத்தால் தங்கள் memory ceiling-க்கு நிரந்தரமாக அருகில் இருப்பதுபோல் தோன்றும்.
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 என்பது drop செய்யக்கூடிய page cache ஆகும். உங்கள் limit-ஐ மொத்த memory பயன்பாட்டை அடிப்படையாகக் கொண்டு அமைக்காமல், anon மற்றும் கூடுதல் margin-ஐ அடிப்படையாகக் கொண்டு அமைக்கவும். memory.events கோப்பு இதைத் தெளிவாக உறுதிப்படுத்தும்: oom_kill counter-ன் மதிப்பு zero-வைவிட அதிகமாக இருந்தால், container தொடங்கியதிலிருந்து kernel அதற்குள் ஏதோ ஒன்றைக் கொன்றுள்ளது என்று பொருள். max counter உயர்ந்து கொண்டிருந்தால், container தற்போது அதன் ceiling-இல் கட்டுப்படுத்தப்படுகிறது என்று பொருள். இரண்டு 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 ஒரே நேரத்தில் உச்சப் பயன்பாட்டை அடையும் நாள் வரை overcommitting செயல்படலாம்.
8GB box-க்குப் பயன்படுத்தக்கூடிய பிரிப்பு:
- Reverse proxy: 128m limit. இது சிறிய process ஆகும். இவ்வளவு குறுகிய limit, கட்டுப்பாடின்றி மீண்டும் மீண்டும் நடைபெறும் 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 அதிகரிப்பின்போது அது ஆரோக்கியமாக இயங்கும் service-ஐ நிறுத்திவிடும்.
ஒரு சிக்கலுக்கு தனியாகக் கவனம் தேவை. Limit பற்றி தெரிவிக்காவிட்டால் பெரும்பாலான runtimes அதைக் கணக்கில் எடுத்துக்கொள்ளாது. PostgreSQL, shared_buffers மற்றும் work_mem ஆகியவற்றை தனது container limit-ஐத் தாண்டியும் அமைத்து, பின்னர் killed ஆகலாம். 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-ஐ killed செய்கிறது.
CPU limits முற்றிலும் வேறுபட்ட முறையில் செயல்படுகின்றன
cpus: "1.5" என்பது CFS (completely fair scheduler) quota ஆகச் செயல்படுத்தப்படும் ஒரு core-ன் 150% ஆகும். ஒவ்வொரு 100ms காலப்பகுதியிலும் container-க்கு 150ms CPU time கிடைக்கும். இது அதன் அனைத்து threads-க்கும் பகிரப்படும். அந்த நேரத்தை முழுமையாகப் பயன்படுத்தியதும், அடுத்த காலப்பகுதி வரை kernel அதை காத்திருக்கச் செய்யும்.
இதுவே முக்கியமான வேறுபாடு. Container அதன் memory limit-ஐ மீறினால், அது நிறுத்தப்படும். Container அதன் CPU limit-ஐ மீறினால், அது throttled நிலையில் தொடர்ந்து இயங்கும்; வேகம் மட்டும் குறையும். எனவே CPU limit-ஐ அதிகமாக அமைப்பது பாதுகாப்பானது. ஆனால் memory limit-க்கு போதிய headroom தேவை.
cpu_shares என்பது வேறுபட்ட கருவியாகும். இது relative weight ஆகும்; CPU-கள் உண்மையில் முழுமையாகப் பயன்படுத்தப்படும் நேரங்களில் மட்டுமே இதன் தாக்கம் இருக்கும். 1024 மற்றும் 512 shares கொண்ட இரண்டு containers, busy core-ஐ தோராயமாக two-to-one விகிதத்தில் பகிர்ந்து கொள்ளும். Idle box-ல், அவற்றில் எதுவும் கட்டுப்படுத்தப்படாது. Services-ன் முக்கியத்துவத்தை வரிசைப்படுத்த shares-ஐப் பயன்படுத்துங்கள். உண்மையான உச்சவரம்பு தேவைப்படும் போது cpus-ஐப் பயன்படுத்துங்கள். உதாரணமாக, nightly 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 என்பதை அச்சிடும். உண்மையில் Swarm தேவைப்படும் deploy-இல் உள்ள keys, mode, placement, update_config மற்றும் endpoint_mode ஆகும்.
Docker Compose-இல் exit code 137 என்பதன் பொருள் என்ன?
முக்கிய process SIGKILL-ஐ பெற்றது என்பதே இதன் பொருள். ஏனெனில் 137 என்பது 128 plus 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 அளவில் பயன்படுத்திக்கொண்டிருந்தும் ஏன் kill செய்யப்படவில்லை?
docker stats-இல் காட்டப்படும் usage figure, page cache-ஐயும் சேர்த்துக் கணக்கிடும். OOM kill-ஐத் தூண்டுவதற்குப் பதிலாக, memory pressure ஏற்படும் போது kernel இதை நீக்கிவிடும். docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat இயக்கி, anon value-ஐப் பார்க்கவும். மீட்டெடுக்க முடியாத working set அதுவாகும். குறைந்த anon value-க்கு அருகில் அதிக file value இருந்தால், அது disk input மற்றும் output செய்யும் container ஆகும். அது விரைவில் நிறுத்தப்படவுள்ள container அல்ல.
8GB VPS-இல் எவ்வளவு RAM-ஐ ஒதுக்காமல் விட வேண்டும்?
Kernel, Docker daemon, sshd, journald மற்றும் உங்கள் சொந்த shell-க்கு சுமார் 1GB விடவும். பின்னர், அனைத்து container limits-களின் கூட்டுத்தொகையை மீதமுள்ள 7GB-க்குக் கீழே வைத்திருக்கவும். உண்மையான load-இல் ஒவ்வொரு container-க்குமான உச்ச anon value-ஐ ஒரு நாள் கண்காணித்த பிறகே values-ஐ நிர்ணயிக்கவும். மொத்தத்தை நிரப்ப வேண்டிய target ஆக அல்ல, budget ஆகக் கருதவும்.