Cara Hadkan Memori Docker Compose Elak Ralat OOM
Ketahui cara menetapkan deploy.resources untuk mengehadkan penggunaan RAM kontena. Elakkan ralat exit 137 dan pastikan VPS anda stabil daripada gangguan servis yang tamak.
Apakah fungsi had memori Docker Compose
Had memori Docker Compose ialah had maksimum yang ditetapkan oleh kernel Linux pada cgroup (control group, ciri kernel yang mengukur sumber untuk sekumpulan proses) sesuatu kontena. Tetapkan deploy.resources.limits.memory pada servis dan kontena tersebut tidak akan dapat menggunakan lebih daripada jumlah yang anda tulis. Apabila ia cuba melebihi had, kernel akan mematikan proses di dalam kontena tersebut, dan kontena itu biasanya akan keluar dengan kod 137.
Perkara ini paling penting pada VPS, di mana RAM adalah tetap dan tiada memori hos tambahan untuk dipinjam. Satu kontena yang mengalami kebocoran memori atau pertanyaan (query) yang buruk akan menggunakan setiap halaman memori bebas pada mesin 8GB. Kernel kemudian akan mematikan proses yang dianggap paling bermasalah, yang selalunya merupakan pangkalan data atau sesi SSH anda dan bukannya kontena yang menyebabkan masalah tersebut. Had memori menukarkan gangguan seluruh pelayan kepada hanya satu servis yang perlu dimulakan semula.
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mGunakan tetapan tersebut dan sahkan had itu aktif:
docker compose up -d
docker stats --no-streamLajur MEM USAGE / LIMIT sepatutnya memaparkan sesuatu seperti 142MiB / 1GiB. Jika lajur had menunjukkan jumlah RAM hos sepenuhnya, tetapan tersebut tidak berjaya digunakan, dan baki panduan ini tidak akan membantu sehingga ia berjaya. Jika fail compose itu sendiri baharu bagi anda, asas Docker Compose untuk VPS merangkumi susun atur fail yang menjadi asas kepada panduan ini.
deploy.resources.limits atau mem_limit: yang mana satu perlu digunakan
Terdapat dua ejaan untuk konsep yang sama, itulah sebabnya perkara ini mengelirukan.
mem_limit, mem_reservation, memswap_limit, cpus dan cpu_shares adalah kunci servis peringkat atas yang diwarisi daripada format fail Compose yang lama. deploy.resources berasal daripada skema Swarm dan kini merupakan sebahagian daripada Spesifikasi Compose, iaitu format yang dibaca oleh docker compose hari ini.
Kedua-duanya berfungsi pada satu hos tunggal. Docker Compose, iaitu pemalam docker compose, menggunakan deploy.resources.limits dan deploy.resources.reservations apabila anda menjalankan docker compose up, tanpa memerlukan kluster Swarm. Pemalam tersebut kini berada pada baris v5 (v5.6.0 merupakan keluaran stabil terkini semasa disemak pada 2026-10-10). Ia melompat daripada v2 terus ke v5, melangkau 3 dan 4 untuk mengelakkan kekeliruan dengan versi format fail Compose yang lama, dan tiada keluaran v5 yang mengubah cara had sumber dikendalikan. Bahagian yang berorientasikan Swarm dalam blok deploy adalah kunci lain, seperti placement, update_config dan endpoint_mode, yang menerangkan penjadualan dan pelancaran merentasi kluster nod untuk docker stack deploy. Oleh itu, nasihat umum bahawa "deploy memerlukan Swarm" adalah salah bagi subseksyen resources, dan jika anda mengikutinya, servis anda tidak akan mempunyai sebarang had.
Pilih satu ejaan bagi setiap projek. Menulis mem_limit: 512m dan deploy.resources.limits.memory: 1g pada servis yang sama bukan sahaja sukar dibaca: Spesifikasi Compose menyatakan bahawa apabila mem_limit ditetapkan, ia mestilah konsisten dengan limits.memory, dan ia tidak menyatakan nilai mana yang akan diguna pakai jika kedua-duanya bercanggah. Walau apa pun ejaan yang anda gunakan, sahkan perkara yang sebenarnya digunakan oleh daemon:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Nilai memori adalah dalam bait, jadi 1g dipaparkan sebagai 1073741824. CPU adalah dalam nano CPU, jadi 1.5 dipaparkan sebagai 1500000000. 0 dalam mana-mana medan bermaksud tiada had ditetapkan. Had memori terkecil yang diterima oleh Docker ialah 6m, dan di bawah nilai tersebut, kontena akan enggan bermula.
Apa yang berlaku apabila kontena mencapai had
Kontena tersebut tidak menjadi perlahan. Ia mati.
Apabila sesuatu proses meminta halaman dan cgroup sudah berada pada memory.max, kernel akan terlebih dahulu menuntut semula apa yang boleh di dalam cgroup tersebut: cache halaman bersih, kemudian halaman yang boleh diswap. Jika tuntutan semula tidak membebaskan ruang yang mencukupi, OOM (out of memory) killer cgroup akan memilih satu proses di dalam kontena dan menghantar SIGKILL kepadanya. Mematikan PID 1 kontena akan menamatkan kontena tersebut. Kod keluar 137 hanyalah 128 ditambah isyarat 9, jadi 137 adalah cap jari bagi sebarang SIGKILL, bukan bukti OOM dengan sendirinya.
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 ialah pembunuhan OOM. false 137 biasanya bermaksud sesuatu yang lain menghantar SIGKILL (program juga boleh keluar dengan 137 secara sengaja, tetapi itu jarang berlaku), dan punca biasa ialah docker compose stop mencapai tempoh tangguh sepuluh saat kerana aplikasi mengabaikan SIGTERM. Perbezaan itu menjimatkan masa berjam-jam, kerana kedua-dua masalah tersebut tidak mempunyai kaitan langsung.
Dua lagi tempat merekodkan peristiwa tersebut. Pantau daemon secara langsung:
docker events --filter event=oomKemudian baca log kernel, yang merupakan rekod yang kekal selepas but semula:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'Pembunuhan cgroup mencetak baris yang bermula dengan Memory cgroup out of memory: Killed process 24713 (node). Baris itu bermaksud satu kontena mencapai hadnya sendiri, dan ia boleh muncul walaupun hos masih mempunyai banyak RAM yang bebas. Baris tanpa awalan Memory cgroup ialah OOM hos, yang bermaksud mesin itu sendiri kehabisan RAM. Itulah kegagalan yang sepatutnya dicegah oleh had, jadi melihatnya biasanya bermaksud jumlah had anda terlalu tinggi, sesetengah servis tidak mempunyai had langsung, atau sesuatu di luar Docker menggunakan lebih banyak daripada ruang simpanan yang anda tinggalkan.
Dengan restart: unless-stopped, gelung OOM tersembunyi dengan baik, kerana servis kelihatan aktif dalam docker compose ps sesaat selepas ia mati. Semak lajur uptime dan kiraan mula semula, dan gandingkan had tersebut dengan healthcheck yang melaporkan aplikasi sebagai tidak sihat supaya kontena yang terus mati dapat dilihat tanpa anda perlu memantaunya.
Reservation ialah petunjuk, had (limit) ialah peraturan
reservations.memory (atau mem_reservation yang lebih lama) ialah had minimum lembut. Docker menyifatkannya sebagai had lembut yang diaktifkan apabila daemon mengesan perebutan sumber atau memori rendah pada hos. Ia tidak pernah menghalang kontena daripada melebihi nilai tersebut, dan ia tidak menjamin memori akan tersedia apabila kontena memintanya. Ia hanya memberi keutamaan kepada kernel untuk menuntut semula memori daripada kontena yang melebihi nilai reservation terlebih dahulu.
Oleh itu, reservation tidak melindungi apa-apa secara sendirian. Gunakannya untuk menandakan servis yang anda mahu diberikan keutamaan semasa tekanan sumber, dan bergantung pada limit untuk keselamatan. Pastikan nilai reservation lebih rendah daripada limit, atau kontena tidak akan bermula: Docker akan menolak konfigurasi tersebut dengan ralat Minimum memory limit can not be less than memory reservation limit.
Perakaunan swap, secara jujur
Kebanyakan imej VPS diedarkan tanpa sebarang fail swap. Jalankan swapon --show dan free -h. Jika jumlah swap adalah sifar, setiap tetapan berkaitan swap di bawah tidak akan berfungsi, dan had memori anda hanyalah had RAM tulen.
memswap_limit bukanlah jumlah swap. Ia adalah jumlah memori ditambah swap. Dengan mem_limit: 1g dan memswap_limit: 2g, kontena mendapat 1GB RAM dan 1GB swap. Menetapkan kedua-dua nilai tersebut kepada nilai yang sama menyebabkan kontena tidak mempunyai sebarang swap. Menetapkan mem_limit dan membiarkan memswap_limit tidak ditetapkan membolehkan kontena menggunakan swap sehingga saiz had memorinya sekali lagi.
Ubuntu 24.04 dan Debian 13 menggunakan cgroup v2 secara lalai, di mana swap merupakan pembilang berasingan (memory.swap.max) dan ini berfungsi tanpa tetapan tambahan. Mesej lama Your kernel does not support swap limit capabilities datang daripada hos cgroup v1 yang but tanpa swapaccount=1. Pada hos tersebut, had memori masih terpakai manakala bahagian swap diabaikan.
Bersikap jujurlah tentang apa yang swap berikan kepada anda. Ia menjadikan OOM kill lebih perlahan, bukan kurang berkemungkinan berlaku, kerana proses yang mengalami kebocoran memori akan mengisi swap sama seperti ia mengisi RAM. Sementara itu, kontena yang melakukan thrashing pada swap di storan VPS kongsi akan melambatkan setiap servis lain pada pelayan tersebut. Bagi apa-apa yang sensitif terhadap latensi, had yang tepat tanpa swap akan gagal dengan lebih cepat dan lebih boleh diramal.
Mengapa penggunaan memori kelihatan lebih buruk daripada keadaan sebenar
Pada Linux, angka MEM USAGE dalam docker stats hanya menolak cache halaman yang tidak aktif (inactive_file pada cgroup v2), jadi cache yang baru digunakan masih dikira, dan kontena yang membaca fail besar akan meningkat ke arah hadnya dan kekal di situ. Ini adalah perkara biasa dan ia bukan kebocoran, kerana cache bersih akan dituntut semula sebelum OOM killer dipanggil. Servis seperti pelayan media Jellyfin yang dihoskan sendiri akan kelihatan sentiasa hampir dengan had silingnya atas sebab ini.
Pecahkan angka tersebut kepada cache dan set kerja sebenar dari dalam kontena:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon ialah memori tanpa nama, iaitu set kerja yang tidak boleh digugurkan. file ialah cache halaman, yang boleh digugurkan. Tentukan had anda berdasarkan anon ditambah dengan margin, bukan berdasarkan jumlah keseluruhan. Fail memory.events menyelesaikan perdebatan ini secara langsung: pembilang oom_kill melebihi sifar bermakna kernel telah mematikan sesuatu dalam kontena ini sejak ia dimulakan, dan pembilang max yang meningkat bermakna kontena tersebut sedang ditahan pada had silingnya sekarang. Kedua-dua arahan memerlukan shell dan coreutils di dalam imej, jadi ia akan gagal pada imej distroless atau scratch.
Had pengehadan saiz pada VPS 8GB
Mulakan daripada hos, bukan daripada aplikasi. Pada VPS 8GB, peruntukkan kira-kira 1GB untuk kernel, daemon Docker, sshd, journald dan shell log masuk anda sendiri. Ini meninggalkan kira-kira 7GB untuk diagihkan, dan jumlah had setiap kontena mestilah tidak melebihi nilai tersebut. Overcommitting mungkin berfungsi sehingga tiba hari dua servis mencapai kemuncak penggunaan secara serentak.
Pembahagian yang praktikal pada mesin 8GB:
- Reverse proxy: had 128m. Ia adalah proses yang kecil, dan had yang ketat ini akan mengesan muat semula konfigurasi yang tidak terkawal dengan serta-merta.
- PostgreSQL: had 2g, dengan
shared_buffersditetapkan kepada kira-kira 512MB dalam konfigurasi pangkalan data. - Kontena aplikasi: had 1g.
- Background worker: had 512m.
- Servis media atau fail: had 2g, yang kebanyakannya akan menjadi page cache.
Jangan salin nombor tersebut ke dalam stack anda sendiri. Jalankan servis di bawah beban sebenar selama sehari, pantau docker stats, ambil nilai anon puncak bagi setiap kontena dan tambahkan kira-kira separuh lagi sebagai ruang tambahan (headroom). Had yang ditetapkan terlalu ketat adalah lebih buruk daripada tiada had, kerana ia akan mematikan servis yang sihat semasa lonjakan trafik biasa.
Satu perangkap perlu diberi perhatian khusus. Sesetengah runtime mengabaikan had tersebut melainkan anda memberitahunya, dan runtime yang membacanya mungkin tidak menetapkan saiz mengikut jangkaan anda. PostgreSQL akan dengan senang hati menetapkan saiz shared_buffers dan work_mem melebihi had kontena dan akan dimatikan. JVM (Java virtual machine) moden mengesan had cgroup secara lalai, tetapi binaan HotSpot seperti OpenJDK kemudiannya hanya memberikan heap sebanyak 25 peratus daripadanya, jadi -XX:MaxRAMPercentage=75 ialah cara biasa untuk membolehkan heap menggunakan lebih banyak had sambil meninggalkan ruang untuk memori di luar heap. Node.js telah menetapkan saiz heap lalainya daripada had cgroup sejak versi 12, pada kira-kira separuh daripada had kontena dan mendatar sekitar 2GB, jadi tetapkan --max-old-space-size dalam megabait hanya apabila anda mahukan siling yang berbeza, dan pastikan ia di bawah had kontena supaya pengumpul sampah (garbage collector) berfungsi sebelum kernel campur tangan. Ollama mempunyai situasi yang sama dengan tombol yang berbeza, kerana meningkatkan num_ctx akan mengembangkan KV cache sebanyak ratusan megabait dan kontena akan mati di pertengahan jalan semasa prompt yang panjang. Cgroup tidak berunding. Ia terus mematikan proses.
Had CPU beroperasi dengan cara yang berbeza sama sekali
cpus: "1.5" bermaksud 150% daripada satu teras, yang dikuatkuasakan sebagai kuota CFS (completely fair scheduler). Kontena tersebut mendapat 150ms masa CPU dalam setiap tempoh 100ms, yang dikongsi merentasi semua thread miliknya. Apabila masa tersebut habis digunakan, kernel akan memaksa kontena menunggu sehingga tempoh seterusnya.
Itulah perbezaan yang penting. Kontena yang melebihi had memori akan ditamatkan (killed). Kontena yang melebihi had CPU akan diperlahankan (throttled) dan terus berjalan, tetapi dengan kelajuan yang lebih rendah. Oleh itu, had CPU selamat untuk ditetapkan secara agresif, manakala had memori memerlukan ruang tambahan (headroom).
cpu_shares ialah alat yang berbeza: pemberat relatif yang hanya penting apabila CPU benar-benar tepu. Dua kontena dengan perkongsian 1024 dan 512 akan membahagikan teras yang sibuk dengan nisbah kira-kira dua kepada satu, dan pada pelayan yang melahu, tiada satu pun yang disekat. Gunakan perkongsian (shares) untuk menyusun kepentingan servis, dan gunakan cpus apabila anda memerlukan had siling yang sebenar, contohnya untuk menghalang tugasan transkod waktu malam daripada melumpuhkan pelayan web anda.
FAQ
Adakah deploy.resources.limits berfungsi tanpa Docker Swarm?
Ya. Docker Compose, pemalam docker compose (kini pada baris v5), menggunakan deploy.resources.limits dan deploy.resources.reservations apabila anda menjalankan docker compose up pada satu hos. Sahkan perkara ini dengan docker inspect --format '{{.HostConfig.Memory}}' <container>, yang memaparkan had dalam bait dan memaparkan 0 apabila tiada had dikenakan. Kunci di dalam deploy yang ditulis dengan mengambil kira Swarm adalah kunci kluster, seperti placement, update_config dan endpoint_mode.
Apakah maksud exit code 137 dalam Docker Compose?
Ia hampir selalu bermaksud proses utama menerima SIGKILL, kerana 137 adalah 128 ditambah isyarat 9 (sesebuah program juga boleh keluar dengan 137 secara sengaja, tetapi ini jarang berlaku). OOM killer kernel adalah punca biasa, tetapi tamat masa penutupan (shutdown timeout) menghasilkan kod yang sama apabila aplikasi mengabaikan SIGTERM. Jalankan docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> untuk membezakan kedua-duanya. true 137 adalah pembunuhan memori, manakala false 137 bukan.
Patutkah saya menetapkan mem_limit atau deploy.resources.limits.memory?
Kedua-duanya berfungsi dengan docker compose. deploy.resources.limits.memory ialah format Spesifikasi Compose semasa dan merupakan pilihan lalai yang lebih baik untuk fail baharu. Kekalkan mem_limit jika fail anda yang lain sudah menggunakan kunci peringkat atas yang lebih lama. Spesifikasi Compose memerlukan kedua-duanya konsisten apabila kedua-duanya ditetapkan, jadi pilih satu dan sahkan hasilnya dengan docker inspect.
Mengapa kontena saya berada pada had memori penuh tanpa dimatikan?
Angka penggunaan dalam docker stats masih mengira cache halaman yang baru digunakan, yang akan dibuang oleh kernel di bawah tekanan dan bukannya mencetuskan OOM kill. Jalankan docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat dan baca nilai anon, iaitu set kerja yang tidak boleh dituntut semula. Nilai file yang tinggi di samping nilai anon yang rendah bermakna kontena sedang melakukan input dan output cakera, bukannya kontena yang akan mati.
Berapa banyak RAM yang perlu saya biarkan tidak diperuntukkan pada VPS 8GB?
Biarkan kira-kira 1GB untuk kernel, daemon Docker, sshd, journald dan shell anda sendiri, kemudian pastikan jumlah semua had kontena berada di bawah baki 7GB. Pantau nilai puncak anon bagi setiap kontena di bawah beban sebenar selama sehari sebelum anda menetapkan angka, dan anggap jumlah tersebut sebagai bajet dan bukannya sasaran untuk dipenuhi.