CPU ditunda, memori dibunuh, dan jumlah proses ditolak — tiga resource kini punya pagarnya masing-masing. Namun ada satu jalur yang setiap container lintasi puluhan ribu kali per detik dan belum menyentuh satu angka pun: perangkat blok. Setiap baris log yang ditulis driver json-file, setiap layer image yang dibaca saat container menyala, setiap checkpoint database yang ditanam — semuanya berbaris di antrean disk yang sama, dan satu container rakus di barisan itu cukup untuk membuat seluruh tetangganya menunggu. Kita sudah sering melihat gejalanya tanpa memberinya nama: kolom BLOCK I/O pada docker stats yang membesar terus sejak container menyala, sebagaimana kita bedah di chapter Docker Stats. Chapter ini memberi nama itu anggarannya: empat flag pembatas blok yang menahan laju byte dan jumlah operasi per detik, dibuktikan dengan dd yang sama besarnya selesai dalam 0,2 detik tanpa rem dan sekitar 20 detik dengan rem 10 MB per detik.
Empat Flag Pembatas Block IO #
Pembatas blok bekerja dengan logika sederhana: identifikasi perangkat fisik tempat data mendarat, lalu kunci laju container terhadap perangkat itu. Ada empat pintu, dan semuanya ditulis dengan format perangkat:nilai:
| Flag | Yang dibatasi | Contoh nilai |
|---|---|---|
--device-read-bps | laju baca byte per detik | /dev/sda:10mb |
--device-write-bps | laju tulis byte per detik | /dev/sda:10mb |
--device-read-iops | jumlah operasi baca per detik | /dev/sda:1000 |
--device-write-iops | jumlah operasi tulis per detik | /dev/sda:1000 |
Dua flag pertama menerima satuan kb, mb, dan gb (kelipatan 1024) untuk membatasi throughput — berapa banyak byte yang boleh mengalir. Dua flag terakhir menerima bilangan bulat operasi per detik untuk membatasi IOPS — berapa sering perangkat boleh disentuh; pada SSD dan disk cloud bersama, jumlah operasi kecil yang sering justru lebih melelahkan daripada sekadar volume besar yang jarang, dan di situlah pasangan iops berperan. Keempatnya boleh dipadukan bebas: satu container bisa membaca leluasa tetapi menulis ter-rem, atau keduanya dibatasi dengan angka yang berbeda.
Perangkat yang Mana lsblk Dulu #
Flag ini melekat pada perangkat blok host tempat data benar-benar ditanam — bukan pada path di dalam container. Karena itu langkah pertama selalu mengenali nama perangkat di mesin tempat daemon berjalan, dan nama itu berbeda antar host: cakram SATA lazim bernama /dev/sda, sementara NVMe modern bernama /dev/nvme0n1:
$ lsblk -o NAME,MAJ:MIN,SIZE,TYPEOutput yang Dihasilkan:
NAME MAJ:MIN SIZE TYPE
sda 8:0 476G disk
├─sda1 8:1 512M part /boot
└─sda2 8:2 475.5G part /
nvme0n1 259:0 931.5G disk
└─nvme0n1p1 259:1 931G part /dataMari kita telaah keluaran yang menentukan flag kita:
- Kolom
MAJ:MINadalah nomor perangkat versi kernel — mayor:minor;/dev/sdaadalah8:0,/dev/nvme0n1adalah259:0, dan pasangan angka inilah yang nanti muncul kembali didocker inspect. - Mesin latihan kita menaruh sistem berkas root di
sda2— partisi kedua cakram pertama;/var/lib/dockertempat layer, volume, dan log container berada ikut menumpuk di sana, sehingga--device-write-bps /dev/sda:...berarti merem seluruh penulisan container ke cakram utama ini. - Cakram kedua menampung data di
nvme0n1p1— volume database yang di-mount dari sana tidak tersentuh rem cakram pertama; inilah cara berpikir yang benar: rem dipasang per perangkat, bukan per container semata.
Demo dd 200 MB dengan dan tanpa Rem #
Saatnya mengukur. Alat ukurnya dd dari busybox alpine: menulis 200 MB dari /dev/zero ke berkas di dalam container — beban tulis murni ke rootfs, yang di mesin kita mendarat di cakram utama. Tanpa rem lebih dulu:
$ docker run --rm alpine:3.20 dd if=/dev/zero of=/bigfile bs=1M count=200Output yang Dihasilkan:
200+0 records in
200+0 records out
209715200 bytes (200MB) copied, 0.21s, 998MB/sSekarang versi yang sama dengan rem 10 MB per detik pada perangkat tulisnya:
$ docker run --rm --device-write-bps /dev/sda:10mb alpine:3.20 dd if=/dev/zero of=/bigfile bs=1M count=200Output yang Dihasilkan (angka ilustratif):
200+0 records in
200+0 records out
209715200 bytes (200MB) copied, 20.6s, 10.2MB/sMari kita telaah dua laporan yang kontras itu:
209715200adalah 200 × 1024² — ukuran yang sama persis di kedua percobaan; yang berubah bukan pekerjaannya, melainkan kecepatan jalannya.- Tanpa rem,
ddmelapor hampir 1 GB per detik — penulisan ter-buffer lewat page cache kernel dan cakram modern memang kencang; container polos tidak mengenal kata antre. - Dengan rem, durasi melonjak menjadi sekitar 20 detik — aritmetikanya polos: 200 MB dibagi 10 MB per detik; kernel menahan penulisan di lapisan blok agar laju rata-rata tidak pernah lewat angka rem, dan
ddterpaksa menunggu antreannya dipanggil kasir. - Angka absolutnya ilustratif, polanya tidak —
ddmenulis lewat page cache, jadi angka persis bergantung pada tekanan memori dan kebiasaan flush host; varianoflag=directmelewati cache agar rem terasa lebih konsisten. Pelajarannya tetap satu: dengan rem, laju rata-rata ke perangkat terkunci di bawah nilai yang kita tulis.
Kolom BLOCK I/O sebagai Kamera Kumulatif #
Kolom BLOCK I/O pada docker stats mencatat byte baca dan tulis container secara kumulatif sejak menyala — sifat yang sudah kita bedah di chapter Docker Stats dan yang membuat kolom ini cocok memverifikasi rem dari luar. Kita hidupkan penulis ber-rem sebagai container bernama, lalu memotretnya di tengah pekerjaan:
$ docker run -d --name pemburu-io --device-write-bps /dev/sda:10mb alpine:3.20 dd if=/dev/zero of=/bigfile bs=1M count=200Output yang Dihasilkan:
3f9d81c2a4e7b0d5f8a2c6e9b1d4f7a0c3e8b5d2f9a1c4e6b8d0f3a5c7e9b2d4$ sleep 6 && docker stats --no-stream pemburu-ioOutput yang Dihasilkan:
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
3f9d81c2a4e7 pemburu-io 1.9% 12.4MiB / 7.63GiB 0.16% 0B / 0B 0B / 61.3MB 1Mari kita telaah foto enam detik itu:
BLOCK I/Omembaca0B / 61.3MB— format kolomnyabaca / tulis; container ini tidak membaca apa pun dari cakram (sumbernya/dev/zero, sebuah perangkat memori), sementara tulisannya sudah menumpuk 61,3 MB.- 61,3 MB dalam 6 detik berarti sekitar 10 MB per detik — persis angka rem; kamera luar dan rem dalam saling menguatkan.
- Pekerjaan 200 MB itu akan selesai sekitar detik ke-20 — angka kumulatif terus naik dengan kemiringan yang dikunci rem sampai
ddusai dan container berhenti sendiri.
Sebelum panggung dibersihkan, kita baca jejak remnya di metadata:
$ docker inspect --format '{{.HostConfig.BlkioDeviceWriteBps}}' pemburu-ioOutput yang Dihasilkan:
[{8:0 10485760}]$ docker rm -f pemburu-ioOutput yang Dihasilkan:
pemburu-ioMari kita telaah satu baris itu:
8:0adalah pasangan mayor:minor/dev/sda— angka yang sama dengan kolomMAJ:MINpadalsblkdi awal chapter; Docker menyimpan rem dalam kosakata kernel, bukan nama path.10485760adalah 10 × 1024 × 1024 — nilai10mbdalam byte polos, konversi yang sama seperti flag memori.- Flag iops meninggalkan jejak serupa di field tetangga —
BlkioDeviceReadBps,BlkioDeviceReadIOps, danBlkioDeviceWriteIOpsmenyimpan pasangan perangkat-nilai dengan bentuk yang identik.
Kapan Rem Ini Dipasang #
Rem blok bukan perlengkapan wajib setiap container — ia obat untuk penyakit yang sudah terdiagnosis. Tiga skenario paling sering menjumpainya. Pertama, batch job yang mengebiri disk utama: pekerja konversi video Budi dari chapter Resource Limit menulis berkas raksasa di jam kerja; dengan rem tulis, pekerjaan itu tetap selesai — hanya lebih lama — sementara toko-backend yang berbagi cakram tidak ikut terseret. Kedua, container yang log-riuh: aplikasi yang menulis log baris demi baris lewat driver json-file menumpuk penulisan ke /var/lib/docker di cakram yang sama dengan rootfs semua container; rem tulis menjinakkannya, dan idealnya dipadukan rotasi max-size yang kita bahas pada chapter Konfigurasi Docker Engine. Ketiga, host cloud dengan disk bersama: IOPS adalah komoditas berbayar yang disita penyewa lain, dan batas iops menjaga satu container tidak menguras kuota seluruh mesin.
Batas Jujur dari Pembatas Ini #
Kejujuran teknis menuntut kita mengakui apa yang TIDAK dikendalikan flag ini. Pembatas bekerja per perangkat blok — bukan per berkas, bukan per direktori, dan bukan per sistem berkas: container yang rootfs-nya overlay di /var/lib/docker pada cakram utama akan mendapati seluruh aksesnya — layer image yang dibaca, berkas yang ditulis, volume yang tersimpan di sana — ikut terhitung dalam satu rem yang sama. Karena semua container di host yang sama menumpuk rootfs di cakram yang sama, memasang rem pada cakram utama untuk satu container tidak menghalangi tetangga lain; rem milik masing-masing container dihitung terpisah, tetapi antrean fisik cakram tetap satu. Sistem berkas jaringan semacam NFS tidak tersentuh sama sekali karena bukan perangkat blok lokal, dan sebagian perangkat virtual di lingkungan cloud menerjemahkan rem dengan cara yang berbeda dari cakram fisik.
Satu lapisan tetangga yang perlu dikenal namanya: io scheduler kernel — komponen semacam mq-deadline, bfq, atau none yang mengatur keadilan antar proses di antrean satu perangkat. Rem cgroup memotong laju sebelum antrean; scheduler mengatur siapa yang didahulukan di dalamnya. Docker tidak mengganti scheduler dan kita hampir tidak pernah perlu — biarkan bawaan distro bekerja kecuali workload-nya sungguh khusus. Yang perlu diingat hanyalah bahwa gejala “disk terasa tidak adil” bisa berasal dari dua lapisan yang berbeda, dan rem cgroup hanya memegang salah satunya.
Resep Ringkas untuk Rem IO #
| Situasi | Resep |
|---|---|
| batch export/backup besar di jam kerja | --device-write-bps /dev/sda:50mb agar pekerjaan menyebar sepanjang waktu |
| aplikasi log-riuh di cakram utama | --device-write-bps /dev/sda:5mb dipadukan rotasi log max-size |
| container membaca dataset besar dari cakram data | --device-read-bps /dev/nvme0n1:100mb menjaga cakram data tetap lega |
| melindungi kuota IOPS disk bersama | --device-read-iops /dev/sda:1000 --device-write-iops /dev/sda:1000 |
Benang merah seluruh resep itu sama: rem memperlambat yang boleh lambat agar yang tidak boleh lambat tetap cepat — kebalikan langsung dari filosofi “tambah cakram baru saja”, dan jauh lebih murah.
Empat resource kini lengkap: kuota CPU di CPU Limit dan Reservation, langit-langit memori dan swap di Memory Limit dan Swap, slot proses di PIDs Limit dan Ulimit, dan rem blok di chapter ini. Semua angka itu dipasang saat container lahir, dan di situlah letak masalahnya yang terakhir: angka yang benar pagi bisa terbukti salah sore — kampanye diskon mendatangkan trafik yang tidak masuk perkiraan, dan batas di berkas mendadak terasa sempit. Mengubah angka dengan cara klasik menuntut container dibuang dan dibuat ulang, sesuatu yang mahal di tengah jam sibuk. Chapter berikutnya, Docker Update, memperkenalkan jalur pintas yang dijaga kernel: docker update yang menulis ulang batas CPU, memori, dan PIDs pada container yang sedang berjalan — tanpa restart, tanpa recreate, dan dengan peringatan penting tentang drift dari berkas compose yang menyertainya.
Referensi #
- docker container run
— sintaks empat flag
--device-*bpsdan--device-*iopsbeserta satuan nilainya. - docker container stats
— arti kolom
BLOCK I/Okumulatif yang dipakai memverifikasi rem. - docker container inspect
— field
HostConfig.BlkioDeviceWriteBpsdan sanak-saudaranya untuk audit konfigurasi.