Teknologi

Kontainer Tidak Ada — Kernel Anda Berbohong kepada Anda

D
Donar Azhar
21 Feb 2026 201 views
Kontainer Tidak Ada — Kernel Anda Berbohong kepada Anda

1. Kebohongan yang Nyaman

Jalankan perintah ini di terminal Linux kamu:

docker run -it ubuntu bash

Dalam hitungan detik, kamu berada di dalam sebuah "Ubuntu". Kamu bisa menjalankan ls, ps, bahkan menginstal paket. Semuanya terasa terisolasi. Terasa terpisah. Terasa seperti komputer lain.

Tapi coba pikirkan ini:

# Di dalam "kontainer Ubuntu"
cat /proc/version
# Linux version 5.15.0-91-generic (Ubuntu 22.04)

# Di host, di terminal lain
cat /proc/version
# Linux version 5.15.0-91-generic (Ubuntu 22.04)

Versi kernel identik. Bukan kebetulan. Mereka memang kernel yang sama persis.

Sekarang coba ini:

# Di dalam kontainer
ls -la /proc/1/
# Kamu melihat proses PID 1 milik kontainer

# Di host
ls -la /proc/1/
# Kamu melihat init/systemd — proses pertama sistem

Kedua /proc itu berbeda. Tapi filesystem /proc itu bukan dua benda terpisah. Kernel hanya menunjukkan tampilan yang berbeda kepada siapa yang bertanya.

Inilah inti dari seluruh artikel ini:

Kontainer bukan teknologi. Kontainer adalah narasi. Yang nyata adalah fitur-fitur kernel Linux yang sudah ada bertahun-tahun sebelum Docker lahir.

2. Virtual Machine — Komputer di Dalam Komputer

Sebelum membongkar kontainer, kita perlu memahami VM — karena kontainer sering dibandingkan dengannya, dan keduanya bekerja dengan cara yang fundamental berbeda.

Apa Itu Virtual Machine?

Virtual Machine (VM) adalah emulasi perangkat keras lengkap yang dijalankan di atas sistem operasi nyata. Di dalamnya berjalan sistem operasi yang sesungguhnya — dengan kernel sendiri, driver sendiri, dan manajemen memori sendiri.

┌─────────────────────────────────────────┐
│           MESIN FISIK (HOST)            │
│                                         │
│  ┌──────────────────────────────────┐   │
│  │      HYPERVISOR (VMware/KVM)     │   │
│  └──────────────────────────────────┘   │
│         │              │                │
│  ┌──────┴──┐    ┌──────┴──┐            │
│  │  VM #1  │    │  VM #2  │            │
│  │         │    │         │            │
│  │ Kernel  │    │ Kernel  │            │
│  │  Linux  │    │ Windows │            │
│  │         │    │         │            │
│  │  App A  │    │  App B  │            │
│  └─────────┘    └─────────┘            │
└─────────────────────────────────────────┘

Dua Tipe Hypervisor

Type 1 — Bare Metal Hypervisor:

Berjalan langsung di atas perangkat keras, tanpa OS host. Ini yang digunakan di data center serius.

Hardware
   └── Hypervisor (VMware ESXi, Microsoft Hyper-V, Xen)
          ├── VM 1 (OS + App)
          ├── VM 2 (OS + App)
          └── VM 3 (OS + App)

Type 2 — Hosted Hypervisor:

Berjalan di atas OS host seperti aplikasi biasa. Lebih mudah digunakan untuk keperluan developer.

Hardware
   └── OS Host (Windows/macOS/Linux)
          └── Hypervisor (VirtualBox, VMware Workstation, QEMU)
                 ├── VM 1 (OS + App)
                 └── VM 2 (OS + App)

Apa yang Dilakukan Hypervisor?

Hypervisor mengintersep setiap instruksi privileged yang dikeluarkan VM. Ketika kernel VM mencoba mengakses perangkat keras, hypervisor menangkap permintaan itu dan menerjemahkannya ke perangkat keras nyata.

Prosesor modern (Intel VT-x, AMD-V) memiliki dukungan hardware untuk ini, sehingga VM bisa berjalan hampir secepat native untuk sebagian besar operasi.

Kernel VM: "Aku ingin tulis ke disk sektor 0x1A2B3C"
     ↓
Hypervisor: "Tidak langsung. Aku terjemahkan permintaan ini ke
             file virtual disk di disk fisik host."
     ↓
Disk Fisik: Data tersimpan di /var/lib/libvirt/images/vm1.qcow2

Karakteristik VM

  • Isolasi penuh — kernel terpisah, memori terpisah, perangkat keras virtual terpisah
  • Overhead signifikan — booting butuh detik hingga menit, memori overhead ratusan MB hingga beberapa GB
  • Keamanan sangat kuat — exploit dari dalam VM sangat sulit menembus hypervisor
  • Bisa menjalankan OS berbeda — Linux VM di atas Windows host, atau sebaliknya

3. Mengapa Kontainer Tidak Ada

Buka source code kernel Linux. Cari file bernama container.c. Cari struct bernama container_t. Cari syscall bernama create_container().

Kamu tidak akan menemukannya.

Yang akan kamu temukan adalah:

kernel/nsproxy.c        — Namespace management
kernel/cgroup.c         — Control Groups
fs/overlayfs/           — OverlayFS
security/apparmor/      — AppArmor
security/seccomp.c      — Secure Computing Mode

Inilah bahan-bahan sesungguhnya dari "kontainer". Docker, Podman, containerd, LXC — semua dari mereka hanyalah antarmuka yang nyaman di atas kombinasi fitur-fitur kernel ini.

Ketika kamu menjalankan docker run ubuntu, yang sesungguhnya terjadi adalah:

# Yang Docker lakukan di balik layar (sangat disederhanakan):

# 1. Buat namespace baru
unshare --pid --net --mount --uts --ipc

# 2. Setup cgroup untuk batasan resource
echo "104857600" > /sys/fs/cgroup/memory/myapp/memory.limit_in_bytes

# 3. Setup filesystem dengan OverlayFS
mount -t overlay overlay \
  -o lowerdir=/var/lib/docker/overlay2/abc123/diff,\
     upperdir=/var/lib/docker/overlay2/abc123/work,\
     workdir=/var/lib/docker/overlay2/abc123/merged \
  /var/lib/docker/overlay2/abc123/merged

# 4. chroot ke root filesystem baru
chroot /var/lib/docker/overlay2/abc123/merged /bin/bash

# 5. Eksekusi proses
exec nginx

4. Namespaces — Seni Berbohong kepada Proses

Konsep Dasar

Namespace adalah fitur kernel Linux yang membungkus sumber daya sistem tertentu sehingga proses-proses di dalam namespace itu hanya melihat versi terisolasi dari sumber daya tersebut.

Analoginya adalah kacamata augmented reality. Kamu berada di ruangan yang sama dengan semua orang, tapi kacamatamu menampilkan versi dunia yang berbeda. Kamu pikir kamu sendirian — padahal kamu hanya melihat sebagian.

Linux memiliki 7 jenis namespace (per kernel 5.x):

Namespace Flag Yang Diisolasi
PID CLONE_NEWPID Nomor proses (PID)
Network CLONE_NEWNET Interface jaringan, routing table, port
Mount CLONE_NEWNS Filesystem mount points
UTS CLONE_NEWUTS Hostname dan domain name
IPC CLONE_NEWIPC Message queue, semaphore, shared memory
User CLONE_NEWUSER User ID dan Group ID
Cgroup CLONE_NEWCGROUP Tampilan cgroup root

PID Namespace — Sistem Penomoran Proses Paralel

Ini yang paling mengejutkan. Di dalam kontainer, proses pertama selalu memiliki PID 1 — sama seperti init/systemd di host. Padahal di luar kontainer, proses tersebut bisa memiliki PID 7823.

HOST:                          KONTAINER:

PID 1: systemd                 PID 1: nginx (= PID 7823 di host)
PID 2: kthreadd               PID 2: nginx worker (= PID 7824 di host)
...                            PID 3: bash (= PID 7830 di host)
PID 7823: nginx ← ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
PID 7824: nginx worker
...
PID 7830: bash

Kernel menjaga dua tabel PID secara bersamaan. Proses nginx terdaftar di tabel global host dengan PID 7823, dan di tabel namespace kontainer dengan PID 1.

Kita bisa membuktikan ini:

# Di dalam kontainer
$ ps aux
USER       PID %CPU %MEM   COMMAND
root         1  0.0  0.1   nginx
nobody       2  0.0  0.1   nginx: worker
root         3  0.0  0.0   bash
root        10  0.0  0.0   ps

# Di host, di terminal berbeda
$ ps aux | grep nginx
root      7823  0.0  0.1   nginx
nobody    7824  0.0  0.1   nginx: worker

# Di host, kita bisa "melihat" proses kontainer dan mengirim sinyal
$ kill -9 7823   # Ini akan mematikan nginx di dalam kontainer!

Kernel yang "jujur" ini berbahaya sekaligus powerful — seorang admin di host selalu bisa melihat dan mengontrol semua proses, termasuk yang ada di dalam kontainer.

Network Namespace — Internet Palsu

Setiap kontainer bisa memiliki network stack sendiri: interface sendiri, IP sendiri, routing table sendiri, bahkan tabel firewall (iptables) sendiri.

# Lihat network namespace yang ada
$ ip netns list
container_abc123
container_def456

# Masuk ke namespace network kontainer
$ nsenter --net=/var/run/docker/netns/abc123 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP>
    inet 127.0.0.1/8
2: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 172.17.0.3/16

# Di host
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP>
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 192.168.1.100/24
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 172.17.0.1/16        ← Bridge virtual untuk kontainer
15: veth3a2b1c@if2:           ← Ujung lain dari eth0 kontainer

Docker membuat virtual ethernet pair (veth) — seperti kabel jaringan virtual. Satu ujung di dalam namespace kontainer (tampak sebagai eth0), satu ujung lagi di host. Semua traffic keluar masuk kontainer melewati kabel virtual ini, lalu di-NAT oleh iptables sebelum keluar ke jaringan nyata.

Mount Namespace — Peta Filesystem yang Berbeda

Mount namespace membuat setiap kontainer bisa memiliki tampilan filesystem yang berbeda. Kontainer bisa me-mount direktori berbeda, menyembunyikan direktori host, atau bahkan memiliki root filesystem yang sama sekali berbeda.

# Di host
$ ls /
bin boot dev etc home lib media mnt opt proc root run srv sys tmp usr var

# Di dalam kontainer ubuntu
$ ls /
bin dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var

# Direktori /home di kontainer kosong — tidak ada file host
$ ls /home
(kosong)

# Padahal di host ada banyak user
$ ls /home
alice  bob  charlie  dave

Kontainer melihat sistem file yang sama sekali berbeda — tapi kernel tetap satu. Trickery mount namespace membuat kernel menjaga dua pohon mount yang berbeda dan menunjukkan pohon yang tepat kepada proses yang tepat.

UTS Namespace — Hostname Palsu

UTS (Unix Timesharing System) namespace memungkinkan setiap kontainer memiliki hostname sendiri:

# Di host
$ hostname
prodserver-01.datacenter.com

# Di dalam kontainer
$ hostname
a3f2c1b9d8e7   ← Container ID sebagai hostname

Secara teknis, ada dua "nama komputer" di mesin yang sama. Kernel hanya menunjukkan nama yang berbeda berdasarkan namespace mana yang bertanya.

User Namespace — Identitas Ganda

Ini yang paling powerful sekaligus kontroversial. User namespace memungkinkan proses di dalam kontainer berjalan sebagai root (UID 0) di dalam namespace — tapi dari perspektif host, proses itu sebenarnya berjalan sebagai pengguna biasa (misalnya UID 1000).

# Di dalam kontainer — terlihat sebagai root
$ id
uid=0(root) gid=0(root) groups=0(root)

$ whoami
root

# Di host, proses kontainer sebenarnya adalah user biasa
$ ps aux | grep nginx
alice    7823  0.0  0.1   nginx   ← UID alice, bukan root!

Ini penting untuk keamanan: jika proses "root" di kontainer berhasil escape dari namespace, dia tidak otomatis jadi root di host.

5. cgroups — Penjaga Sumber Daya

Namespace menyelesaikan masalah visibilitas — apa yang bisa dilihat proses. Tapi ada masalah lain: konsumsi sumber daya.

Tanpa batasan, sebuah proses bisa mengkonsumsi seluruh CPU, RAM, disk I/O, dan bandwidth jaringan mesin. Inilah tugas Control Groups (cgroups).

Apa Itu cgroups?

cgroups adalah mekanisme kernel untuk mengelompokkan proses dan membatasi serta memonitor penggunaan sumber daya mereka.

# Struktur cgroup di filesystem
$ ls /sys/fs/cgroup/
blkio  cpu  cpuacct  cpuset  devices  freezer
hugetlb  memory  net_cls  net_prio  perf_event  pids

# Lihat cgroup milik Docker
$ ls /sys/fs/cgroup/memory/docker/
a3f2c1b9d8e7f6...  ← Container ID
b1e9d4c2a8f3b5...

Membatasi Memori

# Buat cgroup baru
$ mkdir /sys/fs/cgroup/memory/myapp

# Batasi memori maksimum 256MB
$ echo "268435456" > /sys/fs/cgroup/memory/myapp/memory.limit_in_bytes

# Batasi swap
$ echo "0" > /sys/fs/cgroup/memory/myapp/memory.swappiness

# Daftarkan proses ke cgroup ini
$ echo $$ > /sys/fs/cgroup/memory/myapp/cgroup.procs

# Sekarang proses ini dan semua child-nya dibatasi 256MB

Ketika proses mencoba mengalokasikan memori melebihi batas, kernel bisa memilih untuk:

  • Menjalankan OOM Killer (Out of Memory Killer) — membunuh proses yang paling boros
  • Mengembalikan error ENOMEM kepada proses
# Docker melakukan ini saat kamu gunakan --memory flag
$ docker run --memory=256m nginx

# Ekuivalen dengan membuat cgroup dan set limit
$ cat /sys/fs/cgroup/memory/docker/$(docker inspect --format '{{.Id}}' nginx)/memory.limit_in_bytes
268435456

Membatasi CPU

# Batasi kontainer hanya bisa pakai 50% dari satu CPU core
$ echo "50000" > /sys/fs/cgroup/cpu/myapp/cpu.cfs_quota_us
$ echo "100000" > /sys/fs/cgroup/cpu/myapp/cpu.cfs_period_us

# Atau dengan Docker:
$ docker run --cpus=0.5 nginx

Kernel menggunakan Completely Fair Scheduler (CFS) — setiap 100ms (period), proses hanya boleh berjalan maksimal 50ms (quota). Sisanya, proses dibekukan sementara.

Membatasi I/O Disk

# Batasi read/write ke 10MB/s untuk device /dev/sda (major:minor = 8:0)
$ echo "8:0 10485760" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.read_bps_device
$ echo "8:0 10485760" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.write_bps_device

# Docker:
$ docker run --device-read-bps /dev/sda:10mb nginx

cgroups v2 — Hierarki Terpadu

cgroups versi 1 memiliki kekurangan — setiap sumber daya (CPU, memori, dll) memiliki hierarki sendiri yang terpisah. cgroups v2 (diperkenalkan di kernel 4.5, default di kernel 5.x) menyatukan semua dalam satu hierarki:

# cgroups v1 (lama)
/sys/fs/cgroup/
├── cpu/
│   ├── docker/
│   │   └── abc123/
├── memory/
│   ├── docker/
│   │   └── abc123/    ← Terpisah, sulit disinkronisasi
└── blkio/
    ├── docker/
        └── abc123/

# cgroups v2 (baru)
/sys/fs/cgroup/
└── docker/
    └── abc123/         ← Semua resource dalam satu direktori
        ├── memory.max
        ├── cpu.max
        └── io.max      ← Jauh lebih bersih

6. Union Filesystem — Lapisan Demi Lapisan

Sekarang kita tahu bagaimana isolasi proses dan resource bekerja. Tapi bagaimana dengan filesystem? Mengapa image Docker yang sama bisa digunakan oleh 100 kontainer secara bersamaan tanpa menduplikasi data?

Jawabannya adalah Union Filesystem — khususnya OverlayFS yang kini menjadi default Docker.

Konsep Dasar: Copy-on-Write

Bayangkan sebuah buku resep. Alih-alih memberikan fotokopi 100 halaman kepada 100 orang, kamu memberikan satu buku asli, lalu setiap orang mendapat selembar kertas transparan untuk menulis perubahan mereka. Ketika membaca, mereka melihat buku asli + catatan mereka. Buku aslinya tidak pernah berubah.

Inilah Copy-on-Write (CoW):

  • Data asli (image) hanya ada satu salinan di disk
  • Setiap kontainer mendapat layer tipis untuk menyimpan perubahan
  • Membaca = baca dari layer atas, jika tidak ada → baca dari layer bawah
  • Menulis = salin blok yang dimodifikasi ke layer atas, lalu ubah

Cara Kerja OverlayFS

OverlayFS menggabungkan dua direktori: lowerdir (read-only, image) dan upperdir (read-write, data kontainer), lalu menyajikannya sebagai satu filesystem melalui merged.

┌─────────────────────────────────────────┐
│          MERGED (yang dilihat kontainer) │
│  /etc/nginx.conf (dari upper — diubah)  │
│  /usr/bin/nginx  (dari lower — asli)    │
│  /var/log/nginx/ (dari upper — baru)    │
└─────────────────────────────────────────┘
              ↑ overlay
┌──────────────────┐  ┌─────────────────────┐
│   UPPER (rw)     │  │    LOWER (ro)        │
│                  │  │                      │
│ /etc/nginx.conf  │  │  /etc/nginx.conf     │
│ (hasil modifikasi│  │  (asli dari image)   │
│ /var/log/nginx/  │  │  /usr/bin/nginx      │
│ access.log       │  │  /usr/lib/...        │
│ error.log        │  │  (semua file image)  │
└──────────────────┘  └─────────────────────┘

Struktur Layer Docker

Docker image terdiri dari layer-layer yang ditumpuk:

# Dockerfile sederhana:
FROM ubuntu:22.04          # Layer 1: Ubuntu base
RUN apt-get update         # Layer 2: Package list
RUN apt-get install nginx  # Layer 3: Nginx files
COPY app.conf /etc/nginx/  # Layer 4: Konfigurasi kita
CMD ["nginx", "-g", "daemon off;"]

# Melihat layers sebuah image
$ docker history nginx:latest
IMAGE          CREATED      CREATED BY                SIZE
a3f2c1b9d8e7   2 weeks ago  CMD ["nginx" "-g" "da…]  0B
<missing>      2 weeks ago  COPY app.conf /etc/ni…]  1.2kB
<missing>      2 weeks ago  RUN apt-get install n…]  56.2MB
<missing>      2 weeks ago  RUN apt-get update         28.1MB
<missing>      2 weeks ago  Ubuntu 22.04 base          77.8MB

Satu layer bisa dibagi oleh banyak image dan banyak kontainer:

ubuntu:22.04 layer (77.8MB) ← dipakai oleh:
├── nginx image
├── apache image
├── nodejs image
└── python image
... semua berbagi SATU salinan di disk!

Tanpa sharing:
4 image × 77.8MB = 311.2MB
Dengan sharing:
1 × 77.8MB + delta masing-masing = jauh lebih kecil

Melihat OverlayFS Secara Langsung

# Lihat mount point kontainer yang sedang berjalan
$ docker inspect $(docker ps -q) | grep MergedDir
"MergedDir": "/var/lib/docker/overlay2/abc123/merged"

# Lihat detailnya
$ mount | grep overlay
overlay on /var/lib/docker/overlay2/abc123/merged type overlay
  (rw,
   lowerdir=/var/lib/docker/overlay2/layer4/diff:
           /var/lib/docker/overlay2/layer3/diff:
           /var/lib/docker/overlay2/layer2/diff:
           /var/lib/docker/overlay2/layer1/diff,
   upperdir=/var/lib/docker/overlay2/abc123/diff,
   workdir=/var/lib/docker/overlay2/abc123/work)

Layer 1-4 adalah image — read-only, dibagi bersama. abc123/diff adalah layer writable milik kontainer ini saja. Ketika kontainer dihapus, hanya layer ini yang hilang.

7. Bagaimana Docker Menyatukan Semuanya

Sekarang kita bisa melihat gambaran lengkapnya. Ketika kamu menjalankan docker run -p 8080:80 nginx, inilah yang benar-benar terjadi:

Alur Lengkap docker run

1. Docker CLI
   └── Kirim request ke Docker daemon via Unix socket
       /var/run/docker.sock

2. Docker Daemon (dockerd)
   └── Delegasikan ke containerd

3. containerd
   └── Instruksikan runc untuk membuat kontainer

4. runc (OCI runtime)
   └── Panggil kernel syscalls:

   a. FILESYSTEM (OverlayFS)
      ├── Mount lowerdir = image layers
      ├── Mount upperdir = container layer (kosong)
      └── Mount merged = tampilan akhir kontainer

   b. ISOLASI (Namespaces)
      ├── clone(CLONE_NEWPID)   → PID namespace baru
      ├── clone(CLONE_NEWNET)   → Network namespace baru
      ├── clone(CLONE_NEWNS)    → Mount namespace baru
      ├── clone(CLONE_NEWUTS)   → UTS namespace baru
      ├── clone(CLONE_NEWIPC)   → IPC namespace baru
      └── clone(CLONE_NEWUSER)  → User namespace baru (opsional)

   c. RESOURCE CONTROL (cgroups)
      ├── Buat direktori di /sys/fs/cgroup/
      ├── Set memory.limit_in_bytes
      ├── Set cpu.cfs_quota_us
      └── Daftarkan PID proses ke cgroup

   d. KEAMANAN TAMBAHAN
      ├── Terapkan seccomp profile (batasi syscalls)
      ├── Terapkan AppArmor/SELinux policy
      └── Drop capabilities (hapus hak kernel berbahaya)

   e. JARINGAN
      ├── Buat veth pair (virtual ethernet cable)
      ├── Satu ujung masuk ke network namespace kontainer
      ├── Ujung lain di-attach ke docker0 bridge
      └── Setup NAT via iptables untuk akses luar

   f. EKSEKUSI
      └── chroot ke merged filesystem
          └── exec nginx

Arsitektur Komponen Docker

┌──────────────────────────────────────────────────────┐
│                    docker CLI                        │
└────────────────────────┬─────────────────────────────┘
                         │ REST API via Unix socket
┌────────────────────────▼─────────────────────────────┐
│                   dockerd (Docker Daemon)             │
│           Image management, networking, volumes       │
└────────────────────────┬─────────────────────────────┘
                         │ gRPC
┌────────────────────────▼─────────────────────────────┐
│                    containerd                         │
│         Container lifecycle management               │
│         Image pulling, snapshot management           │
└────────────────────────┬─────────────────────────────┘
                         │ OCI Runtime Spec
┌────────────────────────▼─────────────────────────────┐
│                       runc                           │
│         Low-level container runtime                  │
│         Wraps Linux kernel primitives                │
└────────────────────────┬─────────────────────────────┘
                         │ syscalls
┌────────────────────────▼─────────────────────────────┐
│                  Linux Kernel                        │
│   Namespaces │ cgroups │ OverlayFS │ seccomp │ ...   │
└──────────────────────────────────────────────────────┘

Docker adalah orkestrator yang mengkoordinasikan semua primitif kernel ini menjadi pengalaman yang kita kenal sebagai "kontainer".

8. Perbedaan Fundamental: VM vs Kontainer

Sekarang kita bisa melihat dengan jelas mengapa keduanya berbeda secara mendasar — bukan hanya soal kecepatan atau ukuran.

Perbedaan Inti

VIRTUAL MACHINE:

Mesin Fisik
└── Hypervisor
    ├── VM 1
    │   ├── Kernel Linux (versi 5.15) — KERNEL TERPISAH
    │   ├── Proses systemd
    │   └── Aplikasi A
    └── VM 2
        ├── Kernel Windows NT — KERNEL BERBEDA
        ├── Proses services.exe
        └── Aplikasi B


KONTAINER:

Mesin Fisik
└── Kernel Linux (versi 5.15) — SATU KERNEL
    ├── Namespace Group A (terlihat sebagai "Kontainer 1")
    │   ├── Proses nginx (PID 1 di namespace, PID 7823 di host)
    │   └── Proses worker
    └── Namespace Group B (terlihat sebagai "Kontainer 2")
        ├── Proses node (PID 1 di namespace, PID 8102 di host)
        └── Proses worker

Tabel Perbandingan Mendalam

Aspek Virtual Machine Kontainer
Kernel Masing-masing punya kernel sendiri Berbagi kernel host
Waktu start 30 detik – beberapa menit Milidetik – beberapa detik
Ukuran image GB (OS lengkap) MB (hanya aplikasi + library)
Overhead RAM Ratusan MB per VM (OS overhead) Sangat minimal
Isolasi Penuh — beda kernel, beda hardware virtual Parsial — kernel sama, namespace berbeda
Keamanan Sangat kuat (hypervisor barrier) Lebih lemah (berbagi kernel)
OS yang didukung Bisa beda OS (Windows VM di Linux host) Harus kernel yang sama
Portabilitas OVA/VMDK, lebih berat Docker image, sangat mudah dibawa
Cocok untuk Isolasi kuat, multi-OS, legacy apps Microservices, CI/CD, scale cepat

Mengapa Kontainer Lebih Cepat?

Saat VM boot:
1. BIOS/UEFI virtual (ratusan ms)
2. Boot loader virtual
3. Kernel decompress & init (beberapa detik)
4. systemd mulai semua services (beberapa detik)
5. Aplikasi akhirnya bisa jalan ✓

Saat kontainer "boot":
1. Kernel sudah berjalan (0ms tambahan)
2. Buat namespaces (< 1ms)
3. Setup cgroups (< 1ms)
4. Mount OverlayFS (beberapa ms)
5. exec() proses aplikasi ✓

Total: kontainer bisa jalan dalam 50-200ms

9. Implikasi Keamanan — Ketika Ilusi Retak

Inilah bagian yang paling kritis dan sering dilewatkan.

Ancaman #1: Container Escape

Karena kontainer berbagi kernel dengan host, celah keamanan di kernel Linux bisa dieksploitasi dari dalam kontainer untuk melarikan diri ke host.

Contoh nyata — Exploit CVE-2019-5736 (runc vulnerability):

Penyerang menjalankan proses di dalam kontainer
     ↓
Mengeksploitasi cara runc membaca /proc/self/exe
     ↓
Menimpa binary runc di host
     ↓
Kali berikutnya runc dieksekusi di host = kode penyerang berjalan
     ↓
Penyerang punya akses root di HOST

Ini tidak mungkin terjadi di VM — kernel terpisah berarti exploit dari dalam VM tidak bisa langsung menyentuh host.

Ancaman #2: Privileged Container

# JANGAN PERNAH lakukan ini di produksi:
$ docker run --privileged ubuntu bash

# Kontainer "privileged" mendapat semua capabilities kernel
# Ekuivalen dengan tidak ada isolasi sama sekali!

# Di dalam privileged container, penyerang bisa:
$ mount /dev/sda1 /mnt  # Mount disk host!
$ nsenter --target 1 --mount --uts --ipc --net --pid  # Masuk ke namespace host!

Ancaman #3: Syscall Attack Surface

Karena kernel bersama, semua syscall Linux tersedia dari dalam kontainer. Jika ada bug di penanganan syscall tertentu oleh kernel, proses di dalam kontainer bisa mengeksploitasinya.

Solusi: seccomp profiles

// /etc/docker/seccomp-default.json (versi singkat)
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "stat",
                "fstat", "lstat", "poll", "lseek", "mmap"],
      "action": "SCMP_ACT_ALLOW"
    }
    // Syscall berbahaya seperti kexec_load, reboot,
    // create_module — tidak ada di whitelist = diblokir
  ]
}

Ancaman #4: Serangan Lewat /proc dan /sys

# Beberapa path berbahaya yang harus di-mount read-only atau di-mask:

/proc/sys/kernel/core_pattern  # Bisa digunakan untuk privilege escalation
/proc/sys/kernel/dmesg_restrict
/proc/sysrq-trigger            # Bisa crash kernel host!
/sys/firmware/                 # Akses firmware hardware
/sys/kernel/debug/

Docker secara default sudah me-mask beberapa path ini, tapi konfigurasi yang salah bisa membukanya.

Perbandingan Model Keamanan

VM Security Model:
Penyerang → exploit app → dapat root container → ??? → hypervisor barrier → BERHENTI
                                                         (sangat sulit ditembus)

Container Security Model:
Penyerang → exploit app → dapat root container → exploit kernel bug → dapat root HOST
                                                  (satu kernel = satu titik kegagalan)

Defense in Depth untuk Kontainer:
1. Jalankan sebagai non-root (USER directive di Dockerfile)
2. Read-only root filesystem (--read-only)
3. Drop all capabilities, tambahkan hanya yang perlu
4. Gunakan seccomp profile
5. Gunakan AppArmor/SELinux
6. Jangan gunakan --privileged
7. Scan image untuk vulnerability
8. Pertimbangkan gVisor atau Kata Containers untuk isolasi lebih kuat

Solusi: Sandbox yang Lebih Kuat

Untuk workload yang butuh isolasi lebih kuat tapi tetap ingin kecepatan kontainer, ada solusi tengah:

gVisor (Google): Mengimplementasikan kernel Linux dalam userspace — kontainer memanggil syscall ke "kernel palsu" ini, bukan langsung ke kernel host. Mengurangi attack surface secara dramatis.

Kata Containers: Menjalankan kontainer di dalam VM yang sangat ringan (~1 detik boot). Dapat isolasi VM dengan overhead mendekati kontainer.

gVisor Architecture:
App → syscall → Sentry (kernel di userspace) → host syscall minimal
                (attack surface dari 300+ syscall menjadi ~20)

Kata Containers Architecture:
App → syscall → Lightweight VM Kernel → KVM hypervisor → host
                (isolasi penuh, tapi start time ~1 detik)

10. Kapan Pakai VM, Kapan Pakai Kontainer?

Gunakan Virtual Machine jika:

Kebutuhan isolasi keras (regulated industries):

  • Sistem keuangan dan perbankan yang memproses data kartu kredit
  • Healthcare dengan data pasien (HIPAA compliance)
  • Workload yang menjalankan kode pengguna yang tidak dipercaya

Kebutuhan multi-OS:

  • Menjalankan Windows server di samping Linux server di infrastruktur yang sama
  • Testing aplikasi di berbagai OS

Aplikasi legacy:

  • Sistem lama yang membutuhkan konfigurasi OS spesifik
  • Aplikasi yang harus mengontrol kernel parameters secara mendalam

Contoh arsitektur yang tepat:

Data Center
└── Bare Metal Server
    └── KVM Hypervisor
        ├── VM: Payment Processing (PCI-DSS isolated)
        ├── VM: Database Server (dedicated resources)
        └── VM: Windows License Server

Gunakan Kontainer jika:

Microservices dan cloud-native:

  • Banyak service kecil yang perlu deploy dan scale independen
  • CI/CD pipeline yang butuh environment konsisten
  • Service mesh (Kubernetes, Istio)

Development dan testing:

  • "Works on my machine" problem solved — semua developer pakai image yang sama
  • Testing di berbagai versi library tanpa konflik

Efisiensi sumber daya:

  • Menjalankan ratusan instance dengan overhead minimal
  • Skalabilitas cepat (autoscaling dari 1 ke 100 instance dalam detik)

Contoh arsitektur yang tepat:

Kubernetes Cluster
├── Node 1
│   ├── Pod: api-service (2 containers)
│   ├── Pod: auth-service (1 container)
│   └── Pod: cache (1 container)
├── Node 2
│   ├── Pod: api-service (2 containers)  ← auto-scaled
│   └── Pod: worker (3 containers)
└── Node 3
    └── ...

Sering Digunakan Bersamaan

Di dunia nyata, keduanya sering dipakai bersama:

Cloud Provider (AWS/GCP/Azure)
└── Virtual Machine (EC2/GCE/Azure VM)
    └── Kubernetes di dalam VM
        └── Kontainer di dalam Kubernetes

Mengapa? VM memberikan isolasi antar tenant (pelanggan cloud berbeda),
sementara kontainer memberikan efisiensi dan kecepatan deploy.

11. Kesimpulan

Mari tarik benang merah dari semua yang telah kita pelajari.

Apa yang Benar-benar Terjadi

Ketika kamu menjalankan kontainer, kamu tidak menjalankan sebuah "container" dalam pengertian teknis. Yang kamu lakukan adalah:

  1. Meminta kernel Linux membuat namespace baru — sehingga proses melihat "dunia" yang terbatas
  2. Mendaftarkan proses ke cgroup — sehingga kernel membatasi sumber daya yang bisa dikonsumsi
  3. Menyajikan OverlayFS sebagai root filesystem — sehingga setiap "kontainer" punya filesystem sendiri tanpa duplikasi
  4. Menerapkan seccomp dan capabilities — untuk membatasi syscall yang bisa dipanggil

"Kontainer" adalah abstraksi tingkat tinggi yang dibuat oleh Docker (dan runtime lainnya) di atas kombinasi fitur kernel ini.

VM vs Kontainer — Bukan Kompetisi, Tapi Pilihan

Keduanya menjawab masalah yang berbeda:

  • VM memisahkan workload di level kernel dan hardware — isolasi terkuat
  • Kontainer memisahkan workload di level proses dan filesystem — efisiensi tertinggi

Jangan memilih salah satu karena tren atau popularitas. Pilih berdasarkan kebutuhan:

Butuh isolasi terkuat?                  → VM
Butuh deploy cepat & skala dinamis?     → Kontainer
Butuh keduanya?                         → Kontainer di dalam VM
Butuh keamanan kontainer setara VM?     → gVisor / Kata Containers

Mengapa Memahami Ini Penting

Sebagai developer atau sysadmin, memahami apa yang sesungguhnya terjadi di balik docker run membuatmu:

  • Lebih aman — kamu tahu batasan isolasi kontainer dan bagaimana mengatasinya
  • Lebih efisien — kamu bisa optimasi resource berdasarkan pemahaman cgroups
  • Lebih mahir debugging — ketika sesuatu tidak berjalan, kamu tahu harus cari di mana
  • Lebih bijak dalam arsitektur — kamu bisa membuat keputusan desain yang tepat

Kernel tidak benar-benar berbohong. Ia hanya menyajikan cerita yang berbeda kepada pendengar yang berbeda. Dan sekarang kamu tahu bagaimana cerita itu dibuat.

"Any sufficiently advanced abstraction is indistinguishable from magic — until you look at the source code."

— Parafrase dari Arthur C. Clarke, untuk dunia infrastruktur modern

Referensi & Topik Lanjutan

Untuk mendalami lebih jauh, eksplorasi topik-topik ini:

  • Linux Kernel Source: kernel/nsproxy.c, kernel/cgroup.c, fs/overlayfs/
  • OCI (Open Container Initiative): Spesifikasi standar untuk container runtime dan image
  • runc source code: Implementasi referensi OCI runtime yang benar-benar memanggil syscalls
  • Kubernetes internals: Bagaimana Pod menggunakan semua primitif ini dalam skala besar
  • eBPF: Teknologi baru untuk observability dan security di level kernel tanpa modifikasi kernel
  • Linux Capabilities: Daftar lengkap capability yang bisa diberikan/dicabut dari kontainer

Artikel ini ditulis untuk developer, sysadmin, dan siapa pun yang ingin memahami apa yang sebenarnya terjadi di balik kata "kontainer".

Share this article

Related Articles

Continue reading with these related posts