linuxmd

Media informasi Linux — berita, panduan, dan kupasan mendalam soal cara kerja Linux sesungguhnya.

Di Balik Docker dan Kubernetes: Sebenarnya Cuma Namespace dan Cgroup

Di Balik Docker dan Kubernetes: Sebenarnya Cuma Namespace dan Cgroup

Dulu saya kira Docker punya semacam “mode container” rahasia yang tertanam di kernel. Ternyata tidak. Buka source code kernel Linux, cari kata container — tidak akan ketemu subsistem apa pun, tidak ada system call khusus. Docker dan Kubernetes bukan penemu container, dan mereka juga tidak dapat bantuan istimewa dari kernel yang tidak bisa kamu pakai sendiri. Yang sebenarnya mereka lakukan adalah mengendalikan dua fitur lawas yang jauh lebih membosankan, yang sudah ada di Linux lebih dari satu dekade: namespace dan cgroup. Sisanya — image, registry, manifest YAML, scheduler — cuma tooling yang dibungkus di atas dua hal itu.

Sebenarnya apa yang diisolasi?

Gimana caranya satu proses Linux bisa seolah-olah punya mesinnya sendiri — RAM sendiri, CPU sendiri, bahkan serasa punya OS sendiri — padahal jelas-jelas jalan berdampingan dengan ratusan proses lain di kernel yang sama?

Itu masalah yang harus dipecahkan sebuah container: harus terasa seperti mesin kecilnya sendiri — punya filesystem sendiri, daftar proses sendiri, hostname sendiri, dan jatah CPU/memori sendiri supaya tidak menghabiskan sumber daya proses lain di mesin yang sama. Tapi kalau dikupas dari segala pemasaran, container itu cuma proses biasa, jalan di kernel yang sama persis dengan semua proses lain di host itu — jadi ia harus mengira dirinya punya mesin sendirian, sekaligus tetap berperilaku hanya mengambil jatah yang adil.

Ternyata kernel sudah membagi tugas itu jadi dua bagian rapi. Namespace menentukan apa yang boleh dilihat oleh sebuah proses. Cgroup menentukan apa yang boleh dipakainya. Itu saja intinya.

Namespace: jawaban agar aplikasi seolah-olah jalan di server terpisah

Namespace mengambil satu sumber daya global yang dikelola kernel — process ID, antarmuka jaringan, mount point — lalu membungkusnya sehingga proses di dalam namespace itu hanya melihat salinan privatnya sendiri, bukan yang asli milik host. Efeknya persis seperti aplikasi itu dipindah ke server sendiri, padahal fisiknya tidak ke mana-mana. Linux punya beberapa jenis namespace: pid menyembunyikan proses-proses lain di sistem sehingga container mengira aplikasinya adalah PID 1; net memberinya stack jaringan sendiri; mnt memberinya pandangan filesystem sendiri; uts membiarkannya mengatur hostname sendiri tanpa menyentuh punya host; ipc menjaga shared memory dan message queue tetap privat; dan user membuat “root” di dalam container jadi cuma pengguna biasa yang tidak punya hak istimewa apa pun di host.

Saat Docker menjalankan container, di baliknya ia cuma memanggil clone() dengan sederet flag CLONE_NEW*, dan proses hasilnya mendapat namespace baru yang kosong, bukan ikut namespace milik host. Itulah sebabnya ps aux di dalam container cuma menampilkan segelintir proses, walaupun host-nya mungkin sedang menjalankan ribuan proses lain — container-nya memang secara harfiah tidak bisa melihat yang lain.

Kamu tidak perlu Docker untuk mencobanya sendiri. Ini jalan di mesin Linux biasa:

# Jalankan shell di namespace UTS, PID, dan mount yang baru
sudo unshare --uts --pid --mount --fork /bin/bash

# Di dalamnya, shell ini benar-benar mengira dirinya PID 1
ps aux

Itu saja. Itulah “isolasi container.” Tidak ada hypervisor, tidak ada perangkat keras virtual, tidak ada sihir — cuma kernel yang memberi proses pandangan privat atas state yang biasanya dibagikan ke semua orang.

Cgroup: memastikan tidak ada yang rakus sendiri

Namespace saja tidak cukup untuk mencegah satu container menghabiskan seluruh CPU atau memori di host — namespace cuma mengatur apa yang terlihat, bukan seberapa banyak yang dipakai. Di sinilah cgroup (control group) berperan. Cgroup mengelompokkan sekumpulan proses lalu membiarkan kernel membatasi, mengukur, dan memprioritaskan apa yang boleh dikonsumsi kelompok itu secara keseluruhan.

Contoh konkretnya, kalau kamu mau batasi jatah container sampai ketat:

docker run --cpus="1.5" --memory="512m" my-image

Docker tidak mengawasi batas itu sendiri di level userspace. Ia menuliskannya ke filesystem cgroup, dan setelah itu scheduler serta memory manager kernel-lah yang menegakkannya, sama seperti untuk kelompok proses lain mana pun. Di sistem cgroup v2, kamu bisa langsung melihat file-nya sendiri:

cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/cpu.max

Angka-angka itu adalah batasannya. Tidak ada “polisi runtime container” terpisah yang mengawasi proses tersebut — itu murni akuntansi cgroup, melakukan apa yang selalu dilakukannya.

Kalau kernel sudah melakukan semua itu, Docker buat apa?

Pertanyaan bagus. Kalau namespace menangani isolasi dan cgroup menangani batasan, tugas Docker sebenarnya adalah semua hal di sekitar dua primitif itu, bukan penggantinya. Docker memberi format image yang portabel — layer di atas overlayfs — supaya ada sesuatu yang konsisten untuk di-mount ke namespace mnt yang baru. Ia menjalankan daemon yang berbicara ke runtime tingkat rendah bernama runc (yang mengimplementasikan spesifikasi OCI runtime) supaya kamu tidak perlu memanggil clone() dan mengatur cgroup secara manual. Ia juga merangkai antarmuka jaringan virtual dan aturan NAT supaya container-container yang berada di namespace net terpisah tetap bisa saling terhubung dan menjangkau dunia luar. Dan ia menyediakan registry — tempat untuk push dan pull image-image itu.

Kubernetes berada satu lapis lebih tinggi lagi. Ia sama sekali tidak menyentuh namespace atau cgroup secara langsung — ia berbicara ke container runtime lewat CRI (containerd, CRI-O, dan sejenisnya), yang lalu memanggil runc, yang baru berbicara ke kernel. Yang sebenarnya diputuskan Kubernetes adalah node mana tempat sebuah pod ditempatkan, dan berapa banyak jatah cgroup yang didapatnya (itulah resources.requests dan resources.limits di spesifikasi pod). Ini juga alasan kenapa container-container dalam satu pod berbagi localhost — mereka sengaja ditaruh di namespace net yang sama. Kubernetes adalah orkestrasi yang duduk di atas primitif-primitif yang sudah dimiliki kernel jauh sebelum istilah “cloud native” dipakai orang.

Kenapa semua ini penting diketahui

Begitu paham bahwa “container itu cuma proses dengan namespace berbeda dan sebuah cgroup,” banyak hal yang tadinya terasa seperti trivia acak soal Docker jadi masuk akal dengan sendirinya. docker run --pid=host membuat container bisa melihat semua proses di host karena ia memang cuma melewatkan pembuatan namespace PID baru — tidak pernah ada “mode visibilitas host” yang istimewa, cuma satu tembok lebih sedikit yang dibangun. Container dulu bisa membaca /proc/meminfo dan melihat total memori host, bahkan ketika batas --memory sudah diset, karena cgroup membatasi pemakaian, bukan mengubah apa yang dilaporkan /proc — ketidaksesuaian ini sempat bikin banyak JVM bermasalah sebelum tooling-nya menyusul. Kerentanan “container escape” pada dasarnya cuma kesalahan konfigurasi namespace atau capability, bukan model keamanan eksotis khusus container. Dan container bisa mulai dalam hitungan milidetik serta jalan tanpa ekstensi virtualisasi karena memang tidak ada hypervisor sama sekali dalam gambarannya.

Semua ini tidak membuat Docker atau Kubernetes jadi kurang berguna — packaging, scheduling, self-healing, dan jaringan dalam skala besar tetaplah masalah sulit yang layak diselesaikan untukmu. Tapi bagian isolasi, bagian yang terasa seperti sihir itu, sebenarnya bukan milik mereka. Itu Linux, melakukan apa yang sudah diam-diam dilakukannya sejak kernel 2.6.24 merampungkan namespace inti terakhir. Container bukan jenis benda baru yang berjalan di mesinmu. Mereka cuma cara baru yang lebih ramah untuk meminta kernel lama yang sama menyembunyikan dan membagi sumber daya atas namamu.