jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Docker for Developers · Part 9 — Kubernetes Fundamentals

From a single host to a cluster: why Kubernetes exists, its architecture, and the core objects — Pods, Deployments, and Services. Spin up a local cluster with kind and deploy your first app from Compose.

Part 9 of 10 in the Docker → Compose → Kubernetes series {Phần 9/10 trong series Docker → Compose → Kubernetes}. Previous {Trước}: Part 8 — Debugging & Troubleshooting Docker · Next {Tiếp}: Part 10 — Kubernetes in Practice & Debugging. Every part ends with exercises; do them, don’t just read {Mỗi phần kết thúc bằng bài tập; hãy làm, đừng chỉ đọc}.

Docker Compose runs containers on one machine — you declare services in YAML and docker compose up brings the stack online (Part 5, Part 6) {Docker Compose chạy container trên một máy — bạn khai báo service trong YAML và docker compose up bật cả stack (Phần 5, Phần 6)}. That is enough for local dev and many small deployments {Đủ cho dev local và nhiều triển khai nhỏ}. When you need many machines, self-healing (restart crashed workloads), rolling updates without downtime, and horizontal scaling across nodes, you need an orchestrator — and the industry default is Kubernetes (often shortened k8s) {Khi cần nhiều máy, tự phục hồi, rolling update không downtime và scale ngang trên nhiều node, bạn cần orchestrator — chuẩn công nghiệp là Kubernetes (k8s)}.

Be honest: Kubernetes is heavier than Docker alone — more concepts, YAML, and moving parts {Thẳng thắn: Kubernetes nặng hơn Docker — nhiều khái niệm, YAML và bộ phận chuyển động}. You are in the right place because you already understand images, containers, networks, volumes, and Compose from Parts 1–8 {Bạn đúng chỗ vì đã hiểu image, container, mạng, volume và Compose từ Phần 1–8}. This part introduces the mental model and your first cluster on a laptop with kind {Phần này giới thiệu mô hình tư duycluster đầu tiên trên laptop bằng kind}. Part 10 goes deeper on config, probes, and debugging k8s {Phần 10 đi sâu config, probe và debug k8s}.


Cluster architecture {Kiến trúc cluster}

A Kubernetes cluster is a set of machines (physical or virtual) cooperating to run your workloads {Một cluster Kubernetes là tập máy (vật lý hoặc ảo) phối hợp chạy workload của bạn}. One side is the control plane (the brain); the other is worker nodes (where containers actually run) {Một phía là control plane (bộ não); phía kia là worker node (nơi container thực sự chạy)}.

kubectl apply Control plane api-server scheduler controller-mgr etcd Worker node kubelet · kube-proxy · runtime Pod container(s) Pod container(s) Worker node kubelet · kube-proxy · runtime Pod container(s) Pod container(s) schedule pods → nodes
The control plane (api-server, scheduler, controller-manager, etcd) schedules Pods onto worker nodes (kubelet, kube-proxy, runtime)
  • kube-apiserver — REST API; kubectl talks here {API REST; kubectl gọi vào đây}
  • scheduler — picks a node for each new Pod {Chọn node cho Pod mới}
  • controller-manager — keeps Deployments/ReplicaSets matching spec {Giữ Deployment/ReplicaSet khớp spec}
  • etcd — cluster state store {Lưu trạng thái cluster}
  • kubelet — runs Pods on the node via the container runtime {Chạy Pod trên node qua runtime}
  • kube-proxy — Service → Pod networking rules {Quy tắc mạng Service → Pod}

The declarative model {Mô hình khai báo}

You do not SSH in and docker run on node 3 {Bạn không SSH vào node 3 rồi docker run}. You describe desired state in YAML (or Helm, etc.) and apply it; Kubernetes reconciles until reality matches {Bạn mô tả trạng thái mong muốn trong YAML rồi apply; Kubernetes điều chỉnh đến khi thực tế khớp}. If a Pod dies, the controller creates another {Pod chết thì controller tạo cái mới}. If you ask for three replicas, the system keeps three {Bạn yêu cầu ba replica thì hệ thống giữ ba}.


The core objects {Các object lõi}

You will live in three kinds for a long time: Pod, Deployment, Service {Bạn sẽ sống với ba loại này khá lâu: Pod, Deployment, Service}.

Pod — smallest schedulable unit {Pod — đơn vị lập lịch nhỏ nhất}

A Pod wraps one or more containers that share network and storage {Pod bọc một hoặc nhiều container dùng chung mạng và storage}. In practice, one container per Pod is the common case {Thực tế một container mỗi Pod là phổ biến}. Pods are ephemeral — when they die, their IP is gone; you rarely create bare Pods by hand {Pod ngắn hạn — chết thì IP mất; hiếm khi tạo Pod trần bằng tay}.

Deployment — desired replicas & rolling updates {Deployment — replica mong muốn & rolling update}

A Deployment owns a ReplicaSet, which owns Pods {Deployment sở hữu ReplicaSet, ReplicaSet sở hữu Pod}. You set replicas: 2 and the Deployment ensures two matching Pods run, replaces failed ones, and rolls out image changes gradually {Bạn đặt replicas: 2 và Deployment đảm bảo hai Pod khớp, thay Pod hỏng, và rollout image từ từ}.

  Deployment "web"

       │  manages

  ReplicaSet "web-7d4f9c8b6"

       │  owns
       ├──────────┬──────────┐
       ▼          ▼          ▼
    Pod web-1   Pod web-2   (scale adds more)

Service — stable network in front of Pods {Service — mạng ổn định trước Pod}

Pod IPs change when Pods restart {IP Pod đổi khi Pod restart}. A Service is a stable DNS name + virtual IP that load-balances traffic to healthy Pods matching a label selector {Servicetên DNS + IP ảo ổn định, cân bằng tải tới Pod khỏe khớp label selector}.

Deployment replicas: 3 ReplicaSet keeps 3 alive Pod 1 Pod 2 Pod 3 Service stable VIP · LB traffic in →
A Deployment owns a ReplicaSet that keeps N Pods alive; a Service gives them one stable address and load-balances traffic across them

ClusterIP (default) — internal DNS only; NodePort — port on every node; LoadBalancer — cloud LB in prod {ClusterIP — DNS nội bộ; NodePort — cổng trên mọi node; LoadBalancer — LB cloud trên prod}.


kubectl basics {Cơ bản kubectl}

kubectl is the CLI for the Kubernetes API — like docker for a single daemon, but for the cluster {kubectl là CLI cho API Kubernetes — giống docker với một daemon, nhưng cho cả cluster}. Install alongside kind or minikube {Cài cùng kind hoặc minikube}. Everyday commands {Lệnh hằng ngày}:

kubectl get pods                    # list Pods (default namespace)
kubectl get pods -n kube-system     # -n = namespace
kubectl get pods -o wide            # node IP, which node, etc.
kubectl describe pod <name>         # events, state, why Pending/CrashLoop
kubectl logs <pod>                  # stdout/stderr (like docker logs)
kubectl logs -f <pod>               # follow
kubectl exec -it <pod> -- sh        # shell inside (like docker exec)
kubectl apply -f manifest.yaml      # create or update from YAML
kubectl delete -f manifest.yaml     # remove resources in file
kubectl delete pod <name>           # delete one Pod (Deployment recreates it)

Use -n <namespace> to target kube-system, staging, etc. — tutorials often stay in default {Dùng -n cho kube-system, staging… — tutorial thường ở default}. Workflow: edit YAML → kubectl apply -f …get / describe / logs while controllers reconcile {Quy trình: sửa YAML → apply → get/describe/logs, controller điều chỉnh nền}.


A local cluster with kind {Cluster local với kind}

kind runs a cluster inside Docker on your laptop; minikube is a common alternative — same kubectl flow {kind chạy cluster trong Docker; minikube là lựa chọn khác — cùng quy trình kubectl}.

# Install: https://kind.sigs.k8s.io/docs/user/quick-start/
kind create cluster --name dev
kubectl cluster-info --context kind-dev
kubectl get nodes
# NAME                 STATUS   ROLES           AGE   VERSION
# dev-control-plane    Ready    control-plane   1m    v1.33.x

Your kubectl context now points at kind-dev {context kubectl giờ trỏ tới kind-dev}. Check with kubectl config current-context {Kiểm tra bằng kubectl config current-context}.

Loading local images into kind {Nạp image local vào kind}

Images you build with docker build exist on your host Docker, not inside the kind nodes {Image build bằng docker build nằm trên Docker host, không có sẵn trong node kind}. Before a Deployment can pull myapp:local, load it {Trước khi Deployment kéo myapp:local, nạp vào}:

docker build -t myapp:local .
kind load docker-image myapp:local --name dev

Without this step, Pods often sit in ImagePullBackOff {Không bước này, Pod thường kẹt ImagePullBackOff}.

Teardown when done {Dọn khi xong}:

kind delete cluster --name dev

Your first deployment {Deployment đầu tiên}

Create a folder k8s-lab-09/ and save two files {Tạo thư mục k8s-lab-09/ và lưu hai file}.

deployment.yaml — nginx, two replicas, labels the Service will select {nginx, hai replica, label để Service chọn}:

# deployment.yaml — desired state: 2 nginx Pods
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy          # Deployment name (kubectl get deploy)
  labels:
    app: nginx
spec:
  replicas: 2                 # controller keeps 2 Pods running
  selector:
    matchLabels:
      app: nginx              # must match template labels below
  template:                   # Pod template — each Pod looks like this
    metadata:
      labels:
        app: nginx            # Service selector targets this label
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine   # public image; no kind load needed
          ports:
            - containerPort: 80      # container listens here (not published to host yet)

service.yaml — ClusterIP Service (reach it with port-forward) {Service ClusterIP (truy cập bằng port-forward)}:

# service.yaml — stable VIP + DNS name → Pods with app=nginx
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  type: ClusterIP              # default; internal cluster IP
  selector:
    app: nginx                 # must match Pod labels from Deployment
  ports:
    - port: 80                 # Service port (what clients connect to)
      targetPort: 80           # Pod containerPort

Apply and inspect {Apply và kiểm tra}:

kubectl apply -f deployment.yaml -f service.yaml
kubectl get deploy,pods,svc
kubectl get pods -o wide
# wait until both Pods are Running (READY 1/1)

Reach the app from your laptop {Truy cập từ laptop}:

kubectl port-forward svc/nginx-svc 8080:80
# leave running; in another terminal:
curl -s http://localhost:8080 | head -5

port-forward tunnels to the Service — simplest way to curl from your laptop; NodePort works too but needs extra port mapping on kind {port-forward tunnel tới Service — cách đơn giản nhất để curl từ laptop; NodePort cũng được nhưng cần map cổng thêm trên kind}.


From Compose to Kubernetes {Từ Compose sang Kubernetes}

You already think in services from Compose {Bạn đã nghĩ theo service từ Compose}. Kubernetes splits responsibilities differently {Kubernetes tách trách nhiệm khác}:

Compose (compose.yaml)Kubernetes {Kubernetes}
services.webDeployment (Pods) + Service (network) {Deployment + Service}
ports: "8080:80"Service (port / NodePort) or kubectl port-forward {Service hoặc port-forward}
volumes:PersistentVolume + PersistentVolumeClaim (Part 10) {PV + PVC (Phần 10)}
environment: / env_file:env in Pod spec, ConfigMap, Secret (Part 10) {env, ConfigMap, Secret (Phần 10)}
depends_on + healthliveness/readiness probes (Part 10) {probe (Phần 10)}
docker compose upkubectl apply -f … {kubectl apply}
docker compose scale web=3kubectl scale deploy/nginx-deploy --replicas=3 {kubectl scale}

kompose can bootstrap manifests from Compose; hand-writing YAML teaches labels, selectors, and Deployment ↔ Service wiring {kompose khởi tạo manifest từ Compose; viết tay dạy label, selector và nối Deployment ↔ Service}.


Common pitfalls {Các bẫy thường gặp}

  • ImagePullBackOff — cluster cannot find your image. Public images (nginx:alpine) work; local tags need kind load docker-image or push to a registry {Cluster không tìm thấy image. Image public ổn; tag local cần kind load hoặc push registry}.
  • Hitting Pod IP directly — Pod IPs are ephemeral; always go through a Service (or Ingress) for stable access {Gọi thẳng IP Pod — IP ngắn hạn; luôn qua Service (hoặc Ingress)}.
  • Wrong kubectl contextkubectl talks to whichever cluster current-context points at; verify with kubectl config get-contexts before deleting things {context sai — xác nhận kubectl config get-contexts trước khi xóa}.
  • NodePort on kind vs port-forward — NodePort needs extra port mapping on kind; port-forward is simpler for “curl from my Mac” {NodePort trên kind cần map cổng thêm; port-forward đơn giản hơn để curl từ máy bạn}.
  • Expecting Compose networking verbatim — in-cluster DNS is http://<service-name> (same namespace), not http://localhost inside another Pod {Không copy nguyên mạng Compose — DNS trong cluster là http://<tên-service> (cùng namespace)}.

Cheat sheet {Bảng tra nhanh}

# cluster & context
kubectl get nodes
kubectl config current-context
kubectl config use-context kind-dev

# workloads
kubectl apply -f .
kubectl get deploy,rs,pods,svc
kubectl describe deploy nginx-deploy
kubectl scale deploy/nginx-deploy --replicas=4

# debug (mirror Part 8 docker habits)
kubectl logs <pod> -f
kubectl exec -it <pod> -- sh
kubectl get events --sort-by='.lastTimestamp'

# access
kubectl port-forward svc/nginx-svc 8080:80

# cleanup
kubectl delete -f deployment.yaml -f service.yaml
kind delete cluster --name dev

Bài tập / Exercises

Work in k8s-lab-09/ with the manifests from this post (or your own names) {Làm trong k8s-lab-09/ với manifest trong bài (hoặc tên riêng)}. Requires kind (or minikube) and kubectl {Cần kind (hoặc minikube) và kubectl}.

1. Create a kind cluster named lab09, verify nodes are Ready, and confirm your context is kind-lab09 {Tạo cluster kind tên lab09, xác nhận node Ready, context là kind-lab09}.

Solution {Lời giải}
kind create cluster --name lab09
kubectl get nodes
kubectl config current-context   # kind-lab09

2. Apply the nginx Deployment with replicas: 2 and confirm two Pods are Running with kubectl get pods {Apply Deployment nginx replicas: 2, xác nhận hai Pod Running}.

Solution {Lời giải}
kubectl apply -f deployment.yaml
kubectl get pods -w
# Ctrl+C when both show 1/1 Running
kubectl get deploy nginx-deploy

3. Delete one Pod by name (kubectl delete pod …) and watch a new Pod appear — self-healing from the Deployment {Xóa một Pod, xem Pod mới xuất hiện — tự phục hồi từ Deployment}.

Solution {Lời giải}
kubectl get pods
kubectl delete pod nginx-deploy-xxxxxxxxxx-xxxxx   # pick one Pod name from get pods
kubectl get pods -w
# a new Pod is created; replica count stays at 2

4. Apply the Service, port-forward to port 8080, and curl the nginx welcome page {Apply Service, port-forward cổng 8080, curl trang chào nginx}.

Solution {Lời giải}
kubectl apply -f service.yaml
kubectl port-forward svc/nginx-svc 8080:80 &
curl -s http://localhost:8080 | head -3
kill %1   # stop port-forward job

5. Scale the Deployment to 4 replicas, confirm four Pods, then scale back to 2 {Scale Deployment lên 4 replica, xác nhận bốn Pod, rồi scale về 2}.

Solution {Lời giải}
kubectl scale deploy/nginx-deploy --replicas=4
kubectl get pods
kubectl scale deploy/nginx-deploy --replicas=2
kubectl get pods

Stretch {Nâng cao}: build a tiny local image (docker build -t lab09-app:local .), kind load docker-image lab09-app:local --name lab09, change the Deployment image to lab09-app:local with imagePullPolicy: Never, apply, and confirm Pods run {Build image local, kind load, đổi image Deployment thành lab09-app:local với imagePullPolicy: Never, apply, xác nhận Pod chạy}.

Solution {Lời giải}
# in deployment.yaml container spec:
image: lab09-app:local
imagePullPolicy: Never
kind load docker-image lab09-app:local --name lab09
kubectl apply -f deployment.yaml
kubectl get pods

Key takeaways {Điểm chính}

  • Compose = one host; Kubernetes = many nodes with reconciliation, scaling, and rolling updates {Compose = một host; Kubernetes = nhiều node với điều chỉnh, scale và rolling update}.
  • Pod runs containers; Deployment keeps replica count and heals failures; Service gives a stable name/IP in front of Pods {Pod chạy container; Deployment giữ số replica và phục hồi; Service cho tên/IP ổn định trước Pod}.
  • kubectl apply is the daily loop; get / describe / logs / exec mirror Docker debugging from Part 8 {kubectl apply là vòng lặp hằng ngày; get / describe / logs / exec giống debug Docker ở Phần 8}.
  • kind (+ kind load docker-image) is the fastest way to practice without a cloud bill {kind (+ kind load) là cách nhanh nhất để luyện không tốn tiền cloud}.

Next up {Tiếp theo}

Part 10 — Kubernetes in Practice & Debugging — ConfigMaps and Secrets, liveness/readiness probes, persistent volumes, Ingress basics, and a systematic kubectl debug checklist when Pods won’t start {Phần 10 — Kubernetes thực hành & Debug — ConfigMap và Secret, probe liveness/readiness, volume bền, Ingress cơ bản, và checklist debug kubectl khi Pod không lên}. ← Part 8