SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Dikemas kini 2026-08-29

Cara Hadkan Memori Docker Compose Elak OOM

Elakkan pelayan VPS terhenti akibat ralat OOM dengan menetapkan had memori dalam Docker Compose. Gunakan deploy.resources untuk mengelak kod keluar 137 dan kegagalan sistem.

Fungsi had memori Docker Compose

Had memori Docker Compose ialah had maksimum yang dikenakan oleh kernel Linux pada cgroup (kumpulan kawalan, iaitu ciri kernel yang mengukur sumber untuk sekumpulan proses) sesuatu bekas. Tetapkan deploy.resources.limits.memory pada sesuatu servis dan bekas tersebut tidak akan dapat menggunakan lebih daripada jumlah yang anda tulis. Apabila ia cuba melebihi had tersebut, kernel akan menamatkan proses di dalam bekas itu, dan bekas tersebut 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 bekas yang mengalami kebocoran memori atau pertanyaan yang tidak cekap akan menggunakan setiap halaman memori bebas pada pelayan 8GB. Kernel kemudian akan menamatkan proses yang dianggap paling bermasalah, yang selalunya merupakan pangkalan data atau sesi SSH anda dan bukannya bekas 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: 256m

Gunakan tetapan tersebut dan sahkan had itu aktif:

docker compose up -d
docker stats --no-stream

Lajur MEM USAGE / LIMIT sepatutnya memaparkan sesuatu seperti 142MiB / 1GiB. Jika lajur had memori menunjukkan jumlah RAM hos sepenuhnya, tetapan tersebut tidak digunakan, dan baki panduan ini tidak akan membantu sehingga tetapan itu berjaya digunakan. 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. Compose V2, iaitu pemalam docker compose, menggunakan deploy.resources.limits dan deploy.resources.reservations apabila anda menjalankan docker compose up, tanpa memerlukan kluster Swarm. Bahagian dalam blok deploy yang khusus untuk Swarm hanyalah kunci-kunci lain: mode, placement, update_config dan endpoint_mode membawa maksud tertentu kepada docker stack deploy dan diabaikan oleh docker compose up. Oleh itu, nasihat umum yang menyatakan 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 akan menghasilkan fail yang sukar difahami dengan sekali imbas. Daripada meneka nombor mana yang diguna pakai, tanya daemon tersebut:

docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1

Nilai memori adalah dalam bait, jadi 1g dipaparkan sebagai 1073741824. CPU adalah dalam unit nano CPU, jadi 1.5 dipaparkan sebagai 1500000000. Nilai 0 dalam mana-mana medan bermaksud tiada had ditetapkan. Had memori paling kecil yang diterima oleh Docker ialah 6m, dan di bawah nilai tersebut, kontena akan menolak untuk bermula.

Apa yang berlaku apabila kontena mencapai had

Kontena tersebut tidak menjadi perlahan. Ia akan mati.

Apabila sesuatu proses meminta halaman memori dan cgroup sudah mencapai memory.max, kernel akan terlebih dahulu menuntut semula apa yang boleh di dalam cgroup tersebut: cache halaman bersih, kemudian halaman yang boleh diswap. Jika proses 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. Exit code 137 hanyalah 128 ditambah dengan signal 9, jadi 137 adalah tanda bagi sebarang SIGKILL, bukan bukti OOM dengan sendirinya.

docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1

true 137 adalah OOM kill. false 137 bermaksud sesuatu yang lain telah menghantar SIGKILL, dan punca lazimnya ialah docker compose stop mencapai tempoh tangguh sepuluh saat kerana aplikasi mengabaikan SIGTERM. Perbezaan ini menjimatkan masa berjam-jam, kerana kedua-dua masalah tersebut tidak mempunyai kaitan antara satu sama lain.

Dua lagi lokasi merekodkan peristiwa tersebut. Pantau daemon secara langsung:

docker events --filter event=oom

Kemudian baca log kernel, yang merupakan rekod yang kekal selepas but semula:

sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'

Satu kill cgroup mencetak baris yang bermula dengan Memory cgroup out of memory: Killed process 24713 (node). Baris tanpa awalan Memory cgroup adalah OOM hos, yang bermaksud mesin itu sendiri kehabisan RAM. Itu adalah kegagalan yang sepatutnya dicegah oleh had, jadi melihatnya adalah tanda bahawa jumlah had anda terlalu tinggi, atau sesetengah servis tidak mempunyai had langsung.

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 restart, serta padankan had tersebut dengan healthcheck yang melaporkan aplikasi sebagai tidak sihat supaya kontena yang terus mati dapat dilihat tanpa anda perlu memantaunya secara berterusan.

Reservation ialah petunjuk, had (limit) ialah peraturan

reservations.memory (atau mem_reservation yang lebih lama) ialah lantai lembut. Docker menyifatkannya sebagai had lembut yang diaktifkan apabila daemon mengesan perebutan sumber atau memori rendah pada hos. Ia tidak pernah menghalang container daripada melebihi had tersebut, dan ia tidak menjamin memori akan tersedia apabila container memintanya. Ia hanya memihak kepada kernel untuk menuntut semula memori daripada container yang berada di atas nilai reservation mereka terlebih dahulu.

Oleh itu, reservation tidak melindungi apa-apa secara sendirian. Gunakannya untuk menandakan servis yang anda mahu dilayan dengan baik di bawah tekanan, dan bergantung pada limit untuk keselamatan. Pastikan nilai reservation di bawah limit, atau container tidak akan bermula: Docker akan menolak konfigurasi tersebut dengan Minimum memory limit can not be less than memory reservation limit.

Perakaunan swap, secara jujur

Kebanyakan imej VPS tidak menyertakan fail swap langsung. 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 swap langsung. 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.

Jujurlah tentang apa yang swap berikan kepada anda. Ia menjadikan OOM kill lebih perlahan, bukan kurang berkemungkinan, 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 sebarang perkara yang sensitif terhadap latensi, had yang betul tanpa swap akan gagal dengan lebih pantas dan lebih boleh diramal.

Mengapa penggunaan memori kelihatan lebih buruk daripada keadaan sebenar

Angka MEM USAGE dalam docker stats merangkumi page cache, jadi kontena yang membaca fail bersaiz besar akan meningkat menghampiri hadnya dan kekal di situ. Ini adalah perkara biasa dan bukan kebocoran, kerana cache yang bersih akan dituntut semula sebelum OOM killer diaktifkan. Servis seperti pelayan media Jellyfin yang dihoskan sendiri akan kelihatan sentiasa berada hampir pada had maksimumnya atas sebab ini.

Bahagikan 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.events

anon ialah memori tanpa nama (anonymous memory), iaitu set kerja yang tidak boleh digugurkan. file ialah page cache, yang boleh digugurkan. Tentukan had anda berdasarkan anon ditambah dengan margin, bukan berdasarkan jumlah keseluruhan. Fail memory.events menyelesaikan perdebatan ini secara muktamad: pembilang oom_kill yang 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 maksimumnya sekarang. Kedua-dua arahan memerlukan shell dan coreutils di dalam imej, jadi ia akan gagal pada imej distroless atau scratch.

Had saiz pada VPS 8GB

Mulakan daripada hos, bukan daripada aplikasi. Pada VPS 8GB, peruntukkan kira-kira 1GB untuk kernel, Docker daemon, 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 memuncak pada masa yang sama.

Pembahagian yang praktikal pada mesin 8GB:

  • Reverse proxy: had 128m. Ia adalah proses yang kecil, dan had yang ketat ini dapat mengesan konfigurasi reload yang tidak terkawal dengan serta-merta.
  • PostgreSQL: had 2g, dengan shared_buffers ditetapkan 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, perhatikan docker stats, ambil nilai puncak anon 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. Had tersebut tidak kelihatan bagi kebanyakan runtime melainkan anda memberitahu mereka mengenainya. PostgreSQL akan dengan senang hati menetapkan saiz shared_buffers dan work_mem melebihi had kontena dan akan dimatikan. JVM (Java virtual machine) memerlukan -XX:MaxRAMPercentage=75 untuk menetapkan saiz heap daripada had cgroup dan bukannya daripada RAM hos. Node.js memerlukan --max-old-space-size dalam megabait, ditetapkan di bawah had kontena, atau garbage collector akan membiarkan heap berkembang sehingga kernel campur tangan. Ollama juga mengalami masalah yang sama dengan kawalan yang berbeza, kerana meningkatkan num_ctx akan mengembangkan KV cache sebanyak ratusan megabait dan kontena akan mati di tengah-tengah prompt yang panjang. Cgroup tidak berunding. Ia terus mematikan proses.

Had CPU berkelakuan 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 memaksanya menunggu sehingga tempoh seterusnya.

Itulah perbezaan yang penting. Kontena yang melebihi had memorinya akan ditamatkan (killed). Kontena yang melebihi had CPU pula akan diperlahankan (throttled) dan terus berjalan, tetapi dengan lebih perlahan. Oleh itu, had CPU selamat untuk ditetapkan secara agresif, manakala had memori memerlukan ruang tambahan (headroom).

cpu_shares ialah alat yang berbeza: ia merupakan pemberat relatif yang hanya penting apabila CPU benar-benar tepu. Dua kontena dengan bahagian (shares) 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 bahagian 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. Compose V2 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 benar-benar memerlukan Swarm ialah mode, placement, update_config dan endpoint_mode.

Apakah maksud exit code 137 dalam Docker Compose?

Ini bermakna proses utama menerima SIGKILL, kerana 137 ialah 128 ditambah isyarat 9. 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 ialah penamatan disebabkan 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 lama. Menetapkan kedua-duanya pada satu servis hanya menyukarkan fail untuk dibaca, jadi pilih satu dan sahkan hasilnya dengan docker inspect.

Mengapa kontena saya berada pada had memori penuh tanpa ditamatkan?

Angka penggunaan dalam docker stats termasuk cache halaman, 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 hampir 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.