SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor

Cara Hadkan Memori Docker Compose Elak Ralat OOM

Tetapkan had memori dan CPU dalam Docker Compose untuk elak kontena tamak menjatuhkan VPS anda. Ketahui cara guna deploy.resources bagi mengelakkan ralat exit 137.

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: 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 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 terpakai

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 sebarang kluster Swarm. Pemalam tersebut kini berada pada siri v5 (v5.6.0 merupakan keluaran stabil terkini semasa disemak pada 2026-10-06). 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 khusus Swarm dalam blok deploy adalah 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 mengikutinya akan menyebabkan servis anda tidak 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 secara sepintas lalu. 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 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 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 tersebut dan menghantar SIGKILL kepadanya. Mematikan PID 1 kontena akan menamatkan kontena itu. Exit code 137 hanyalah 128 ditambah dengan signal 9, jadi 137 adalah cap jari bagi mana-mana SIGKILL, bukan bukti OOM dengan sendirinya.

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

true 137 ialah 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 langsung.

Dua lagi tempat merekodkan peristiwa ini. Pantau daemon secara langsung:

docker events --filter event=oom

Kemudian baca log kernel, iaitu 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 ialah 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 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 tinggi daripada keadaan sebenar

Angka MEM USAGE dalam docker stats merangkumi page cache, jadi kontena yang membaca fail bersaiz besar akan 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 dengan had maksimumnya atas sebab ini.

Bahagikan angka tersebut kepada cache dan working set 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 working set yang tidak boleh dibuang. file ialah page cache, yang boleh dibuang. Tetapkan 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, 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 hari di mana dua servis mencapai kemuncak penggunaan secara serentak.

Pembahagian yang praktikal pada pelayan 8GB:

  • Reverse proxy: had 128m. Ia merupakan proses yang kecil, dan had yang ketat ini dapat mengesan konfigurasi yang bermasalah semasa reload 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 digunakan sebagai page cache.

Jangan salin nombor tersebut ke dalam stack anda sendiri. Jalankan servis di bawah beban sebenar selama sehari, pantau docker stats, ambil nilai puncak anon bagi setiap kontena dan tambahkan kira-kira separuh lagi sebagai ruang tambahan (headroom). Had yang terlalu ketat adalah lebih buruk daripada tiada had, kerana ia akan mematikan servis yang sihat semasa lonjakan trafik biasa.

Satu perangkap memerlukan perhatian khusus. Had tersebut tidak kelihatan kepada kebanyakan runtime melainkan anda memberitahu mereka mengenainya. PostgreSQL akan dengan gembira menetapkan saiz shared_buffers dan work_mem melebihi had kontena dan akhirnya akan dimatikan. JVM (Java virtual machine) memerlukan -XX:MaxRAMPercentage=75 untuk menetapkan saiz heap berdasarkan had cgroup dan bukannya daripada RAM hos. Node.js memerlukan --max-old-space-size dalam megabait, ditetapkan di bawah had kontena, atau pengutip sampah (garbage collector) akan membiarkan heap berkembang sehingga kernel campur tangan. Ollama juga mengalami situasi yang sama dengan tetapan 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 tersebut.

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, iaitu 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 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 merupakan punca biasa, tetapi tamat masa penutupan (shutdown timeout) juga menghasilkan kod yang sama apabila aplikasi mengabaikan SIGTERM. Jalankan docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> untuk membezakan kedua-duanya. true 137 ialah pembunuhan 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.

Mengapakah kontena saya berada pada had memori penuh tanpa dimatikan?

Angka penggunaan dalam docker stats merangkumi 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 7GB yang selebihnya. 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.