Batas Memori Docker Compose agar Container Tak OOM
Pelajari cara menetapkan batas memori dan CPU Docker Compose dengan deploy.resources atau mem_limit, memahami exit 137, swap, dan ukuran VPS yang tepat.
Batas memori Docker Compose
Batas memori Docker Compose adalah batas maksimum yang diterapkan kernel Linux pada cgroup (control group, fitur kernel yang mengukur penggunaan resource untuk sekumpulan proses) milik satu container. Tetapkan deploy.resources.limits.memory pada sebuah service, dan container tersebut tidak akan pernah dapat menggunakan memori melebihi nilai yang ditulis. Jika mencoba melampauinya, kernel akan menghentikan sebuah proses di dalam container, dan container biasanya keluar dengan kode 137.
Hal ini paling penting pada VPS, karena jumlah RAM tetap dan tidak ada memori host cadangan yang dapat digunakan. Satu container dengan memory leak atau query yang buruk dapat menghabiskan setiap page yang tersedia pada server 8GB. Kernel kemudian menghentikan proses yang dinilainya paling bermasalah. Proses tersebut sering kali merupakan database atau sesi SSH Anda, bukan container yang menyebabkan masalah. Batas memori mengubah gangguan seluruh server menjadi masalah pada satu service yang dapat di-restart.
services:
app:
image: ghcr.io/example/app:1.4
deploy:
resources:
limits:
cpus: "1.5"
memory: 1g
reservations:
memory: 256mTerapkan pengaturan tersebut dan pastikan batasnya aktif:
docker compose up -d
docker stats --no-streamKolom MEM USAGE / LIMIT seharusnya menampilkan nilai seperti 142MiB / 1GiB. Jika kolom batas menampilkan seluruh RAM host, pengaturan belum diterapkan. Panduan selanjutnya tidak akan membantu sampai pengaturan tersebut aktif. Jika file compose masih baru bagi Anda, dasar-dasar Docker Compose untuk VPS menjelaskan struktur file yang digunakan di sini.
deploy.resources.limits atau mem_limit: mana yang berlaku
Ada dua penulisan untuk gagasan yang sama, sehingga hal ini membingungkan.
mem_limit, mem_reservation, memswap_limit, cpus, dan cpu_shares adalah kunci service tingkat teratas yang diwarisi dari format file Compose versi lama. deploy.resources berasal dari skema Swarm dan kini menjadi bagian dari Compose Specification, yaitu format yang dibaca docker compose saat ini.
Keduanya dapat digunakan pada satu host. Compose V2, yaitu plugin docker compose, menerapkan deploy.resources.limits dan deploy.resources.reservations saat Anda menjalankan docker compose up, tanpa cluster Swarm apa pun. Bagian khusus Swarm dalam blok deploy adalah kunci lainnya: mode, placement, update_config, dan endpoint_mode memiliki arti bagi docker stack deploy, tetapi diabaikan oleh docker compose up. Jadi, anggapan umum bahwa "deploy memerlukan Swarm" salah untuk subseksi resources. Jika mengikuti anggapan tersebut, service Anda tidak memiliki batas sama sekali.
Pilih satu penulisan untuk setiap project. Menulis mem_limit: 512m dan deploy.resources.limits.memory: 1g pada service yang sama menghasilkan file yang sulit dipahami sekilas. Jangan menebak angka mana yang berlaku. Tanyakan langsung kepada daemon:
docker inspect --format '{{.HostConfig.Memory}} {{.HostConfig.MemoryReservation}} {{.HostConfig.NanoCpus}}' app-1Nilai memori menggunakan satuan byte, sehingga 1g ditampilkan sebagai 1073741824. CPU menggunakan satuan nano CPU, sehingga 1.5 ditampilkan sebagai 1500000000. Nilai 0 pada field mana pun berarti tidak ada batas yang ditetapkan. Batas memori terkecil yang diterima Docker adalah 6m. Di bawah nilai tersebut, container tidak dapat dijalankan.
Apa yang terjadi ketika container mencapai batas
Container tidak melambat. Container berhenti.
Ketika suatu proses meminta page dan cgroup sudah mencapai memory.max, kernel terlebih dahulu mereklamasi resource yang dapat diambil dari dalam cgroup tersebut: page cache yang bersih, lalu page yang dapat di-swap. Jika reclaim tidak membebaskan cukup banyak resource, OOM (out of memory) killer pada cgroup memilih suatu proses di dalam container dan mengirimkan SIGKILL. Jika PID 1 milik container dihentikan, container ikut berhenti. Exit code 137 hanyalah 128 ditambah signal 9. Jadi, 137 merupakan tanda SIGKILL apa pun, bukan bukti OOM dengan sendirinya.
docker compose ps -a
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' app-1true 137 berarti terjadi OOM kill. false 137 berarti ada pihak lain yang mengirim SIGKILL. Penyebab yang umum adalah docker compose stop mencapai grace period sepuluh detik karena aplikasi mengabaikan SIGTERM. Perbedaan ini dapat menghemat waktu berjam-jam karena kedua masalah tersebut tidak berkaitan.
Ada dua tempat lain yang mencatat peristiwa ini. Pantau daemon secara langsung:
docker events --filter event=oomKemudian baca log kernel, yaitu catatan yang tetap tersedia setelah restart:
sudo dmesg -T | grep -i -E 'memory cgroup out of memory|killed process'Cgroup kill mencetak baris yang diawali Memory cgroup out of memory: Killed process 24713 (node). Baris tanpa prefix Memory cgroup menunjukkan host OOM. Artinya, mesin itu sendiri kehabisan RAM. Kondisi inilah yang seharusnya dicegah oleh limit. Jika kondisi ini muncul, berarti jumlah seluruh limit terlalu tinggi atau ada service yang sama sekali tidak memiliki limit.
Dengan restart: unless-stopped, loop OOM sulit terlihat karena service tampak berjalan pada docker compose ps satu detik setelah berhenti. Periksa kolom uptime dan jumlah restart. Pasangkan limit tersebut dengan healthcheck yang melaporkan aplikasi sebagai tidak sehat agar container yang terus berhenti dapat terlihat tanpa harus terus memantaunya.
Reservation adalah petunjuk, limit adalah aturan
reservations.memory (mem_reservation versi lama) adalah batas bawah lunak. Docker menjelaskannya sebagai soft limit yang diaktifkan ketika daemon mendeteksi contention atau memori rendah pada host. Reservation tidak pernah menghentikan container untuk melebihinya, dan tidak pernah menjamin bahwa memori akan tersedia saat container memintanya. Reservation hanya mengarahkan kernel untuk terlebih dahulu mengambil kembali memori dari container yang berada di atas reservation.
Jadi, reservation tidak melindungi apa pun dengan sendirinya. Gunakan reservation untuk menandai service yang ingin diperlakukan dengan prioritas saat terjadi tekanan, dan andalkan limit untuk keamanan. Pertahankan reservation di bawah limit. Jika tidak, container tidak akan berjalan: Docker menolak konfigurasi dengan Minimum memory limit can not be less than memory reservation limit.
Penghitungan swap secara tepat
Sebagian besar image VPS tidak menyediakan file swap sama sekali. Jalankan swapon --show dan free -h. Jika total swap bernilai nol, semua pengaturan terkait swap di bawah tidak berpengaruh, dan batas memori Anda hanya membatasi RAM.
memswap_limit bukan jumlah swap. Nilai ini adalah total memori dan swap. Dengan mem_limit: 1g dan memswap_limit: 2g, container memperoleh 1GB RAM dan 1GB swap. Jika kedua nilai tersebut disamakan, container tidak memperoleh swap sama sekali. Dengan menetapkan mem_limit dan membiarkan memswap_limit tidak diatur, container dapat menggunakan swap hingga sebesar batas memorinya lagi.
Ubuntu 24.04 dan Debian 13 secara default menggunakan cgroup v2. Pada versi ini, swap memiliki penghitung terpisah (memory.swap.max), dan konfigurasi ini berfungsi tanpa pengaturan tambahan. Pesan lama Your kernel does not support swap limit capabilities berasal dari host cgroup v1 yang melakukan boot tanpa swapaccount=1. Pada host tersebut, batas memori tetap berlaku, sedangkan bagian swap diabaikan.
Pahami manfaat swap secara realistis. Swap membuat proses OOM kill berlangsung lebih lambat, bukan membuatnya lebih kecil kemungkinannya terjadi, karena proses yang mengalami kebocoran memori akan memenuhi swap sama mudahnya seperti memenuhi RAM. Sementara itu, container yang terus-menerus melakukan swap pada penyimpanan VPS bersama akan memperlambat semua service lain di server. Untuk beban kerja yang sensitif terhadap latensi, batas yang tepat tanpa swap akan gagal lebih cepat dan lebih mudah diprediksi.
Tampilan penggunaan memori yang lebih buruk daripada kondisi sebenarnya
Angka MEM USAGE dalam docker stats mencakup page cache. Karena itu, container yang membaca file berukuran besar akan terus mendekati batasnya lalu bertahan di sana. Kondisi ini normal dan bukan kebocoran memori, karena cache bersih akan direklamasi sebelum OOM killer dipanggil. Service seperti server media Jellyfin yang di-host sendiri akan terlihat terus-menerus mendekati batasnya karena alasan tersebut.
Pisahkan angka tersebut menjadi cache dan working set aktual dari dalam container:
docker compose exec app grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat
docker compose exec app cat /sys/fs/cgroup/memory.eventsanon adalah memori anonim, yaitu working set yang tidak dapat dibuang. file adalah page cache yang dapat direklamasi. Tetapkan batas berdasarkan anon ditambah margin, bukan berdasarkan totalnya. File memory.events memberikan kepastian: nilai counter oom_kill di atas nol berarti kernel telah menghentikan sesuatu dalam container ini sejak container tersebut dimulai, sedangkan counter max yang terus meningkat berarti container sedang ditahan pada batasnya saat ini. Kedua perintah memerlukan shell dan coreutils di dalam image, sehingga tidak dapat dijalankan pada image distroless atau scratch.
Batas ukuran pada VPS 8GB
Mulai dari host, bukan dari aplikasi. Pada VPS 8GB, sisakan sekitar 1GB untuk kernel, daemon Docker, sshd, journald, dan shell login Anda. Dengan demikian, tersisa sekitar 7GB untuk dibagikan, dan jumlah seluruh batas container harus tetap di bawah angka tersebut. Overcommit dapat berjalan baik sampai dua service mencapai penggunaan puncak secara bersamaan.
Pembagian yang dapat digunakan pada server 8GB:
- Reverse proxy: batas 128m. Prosesnya kecil, dan batas yang ketat seperti ini segera mendeteksi reload konfigurasi yang tidak terkendali.
- PostgreSQL: batas 2g, dengan
shared_buffersditetapkan sekitar 512MB dalam konfigurasi database. - Container aplikasi: batas 1g.
- Worker latar belakang: batas 512m.
- Service media atau file: batas 2g, yang sebagian besar akan digunakan sebagai page cache.
Jangan menyalin angka tersebut ke stack Anda sendiri. Jalankan service dalam beban nyata selama sehari, pantau docker stats, ambil nilai puncak anon untuk setiap container, lalu tambahkan sekitar setengahnya sebagai headroom. Batas yang terlalu ketat lebih buruk daripada tanpa batas, karena dapat menghentikan service yang sehat saat terjadi lonjakan trafik normal.
Ada satu jebakan yang perlu dibahas secara khusus. Sebagian besar runtime tidak mengetahui batas tersebut kecuali Anda memberitahukannya. PostgreSQL dapat dengan mudah menetapkan ukuran shared_buffers dan work_mem melebihi batas containernya, lalu prosesnya dihentikan. JVM (Java virtual machine) memerlukan -XX:MaxRAMPercentage=75 agar dapat menetapkan ukuran heap berdasarkan batas cgroup, bukan berdasarkan RAM host. Node.js memerlukan --max-old-space-size dalam megabyte dan nilainya harus ditetapkan di bawah batas container. Jika tidak, garbage collector akan membiarkan heap bertambah sampai kernel melakukan intervensi. Ollama menghadapi masalah yang sama dengan parameter berbeda, karena menaikkan num_ctx memperbesar cache KV hingga ratusan megabyte dan container dapat dihentikan di tengah prompt yang panjang. cgroup tidak melakukan negosiasi. cgroup menghentikannya.
Batas CPU berperilaku sangat berbeda
cpus: "1.5" berarti 150% dari satu core, yang diberlakukan sebagai kuota CFS (completely fair scheduler). Container mendapatkan waktu CPU selama 150ms dalam setiap periode 100ms, yang dibagi di antara semua thread-nya. Setelah waktu tersebut habis, kernel membuatnya menunggu hingga periode berikutnya.
Inilah perbedaan pentingnya. Container yang melampaui batas memorinya akan dihentikan. Container yang melampaui batas CPU akan dikenai throttling dan tetap berjalan, tetapi lebih lambat. Karena itu, batas CPU aman untuk ditetapkan secara agresif, sedangkan batas memori memerlukan ruang cadangan.
cpu_shares adalah alat yang berbeda: bobot relatif yang hanya berpengaruh ketika CPU benar-benar jenuh. Dua container dengan shares sebesar 1024 dan 512 akan membagi core yang sibuk dengan perbandingan kira-kira dua banding satu. Pada host yang idle, keduanya tidak dibatasi. Gunakan shares untuk menentukan prioritas layanan berdasarkan kepentingannya, dan gunakan cpus jika memerlukan batas maksimum yang nyata, misalnya untuk mencegah pekerjaan transcoding malam hari menghabiskan kapasitas CPU sehingga web server kekurangan sumber daya.
FAQ
Apakah deploy.resources.limits berfungsi tanpa Docker Swarm?
Ya. Compose V2 menerapkan deploy.resources.limits dan deploy.resources.reservations saat Anda menjalankan docker compose up pada satu host. Konfirmasikan dengan docker inspect --format '{{.HostConfig.Memory}}' <container>, yang mencetak batas dalam byte dan mencetak 0 jika tidak ada batas yang diterapkan. Kunci di dalam deploy yang benar-benar memerlukan Swarm adalah mode, placement, update_config, dan endpoint_mode.
Apa arti kode keluar 137 di Docker Compose?
Artinya proses utama menerima SIGKILL, karena 137 adalah 128 ditambah sinyal 9. OOM killer pada kernel adalah penyebab yang umum, tetapi batas waktu shutdown menghasilkan kode yang sama ketika aplikasi mengabaikan SIGTERM. Jalankan docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}}' <container> untuk membedakan keduanya. true 137 adalah penghentian karena kekurangan memori, sedangkan false 137 bukan.
Sebaiknya saya menetapkan mem_limit atau deploy.resources.limits.memory?
Keduanya berfungsi dengan docker compose. deploy.resources.limits.memory adalah bentuk Compose Specification saat ini dan merupakan pilihan default yang lebih baik untuk file baru. Pertahankan mem_limit jika bagian lain dari file Anda sudah menggunakan kunci tingkat teratas yang lebih lama. Menetapkan keduanya pada satu service hanya membuat file lebih sulit dibaca, jadi pilih salah satu dan verifikasi hasilnya dengan docker inspect.
Mengapa container saya menggunakan batas memori penuhnya tanpa dihentikan?
Angka penggunaan dalam docker stats mencakup page cache, yang akan dilepas oleh kernel saat terjadi tekanan memori, bukan memicu OOM kill. Jalankan docker compose exec <service> grep -E '^(anon|file) ' /sys/fs/cgroup/memory.stat dan baca nilai anon, yaitu working set yang tidak dapat direklamasi. Nilai file yang tinggi di samping nilai anon yang rendah menunjukkan bahwa container sedang melakukan input dan output disk, bukan akan segera dihentikan.
Berapa banyak RAM yang harus saya biarkan tidak dialokasikan pada VPS 8GB?
Sisakan sekitar 1GB untuk kernel, Docker daemon, sshd, journald, dan shell Anda sendiri, lalu pertahankan jumlah seluruh batas container di bawah sisa 7GB. Pantau nilai puncak anon untuk setiap container di bawah beban nyata selama satu hari sebelum menetapkan angkanya, dan perlakukan total tersebut sebagai anggaran, bukan target yang harus dihabiskan.