Container web kini bangkit sendiri setelah crash dan ikut bangkit setelah reboot — janji Restart Policy sudah terbayar. Tetapi kebangkitan hanya mengurus waktu, belum mengurus tempat: seluruh container kita berbagi satu kernel, satu tumpukan core CPU, dan satu batang RAM yang sama. Chapter ini membuka wilayah yang selama ini kita diamkan saja: apa yang terjadi ketika satu container rakus menggerogoti jatah tetangganya, siapa yang berhak menghukum, dan bagaimana Docker menuliskan aturan itu ke mekanisme kernel bernama cgroups. Ini adalah chapter konsep — kita akan memahami mengapa dan apa saja perangkatnya dulu, sementara bedah flag satu per satu beserta uji OOM killer yang sesungguhnya menanti pada chapter berikutnya.
Masalah Tetangga yang Berisik #
Kita mulai dari penyakit yang paling sering dialami host bersama, dan namanya cukup warna untuk dikenang: noisy neighbor. Budi mengalami persis skenarionya pada laptop kerjanya — web untuk myapp berjalan damai di samping container pekerja konversi video yang ia jalankan sesekali. Begitu pekerjaan konversi dimulai, container konversi memakan seluruh core CPU yang bisa dijangkau, dan web yang kebagian sisa mulai tersiksa: responsnya melambat, healthcheck-nya gagal, dan pengguna melihat halaman yang lama dimuat. Tidak ada container yang crash, tidak ada exit code yang mencurigakan — semua pihak “sehat”, hanya saja satu pihak merampas seluruh ruang makan.
Akar masalahnya bukan bug, melainkan sifat dasar container yang sudah kita kenal sejak Chapter Apa itu Docker?: container adalah proses Linux biasa. Proses biasa tidak punya tata krama bawaan — selama kernel tidak memberinya batas, sebuah proses bebas mencicipi setiap core dan setiap halaman memori yang tersedia. Docker tidak menambahkan tata krama itu secara otomatis; secara bawaan, docker run melahirkan proses tanpa satu pun pembatasan resource. Kebijaksanaan itu masuk akal untuk kepraktisan, tetapi berbahaya begitu lebih dari satu container berbagi satu host.
Tiga Alasan Membatas Resource #
Kenapa kita harus repot-repot memasang batas padahal semuanya berjalan “cukup baik”? Setidaknya tiga alasan nyata, dan ketiganya menyasar penderita yang berbeda.
Pertama, keadilan antar tetangga — kebalikan langsung dari noisy neighbor. Batas CPU memastikan pekerja konversi video Budi paling banyak menggerus jumlah core yang kita izinkan, sehingga sisanya tetap milik web dan container lain. Kedua, keselamatan host dari kehabisan memori. Tanpa batas, container yang bocor memori akan terus makan sampai RAM host benar-benar kosong — dan pada titik itu kernel memasuki keadaan darurat: OOM killer menjalankan penilaian atas seluruh proses di host, lalu membunuh proses dengan skor terburuk. Korbannya bisa jadi container yang sama sekali tidak bersalah, bahkan bisa proses host seperti daemon SSH — pilihan yang sama sekali tidak kita rencanakan. Ketiga, biaya. Di cloud, VM dengan RAM 16 GiB yang disewa “supaya aman” sementara seluruh container hanya memakan 4 GiB adalah uang yang terbakar tiap bulan; batas per container membuat kebutuhan nyata terbaca sebagai angka, sehingga ukuran mesin bisa diturunkan dengan keyakinan.
| Alasan | Tanpa batas | Dengan batas |
|---|---|---|
| Noisy neighbor | satu container merampas CPU, tetangga tersiksa | jatah CPU terkurung per container |
| Kehabisan memori host | OOM killer menjatuhkan proses acak di seluruh host | korban terkurung di cgroup container yang rakus |
| Biaya cloud | VM oversize disewa karena takut | kebutuhan nyata terukur, ukuran mesin bisa pas |
cgroups: Penegaknya Kernel, Penulisnya Docker #
Lalu siapa yang benar-benar menegakkan batas itu? Bukan Docker — Docker hanya sang penulis aturan. Penegak sebenarnya adalah cgroups, fitur kernel Linux yang sudah kita sempat kenal sebagai salah satu dari dua fondasi isolasi container pada Chapter Arsitektur Docker: namespace membatasi apa yang sebuah proses lihat, cgroups membatasi berapa banyak yang proses itu boleh pakai. Saat kita menjalankan docker run --memory 256m, daemon menerjemahkan flag tersebut menjadi angka pada berkas cgroups milik container itu — sejak saat itu kernel-lah yang membaca angka tersebut setiap saat penjadwalan CPU terjadi dan setiap halaman memori diminta, tanpa perlu bertanya kepada Docker lagi.
Cara kernel menegakkan dua jenis resource ini berbeda drastis, dan perbedaan itu menjelaskan segala gejala yang akan kita saksikan pada chapter berikutnya. CPU bisa ditunda: proses yang melebihi kuota tidak dibunuh, hanya di-throttle — disuruh menunggu jatah waktu berikutnya, sehingga aplikasi terasa lambat tetapi tetap hidup. Memori tidak bisa ditunda: halaman yang sudah dialokasikan tidak bisa “dikembalikan sedikit demi sedikit”, sehingga saat container menabrak langit-langitnya, kernel memilih jalan tegas — membunuh proses dalam container itu lewat OOM killer. Batas CPU membuat aplikasi melambat; batas memori membuat aplikasi mati.
Limit dan Reservasi: Langit-langit dan Jaminan Kursi #
Perbendaharaan flag resource terbagi dalam dua keluarga yang sering tertukar. Limit adalah langit-langit keras: berapa pun ruang yang masih tersisa di host, container tidak boleh melewati angka itu — melanggar batas CPU berarti di-throttle, melanggar batas memori berarti dibunuh. Reservation adalah jaminan kursi yang lunak: saat host lapang, jaminan ini tidak terasa apa-apa; saat host mulai padat dan perebutan terjadi, container dengan reservasi dijanjikan porsi minimum yang sudah dipesannya. Batas yang eksplisit — angka yang kita tulis sendiri — jauh lebih jujur daripada batas implisit yang kebetulan terjadi karena “hostnya besar”.
Empat flag berikut adalah warga utama keluarga ini, dan keempatnya akan kita praktikkan satu per satu pada chapter berikutnya:
| Flag | Keluarga | Efek ringkas |
|---|---|---|
--cpus 1.5 | limit CPU | rata-rata maksimum 1,5 core — kelebihannya di-throttle |
--cpu-shares 512 | bobot CPU | porsi relatif saat host sibuk; bawaan 1024 |
--memory 256m | limit memori | langit-langit keras — ditabrak berarti OOM kill |
--memory-reservation 128m | reservasi memori | jaminan lunak saat host tertekan |
Perhatikan bahwa angka limit dan reservasi boleh — dan lazim — dipasangkan: --memory 256m --memory-reservation 128m berarti “jamin saya 128 MB saat sempit, tapi jangan biarkan saya melebihi 256 MB dalam keadaan apa pun”. Detail tiap flag, nilai yang terbaca di docker inspect, hingga uji OOM yang disengaja menanti chapter CPU dan Memory Limit; yang perlu kita pegang sekarang adalah peta perannya masing-masing.
OOM Killer dan Jejaknya #
Karena pembunuhan memori akan menjadi bintang chapter berikutnya, mari kita kenali dulu jejaknya — sebagian sudah pernah kita lihat. Saat kernel membunuh proses container karena memori, PID 1 tumbang oleh SIGKILL, dan container berhenti dengan exit code 137 — angka 128 + 9 yang kita bedah pada Chapter Memahami Exit Code. Yang membedakan kematian OOM dari docker kill biasa adalah satu field pada metadata container: docker inspect --format '{{.State.OOMKilled}}' menjawab true — pengakuan resmi bahwa pembunuhnya adalah kernel yang kehabisan kesabaran memori, bukan tangan manusia. Ketiga jejak ini — kode 137, field OOMKilled, dan baris pengakuan di log kernel — akan kita tangkap semuanya pada uji yang akan datang.
Melihat Batas Turun di docker stats #
Sebagai penutup konsep, mari kita saksikan betapa nyatanya “langit-langit” itu, cukup lewat satu perintah pemantauan yang akan menjadi sahabat kita sepanjang pembahasan resource. Hidupkan dulu web polos tanpa satu batas pun:
$ docker run -d --name web nginxOutput yang Dihasilkan:
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a$ docker stats --no-streamOutput yang Dihasilkan:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
f7c2a91b3e55 web 0.00% 5.281MiB / 7.63GiB 0.07% 656B / 0B 9.22MB / 0B 3Kita ganti container itu dengan kembarannya yang berbatas, lalu bandingkan:
$ docker rm -f webOutput yang Dihasilkan:
web$ docker run -d --name web --cpus 1.5 --memory 256m nginxOutput yang Dihasilkan:
f7c2a91b3e55913c4a5f1c6c0a4d8e2b3f4a5b6c7d8e9f0a1b2c3d4e5f60718a$ docker stats --no-streamOutput yang Dihasilkan:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
f7c2a91b3e55 web 0.00% 5.312MiB / 256MiB 2.07% 656B / 0B 9.22MB / 0B 3Mari kita telaah perubahan pada kolom MEM USAGE / LIMIT:
- Tampilan pertama membaca
7.63GiB— laptop Budi ber-RAM 8 GiB, dan tanpa--memory, langit-langit container sama tinggi dengan seluruh memori host; satu-satunya hal yang menghentikanwebmemakan semuanya adalah kebetulan aplikasinya ramping. - Tampilan kedua membaca
256MiB— sepuluh baris konfigurasi saja tidak berubah; yang berubah hanya dua flag padadocker run, dan angka itu ditulis cgroups sebagai batas keras milik container ini semata. MEM %ikut berubah makna — persentase kini dihitung terhadap 256 MiB, bukan terhadap host; angka pemantauan yang sama, langit-langit yang berbeda.- Batas turun tanpa aplikasi tahu apa-apa — nginx sama sekali tidak sadar bahwa dunianya mengecil; inilah kekuatan penegakan dari sisi kernel — aplikasi tidak perlu diajak kerja sama untuk dibatasi.
Bekas latihan kita bereskan sebelum menutup chapter:
$ docker rm -f webOutput yang Dihasilkan:
webKini kita memegang peta lengkap wilayah resource: masalahnya noisy neighbor dan OOM killer, penegaknya cgroups, dan perangkatnya empat flag yang terbagi limit dan reservasi. Bedah mendalam tentang cara kerja cgroups, pengelolaan resource di tingkat produksi, serta pemantauannya yang menyeluruh menanti Section Manajemen Resource — tetapi sebelum sejauh itu, kita perlu menguasai perangkat dasarnya terlebih dahulu dengan tangan sendiri. Chapter berikutnya, CPU dan Memory Limit, membedah tiap flag satu per satu: tiga cara membatasi CPU, unit-unit memori, jebakan --memory-swap, dan uji OOM killer yang disengaja dengan container bernama lapar.
Referensi #
- Runtime options with Memory, CPUs, and GPUs — halaman inti pembatasan resource: limit, reservasi, dan mekanisme OOM.
- docker container run
— daftar flag
--cpus,--cpu-shares,--memory, dan--memory-reservation. - docker container stats — pemakaian resource secara langsung beserta arti tiap kolomnya.