Peta runtime di chapter sebelumnya menempatkan containerd dan runc sebagai dua lapisan di bawah dockerd, tetapi peta tidak memperlihatkan lalu lintas. Bagaimana persisnya satu perintah di terminal berujung pada satu proses yang diisolasi kernel? Pertanyaan ini bukan kuriositas akademik: ketika daemon restart dan container tetap hidup, ketika log terus mengalir padahal CLI sudah ditutup, atau ketika debugging perlu turun satu lapisan di bawah API Docker — jawabannya selalu berada di sepanjang rantai ini. Chapter ini menelusuri perjalanan satu perintah paling sederhana yang bisa kita tulis, berhenti di lima stasiun, lalu memverifikasi bahwa stasiun-stasiun itu sungguh hidup di dalam mesin kita.

Lima Perhentian Sebuah docker run #

Perintah yang kita telusuri sengaja dipilih sekecil mungkin:

bash
$ docker run --rm busybox echo halo

Output yang Dihasilkan:

text
halo

Satu kata di balik lima perhentian:

  1. CLI ke socket dockerd — klien docker mengurai argumen, lalu mengirim permintaan REST API pembuatan serta pengaktifan container ke Unix socket /var/run/docker.sock; tidak satu baris pun pekerjaan berat terjadi di sisi klien.
  2. dockerd ke containerd lewat gRPC — daemon menerima permintaan, menyelesaikan image busybox ke digest, lalu meneruskannya ke containerd lewat socket gRPC miliknya; dockerd tidak pernah menyentuh proses container dengan tangannya sendiri.
  3. Snapshotter overlayfs menyiapkan layer — containerd membongkar layer-layer image menjadi rootfs lewat snapshotter dengan driver overlayfs — mekanisme tumpukan layer dan copy-on-write yang kita bedah di Chapter Konsep Image Layer, kali ini dikerjakan di lapisan containerd.
  4. containerd-shim mengambil peran — untuk container ini, containerd melahirkan satu proses containerd-shim-runc-v2 yang menjadi orang tua proses container; peran shim kita bedah tuntas di bagian berikutnya.
  5. runc membentuk isolasi lalu exec — shim memanggil runc dengan konfigurasi hasil serialisasi runtime-spec; runc meng-clone namespace pid, net, mnt, uts, dan ipc, menulis cgroups, lalu men-exec echo halo sebagai proses utama container; setelah proses hidup, runc selesai dan mundur.

Diagram berikut memotret kelima perhentian itu sebagai percakapan:

Mari kita telaah arti setiap panah: tiga panah penuh pertama adalah perpindahan permintaan ke bawah — CLI hanya tahu dockerd, dockerd hanya tahu containerd, dan containerd yang menunjuk shim; panah melingkar pada containerd dan runc adalah pekerjaan yang dikerjakan sendirian (penyiapan rootfs oleh snapshotter, pembentukan isolasi oleh runc); tiga panah putus-putus adalah perjalanan balik hasil — proses hidup, exit code naik dari shim ke containerd, dan keluaran akhirnya sampai ke terminal. Perhatikan bahwa CLI tidak pernah bicara langsung dengan containerd, dan dockerd tidak pernah bicara dengan runc: setiap lapisan hanya mengenal tetangganya.

Peran Shim yang Sering Terlewat #

Komponen paling awet tak terlihat di sepanjang rantai ini adalah shim, dan perannya layak dibedah sendiri. Shim adalah orang tua proses container: proses echo kita adalah anak dari containerd-shim-runc-v2, bukan anak dari dockerd ataupun containerd. Konsekuensinya mengejutkan bagi banyak orang:

Dua tugas lain menyertai perannya sebagai orang tua. Pertama, shim memelihara FIFO — pipa bernama — yang mengalirkan stdout dan stderr proses ke siapa pun yang membaca docker logs; CLI boleh ditutup, log tetap mengalir karena pemilik alirannya bukan CLI. Kedua, shim meneruskan sinyal dan exit code: docker stop mengirim SIGTERM melalui shim ke proses utama, dan exit code yang kita baca di Chapter Memahami Exit Code — 137 untuk SIGKILL, 143 untuk SIGTERM — naik dari proses ke shim, dari shim ke containerd, sampai ke API.

containerd Berdiri Sendiri #

Karena containerd adalah runtime mandiri, ia punya kehidupan di luar dockerd — dan kehidupan itu diatur lewat namespace internalnya. Container milik Docker Engine hidup di namespace moby; container milik kubelet hidup di namespace k8s.io. Satu proses containerd dapat melayani keduanya sekaligus tanpa saling melihat, dan pemisahan inilah yang memungkinkan satu mesin memelihara Docker dan Kubernetes yang masing-masing bersih. CLI resmi containerd bernama ctr — murni alat debugging, kasar dan tanpa kenyamanan, tetapi jujur memperlihatkan lapisan paling dasar:

bash
$ sudo ctr --namespace moby containers list

Output yang Dihasilkan (dipotong):

text
CONTAINER                                                           IMAGE                                     RUNTIME
1a055ee6a4246dac3ca5d83e89b3b1226ffab399bb71e1f553d7e3ff49170b80   registry.example.com/toko/backend:1.6.0   io.containerd.runc.v2

Kolom pertama adalah id container 64 karakter heksadesimal — angka yang sama dengan docker inspect --format '{{.Id}}' pada container yang sama; itulah bukti bahwa docker ps dan ctr sedang melihat objek yang identik dari dua pintu berbeda. Kolom RUNTIME menunjukkan runner yang dipilih: io.containerd.runc.v2 adalah nama resmi shim versi 2 yang bekerja bersama runc.

runc dan crun #

runc adalah implementasi referensi runtime-spec: ditulis dalam Go, lahir dari kode libcontainer yang dulu menjadi jantung Docker, dan kini dikelola di bawah naungan komunitas containerd. “Referensi” berarti perilakunya menjadi tolok ukur — konfigurasi OCI yang berjalan di runc hampir pasti berjalan di runtime lain. crun adalah pesaing yang menulis spesifikasi yang sama dalam bahasa C: waktu start container lebih cepat dan jejak memori per container lebih kecil, keunggulan yang terasa pada cluster dengan ribuan container lahir-mati cepat. Keduanya membaca bundle yang sama, membentuk namespace dan cgroups yang sama, dan menghasilkan proses yang sama — pilihan di antara keduanya lebih dekat ke pilihan mesin daripada pilihan filosofi.

Melihat Rantai di Host #

Seluruh rantai yang dibedah di atas dapat diverifikasi dengan mata telanjang di mesin Linux mana pun yang menjalankan Docker:

bash
$ ps aux | grep containerd

Output yang Dihasilkan (dipotong):

text
root         914  0.6  2.1 1451904 44240 ?       Ssl  09:12   0:39 /usr/bin/containerd
root        1843  0.1  0.9 1085300 19224 ?       Sl   09:15   0:02 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 1a055ee6a4246dac3ca5d83e89b3b1226ffab399bb71e1f553d7e3ff49170b80 -address /run/containerd/containerd.sock
root        1907  0.1  0.9 1085312 19216 ?       Sl   09:15   0:02 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 6cb93292cf87f08437350fca8ab3b507014a000d6e5443ff0602be88fa6e7b6b -address /run/containerd/containerd.sock
budi        2356  0.0  0.1   9472  2304 pts/0    S+   10:04   0:00 grep --color=auto containerd

Mari kita telaah baris-baris tersebut:

  1. Baris pertama adalah daemon containerd itu sendiri — proses panjang umur yang diawasi dockerd; satu mesin, satu containerd, melayani seluruh namespace.
  2. Dua baris containerd-shim-runc-v2 — satu shim per container: dua container sedang hidup di mesin ini, masing-masing dengan orang tuanya sendiri. Flag -namespace moby membuktikan container itu milik dunia Docker, -id adalah id container 64 heksadesimal yang persis sama dengan dunia docker inspect, dan -address menunjuk socket containerd tempat shim melapor.
  3. Baris terakhir adalah baris grep itu sendiri — artefak klasik pola ps aux | grep: perintah grep ikut tampil karena namanya mengandung kata yang dicari; bukan proses sistem, melainkan bayangan pencari.

Dari compose.yaml di tangan sampai proses echo yang di-clone runc dengan namespace dan cgroupsnya — kita kini telah menempuh seluruh tumpukan. Satu pertanyaan terakhir tinggal di ujung perjalanan Course ini: untuk toko kita, di platform mana tumpukan itu sebaiknya berdiri? Chapter Memilih Platform Orchestration menutup section dengan kerangka keputusan yang jujur — tanpa hype, tanpa obligasi mengikuti keramaian.

Referensi #