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:

FlagYang dibatasiContoh nilai
--device-read-bpslaju baca byte per detik/dev/sda:10mb
--device-write-bpslaju tulis byte per detik/dev/sda:10mb
--device-read-iopsjumlah operasi baca per detik/dev/sda:1000
--device-write-iopsjumlah 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:

bash
$ lsblk -o NAME,MAJ:MIN,SIZE,TYPE

Output yang Dihasilkan:

text
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 /data

Mari kita telaah keluaran yang menentukan flag kita:

  1. Kolom MAJ:MIN adalah nomor perangkat versi kernel — mayor:minor; /dev/sda adalah 8:0, /dev/nvme0n1 adalah 259:0, dan pasangan angka inilah yang nanti muncul kembali di docker inspect.
  2. Mesin latihan kita menaruh sistem berkas root di sda2 — partisi kedua cakram pertama; /var/lib/docker tempat 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.
  3. 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:

bash
$ docker run --rm alpine:3.20 dd if=/dev/zero of=/bigfile bs=1M count=200

Output yang Dihasilkan:

text
200+0 records in
200+0 records out
209715200 bytes (200MB) copied, 0.21s, 998MB/s

Sekarang versi yang sama dengan rem 10 MB per detik pada perangkat tulisnya:

bash
$ docker run --rm --device-write-bps /dev/sda:10mb alpine:3.20 dd if=/dev/zero of=/bigfile bs=1M count=200

Output yang Dihasilkan (angka ilustratif):

text
200+0 records in
200+0 records out
209715200 bytes (200MB) copied, 20.6s, 10.2MB/s

Mari kita telaah dua laporan yang kontras itu:

  1. 209715200 adalah 200 × 1024² — ukuran yang sama persis di kedua percobaan; yang berubah bukan pekerjaannya, melainkan kecepatan jalannya.
  2. Tanpa rem, dd melapor hampir 1 GB per detik — penulisan ter-buffer lewat page cache kernel dan cakram modern memang kencang; container polos tidak mengenal kata antre.
  3. 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 dd terpaksa menunggu antreannya dipanggil kasir.
  4. Angka absolutnya ilustratif, polanya tidak — dd menulis lewat page cache, jadi angka persis bergantung pada tekanan memori dan kebiasaan flush host; varian oflag=direct melewati 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:

bash
$ docker run -d --name pemburu-io --device-write-bps /dev/sda:10mb alpine:3.20 dd if=/dev/zero of=/bigfile bs=1M count=200

Output yang Dihasilkan:

text
3f9d81c2a4e7b0d5f8a2c6e9b1d4f7a0c3e8b5d2f9a1c4e6b8d0f3a5c7e9b2d4
bash
$ sleep 6 && docker stats --no-stream pemburu-io

Output yang Dihasilkan:

text
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   1

Mari kita telaah foto enam detik itu:

  1. BLOCK I/O membaca 0B / 61.3MB — format kolomnya baca / tulis; container ini tidak membaca apa pun dari cakram (sumbernya /dev/zero, sebuah perangkat memori), sementara tulisannya sudah menumpuk 61,3 MB.
  2. 61,3 MB dalam 6 detik berarti sekitar 10 MB per detik — persis angka rem; kamera luar dan rem dalam saling menguatkan.
  3. Pekerjaan 200 MB itu akan selesai sekitar detik ke-20 — angka kumulatif terus naik dengan kemiringan yang dikunci rem sampai dd usai dan container berhenti sendiri.

Sebelum panggung dibersihkan, kita baca jejak remnya di metadata:

bash
$ docker inspect --format '{{.HostConfig.BlkioDeviceWriteBps}}' pemburu-io

Output yang Dihasilkan:

text
[{8:0 10485760}]
bash
$ docker rm -f pemburu-io

Output yang Dihasilkan:

text
pemburu-io

Mari kita telaah satu baris itu:

  1. 8:0 adalah pasangan mayor:minor /dev/sda — angka yang sama dengan kolom MAJ:MIN pada lsblk di awal chapter; Docker menyimpan rem dalam kosakata kernel, bukan nama path.
  2. 10485760 adalah 10 × 1024 × 1024 — nilai 10mb dalam byte polos, konversi yang sama seperti flag memori.
  3. Flag iops meninggalkan jejak serupa di field tetangga — BlkioDeviceReadBps, BlkioDeviceReadIOps, dan BlkioDeviceWriteIOps menyimpan 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 #

SituasiResep
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 #