Container, mengirim kode Anda beserta dunianya.
Penelusuran dari prinsip dasar tentang cara kode backend dikemas dan dijalankan di produksi, dari fitur kernel yang membuat container (namespaces dan cgroups), lewat image Docker, layer, dan multi-stage build, ke orkestrasi Kubernetes (pod, deployment, service, probe, autoscaling), dan keluar ke pipeline CI/CD serta rollout zero-downtime. Ditulis bukan hanya menyebut apa fungsi tiap bagian, tapi kenapa ia ada dan bagaimana ia bekerja di dalamnya. Contoh aplikasi di Go dan Python.
Masalah yang Dipecahkan Container
Setiap konsep container belakangan di manual ini adalah jawaban atas satu rasa sakit yang berulang: kode yang berjalan di satu mesin gagal di mesin lain karena kedua mesin tidak identik. Laptop Anda punya Python 3.11, server punya 3.9; build Anda mengasumsikan library sistem yang tidak ada di produksi; environment variable di-set di sini dan tidak di-set di sana. Hasilnya lelucon tertua di perangkat lunak, "tapi berfungsi di mesin saya", dan ia bukan lelucon, ia kelas pemadaman.
Akar masalahnya program yang berjalan tidak pernah hanya source code-nya. Ia kode plus seluruh lingkungan tak terlihat: runtime bahasa dan versi persisnya, library pihak ketiga, paket sistem, konfigurasi, path file, dan dependensi level OS. Secara tradisional Anda mereproduksi lingkungan itu manual di setiap mesin, langkah instalasi, skrip setup, halaman wiki yang tidak pernah diperbarui siapa pun, dan setiap penyimpangan antar lingkungan menjadi bug yang hanya muncul di satu tempat.
Tiga masalah, satu solusi
- Dependency hell & drift: runtime dan library persisnya dibakar ke dalam, jadi tidak ada "langkah instalasi" yang bisa salah dan tidak ada ketimpangan versi antar lingkungan.
- Isolasi: dua aplikasi yang butuh versi bertentangan dari library yang sama bisa berjalan berdampingan di satu host, masing-masing di kotak tertutupnya sendiri, tanpa saling mengganggu.
- Kepadatan & portabilitas: container cukup ringan untuk dikemas banyak ke satu mesin dan dipindah bebas antara laptop, CI, dan cloud mana pun, fondasi yang dibangun orkestrasi (Bagian IV) dan CI/CD modern (Bagian V).
Container membuat "yang berjalan di produksi" identik dengan "yang Anda bangun dan uji." Segala hal lain, image, Docker, Kubernetes, pipeline, adalah mesin untuk membangun, mengirim, dan menjalankan artefak identik itu di skala.
Container vs Mesin Virtual
Pertanyaan naluriahnya "bukankah ini cuma mesin virtual?" Jawabannya mengungkap apa sebenarnya container itu. VM me-virtualisasi perangkat keras: hypervisor mengemulasi seluruh mesin, dan tiap VM menjalankan sistem operasi lengkapnya sendiri, kernelnya sendiri, di atasnya duduk library dan aplikasi Anda. Container me-virtualisasi sistem operasi: semua container di satu host berbagi satu kernel host, dan isolasi ditegakkan fitur kernel alih-alih mengemulasi perangkat keras terpisah.
| Dimensi | Mesin Virtual | Container |
|---|---|---|
| Mengisolasi dengan | Mengemulasi perangkat keras (hypervisor) | Fitur kernel (namespaces + cgroups) |
| OS per instance | OS tamu penuh + kernel sendiri | Berbagi kernel host; tanpa OS tamu |
| Ukuran | Gigabyte | Megabyte |
| Waktu mulai | Detik sampai menit (boot OS) | Milidetik (mulai proses) |
| Kepadatan per host | Puluhan | Ratusan sampai ribuan |
| Kekuatan isolasi | Lebih kuat (kernel terpisah) | Lebih lemah (kernel bersama), kompromi nyata |
Berbagi satu kernel yang membuat container ringan dan kelemahan keamanan utama mereka: escape level kernel memengaruhi setiap container di host, sementara VM punya batas lebih tebal. Inilah kenapa platform multi-tenant sering menjalankan container di dalam VM, dan kenapa keamanan container (bagian 17, dan bab keamanan) fokus pada least privilege, user non-root, dan membuang capabilities. Container adalah isolasi, bukan sandbox keamanan keras secara default.
Apa Sebenarnya Container Itu
Di-demistifikasi: container hanyalah proses Linux normal (atau beberapa) yang diberi tahu kernel untuk diisolasi. Tidak ada objek "container" di kernel, sihirnya tiga fitur Linux lama yang digabung sehingga proses percaya ia punya mesin untuk dirinya sendiri. Memahami ketiganya menghapus semua misteri dan menjelaskan setiap batas dan perilaku yang akan Anda temui.
1. Namespaces: ilusi sendirian
Namespace mempartisi resource kernel global sehingga proses hanya melihat irisan miliknya. PID namespace membuat proses utama container percaya ia PID 1 tanpa saudara; network namespace memberinya interface dan port sendiri; mount namespace memberinya pandangan file system sendiri. Dari dalam, ia tampak seperti mesin privat. Dari host, ia hanya proses dengan label tambahan, ps di host melihat semuanya.
2. cgroups: menegakkan batas
Namespace menyembunyikan sisa sistem; control group (cgroups) membatasi berapa banyak yang boleh dikonsumsi proses, CPU, memori, I/O, jumlah proses. Inilah yang menghentikan satu container melaparkan tetangganya, dan persis mekanisme yang digerakkan Kubernetes saat Anda men-set resource requests dan limits (bagian 17). Lewati batas memori dan kernel OOM killer menghentikan container, status OOMKilled yang terkenal.
3. Image file system: root-nya sendiri
Terakhir proses butuh file: / sendiri dengan runtime, library, dan biner Anda. Itu berasal dari image, dilapiskan ke mount namespace sebagai root file system container (bagian 04). Proses tidak bisa melihat file host, hanya milik image, plus apa pun yang Anda mount eksplisit (bagian 09).
Namespaces dan cgroups fitur kernel Linux, jadi container aslinya teknologi Linux. Docker di macOS atau Windows diam-diam menjalankan VM Linux ringan dan container Anda hidup di dalamnya, itulah kenapa "container" di Mac Anda sebenarnya Linux-di-dalam-VM di balik layar. Artefaknya portabel; fitur kernel yang ia butuhkan milik Linux.
Image, Layer & Union Filesystem
Dua kata sering dikacaukan, jadi kunci dulu. Image adalah template immutable di disk, file system terkemas plus metadata (apa yang dijalankan, port mana, env). Container adalah instance berjalan dari image, image dihidupkan sebagai proses terisolasi (bagian 03). Hubungannya persis class-ke-objek: satu image, banyak container.
Image adalah tumpukan layer read-only
Bagian cerdasnya bagaimana image menyimpan file system-nya: sebagai tumpukan layer, masing-masing satu set perubahan file, ditumpuk union filesystem yang menggabungkannya menjadi / koheren tunggal. Setiap instruksi di build Anda menambah satu layer (bagian 05). Layer immutable dan di-alamat-kan oleh konten, jadi layer identik disimpan sekali dan dibagikan lintas image, jika sepuluh image memakai base yang sama, base itu hidup di disk dan bepergian lewat jaringan hanya sekali.
Apa pun yang ditulis container ke file system-nya sendiri mati bersama container. Log, upload, file database yang ditulis "di dalam" hilang saat restart. Data persisten harus hidup di volume (bagian 09) atau layanan eksternal. Ephemeralitas ini fitur, inilah yang membuat container sekali pakai dan bisa diganti, tapi ia jebakan pemula yang paling umum.
Dockerfile
Dockerfile resep yang membangun image: urutan instruksi, dieksekusi atas ke bawah, masing-masing menghasilkan satu layer (bagian 04). docker build membacanya, menjalankan tiap langkah, dan meng-cache hasilnya. Karena file-nya teks polos di repo Anda, build Anda reproducible dan bisa direview, lingkungan menjadi kode. Ini service web yang sama di-container-kan di Go dan Python:
# FROM picks the base image, the first (bottom) layer. Pin a version, never :latest
FROM golang:1.22
# WORKDIR sets the working dir for the instructions that follow (and at runtime)
WORKDIR /app
# Copy dependency manifests FIRST and download, this layer caches across code edits (sec 7)
COPY go.mod go.sum ./
RUN go mod download
# Now copy the source and build the binary
COPY . .
RUN go build -o /server ./cmd/server
# Document the port the app listens on (metadata; publishing happens at run time sec 8)
EXPOSE 8080
# CMD is the default command run when a container starts from this image
CMD ["/server"]# Pin a slim base, smaller and fewer CVEs than the full image
FROM python:3.11-slim
WORKDIR /app
# Copy the manifest FIRST so the dependency layer caches across code edits (sec 7)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Then copy the application code
COPY . .
EXPOSE 8080
# Exec form (JSON array), runs the process directly as PID 1, so signals work (sec graceful shutdown)
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]| Instruksi | Fungsinya |
|---|---|
FROM | Base image, layer awal. Semua dibangun di atasnya. |
WORKDIR | Men-set (dan membuat) direktori saat ini untuk langkah berikutnya dan saat runtime. |
COPY / ADD | Salin file dari build context Anda ke image. Prefer COPY; ADD punya perilaku URL/tar yang mengejutkan. |
RUN | Jalankan perintah saat build (instal deps, kompilasi), membakar hasilnya ke layer. |
ENV | Set environment variable yang dibakar ke image (default config, bagian 16). |
EXPOSE | Mendokumentasikan port listening. Informatif; tidak mem-publish-nya (bagian 08). |
ENTRYPOINT / CMD | Yang berjalan saat container mulai. ENTRYPOINT = executable, CMD = argumen default. |
RUN vs CMD
Dua kebingungan yang sering. Build context adalah direktori yang Anda arahkan ke docker build, semua isinya dikirim ke builder, jadi context yang bengkak (mis. node_modules, folder .git) memperlambat build; pangkas dengan .dockerignore (bagian 07). Dan RUN dieksekusi saat build (dibakar ke image) sementara CMD/ENTRYPOINT dieksekusi saat run (saat container mulai). Mencampurnya kesalahan awal klasik.
Multi-Stage Build
Teknik paling berdampak tunggal untuk image produksi. Masalahnya: membangun butuh tool berat (kompiler, SDK penuh, dependensi dev build-time) yang tidak dibutuhkan aplikasi yang berjalan. Kirim semuanya dan image Anda besar dan penuh permukaan serangan. Multi-stage build memakai satu stage untuk membangun dan stage kedua yang bersih yang hanya menyalin artefak jadi, toolchain-nya dibuang.
# ---- Stage 1: build with the full Go toolchain ----
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# static binary, no libc dependency, perfect for a scratch/distroless final image
RUN CGO_ENABLED=0 go build -o /server ./cmd/server
# ---- Stage 2: tiny runtime, no compiler, non-root ----
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /server /server # copy ONLY the artifact across stages
EXPOSE 8080
USER nonroot:nonroot # never run as root (sec 17, security)
ENTRYPOINT ["/server"]
# Result: a ~10-15 MB image with no shell, no package manager, minimal CVEs# ---- Stage 1: install deps into a venv with build tools available ----
FROM python:3.11 AS builder
WORKDIR /app
RUN python -m venv /venv
ENV PATH="/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt # may compile native wheels here
# ---- Stage 2: slim runtime, copy the ready venv, run non-root ----
FROM python:3.11-slim
COPY --from=builder /venv /venv # copy ONLY the built virtualenv
ENV PATH="/venv/bin:$PATH"
WORKDIR /app
COPY . .
RUN useradd -m app && chown -R app /app
USER app # non-root (sec 17)
EXPOSE 8080
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]Go mengompilasi ke satu biner statis, jadi stage akhir bisa hampir kosong (distroless/scratch). Python butuh interpreter, jadi keuntungannya datang dari menyalin virtualenv yang sudah dibangun ke base -slim alih-alih membawa kompilernya.
Image lebih kecil menarik lebih cepat (autoscaling dan rollout lebih cepat, bagian 18), lebih murah disimpan dan ditransfer, dan mengecilkan permukaan serangan dramatis, image distroless tidak punya shell untuk dipakai penyerang. Multi-stage build default untuk image layanan serius mana pun.
Optimasi Image & Layer Caching
Dua tuas membuat build cepat dan image ramping, dan keduanya mengikuti langsung dari model layer (bagian 04). Yang pertama urutan cache; yang kedua pilihan base image plus memangkas yang Anda salin.
Urutkan instruksi dari yang paling jarang berubah ke paling sering berubah
Docker meng-cache tiap layer dan memakainya ulang di build berikutnya selama instruksi itu dan semua sebelumnya tidak berubah. Begitu input satu layer berubah, layer itu dan setiap layer sesudahnya dibangun ulang. Jadi aturan emasnya: taruh yang jarang berubah (memasang dependensi) sebelum yang berubah di setiap commit (menyalin source Anda). Itulah kenapa setiap Dockerfile di atas menyalin manifest dependensi dan memasangnya sebelum menyalin source penuh, Anda mengedit kode terus tapi dependensi jarang, jadi layer instalasi mahal tetap ter-cache.
Pilih base kecil & salin lebih sedikit
- Base image: varian
-slimmembuang ratusan MB; alpine mungil (musl libc, waspadai kompatibilitas); distroless hanya mengirim aplikasi Anda dan runtime-nya tanpa shell atau package manager (paling aman). Pilih yang terkecil yang menjalankan aplikasi Anda. .dockerignore: menjaga sampah keluar dari build context dan image (build lebih cepat, image lebih kecil, tanpa secret bocor masuk).- Gabungkan
RUNyang berhubungan & bersihkan di layer yang sama: menghapus cache di layer belakangan tidak mengecilkan image (byte-nya masih hidup di layer awal); bersihkan di dalamRUNyang sama.
.git
node_modules
*.md
.env # never bake secrets into an image (sec 16)
**/__pycache__
*.log
dist/
.vscode/
RUN apt-get install ... di satu baris dan RUN rm -rf /var/lib/apt/lists/* di baris berikutnya tidak mengurangi ukuran, file-nya masih ada di layer instalasi. Rantai keduanya: RUN apt-get update && apt-get install -y X && rm -rf /var/lib/apt/lists/* supaya pembersihan terjadi sebelum layer disegel.
Runtime, Registry & Tag
Anda sudah membangun image, sekarang jalankan, bagikan, dan beri versi. Tiga perintah mencakup 90% pemakaian harian, dan tiga konsep (registry, tag, runtime) membingkai siklus hidupnya.
# BUILD an image from the Dockerfile in . and name:tag it
docker build -t myapp:1.4.0 .
# RUN a container from it:
# -d detached -p host:container publishes a port -e sets an env var (sec 16)
docker run -d -p 8080:8080 -e DATABASE_URL=$DB myapp:1.4.0
# Inspect & debug
docker ps # running containers
docker logs -f <id> # stream stdout/stderr (your app should log there sec logging)
docker exec -it <id> sh # shell INTO a running container to poke around
docker stop <id> # sends SIGTERM, then SIGKILL after a grace period (sec graceful shutdown)
# SHARE via a registry
docker tag myapp:1.4.0 registry.example.com/team/myapp:1.4.0
docker push registry.example.com/team/myapp:1.4.0 # upload
docker pull registry.example.com/team/myapp:1.4.0 # download (what prod/K8s does)
Registry, repository, tag
Registry server penyimpan image (Docker Hub, GitHub Container Registry, AWS ECR, Google Artifact Registry). Repository image bernama di dalamnya; tag label untuk versi spesifik (myapp:1.4.0). Push mengunggah layer Anda; pull mengunduhnya, dan berkat layer ber-alamat-konten (bagian 04), hanya layer yang belum dimiliki registry yang bergerak lewat kabel. Siklus push/pull ini serah terima antara build Anda (bagian 19) dan tempat image berjalan (Bagian IV).
:latest
latest hanya tag yang bergerak; ia tidak berarti "terbaru" dan tidak memberi tahu apa yang berjalan. Dua mesin menarik :latest berselang sehari bisa mendapat image berbeda, persis non-reproducibility yang ingin dimatikan container. Tag dengan versi immutable atau git commit SHA (myapp:1.4.0, myapp:git-9f2c1a) supaya container yang berjalan memetakan ke source persis.
"Docker image" singkatan, formatnya distandarkan Open Container Initiative (OCI), itulah kenapa image yang dibangun Docker berjalan di runtime lain (containerd, CRI-O, Podman) dan di Kubernetes tanpa Docker terpasang. Anda membangun ke spesifikasi, bukan ke satu vendor.
Networking & Volume
Dua kebutuhan muncul begitu Anda menjalankan lebih dari mainan: container harus bicara satu sama lain, dan sebagian data harus lebih awet dari container. Docker menjawabnya dengan network dan volume, dan keduanya langsung mengisi celah yang ditinggalkan model isolasi (bagian 03) dan layer writable ephemeral (bagian 04).
Networking: dari terisolasi ke terhubung
Secara default tiap container punya network namespace sendiri, jadi ia tidak bisa melihat yang lain. Taruh container di bridge network user-defined yang sama dan Docker memberi mereka LAN virtual privat plus DNS bawaan: container mencapai yang lain cukup dengan namanya (http://db:5432). Ini fondasi service discovery, dan itulah kenapa Compose (bagian 10) dan Kubernetes (bagian 15) membiarkan service saling mengalamati dengan nama alih-alih IP rapuh. Terpisah, port publishing (-p 8080:8080) melubangi host ke container supaya dunia luar bisa menjangkaunya.
Volume: data yang bertahan
Karena layer writable container dihancurkan bersama container (bagian 04), apa pun yang harus persisten, file database, upload pengguna, hidup di volume: penyimpanan yang dikelola Docker (atau host) dan di-mount ke container, terlepas dari siklus hidupnya. Hapus dan buat ulang container; volume dan datanya tetap. Ada dua rasa: named volume (dikelola Docker, default untuk data sungguhan) dan bind mount (direktori host dipetakan masuk, berguna di pengembangan untuk mengedit kode langsung tanpa rebuild).
| Mekanisme | Pakai untuk |
|---|---|
Named volume -v dbdata:/var/lib/postgresql/data | Data aplikasi persisten yang dikelola Docker, database, cache yang ingin Anda simpan. |
Bind mount -v ./src:/app/src | Live reload saat dev, edit di host, lihat perubahan di container seketika. |
| tmpfs | Data scratch sensitif yang hanya di RAM, tidak pernah ditulis ke disk. |
Perlakukan container sebagai sekali pakai dan simpan semua state tahan lama di volume atau layanan terkelola eksternal. Di Kubernetes khususnya, pola yang berlaku stateless app container (mudah di-scaling dan diganti, bagian 18) dengan state didorong ke database terkelola atau persistent volume. Jika mematikan container kehilangan data, desainnya membocorkan state ke tempat yang salah.
Docker Compose
Menjalankan aplikasi nyata berarti beberapa container, API, database, cache, mungkin worker (setup bab task-queue), dengan network, volume, env, dan urutan mulai yang tepat. Merangkainya manual dengan banyak perintah docker run membosankan dan rawan error. Docker Compose mendeklarasikan seluruh stack lokal dalam satu file YAML dan menaikkannya dengan satu perintah. Ia standar untuk pengembangan lokal dan deployment single-host sederhana.
services:
api:
build: . # build the image from the local Dockerfile (sec 5/6)
ports:
- "8080:8080" # publish to the host (sec 9)
environment: # config via env, not baked in (sec 16)
DATABASE_URL: postgres://app:secret@db:5432/app # reach "db" by name (sec 9 DNS)
REDIS_URL: redis://cache:6379
depends_on:
db:
condition: service_healthy # wait until the DB passes its healthcheck
cache:
condition: service_started
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
POSTGRES_DB: app
volumes:
- dbdata:/var/lib/postgresql/data # named volume = data survives restarts (sec 9)
healthcheck: # so depends_on can wait for readiness
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5
cache:
image: redis:7
volumes:
dbdata: # declared once, referenced above
docker compose up -d # build (if needed) + start everything in the background
docker compose ps # status of all services
docker compose logs -f api # tail one service's logs
docker compose down # stop & remove containers + network (volumes kept)
docker compose down -v # ...and delete the volumes too (fresh DB)
Compose untuk satu host: hebat untuk dev lokal dan deployment kecil, tapi ia tidak menjadwalkan lintas mesin, menyembuhkan diri, atau autoscale. Begitu Anda butuh banyak node, restart otomatis, update bergulir, dan scaling horizontal, Anda sudah melampaui Compose dan ingin orkestrator (Bagian IV). Model mentalnya terbawa: service Compose memetakan dekat ke Deployment + Service Kubernetes.
Kenapa Orkestrasi
Docker memberi Anda container; ia tidak, dengan sendirinya, menjalankannya andal lintas banyak mesin. Begitu Anda punya puluhan container di armada server, muncul set masalah baru yang tidak bisa dipecahkan docker run mana pun, dan orkestrasi lapisan yang memecahkannya. Kubernetes (sering "K8s") standar de facto.
| Masalah di skala armada | Yang dilakukan orkestrator |
|---|---|
| Mesin mana yang harus ditempati tiap container? | Scheduling: menempatkan container di node berdasarkan CPU/memori tersedia. |
| Container (atau seluruh node) mati jam 3 pagi. | Self-healing: me-restart container yang crash, menjadwal ulang dari node mati, otomatis. |
| Lonjakan lalu lintas; Anda butuh lebih banyak salinan, lalu lebih sedikit. | Scaling: menjalankan N replika dan menambah/menghapusnya sesuai permintaan (bagian 18). |
| Container datang dan pergi; IP mereka berubah. | Service discovery + load balancing: alamat stabil yang menyebar ke replika sehat (bagian 15). |
| Kirim versi baru tanpa downtime. | Rolling update & rollback: mengganti replika bertahap, kembali saat gagal (bagian 18). |
| Config & secret berbeda per lingkungan. | Manajemen config: menyuntikkan setelan/secret tanpa membangun ulang image (bagian 16). |
Gagasan inti: deklaratif, desired state
Pergeseran pola pikir yang membuat Kubernetes klik: Anda tidak mengeluarkan perintah imperatif ("mulai container di sini"). Anda mendeklarasikan desired state ("aku mau 5 replika image ini, terjangkau di alamat ini") dan Kubernetes terus bekerja membuat kenyataan cocok. Jika container mati, aktual turun ke 4, dan control loop melihat celahnya dan memulai yang lain, tanpa Anda melakukan apa pun. Anda mendeskripsikan apa; Kubernetes menangani bagaimana dan menjaganya tetap benar.
Semuanya di Kubernetes adalah control loop yang membandingkan desired state (yang Anda deklarasikan) dengan actual state (yang berjalan) dan mengambil tindakan menutup celah. Internalisasi loop itu dan seluruh sistem, Deployment, scaling, penyembuhan, rollout, menjadi satu gagasan yang diterapkan berulang-ulang.
Arsitektur Kubernetes
Klaster Kubernetes dua jenis mesin: control plane (otak yang memutuskan apa yang harus berjalan) dan set worker node (otot tempat container sebenarnya berjalan). Anda hanya bicara ke control plane, Anda mengirimkan desired state, dan klaster mewujudkannya.
| Komponen | Peran |
|---|---|
| api-server | Satu-satunya titik masuk. Setiap baca/tulis state klaster melewatinya (kubectl Anda, setiap komponen). |
| etcd | Key-value store terdistribusi yang memegang seluruh state klaster, sumber kebenaran. |
| scheduler | Memutuskan node mana yang ditempati pod baru, berdasarkan resource requests dan batasan. |
| controller-manager | Menjalankan loop rekonsiliasi (controller Deployment, dll.) yang menggerakkan aktual menuju desired. |
| kubelet | Agen di tiap node; memulai/menghentikan container dan melaporkan kesehatannya ke api-server. |
| kube-proxy | Memprogram networking node supaya virtual IP Service merutekan ke pod yang tepat (bagian 15). |
Kubernetes terkelola (GKE, EKS, AKS) mengoperasikan control plane untuk Anda, jadi dalam praktik Anda berinteraksi dengan klaster murni lewat api-server via kubectl dan YAML. Mengetahui bagian-bagiannya tetap penting untuk debugging, "pod stuck in Pending" biasanya scheduler tidak menemukan node dengan ruang (bagian 17).
Pod
Pod unit atomik Kubernetes, hal terkecil yang ia jadwalkan. Yang penting, Anda tidak men-deploy container langsung; Anda men-deploy pod, dan pod membungkus satu atau lebih container yang selalu di-lokasi-bersama di node yang sama dan berbagi network namespace dan penyimpanan. Container dalam pod saling menjangkau di localhost dan bisa berbagi volume ter-mount, mereka satu unit kooperatif.
Biasanya pod memegang tepat satu container (aplikasi Anda). Kasus multi-container adalah pola sidecar: pembantu di samping aplikasi utama, log shipper, metrik exporter, atau proxy service-mesh (Envoy dari bab gRPC bagian 19). Sidecar berbagi network pod jadi ia bisa mengamati atau mem-proxy lalu lintas aplikasi secara transparan.
Pod ephemeral: ia bisa dibunuh dan diganti kapan pun (kegagalan node, scale-down, rollout), dan penggantinya mendapat IP baru. Jadi Anda tidak pernah hard-code IP pod dan tidak pernah mengandalkan pod spesifik bertahan. Persis inilah kenapa Service ada (bagian 15), memberi alamat stabil di depan set pod yang terus berubah, dan kenapa Anda membiarkan Deployment mengelola pod alih-alih membuatnya langsung (bagian 14).
Deployment & ReplicaSet
Anda hampir tidak pernah membuat pod manual, pod telanjang tidak diganti jika mati. Gantinya Anda mendeklarasikan Deployment: "jaga N replika template pod ini berjalan, selalu." Deployment mengelola ReplicaSet, yang satu-satunya tugas menjaga jumlah replika, dan ReplicaSet mengelola pod. Ini loop rekonsiliasi (bagian 11) dibuat konkret: deklarasikan 5, pod mati, aktual jadi 4, controller memulai yang baru, self-healing, gratis.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3 # desired state: always keep 3 pods
selector:
matchLabels: { app: api } # which pods this Deployment owns
template: # the POD TEMPLATE stamped out for each replica
metadata:
labels: { app: api }
spec:
containers:
- name: api
image: registry.example.com/team/myapp:1.4.0 # pinned tag, never :latest (sec 8)
ports:
- containerPort: 8080
# resources, env, and probes go here too, see sec 16 and sec 17
kubectl apply -f deployment.yaml # submit desired state (declarative)
kubectl get pods -l app=api # see the 3 pods it created
kubectl scale deployment/api --replicas=10 # change desired count -> controller adds 7
kubectl rollout status deployment/api # watch a new version roll out (sec 18)
kubectl rollout undo deployment/api # roll back to the previous version (sec 18)
Deployment untuk aplikasi stateless (kasus umum). Ada saudara untuk bentuk lain: StatefulSet untuk aplikasi ber-state yang butuh identitas/penyimpanan stabil (database), DaemonSet untuk menjalankan satu pod per node (agen log/metrik), dan Job/CronJob untuk run-to-completion dan tugas terjadwal (sisi batch dari bab task-queue). Gagasan rekonsiliasi yang sama, jaminan berbeda.
Service & Ingress
Pod fana dan IP mereka berganti (bagian 13), jadi Anda tidak bisa mengarahkan lalu lintas ke pod. Service perbaikannya: virtual IP dan nama DNS yang stabil di depan set pod dinamis, dengan load balancing bawaan. Ia memakai label selector untuk melacak "semua pod dengan app: api", saat pod datang dan pergi, set endpoint sehat Service diperbarui otomatis, dan pemanggil tidak pernah menyadarinya. Ini service discovery Kubernetes, dan itulah kenapa satu service mengalamati yang lain dengan nama (http://api), persis seperti Compose di satu host (bagian 9).
Tipe Service & Ingress
Service ClusterIP (default) hanya terjangkau di dalam klaster, pas untuk panggilan antar-layanan. Untuk menerima lalu lintas dari luar, Anda memakai Service LoadBalancer (cloud menyediakan load balancer eksternal, satu per service, mahal di skala) atau, jauh lebih umum untuk HTTP, Ingress: satu titik masuk L7 yang merutekan berdasarkan host dan path ke banyak Service pendukung. Ingress tempat TLS termination dan routing host/path hidup, pintu depan publik klaster, dan rumah alami untuk edge REST yang dijelaskan di bab gRPC (bagian 18-19 di sana).
| Eksposur | Terjangkau dari | Pakai untuk |
|---|---|---|
ClusterIP | Hanya di dalam klaster | Antar-layanan (kebanyakan service internal) |
NodePort | Sebuah port di setiap node | Akses eksternal dasar/dev |
LoadBalancer | Internet (LB cloud) | Satu service TCP/UDP diekspos langsung |
| Ingress | Internet, via aturan L7 | Routing host/path HTTP + TLS untuk banyak service lewat satu titik masuk |
apiVersion: v1
kind: Service
metadata:
name: api # other pods reach this as http://api (cluster DNS)
spec:
selector: { app: api } # front the pods labelled app=api (from the Deployment sec 14)
ports:
- port: 80 # the Service port callers use
targetPort: 8080 # the containerPort it forwards to
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: edge
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api # route external HTTP to the api Service
port: { number: 80 }
Klien eksternal -> Ingress (TLS, routing host/path) -> Service (IP stabil, load-balance) -> Pod sehat -> container Anda. Setiap hop menambah satu perhatian, routing, penemuan, kerja sebenarnya, dan bersama-sama mereka cara satu paket dari internet mencapai satu replika aplikasi Anda.
Config, Secret & Environment
Image harus agnostik lingkungan: image yang sama yang diuji berjalan di dev, staging, dan prod, hanya konfigurasi yang berbeda (URL database, feature flag, kredensial). Jadi konfigurasi disuntikkan saat run time, tidak pernah dibakar ke image. Ini bentuk containerized dari aturan inti bab manajemen config, dan ekspresi klasik twelve-factor "config in the environment." Kubernetes menyediakan dua objek: ConfigMap untuk setelan non-rahasia dan Secret untuk nilai sensitif.
apiVersion: v1
kind: ConfigMap
metadata: { name: api-config }
data:
LOG_LEVEL: "info"
FEATURE_NEW_CHECKOUT: "true"
---
apiVersion: v1
kind: Secret
metadata: { name: api-secrets }
type: Opaque
stringData: # plaintext here; stored base64-encoded in etcd
DATABASE_URL: "postgres://app:secret@db:5432/app"
---
# ...inside the Deployment's container spec (sec 14):
# envFrom:
# - configMapRef: { name: api-config } # all keys -> env vars
# - secretRef: { name: api-secrets } # secret keys -> env vars
# Your app then reads os.Getenv("LOG_LEVEL") / os.environ["DATABASE_URL"] (sec config chapter)
Secret Kubernetes hanya di-encode base64 di etcd, tidak dienkripsi, base64 encoding, bukan proteksi. Untuk keamanan nyata aktifkan encryption at rest untuk etcd, kunci RBAC supaya sedikit identitas yang bisa membaca Secret, dan untuk sistem sensitif pakai manajer eksternal (Vault, cloud secret store, atau Secrets Store CSI driver). Dan jangan pernah commit nilai secret nyata ke Git, disiplin jangan-bocorkan-kredensial yang sama dengan bab keamanan dan auth.
Health Probe & Manajemen Resource
Agar self-healing dan zero-downtime bekerja, Kubernetes perlu tahu dua hal tentang tiap pod: apakah ia hidup? dan apakah ia siap menerima lalu lintas? Ia tidak bisa menebak, aplikasi Anda harus memberitahunya, via probe. Dan untuk menjadwalkan dan mengisolasi dengan adil, ia perlu tahu berapa banyak CPU dan memori yang diinginkan tiap container, via requests dan limits (cgroups dari bagian 03, dimunculkan sebagai config).
Tiga probe, tiga pertanyaan
- Liveness: "apakah proses sehat, atau macet?" Jika gagal, Kubernetes me-restart container. Menangkap deadlock dan state menggantung yang tidak akan ditangkap crash.
- Readiness: "bisakah ia melayani request sekarang?" Jika gagal, pod ditarik dari rotasi Service-nya (bagian 15) tapi tidak di-restart. Inilah yang memungkinkan rollout zero-downtime dan backpressure: pod yang sedang memanas atau sebentar kelebihan beban berhenti menerima lalu lintas sampai pulih.
- Startup: "apakah aplikasi yang mulai lambat selesai boot?" Menjaga probe lain supaya cold start panjang tidak disangka kegagalan.
Requests & limits
Request jumlah yang direservasi scheduler untuk container (dan dipakai memilih node); limit plafon keras yang ditegakkan runtime via cgroups. Lewati batas CPU dan container di-throttle; lewati batas memori dan ia dibunuh (OOMKilled). Men-set keduanya dengan baik pembeda antara klaster stabil dan kekacauan tetangga berisik, dan mereka memberi makan autoscaling (bagian 18).
# ...inside the Deployment's containers: entry (sec 14)
livenessProbe:
httpGet: { path: /healthz, port: 8080 } # wedged? -> restart
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet: { path: /ready, port: 8080 } # ready for traffic? -> in/out of rotation
periodSeconds: 5
resources:
requests: { cpu: "100m", memory: "128Mi" } # scheduler reserves this
limits: { cpu: "500m", memory: "256Mi" } # cgroup ceiling; over memory -> OOMKilled
Probe hanya separuh penanganan siklus hidup yang bersih. Separuh lainnya bab graceful shutdown: saat rollout atau scale-down, Kubernetes mengirim SIGTERM dan menunggu masa tenggang sebelum SIGKILL. Aplikasi Anda harus menggagalkan readiness probe seketika (hentikan lalu lintas baru), selesaikan request in-flight, lalu keluar, supaya tidak ada request yang dijatuhkan di tengah jalan. Probe memutuskan apa yang mendapat lalu lintas; graceful shutdown memutuskan bagaimana pod pergi.
Scaling & Rollout
Dua operasi sehari-hari menutup orkestrasi: menangani lebih banyak beban (scaling) dan mengirim versi baru tanpa downtime (rollout). Keduanya bersandar pada semua yang dibangun sejauh ini, replika, Service, readiness probe, dan keduanya ekspresi Kubernetes dari topik bab scaling.
Scaling horizontal & autoscaling
Scaling horizontal berarti menjalankan lebih banyak pod replika (lebih disukai daripada membuat satu pod lebih besar, tema horizontal-vs-vertikal dari bab scaling). Anda bisa set jumlahnya manual, atau menyerahkannya ke Horizontal Pod Autoscaler (HPA), yang memantau metrik (biasanya CPU) dan menyesuaikan replika untuk menahan target, scaling keluar di bawah beban dan kembali saat mereda. Karena app pod stateless (bagian 9), replika mana pun bisa melayani request apa pun, jadi menambah pod tinggal bekerja.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: api }
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 70 } # keep avg CPU ~70%
# Requires resource requests (sec 17), utilization is measured against the request.
Rolling update & rollback
Saat Anda mengubah tag image dan menerapkan ulang, Deployment melakukan rolling update secara default: ia menaikkan pod versi baru beberapa sekaligus, menunggu tiap pod lolos probe readiness-nya (bagian 17) sebelum mengirimnya lalu lintas, dan baru setelah itu pensiunkan pod lama. Di setiap momen cukup pod sehat yang melayani, jadi pengguna tidak melihat downtime. Jika pod baru gagal readiness, rollout berhenti alih-alih merobohkan aplikasi, dan satu perintah kembali ke versi baik terakhir.
Deploy zero-downtime bukan satu fitur, ia komposisi dari replika (bagian 14), readiness probe (bagian 17), graceful shutdown, dan abstraksi Service (bagian 15) yang menjaga alamat stabil di atas set pod yang bergeser. Setiap bagian mendapat tempatnya; rollout tempat mereka membayar.
Pipeline CI/CD
Container membuat artefak reproducible; CI/CD membuat jalur dari commit kode ke artefak itu berjalan di produksi otomatis. CI (Continuous Integration) paruh build-dan-verifikasi, di setiap push, bangun image, jalankan tes, pindai. CD (Continuous Delivery/Deployment) paruh pengiriman, dorong image ke registry dan rollout. Intinya menghapus langkah manual yang rawan error supaya deploy sering, kecil, dan membosankan.
name: ci-cd
on:
push:
branches: [main]
jobs:
build-test-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests # CI gate, must pass before we ship (sec testing chapter)
run: make test # go test ./... | pytest
- name: Build image
run: docker build -t $REGISTRY/myapp:${{ github.sha }} . # tag = commit SHA (sec 8)
- name: Scan image for vulnerabilities
run: trivy image --severity HIGH,CRITICAL --exit-code 1 $REGISTRY/myapp:${{ github.sha }}
- name: Push to registry
run: |
echo "$REGISTRY_TOKEN" | docker login $REGISTRY -u ci --password-stdin
docker push $REGISTRY/myapp:${{ github.sha }}
- name: Deploy to Kubernetes # update the Deployment's image -> triggers a rolling update (sec 18)
run: kubectl set image deployment/api api=$REGISTRY/myapp:${{ github.sha }}
Men-tag image dengan git SHA (dan men-deploy tag persis itu) memberi tautan presisi immutable dari pod yang berjalan kembali ke source yang membangunnya, tujuan reproducibility dari bagian 8, end to end. Saat sesuatu rusak di produksi Anda tahu persis commit mana yang berjalan, dan rollback (bagian 18) tidak ambigu.
Strategi Deployment & GitOps
Rolling update (bagian 18) default, tapi rilis berisiko menuntut strategi yang membatasi radius ledakan, dan tim matang mengubah cara deploy dipicu sama sekali dengan GitOps.
| Strategi | Cara kerjanya | Kompromi |
|---|---|---|
| Rolling | Ganti pod bertahap (default) | Sederhana, zero-downtime; lama + baru berjalan sebentar bersama |
| Blue-green | Dirikan versi baru penuh (green) di samping lama (blue), lalu alihkan semua lalu lintas sekaligus | Peralihan instan & rollback instan; butuh kapasitas dua kali lipat selama cutover |
| Canary | Kirim % kecil lalu lintas ke versi baru, amati metrik, lalu naikkan | Paling aman untuk perubahan berisiko; butuh lebih banyak orkestrasi/observabilitas |
GitOps: Git sebagai sumber kebenaran
Pipeline di bagian 19 mendorong perubahan ke klaster. GitOps membaliknya: desired state klaster hidup sepenuhnya di repositori Git (semua manifest YAML itu), dan agen di dalam klaster (Argo CD, Flux) terus menarik dari Git dan merekonsiliasi klaster agar cocok. Ini loop rekonsiliasi bagian 11 diperluas ke deployment itu sendiri: Git adalah desired state, klaster actual state, agen menutup celah. Anda deploy dengan menggabungkan pull request; rollback dengan git revert; dan repo-nya riwayat lengkap teraudit dari setiap perubahan ke produksi.
Perhatikan gagasan yang sama di tiga skala: container menjaga artefak identik, Deployment menjaga pod yang berjalan cocok dengan spesifikasi, dan GitOps menjaga seluruh klaster cocok dengan Git. Deklarasikan desired state, biarkan loop merekonsiliasi kenyataan, itu container dan Kubernetes turun ke bawah.
Crib-Sheet Produksi
Seluruh manual dipadatkan menjadi yang Anda raih saat tertekan.
| Konsep | Satu baris |
|---|---|
| Container | Proses terisolasi, namespaces (apa yang ia lihat) + cgroups (apa yang ia pakai) + file system image. |
| vs VM | Container berbagi satu kernel host; VM masing-masing mengirim OS penuh. Lebih ringan, lebih cepat, isolasi lebih lemah. |
| Image vs container | Image = template immutable (class); container = instance berjalan (objek). |
| Layer | Image adalah layer read-only bertumpuk, dibagikan & di-cache; lapisan writable container ephemeral. |
| Dockerfile | Resep; tiap instruksi = satu layer. RUN saat build, CMD saat run. |
| Multi-stage | Bangun di stage berat, salin hanya artefaknya ke image akhir mungil. |
| Urutan cache | Dependensi sebelum kode, layer yang berubah membangun ulang semua di bawahnya. |
| Registry & tag | Push/pull image berdasarkan nama:tag; pin versi atau SHA, jangan pernah :latest. |
| Volume | Data yang harus bertahan hidup di volume, bukan layer writable container. |
| Compose | Stack multi-container deklaratif di satu host, dev lokal. |
| Orkestrasi | Scheduling, self-healing, scaling, penemuan, rollout lintas banyak node. |
| Deklaratif | Deklarasikan desired state; control loop merekonsiliasi aktual menuju ke sana. Seluruh model. |
| Pod | Unit terkecil; satu+ container berbagi network & storage. Fana, dapat IP baru saat diganti. |
| Deployment | Menjaga N replika pod berjalan; self-heal; melakukan rolling update. |
| Service | IP/DNS stabil load-balancing lintas pod yang cocok, service discovery. |
| Ingress | Satu titik masuk L7: routing host/path + TLS ke banyak Service. |
| Config/Secret | Suntikkan saat run time (ConfigMap/Secret); image sama di mana-mana. Secret tidak dienkripsi secara default. |
| Probe | Liveness -> restart; readiness -> masuk/keluar rotasi; startup -> jaga boot lambat. |
| Requests/limits | Request = direservasi (scheduling); limit = plafon cgroup. Lewati memori -> OOMKilled. |
| Scaling | Jalankan lebih banyak replika; HPA autoscale pada metrik (butuh requests di-set). |
| Rollout/rollback | Ganti bertahap digerbangi readiness; kubectl rollout undo mengembalikan. |
| CI/CD | Commit -> build -> test -> scan -> push -> deploy, otomatis; tag dengan SHA. |
| Strategi | Rolling (default), blue-green (peralihan instan), canary (menetes & amati). |
| GitOps | Git adalah desired state; agen menarik & merekonsiliasi. Deploy dengan PR, rollback dengan revert. |
Seluruh topik dalam satu tarikan napas: container mengemas aplikasi Anda dengan seluruh lingkungannya supaya "yang berjalan di prod" sama dengan "yang Anda uji" (bagian 1), dicapai dengan namespaces dan cgroups Linux, bukan VM berat (bagian 2-3). Anda membangun image sebagai layer ter-cache yang bisa dibagikan (bagian 4) dari Dockerfile (bagian 5), mengecilkan dan mengeraskannya dengan multi-stage build dan urutan cache yang baik (bagian 6-7), lalu push image ber-tag SHA ke registry (bagian 8). Secara lokal Anda merangkai beberapa container dengan network, volume, dan Compose (bagian 9-10). Di skala armada, Kubernetes merekonsiliasi desired state yang dideklarasikan ke node (bagian 11-12): Deployment menjaga replika pod tetap hidup dan self-healing (bagian 13-14), Service dan Ingress memberi pengalamatan dan routing stabil (bagian 15), ConfigMap/Secret menyuntikkan config per-lingkungan (bagian 16), probe dan resource requests/limits menggerakkan kesehatan dan scheduling (bagian 17), dan HPA plus rolling update mengirim scaling dan rilis zero-downtime (bagian 18). CI/CD mengotomasi commit->build->test->scan->push->deploy (bagian 19), dan blue-green/canary/GitOps membuat rilis aman dan teraudit (bagian 20). Satu gagasan berulang di setiap lapisan: deklarasikan desired state dan biarkan loop menjaga kenyataan cocok dengannya.
Berpijak pada dokumentasi Docker & Kubernetes / OCI image-spec / Twelve-Factor App / contoh aplikasi Go 1.22+ / Python 3.11+.