K8S Lab Bài 01: Kiến trúc Kubernetes
k8s v1.35 Ubuntu 24.04
Bài 01

Kiến trúc Kubernetes

Hiểu cấu trúc chi tiết từng thành phần của một K8S cluster — từ Control Plane đến Worker Node, Pod, Container và các layer bên trong

Phiên bảnKubernetes v1.35
Hệ điều hànhUbuntu 24.04
Mô hình labOn-premise / kubeadm
Số VM3 VM
NodeVai tròMô tả
master-01Control PlaneChạy các thành phần điều khiển chính
worker-01Worker NodeChạy workload ứng dụng
worker-02Worker NodeChạy workload ứng dụng

🎯 Mục tiêu buổi học

Sau buổi học này, cần nắm được:

  • Kubernetes là gì và vì sao cần Kubernetes
  • Kiến trúc tổng thể của một Kubernetes cluster
  • Sự khác nhau giữa Control Plane NodeWorker Node
  • Vai trò chi tiết của từng thành phần: kube-apiserver, etcd, kube-scheduler, kube-controller-manager, kubelet, kube-proxy, Container Runtime, CNI Plugin, CoreDNS
  • Pod là gì, cấu trúc bên trong Pod gồm những gì
  • Namespace, Static Pod trong mô hình kubeadm
  • Luồng hoạt động khi tạo một Pod đi qua từng thành phần như thế nào
  • kubectl giao tiếp với cluster thông qua API Server như thế nào

Phần 1: Kubernetes là gì?

Kubernetes là một nền tảng mã nguồn mở dùng để triển khai, vận hành và quản lý các ứng dụng containerized. Nếu Docker hoặc container runtime giúp chạy một container, thì Kubernetes giúp quản lý rất nhiều container chạy trên nhiều máy chủ khác nhau.

Một hệ thống thực tế thường không chỉ có một container. Ví dụ một ứng dụng web có thể gồm: Frontend, Backend API, Database, Cache, Message queue, Worker xử lý nền, Monitoring agent, Logging agent. Kubernetes giải quyết các bài toán:

  • Container chết thì tự động khởi động lại
  • Server chết thì workload chuyển sang node khác
  • Cần chạy nhiều bản sao ứng dụng (scaling)
  • Cần rolling update không downtime
  • Service discovery và load balancing nội bộ
  • Giới hạn tài nguyên CPU/RAM cho từng ứng dụng
  • Expose ứng dụng ra bên ngoài cluster

Phần 2: Kubernetes Cluster là gì?

Một Kubernetes cluster là tập hợp các máy chủ cùng hoạt động để chạy ứng dụng container. Cluster luôn có hai nhóm thành phần lớn: Control PlaneWorker Nodes.

text – Cấu trúc tổng quan cluster
Kubernetes Cluster
├── Control Plane (master-01)
│   ├── kube-apiserver        ← Cổng giao tiếp trung tâm
│   ├── etcd                  ← Cơ sở dữ liệu trạng thái
│   ├── kube-scheduler        ← Chọn node cho Pod
│   └── kube-controller-manager ← Duy trì desired state
│
└── Worker Nodes
    ├── worker-01
    │   ├── kubelet            ← Node agent
    │   ├── kube-proxy         ← Network rules
    │   ├── containerd         ← Container runtime
    │   └── Pods               ← Đơn vị chạy ứng dụng
    └── worker-02
        ├── kubelet
        ├── kube-proxy
        ├── containerd
        └── Pods

Phần 3: Control Plane — Bộ não của cluster

Control Plane là bộ não của Kubernetes cluster. Nó không trực tiếp chạy ứng dụng business như web, API hay database. Thay vào đó, nó chịu trách nhiệm quản lý trạng thái mong muốn (desired state) của toàn bộ cluster.

Trong mô hình kubeadm, các thành phần Control Plane chạy dưới dạng Static Pod, manifest được đặt tại /etc/kubernetes/manifests/ trên node master:

text – Static Pod manifests
/etc/kubernetes/manifests/
├── kube-apiserver.yaml
├── etcd.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml

3.1 kube-apiserver

kube-apiserver là cổng giao tiếp trung tâm của Kubernetes. Tất cả yêu cầu gửi vào cluster đều phải đi qua API Server — từ người dùng, CI/CD tool, cho đến các thành phần nội bộ khác.

text – Luồng giao tiếp qua API Server
User / Admin / CI-CD Tool
        |
        v
kubectl / API Client / Dashboard
        |
        v (HTTPS + mTLS)
kube-apiserver  ←→  etcd  (lưu/đọc trạng thái)
        |
        v
kube-scheduler / kube-controller-manager / kubelet

Vai trò chính của kube-apiserver:

  • Cung cấp Kubernetes REST API (HTTP/HTTPS)
  • Xác thực (Authentication) — kiểm tra danh tính người gửi request
  • Ủy quyền (Authorization) — kiểm tra người đó có quyền thực hiện hành động không
  • Kiểm tra tính hợp lệ (Admission Control) của object
  • Ghi trạng thái mong muốn vào etcd
  • Là điểm giao tiếp duy nhất giữa các thành phần trong cluster
💡 Nguyên tắc quan trọng

Muốn nói chuyện với Kubernetes, gần như luôn phải đi qua kube-apiserver. Không có thành phần nào giao tiếp trực tiếp với etcd ngoại trừ API Server.

3.2 etcd

etcd là cơ sở dữ liệu key-value phân tán của Kubernetes. Toàn bộ trạng thái của cluster được lưu trong etcd — đây là "bộ nhớ dài hạn" duy nhất của hệ thống.

Những gì được lưu trong etcd:

  • Danh sách và cấu hình tất cả node trong cluster
  • Định nghĩa của tất cả Pod, Deployment, Service, ConfigMap, Secret...
  • Trạng thái hiện tại và trạng thái mong muốn của mọi object
  • Thông tin Namespace, RBAC, Certificate...
text – etcd trong mô hình lab
master-01
└── etcd (chạy dưới dạng Static Pod)
    ├── Lưu trữ: toàn bộ Kubernetes state
    ├── Port: 2379 (client), 2380 (peer)
    ├── Dữ liệu: /var/lib/etcd/
    └── Chỉ API Server được phép giao tiếp trực tiếp

⚠️  Nếu etcd mất dữ liệu mà không có backup → cluster mất toàn bộ state
💡 Mô hình lab vs Production

Trong lab 1 master + 2 worker, etcd chỉ chạy trên master-01 — không phải mô hình HA. Trong production, nên chạy etcd cluster với 3 hoặc 5 node để đảm bảo fault tolerance, và phải có backup etcd định kỳ.

3.3 kube-scheduler

kube-scheduler là thành phần quyết định Pod sẽ chạy trên node nào. Khi một Pod mới được tạo ra, ban đầu Pod chưa được gán vào worker node cụ thể. Scheduler liên tục theo dõi các Pod chưa có node và chọn node phù hợp.

text – Quá trình Scheduling
Pod mới được tạo (chưa có node)
        |
        v
kube-scheduler phát hiện Pod chưa được gán
        |
        v
Filtering (Lọc): loại bỏ node không đủ điều kiện
  - Không đủ CPU/RAM
  - Node đang bị taint không phù hợp
  - nodeSelector không khớp
        |
        v
Scoring (Cho điểm): xếp hạng các node còn lại
  - Node có ít Pod nhất được ưu tiên
  - Node có tài nguyên dư nhiều hơn
  - Cân bằng tải giữa các node
        |
        v
Chọn node có điểm cao nhất → Gắn Pod vào node đó
        |
        v
kubelet trên node được chọn sẽ thực thi
💡 Scheduler chỉ chọn node, không chạy container

Sau khi scheduler gắn Pod vào node, kubelet trên node đó mới là thành phần thực sự tạo và chạy container. Các tính năng nâng cao như nodeSelector, affinity, taint/toleration sẽ học ở buổi Scheduling nâng cao.

3.4 kube-controller-manager

kube-controller-manager chạy các controller — những vòng lặp điều khiển liên tục quan sát trạng thái thực tế của cluster và cố gắng đưa về trạng thái mong muốn.

text – Control Loop (Vòng lặp điều khiển)
Trạng thái mong muốn (Desired State):
  → Cần 3 bản sao của Pod nginx

Trạng thái thực tế (Actual State):
  → Chỉ có 2 Pod nginx đang chạy (1 Pod đã crash)

Controller phát hiện lệch → Hành động:
  → Tạo thêm 1 Pod nginx mới để đủ 3 bản sao

Vòng lặp này chạy liên tục (Reconciliation Loop)

Một số controller phổ biến (sẽ học chi tiết ở các buổi sau):

ControllerChức năng
Node ControllerTheo dõi trạng thái node, xử lý khi node mất kết nối
ReplicaSet ControllerĐảm bảo đúng số lượng Pod replica theo khai báo
Deployment ControllerQuản lý rolling update, rollback cho Deployment
Job ControllerĐảm bảo Job chạy đến khi hoàn thành
EndpointSlice ControllerDuy trì danh sách IP của Pod phía sau Service

3.5 cloud-controller-manager (tùy chọn)

cloud-controller-manager chỉ dùng khi Kubernetes tích hợp với cloud provider như AWS, Azure, Google Cloud hoặc OpenStack. Trong mô hình on-premise bằng kubeadm với 3 VM Ubuntu, thành phần này thường không được sử dụng. Chức năng: tạo Load Balancer cloud, đọc thông tin node từ cloud provider, quản lý route cloud.

Phần 4: Worker Node — Nơi chạy workload

Worker Node là nơi chạy workload ứng dụng thực tế. Trong mô hình lab, worker-01worker-02 sẽ chạy Pod của ứng dụng. Mỗi worker node có cấu trúc cố định:

text – Cấu trúc chi tiết một Worker Node
Worker Node (ví dụ: worker-01)
├── OS: Ubuntu 24.04
│
├── kubelet                   ← Chạy như systemd service
│   ├── Đọc PodSpec từ API Server
│   ├── Gọi container runtime (CRI)
│   ├── Theo dõi health của container
│   └── Báo cáo trạng thái về API Server
│
├── kube-proxy                ← Chạy như DaemonSet Pod
│   ├── Quản lý iptables/IPVS rules
│   └── Đảm bảo traffic đến Service được forward đúng Pod
│
├── containerd                ← Container Runtime (CRI)
│   ├── Nhận lệnh từ kubelet qua CRI interface
│   ├── Pull image từ container registry
│   ├── Tạo và quản lý container
│   └── Dùng runc để thực sự chạy container
│
├── CNI Plugin (ví dụ: Calico)
│   ├── Cấp IP cho mỗi Pod
│   ├── Cấu hình network namespace cho Pod
│   └── Cho phép Pod trên các node khác nhau giao tiếp
│
└── Pods
    ├── Pod A (ứng dụng)
    │   ├── Container 1
    │   └── Container 2 (sidecar)
    └── Pod B (ứng dụng)
        └── Container 1

4.1 kubelet — Node Agent

kubelet là agent chạy trên mỗi node trong cluster — bao gồm cả control plane node và worker node. kubelet chạy như một systemd service, không phải một Pod. Đây là thành phần duy nhất không do Kubernetes tự quản lý.

Vai trò chính của kubelet:

  • Đăng ký node với Kubernetes API Server khi node khởi động
  • Nhận PodSpec từ API Server và đảm bảo các container trong Pod đang chạy đúng
  • Gọi container runtime (qua CRI interface) để tạo, start, stop container
  • Theo dõi health của container bằng liveness probe, readiness probe
  • Báo cáo trạng thái node và Pod về API Server định kỳ
  • Trên master: kubelet cũng quản lý các Static Pod control plane
text – Luồng kubelet nhận và thực thi lệnh
kube-apiserver
      |  (thông báo có Pod cần chạy trên node này)
      v
kubelet trên worker-01
      |  (đọc PodSpec: image, port, env, volume...)
      v
CRI (Container Runtime Interface)
      |  (giao tiếp chuẩn hóa với runtime)
      v
containerd
      |  (pull image, tạo namespace, cấu hình mạng)
      v
runc
      |  (tạo container process thực sự)
      v
Container đang chạy bên trong Pod

4.2 Container Runtime — containerd

Container Runtime là phần mềm chịu trách nhiệm chạy container thật sự trên node. Kubernetes không tự mình chạy container mà giao tiếp với container runtime thông qua chuẩn CRI (Container Runtime Interface).

LayerThành phầnVai trò
High-level runtimecontainerdQuản lý vòng đời container, pull image, lưu image
Low-level runtimeruncTạo container process thực sự theo chuẩn OCI
InterfaceCRIGiao diện chuẩn để kubelet giao tiếp với runtime
text – Kiến trúc Container Runtime layer
kubelet
  │  giao tiếp qua CRI (gRPC)
  ▼
containerd  (/run/containerd/containerd.sock)
  │  quản lý: image pull, snapshot, network setup
  ▼
containerd-shim
  │  giữ container chạy độc lập với containerd
  ▼
runc
  │  tạo Linux namespace, cgroups, mount rootfs
  ▼
Container Process (pid 1 trong container)

4.3 kube-proxy — Network Rules

kube-proxy chạy trên mỗi node (dưới dạng DaemonSet) và chịu trách nhiệm duy trì network rules để traffic đến Kubernetes Service được chuyển đến đúng Pod. Trên Linux, kube-proxy thường làm việc với iptables hoặc IPVS.

Ở buổi này, chỉ cần nhớ kube-proxy liên quan đến việc forward traffic trong cluster. Chi tiết về Service và kube-proxy sẽ học ở buổi Service & Networking.

4.4 CNI Plugin — Pod Networking

CNI (Container Network Interface) plugin cung cấp mạng cho Pod. Kubernetes cần CNI plugin để mỗi Pod nhận được địa chỉ IP và có thể giao tiếp với Pod khác dù trên cùng hay khác node.

CNI PluginĐặc điểm
CalicoPhổ biến, hỗ trợ NetworkPolicy, dùng BGP routing. Lab này sử dụng Calico.
CiliumHiệu năng cao, dùng eBPF, hỗ trợ L7 NetworkPolicy
FlannelĐơn giản, nhẹ, phù hợp môi trường học tập nhỏ
Weave NetDễ cài đặt, hỗ trợ mã hóa traffic giữa node
⚠️ Quan trọng

Nếu chưa cài CNI plugin sau kubeadm init, nhiều Pod hệ thống như CoreDNS sẽ ở trạng thái Pending hoặc ContainerCreating và không thể chạy. Không có CNI → Pod networking trong cluster sẽ không hoàn chỉnh.

4.5 CoreDNS — DNS nội bộ

CoreDNS là DNS addon mặc định của Kubernetes. CoreDNS giúp các Pod phân giải tên miền nội bộ trong cluster thay vì phải dùng IP cứng. CoreDNS chạy như một Deployment trong namespace kube-system.

text – Ví dụ DNS trong cluster
Pod A muốn giao tiếp với Service tên là 'my-api':

Pod A → DNS query: my-api.default.svc.cluster.local
                       ↓
                   CoreDNS
                       ↓ trả về ClusterIP của Service
Pod A → kết nối đến ClusterIP → kube-proxy forward → Pod của my-api

Format DNS đầy đủ của Service:
{service-name}.{namespace}.svc.{cluster-domain}
Ví dụ: my-api.default.svc.cluster.local

Phần 5: Pod — Đơn vị triển khai nhỏ nhất

Pod là đơn vị triển khai nhỏ nhất mà Kubernetes có thể tạo và quản lý. Kubernetes không quản lý container trực tiếp — Kubernetes quản lý Pod, và container chạy bên trong Pod.

text – Cấu trúc chi tiết bên trong một Pod
Pod: nginx-pod (chạy trên worker-01)
│
├── Pause Container (infrastructure container)
│   ├── Tạo network namespace dùng chung cho cả Pod
│   ├── Giữ IP address của Pod
│   └── Chạy ẩn, người dùng không thấy
│
├── Container 1: nginx (main container)
│   ├── Image: nginx:1.27
│   ├── Port: 80
│   ├── Dùng chung network namespace với pause container
│   └── Giao tiếp với container khác qua localhost
│
└── Container 2: log-agent (sidecar - tùy chọn)
    ├── Image: fluentd:latest
    ├── Dùng chung Volume với container nginx
    └── Giao tiếp với nginx qua localhost:80

Tài nguyên dùng chung trong 1 Pod:
  ✓ Network namespace (IP, port space)
  ✓ Volume (nếu được mount)
  ✗ Process namespace (mặc định tách biệt)

5.1 Đặc điểm quan trọng của Pod

Đặc điểmGiải thích
Mỗi Pod có 1 IP duy nhấtIP được cấp bởi CNI plugin, gắn với Pod suốt vòng đời
Pod là ephemeral (tạm thời)Pod có thể bị xóa và tạo lại bất kỳ lúc nào, IP sẽ thay đổi
Container trong Pod dùng chung networkGiao tiếp nhau qua localhost, không cần IP riêng
Pod chạy trên đúng 1 nodeTất cả container trong Pod luôn chạy trên cùng một node
Pod không tự heal khi bị xóaCần Deployment/ReplicaSet để tự động tạo lại Pod khi Pod chết

5.2 Pod đơn container vs Multi-container Pod

text – Hai mô hình Pod phổ biến
=== Mô hình 1: Single Container Pod (phổ biến nhất) ===
Pod
└── Container: nginx
    └── Chạy web server

=== Mô hình 2: Multi-Container Pod (Sidecar Pattern) ===
Pod
├── Container 1: nginx (main)         → Xử lý HTTP request
├── Container 2: fluentd (sidecar)    → Đọc log của nginx, gửi lên Elasticsearch
└── Container 3: envoy (sidecar)      → Proxy, xử lý mTLS, tracing

Cả 3 container dùng chung localhost và volume
→ Container 2 đọc file log của Container 1 qua shared volume
→ Container 3 nhận traffic trước Container 1 qua localhost

5.3 Pod trong mô hình phân cấp Kubernetes

text – Vị trí Pod trong hệ thống phân cấp
Kubernetes Cluster
└── Node: worker-01
    └── Pod: nginx-pod         ← Đơn vị nhỏ nhất Kubernetes quản lý
        └── Container: nginx   ← Đơn vị nhỏ nhất container runtime quản lý

Trong production thực tế (sẽ học ở buổi sau):
Deployment (quản lý rolling update)
└── ReplicaSet (đảm bảo số lượng Pod)
    ├── Pod 1: nginx
    ├── Pod 2: nginx
    └── Pod 3: nginx

Phần 6: Namespace — Phân vùng logic trong cluster

Namespace là cơ chế phân chia tài nguyên logic trong Kubernetes. Các object trong cùng namespace chia sẻ một không gian tên, và có thể áp dụng quota, RBAC riêng cho từng namespace.

NamespaceMục đíchNội dung thường có
defaultNamespace mặc định cho workloadPod, Deployment người dùng tạo nếu không chỉ định namespace
kube-systemNamespace hệ thống Kubernetesetcd, apiserver, scheduler, controller, coredns, kube-proxy, calico
kube-publicNamespace công khai (readable by all)ConfigMap cluster-info, thông tin có thể đọc công khai
kube-node-leaseHeartbeat của nodeLease object — kubelet cập nhật định kỳ để báo node còn sống
text – Ví dụ phân chia Namespace theo team/môi trường
Trong production, thường tổ chức namespace theo môi trường hoặc team:

Cluster Production
├── namespace: frontend-team
│   ├── Pod: react-app
│   └── Pod: nginx-proxy
├── namespace: backend-team
│   ├── Pod: api-server
│   └── Pod: worker
├── namespace: monitoring
│   ├── Pod: prometheus
│   └── Pod: grafana
└── namespace: kube-system
    └── (các Pod hệ thống)

Phần 7: Static Pod trong mô hình kubeadm

Trong cluster được tạo bằng kubeadm, các control plane component thường chạy dưới dạng Static Pod. Đây là loại Pod đặc biệt được quản lý trực tiếp bởi kubelet trên một node cụ thể, không phải được tạo qua API Server theo cách thông thường.

text – Cơ chế hoạt động của Static Pod
Thư mục: /etc/kubernetes/manifests/
├── etcd.yaml                        ← kubelet đọc file này
├── kube-apiserver.yaml              ← kubelet đọc file này
├── kube-controller-manager.yaml    ← kubelet đọc file này
└── kube-scheduler.yaml             ← kubelet đọc file này

Luồng khởi động:
1. kubelet trên master-01 khởi động cùng OS
2. kubelet scan /etc/kubernetes/manifests/
3. kubelet yêu cầu containerd tạo các Static Pod
4. kube-apiserver Static Pod khởi động thành công
5. Từ đây API Server hoạt động bình thường
6. Các Static Pod được mirror lên API Server (mirror pods)

⚠️ Nếu xóa Static Pod qua kubectl → Pod sẽ tự được tạo lại
   Muốn dừng Static Pod → phải xóa hoặc sửa file .yaml trong thư mục

Phần 8: kubectl và kubeconfig

kubectl là công cụ dòng lệnh chính để làm việc với Kubernetes. Khi chạy bất kỳ lệnh kubectl nào, luồng xử lý đều đi qua API Server.

text – Luồng hoạt động của kubectl
kubectl get nodes
  |
  | 1. kubectl đọc ~/.kube/config (kubeconfig)
  |    → Biết địa chỉ API Server
  |    → Biết certificate/token để xác thực
  v
kube-apiserver (https://MASTER_IP:6443)
  |
  | 2. API Server xác thực request
  | 3. API Server kiểm tra quyền (Authorization)
  | 4. API Server đọc dữ liệu từ etcd
  v
etcd
  |
  | 5. Trả dữ liệu về API Server
  v
kube-apiserver
  |
  | 6. API Server trả response về kubectl
  v
kubectl hiển thị kết quả ra terminal

Cấu trúc file kubeconfig

File kubeconfig (~/.kube/config) chứa thông tin để kubectl kết nối đến cluster. Với cluster tạo bằng kubeadm, file admin kubeconfig nằm tại /etc/kubernetes/admin.conf.

yaml – Cấu trúc kubeconfig
apiVersion: v1
kind: Config
clusters:
  - name: kubernetes                    # Tên cluster
    cluster:
      server: https://10.10.10.11:6443  # Địa chỉ API Server
      certificate-authority-data: ...   # CA certificate để verify API Server
users:
  - name: kubernetes-admin              # Tên user
    user:
      client-certificate-data: ...      # Client cert (xác thực)
      client-key-data: ...              # Client private key
contexts:
  - name: kubernetes-admin@kubernetes   # Kết hợp user + cluster
    context:
      cluster: kubernetes
      user: kubernetes-admin
current-context: kubernetes-admin@kubernetes  # Context đang dùng

Phần 9: Luồng hoạt động đầy đủ khi tạo một Pod

Đây là một trong những luồng quan trọng nhất cần nắm trong buổi đầu. Hiểu luồng này giúp bạn biết khi Pod không chạy, vấn đề đang nằm ở thành phần nào.

text – Luồng tạo Pod chi tiết (10 bước)
Người dùng chạy: kubectl apply -f nginx-pod.yaml
│
│ Bước 1: kubectl đọc kubeconfig → kết nối API Server
▼
kube-apiserver nhận request
│
│ Bước 2: Authentication — Xác minh danh tính (cert/token hợp lệ?)
│ Bước 3: Authorization — Kiểm tra quyền (có được tạo Pod không?)
│ Bước 4: Admission Control — Validate object (YAML đúng không?)
▼
│ Bước 5: Ghi Pod object vào etcd với status Pending (chưa có node)
▼
kube-scheduler (đang watch API Server liên tục)
│
│ Bước 6: Phát hiện Pod chưa có node
│          Filtering: loại bỏ node không đủ điều kiện
│          Scoring: xếp hạng các node còn lại
│          Chọn: worker-01 (điểm cao nhất)
▼
│ Bước 7: Cập nhật Pod trong etcd: nodeName = worker-01
▼
kubelet trên worker-01 (đang watch API Server liên tục)
│
│ Bước 8: Phát hiện có Pod được gán cho node mình
│          Đọc PodSpec (image, port, env, volume...)
▼
containerd (qua CRI interface)
│
│ Bước 9: Pull image nginx:1.27 từ Docker Hub (nếu chưa có)
│          Tạo container network namespace
│          Gọi CNI plugin để cấp IP cho Pod
│          Tạo container và start process
▼
│ Bước 10: kubelet báo cáo trạng thái về API Server
│           API Server cập nhật Pod status trong etcd:
│           status: Running | podIP: 10.244.1.10 | node: worker-01
▼
kubectl get pods → Hiển thị: nginx-pod   Running

🧪 Câu hỏi kiểm tra cuối buổi

1. Trong Kubernetes, thành phần nào là cổng giao tiếp trung tâm của cluster?

2. Thành phần nào lưu toàn bộ trạng thái của Kubernetes cluster?

3. Khi tạo Pod, thành phần nào quyết định Pod sẽ chạy trên node nào?

4. Các container trong cùng một Pod giao tiếp với nhau như thế nào?

5. Static Pod trong mô hình kubeadm được quản lý bởi thành phần nào?

6. Điều gì xảy ra nếu chưa cài CNI plugin sau khi chạy kubeadm init?

7. kubelet chạy dưới dạng gì trên mỗi node?

8. Thành phần nào thực sự tạo và chạy container bên dưới kubelet?

Bài 02

Triển khai Kubernetes Cluster

Cài đặt và khởi tạo cluster K8S từ đầu bằng kubeadm trên Ubuntu 24.04

Toolkubeadm
Runtimecontainerd v2.2.2
K8Sv1.35
NetworkCalico v3.30.3

Phần 1: Triển khai Master Node

1.1 Chuẩn bị Linux

Cập nhật danh sách package từ repository để đảm bảo cài đặt được phiên bản mới nhất:

bash
apt update -y

Tắt swap và gỡ cấu hình swap khỏi /etc/fstab. Kubernetes yêu cầu swap phải được tắt để quản lý bộ nhớ chính xác — nếu swap còn bật, kubelet sẽ từ chối khởi động:

bash
swapoff -a
# Sau đó mở file và xóa dòng có chứa 'swap'
vi /etc/fstab

1.2 Cài đặt Container Runtime (containerd)

Tải các kernel module cần thiết và cấu hình để tự động load khi khởi động. Module overlay hỗ trợ hệ thống file OverlayFS cần thiết cho containerd. Module br_netfilter cho phép kiểm soát lưu lượng mạng giữa các container qua Linux bridge:

bash
printf "overlay\nbr_netfilter\n" >> /etc/modules-load.d/containerd.conf
modprobe overlay
modprobe br_netfilter

Cấu hình các thông số mạng hệ thống cần thiết cho Kubernetes. bridge-nf-call-iptables cho phép iptables kiểm soát traffic qua bridge. ip_forward cho phép chuyển tiếp gói tin IP giữa các mạng — bắt buộc để Pod trên các node khác nhau liên lạc được với nhau:

bash
printf "net.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\n" >> /etc/sysctl.d/99-kubernetes-cri.conf
sysctl --system

Tải và cài đặt containerd v2.2.2 từ GitHub. Sau đó tải file service unit cho systemd và kích hoạt service để containerd tự khởi động cùng hệ thống:

bash
wget https://github.com/containerd/containerd/releases/download/v2.2.2/containerd-2.2.2-linux-amd64.tar.gz -P /tmp/
tar Cxzvf /usr/local /tmp/containerd-2.2.2-linux-amd64.tar.gz
mkdir -p /usr/local/lib/systemd/system/
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -P /usr/local/lib/systemd/system/
systemctl daemon-reload
systemctl restart containerd
systemctl enable containerd

Cài đặt runc v1.4.2 — đây là low-level container runtime thực sự tạo và chạy container theo chuẩn OCI. containerd sử dụng runc phía sau để thực thi container:

bash
wget https://github.com/opencontainers/runc/releases/download/v1.4.2/runc.amd64 -P /tmp/
install -m 755 /tmp/runc.amd64 /usr/local/sbin/runc

Cài đặt các CNI plugin v1.9.1 vào thư mục /opt/cni/bin. Các plugin này cung cấp chức năng mạng cơ bản cho Kubernetes: cấp phát địa chỉ IP cho Pod, cấu hình routing, và thiết lập iptables rules:

bash
wget https://github.com/containernetworking/plugins/releases/download/v1.9.1/cni-plugins-linux-amd64-v1.9.1.tgz -P /tmp/
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin /tmp/cni-plugins-linux-amd64-v1.9.1.tgz

Tạo file cấu hình mặc định cho containerd, sau đó mở bằng vi để chỉnh sửa. Tìm dòng SystemdCgroup (khoảng dòng 109) và đổi giá trị thành true. Điều này để containerd sử dụng cgroup của systemd thay vì cgroupfs, giúp quản lý tài nguyên đồng nhất với Kubernetes:

bash
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
# Mở file và đổi SystemdCgroup = false -> SystemdCgroup = true (dòng ~109)
vi /etc/containerd/config.toml

1.3 Cài đặt Kubernetes Components

Cài đặt các package cần thiết và thêm GPG key để xác thực Kubernetes repository. Việc thêm GPG key đảm bảo các package được ký bởi Kubernetes project chính thức:

bash
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.35/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

Thêm Kubernetes v1.35 package repository vào apt sources. Repository này cung cấp các package kubelet, kubeadm, kubectl:

bash
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.35/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

Cập nhật repository và cài đặt ba thành phần Kubernetes. kubelet: node agent. kubeadm: tool khởi tạo cluster. kubectl: CLI để quản lý cluster. Lệnh apt-mark hold ngăn apt tự động nâng cấp các package này — giúp giữ version ổn định cho cluster:

bash
apt update -y
apt install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
systemctl enable --now kubelet
reboot

1.4 Bootstrap Master Node

Khởi tạo control plane với kubeadm. Flag --pod-network-cidr 192.168.0.0/16 định nghĩa dải IP sẽ dùng cho Pod — phải khớp với cấu hình của CNI plugin Calico. Sau khi chạy xong, lệnh này sẽ in ra join command để thêm worker node:

bash
kubeadm init --pod-network-cidr 192.168.0.0/16

Cấu hình kubeconfig cho user hiện tại để có thể dùng kubectl. File admin.conf chứa thông tin kết nối và credential admin — copy vào ~/.kube/config để kubectl tự động sử dụng:

bash
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Cài đặt Calico CNI plugin để cung cấp network cho Pod. Lệnh đầu tạo Tigera Operator (controller quản lý Calico), lệnh sau áp dụng cấu hình mạng cho cluster. sleep 10 đảm bảo operator khởi động xong trước khi áp dụng custom resources:

bash
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/tigera-operator.yaml
sleep 10
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.30.3/manifests/custom-resources.yaml

1.5 Lệnh kiểm tra sau khi cài đặt Master

Tập hợp các lệnh kiểm tra để xác nhận master node đã hoạt động bình thường. Chạy tuần tự để xác nhận node ở trạng thái Ready, các component healthy, và tất cả system pods đang chạy:

bash
# Xem thông tin node
kubectl get nodes -o wide

# Kiểm tra sức khỏe control plane components
kubectl get componentstatuses

# Kiểm tra trạng thái các system pod
kubectl get pods --all-namespaces -o wide

# Lấy join command để thêm worker node (chạy lệnh này trên master)
kubeadm token create --print-join-command

Phần 2: Triển khai Worker Node

Các bước dưới đây được thực hiện trên mỗi worker node (worker-01, worker-02). Quy trình tương tự master nhưng không cần bootstrap cluster.

2.1 Chuẩn bị Linux

Cập nhật và nâng cấp toàn bộ package trên worker node. Sau đó tắt swap ngay lập tức — tương tự như master, đây là yêu cầu bắt buộc của Kubernetes:

bash
apt update -y
apt upgrade -y
swapoff -a

2.2 Cài đặt Container Runtime

Load kernel modules và cấu hình network kernel parameters — giống hệt các bước trên master node. Các thiết lập này cần thiết trên mọi node trong cluster:

bash
printf "overlay\nbr_netfilter\n" >> /etc/modules-load.d/containerd.conf
modprobe overlay
modprobe br_netfilter
printf "net.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\n" >> /etc/sysctl.d/99-kubernetes-cri.conf
sysctl --system

Cài đặt containerd, runc và CNI plugins cho worker node. Lưu ý worker node dùng containerd v1.7.13 (có thể khác với master). Sau đó cấu hình SystemdCgroup = true trong config.toml tại dòng ~137:

bash
# Cài containerd
wget https://github.com/containerd/containerd/releases/download/v1.7.13/containerd-1.7.13-linux-amd64.tar.gz -P /tmp/
tar Cxzvf /usr/local /tmp/containerd-1.7.13-linux-amd64.tar.gz
wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -P /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now containerd

# Cài runc
wget https://github.com/opencontainers/runc/releases/download/v1.1.12/runc.amd64 -P /tmp/
install -m 755 /tmp/runc.amd64 /usr/local/sbin/runc

# Cài CNI plugins
wget https://github.com/containernetworking/plugins/releases/download/v1.4.0/cni-plugins-linux-amd64-v1.4.0.tgz -P /tmp/
mkdir -p /opt/cni/bin
tar Cxzvf /opt/cni/bin /tmp/cni-plugins-linux-amd64-v1.4.0.tgz

# Cấu hình containerd
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
# Sửa SystemdCgroup = true (~dòng 137)
vi /etc/containerd/config.toml

2.3 Cài đặt Kubernetes Components trên Worker

Thêm Kubernetes repository và cài đặt kubelet, kubeadm, kubectl cho worker node. Ở đây dùng version v1.29.1 (pin cụ thể). Sau khi cài xong, reboot để áp dụng tất cả cấu hình kernel:

bash
apt-get install ca-certificates curl gnupg lsb-release apt-transport-https gpg
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
apt update -y
reboot

Sau khi reboot, cài đặt và ghim phiên bản Kubernetes components. Pin version cụ thể giúp tránh việc worker node bị nâng cấp không đồng bộ với master:

bash
apt install -y kubelet=1.29.1-1.1 kubeadm=1.29.1-1.1 kubectl=1.29.1-1.1
apt-mark hold kubelet kubeadm kubectl
systemctl enable --now kubelet

2.4 Join Worker Node vào Cluster

Chạy join command lấy được từ master node (lệnh kubeadm token create --print-join-command). Lệnh này sẽ kết nối worker node với API Server của master, download cluster CA, và đăng ký node vào cluster:

bash
# Lấy join command từ master:
# kubeadm token create --print-join-command

# Chạy lệnh join trên worker node (ví dụ):
kubeadm join <MASTER_IP>:6443 --token <TOKEN> \
  --discovery-token-ca-cert-hash sha256:<HASH>

Phần 3: Triển khai Kubernetes Dashboard

3.1 Cài đặt Dashboard UI

Tải manifest dashboard từ repository và apply vào cluster. Dashboard sẽ được tạo trong namespace kubernetes-dashboard với đầy đủ Deployment, Service và các RBAC object cần thiết:

bash
wget https://raw.githubusercontent.com/kbuor/containerization/refs/heads/main/kubernetes/manifest/dashboard.yaml
kubectl apply -f dashboard.yaml

3.2 Tạo SSL Certificate cho Dashboard

Tạo self-signed SSL certificate bằng OpenSSL để bảo mật Dashboard. Lệnh đầu tạo private key và CSR (Certificate Signing Request). Lệnh sau tự ký certificate với thời hạn 365 ngày. Cuối cùng tạo Kubernetes secret chứa certificate này:

bash
mkdir certs && chmod -R 777 certs
openssl req -nodes -newkey rsa:2048 -keyout certs/dashboard.key -out certs/dashboard.csr \
  -subj "/C=/ST=/L=/O=/OU=/CN=kubernetes-dashboard"
openssl x509 -req -sha256 -days 365 -in certs/dashboard.csr -signkey certs/dashboard.key -out certs/dashboard.crt
kubectl create secret generic kubernetes-dashboard-certs --from-file=certs -n kubernetes-dashboard

3.3 Tạo User và Lấy Token đăng nhập

Tạo Service Account admin-user và ClusterRoleBinding để cấp quyền cluster-admin. Sau đó tạo token đăng nhập cho dashboard. Token này được dùng để xác thực khi truy cập web UI của Dashboard:

bash
wget https://raw.githubusercontent.com/kbuor/containerization/refs/heads/main/kubernetes/manifest/service-account.yaml
kubectl apply -f service-account.yaml

wget https://raw.githubusercontent.com/kbuor/containerization/refs/heads/main/kubernetes/manifest/cluster-role-binding.yaml
kubectl apply -f cluster-role-binding.yaml

# Lấy token để đăng nhập vào Dashboard
kubectl -n kubernetes-dashboard create token admin-user

(Tùy chọn) Cài đặt K9s — một TUI (Terminal UI) mạnh mẽ để quản lý Kubernetes trực tiếp trong terminal, thay thế cho việc gõ kubectl thủ công:

bash
curl -sS https://webinstall.dev/k9s | bash

Phần 4: Nâng cấp Cluster

4.1 Upgrade Master Node

Kiểm tra phiên bản hiện tại của kubeadm và kubectl trước khi nâng cấp để có cơ sở so sánh:

bash
kubeadm version -o short
kubectl version --short

Bỏ hold, nâng cấp kubeadm lên version mới, rồi hold lại. Thực hiện nâng cấp theo từng minor version một (ví dụ 1.26 → 1.27), không được bỏ qua version:

bash
apt-mark unhold kubeadm
apt install -y kubeadm=1.27.3-00
apt-mark hold kubeadm

Kiểm tra kế hoạch nâng cấp. Lệnh này sẽ phân tích cluster và in ra version có thể nâng cấp, các component sẽ được cập nhật, và command cần chạy để thực hiện upgrade:

bash
kubeadm upgrade plan

Thực hiện nâng cấp master và tất cả control plane components. Thay <plan> bằng version từ output của kubeadm upgrade plan (ví dụ: v1.27.3). Lệnh này sẽ nâng cấp kube-apiserver, etcd, scheduler, controller-manager:

bash
kubeadm upgrade apply v1.27.3

Sau khi nâng cấp control plane, tiến hành nâng cấp kubelet và kubectl trên master node. Kubelet cần restart để áp dụng version mới:

bash
# Nâng cấp kubelet
apt-mark unhold kubelet
apt install -y kubelet=1.27.3-00
apt-mark hold kubelet

# Nâng cấp kubectl
apt-mark unhold kubectl
apt install -y kubectl=1.27.3-00
apt-mark hold kubectl

4.2 Upgrade Worker Node

Trước khi nâng cấp worker node, cần cordon node để ngăn Kubernetes schedule Pod mới vào. Sau đó drain để di chuyển tất cả Pod đang chạy sang node khác — đảm bảo workload không bị gián đoạn trong quá trình upgrade:

bash
# Chạy trên master node:
kubectl cordon k8s-node-01
kubectl drain k8s-node-01 --ignore-daemonsets --delete-emptydir-data

SSH vào worker node và nâng cấp kubelet, kubectl. Sau khi nâng cấp xong, quay lại master node và uncordon để cho phép schedule Pod trở lại node này:

bash
# Trên worker node:
apt-mark unhold kubelet
apt install -y kubelet=1.27.3-00
apt-mark hold kubelet

apt-mark unhold kubectl
apt install -y kubectl=1.27.3-00
apt-mark hold kubectl

# Quay lại master node, uncordon worker:
kubectl uncordon k8s-node-01

# Kiểm tra kết quả
kubectl get nodes -o wide
Bài 03

Kubernetes Workloads

Hiểu và thực hành đầy đủ các loại workload: Pod, ReplicaSet, Deployment, DaemonSet, StatefulSet, Job và CronJob

Công cụkubectl
K8Sv1.35
Namespacedefault

🎯 Mục tiêu buổi học

  • Phân biệt rõ các loại workload resource: Pod, ReplicaSet, Deployment, DaemonSet, StatefulSet, Job, CronJob
  • Hiểu khi nào dùng loại workload nào trong thực tế
  • Thực hành tạo, scale, update và xóa Deployment
  • Hiểu Pod Lifecycle: các trạng thái, restart policy, init container
  • Cấu hình và debug Container Probes: liveness, readiness, startup
  • Thực hành multi-container Pod với sidecar pattern
  • Hiểu StatefulSet dùng cho workload stateful như database
  • Tạo và quản lý Job, CronJob để chạy tác vụ định kỳ

Phần 1: Tổng quan các loại Workload Resource

Kubernetes cung cấp nhiều loại workload resource để phù hợp với từng mục đích triển khai khác nhau. Việc chọn đúng loại workload là yếu tố then chốt trong thiết kế hệ thống.

text – Bản đồ các loại Workload
Kubernetes Workload Resources
│
├── Pod                  → Đơn vị cơ bản, ít dùng trực tiếp trong production
│
├── ReplicaSet           → Đảm bảo số lượng Pod replica (thường không dùng trực tiếp)
│
├── Deployment           → Stateless app: web, API, worker (phổ biến nhất)
│   └── manages → ReplicaSet → manages → Pods
│
├── DaemonSet            → Chạy 1 Pod trên mỗi node: log agent, monitoring agent
│
├── StatefulSet          → Stateful app: database, message queue (cần stable identity)
│
├── Job                  → Tác vụ chạy 1 lần đến khi hoàn thành: batch, migration
│
└── CronJob              → Tác vụ chạy định kỳ theo lịch: backup, report, cleanup
WorkloadDùng khi nàoVí dụ thực tế
DeploymentApp stateless, cần scale, rolling updateReact frontend, REST API, Nginx
StatefulSetApp cần stable network ID, persistent storageMySQL, PostgreSQL, Kafka, Zookeeper
DaemonSetCần 1 instance trên mỗi nodeFluentd, Prometheus node-exporter, Calico
JobTác vụ chạy 1 lần đến hoàn thànhDB migration, data import, batch processing
CronJobTác vụ lặp lại theo lịchBackup hàng đêm, gửi report, cleanup log

Phần 2: Pod — Thực hành chi tiết

Trước khi học các workload cao cấp, cần nắm chắc Pod. Trong thực tế, Pod thường không được tạo thủ công mà thông qua Deployment — nhưng hiểu Pod giúp debug hiệu quả hơn.

2.1 Tạo Pod đơn giản

Theo dõi cluster events trong terminal riêng để quan sát toàn bộ quá trình scheduling → image pulling → container starting khi tạo Pod:

bash
kubectl get events --watch

Manifest Pod đơn giản chạy ứng dụng hello-world. Field apiVersion: v1kind: Pod xác định loại object. spec.containers liệt kê danh sách container — ở đây chỉ có 1 container:

yaml – hello-world-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello-world-pod
  labels:
    app: hello-world
spec:
  containers:
    - name: hello-world
      image: gcr.io/google-samples/hello-app:1.0
      ports:
        - containerPort: 8080

Apply manifest để tạo Pod. Quan sát events terminal: sẽ thấy tuần tự — Scheduled (scheduler chọn node) → Pulling (pull image) → CreatedStarted:

bash
kubectl apply -f hello-world-pod.yaml
kubectl get pods -o wide

2.2 Truy cập Pod bằng port-forward

kubectl port-forward tạo tunnel từ máy local đến Pod, đi qua API Server — không cần Service. Cú pháp: LOCAL_PORT:CONTAINER_PORT. Dùng & để chạy nền, giải phóng terminal:

bash
# Chạy port-forward ở nền (dùng & để background)
kubectl port-forward hello-world-pod 8080:8080 &

# Test kết nối — kubectl forward traffic qua API Server đến Pod
curl http://localhost:8080

# Dừng port-forward khi xong
fg
# Nhấn Ctrl+C

2.3 Multi-Container Pod (Sidecar Pattern)

Multi-container Pod là pattern phổ biến trong production. Các container trong cùng Pod dùng chung network và có thể dùng chung Volume. Pattern phổ biến nhất là Sidecar — container phụ hỗ trợ container chính.

Manifest multi-container Pod với 2 container: producer tạo nội dung HTML ghi vào shared volume, consumer (nginx) phục vụ file đó. Cả hai dùng chung emptyDir volume — volume tạm thời tồn tại trong vòng đời Pod:

yaml – multicontainer-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: multicontainer-pod
spec:
  containers:
    - name: producer
      image: ubuntu
      command: ["/bin/bash"]
      args: ["-c", "while true; do echo $(date) >> /var/log/index.html; sleep 10; done"]
      volumeMounts:
        - name: webcontent
          mountPath: /var/log
    - name: consumer
      image: nginx
      ports:
        - containerPort: 80
      volumeMounts:
        - name: webcontent
          mountPath: /usr/share/nginx/html
  volumes:
    - name: webcontent
      emptyDir: {}

Tạo Pod và truy cập từng container. Không chỉ định --container sẽ mặc định vào container đầu tiên. Quan sát shared volume — producer ghi, consumer đọc:

bash
kubectl apply -f multicontainer-pod.yaml

# Vào container đầu tiên (producer) - không cần chỉ định tên
kubectl exec -it multicontainer-pod -- /bin/sh
ls -la /var/log
tail /var/log/index.html
exit

# Vào container thứ hai (consumer) - phải chỉ định tên
kubectl exec -it multicontainer-pod --container consumer -- /bin/sh
ls -la /usr/share/nginx/html
tail /usr/share/nginx/html/index.html
exit

# Test qua port-forward
kubectl port-forward multicontainer-pod 8080:80 &
curl http://localhost:8080
fg  # Ctrl+C để dừng

# Dọn dẹp
kubectl delete pod multicontainer-pod

Phần 3: Pod Lifecycle — Vòng đời và Restart Policy

Hiểu Pod lifecycle giúp debug hiệu quả khi Pod không hoạt động đúng. Mỗi Pod đi qua nhiều trạng thái khác nhau trong vòng đời của nó.

3.1 Các trạng thái của Pod

Trạng tháiÝ nghĩaNguyên nhân thường gặp
PendingPod đã được tạo, chưa được schedule hoặc đang pull imageKhông đủ tài nguyên, image đang pull, chưa có CNI
RunningPod đang chạy, ít nhất 1 container đang hoạt độngTrạng thái bình thường
SucceededTất cả container đã kết thúc thành công (exit 0)Job hoàn thành, init container xong
FailedÍt nhất 1 container kết thúc với lỗi (exit ≠ 0)App crash, OOMKilled, lệnh không tồn tại
CrashLoopBackOffContainer liên tục crash và restart với backoff delayApp lỗi khi khởi động, thiếu config, lỗi ENV
ImagePullBackOffKhông thể pull container imageImage không tồn tại, sai tag, thiếu pull secret

3.2 Restart Policy

Restart policy quyết định container sẽ được restart như thế nào khi kết thúc. Được cấu hình ở cấp Pod, áp dụng cho tất cả container trong Pod.

PolicyHành viDùng khi nào
AlwaysLuôn restart khi container kết thúc (mặc định)Web server, API — phải luôn chạy
OnFailureChỉ restart khi container kết thúc lỗi (exit ≠ 0)Batch job — chạy lại khi lỗi, dừng khi thành công
NeverKhông bao giờ restartOne-shot task — chỉ chạy 1 lần

Manifest tạo 2 Pod với restart policy khác nhau để quan sát hành vi. Pod never sẽ không restart khi process chết. Pod onfailure sẽ restart khi crash:

yaml – pod-restart-policy.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello-world-never-pod
spec:
  restartPolicy: Never
  containers:
    - name: hello-world
      image: gcr.io/google-samples/hello-app:1.0
---
apiVersion: v1
kind: Pod
metadata:
  name: hello-world-onfailure-pod
spec:
  restartPolicy: OnFailure
  containers:
    - name: hello-world
      image: gcr.io/google-samples/hello-app:1.0

Tạo 2 Pod và thực nghiệm kill main process để quan sát sự khác biệt giữa NeverOnFailure. Chú ý cột RESTARTSSTATUS thay đổi:

bash
kubectl apply -f pod-restart-policy.yaml
kubectl get pods

# Kill process trong pod restartPolicy=Never
kubectl exec -it hello-world-never-pod -- /usr/bin/killall hello-app
kubectl get pods hello-world-never-pod
# STATUS: Completed hoặc Error — không restart

# Xem chi tiết: Containers->State, Reason, Exit Code
kubectl describe pod hello-world-never-pod

# Kill process trong pod restartPolicy=OnFailure
kubectl exec -it hello-world-onfailure-pod -- /usr/bin/killall hello-app
kubectl get pods hello-world-onfailure-pod
# RESTARTS tăng lên 1 — container đã được restart

3.3 Init Container

Init container là container đặc biệt chạy trước các container chính trong Pod. Chúng chạy tuần tự, mỗi init container phải hoàn thành trước khi init container tiếp theo (hoặc container chính) bắt đầu. Dùng để: chờ dependency sẵn sàng, tải config, chạy DB migration.

yaml – pod-with-init.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
spec:
  initContainers:
    - name: init-wait-db
      image: busybox
      command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done']
    - name: init-copy-config
      image: busybox
      command: ['sh', '-c', 'echo config data > /config/app.conf']
      volumeMounts:
        - name: config-vol
          mountPath: /config
  containers:
    - name: myapp
      image: nginx
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      emptyDir: {}
💡 Init Container vs Sidecar Container
Đặc điểmInit ContainerSidecar Container
Thời điểm chạyTrước container chính, chạy xong thì dừngSong song với container chính, chạy suốt
Mục đíchSetup, validation, chờ dependencyLogging, proxy, monitoring
RestartRestart nếu fail, chạy lại từ đầuRestart theo restart policy của Pod

Phần 4: Container Probes — Health Check

Kubernetes dùng Probe để kiểm tra sức khỏe container. Cấu hình probe đúng là yếu tố quan trọng đảm bảo ứng dụng production ổn định.

4.1 Ba loại Probe

ProbeMục đíchHành động khi fail
livenessProbeContainer có còn sống không? Có bị deadlock không?Restart container
readinessProbeContainer đã sẵn sàng nhận traffic chưa?Xóa Pod khỏi Service endpoint — không restart
startupProbeApp khởi động chậm, cần thêm thời gian trước khi kiểm traRestart container nếu quá thời gian

4.2 Các cơ chế kiểm tra Probe

Cơ chếMô tảDùng khi nào
httpGetHTTP GET đến endpoint — thành công nếu status 200-399Web app có health check endpoint
tcpSocketKiểm tra TCP connection đến port — thành công nếu kết nối đượcDatabase, message broker
execChạy lệnh trong container — thành công nếu exit code 0Custom check, kiểm tra file tồn tại
grpcgRPC health check protocolApp dùng gRPC

4.3 Manifest Probe đầy đủ

Manifest Pod với đầy đủ 3 loại probe. Chú ý các tham số quan trọng: initialDelaySeconds (chờ bao lâu trước khi probe lần đầu), periodSeconds (probe cách bao lâu 1 lần), failureThreshold (fail bao nhiêu lần mới hành động):

yaml – container-probes.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello-world-probe
  labels:
    app: hello-world
spec:
  containers:
    - name: hello-world
      image: gcr.io/google-samples/hello-app:1.0
      ports:
        - containerPort: 8080
      startupProbe:
        httpGet:
          path: /
          port: 8080
        initialDelaySeconds: 5   # Chờ 5s sau khi container start
        periodSeconds: 5          # Probe mỗi 5s
        failureThreshold: 30      # Cho phép fail 30 lần (= 150s để khởi động)
      livenessProbe:
        httpGet:
          path: /
          port: 8080              # ⚠️ Phải khớp với containerPort!
        initialDelaySeconds: 10
        periodSeconds: 10
        failureThreshold: 3       # Fail 3 lần → restart container
        timeoutSeconds: 5
      readinessProbe:
        httpGet:
          path: /
          port: 8080
        initialDelaySeconds: 10
        periodSeconds: 10
        failureThreshold: 3       # Fail 3 lần → xóa khỏi Service endpoints
        successThreshold: 1

Tạo Pod và quan sát probe hoạt động. Lỗi thường gặp là cấu hình sai port — probe trỏ đến port không tồn tại. Dùng kubectl describe để đọc trạng thái probe và events:

bash
kubectl apply -f container-probes.yaml

# Xem Pod đang khởi động
kubectl get pods --selector app=hello-world --watch

# Xem chi tiết probe configuration và trạng thái
# Tìm: Containers > Liveness / Readiness / Startup section
# Tìm: Conditions > Ready, ContainersReady
# Tìm: Events > probe failure messages
kubectl describe pod hello-world-probe

# Nếu probe fail: Pod sẽ hiện RESTARTS tăng (liveness)
# hoặc READY 0/1 (readiness)

kubectl delete pod hello-world-probe

Phần 5: ReplicaSet — Đảm bảo số lượng replica

ReplicaSet đảm bảo một số lượng Pod replica cụ thể luôn chạy trong cluster. Nếu Pod crash hoặc bị xóa, ReplicaSet sẽ tự tạo Pod mới để bù đắp.

text – Cơ chế hoạt động ReplicaSet
ReplicaSet: hello-world-rs
  replicas: 3   ← desired state

  Trạng thái thực tế: 3 Pod đang chạy ✓
  
  → Pod A crash
  
  Trạng thái thực tế: 2 Pod đang chạy ✗
  ReplicaSet Controller phát hiện: 2 ≠ 3
  → Tạo Pod D mới
  
  Trạng thái thực tế: 3 Pod đang chạy ✓

Label Selector — cơ chế ReplicaSet tìm Pod của mình:
  ReplicaSet selector: app=hello-world
  Pod labels: app=hello-world  → Pod thuộc về RS này
⚠️ Tại sao không dùng ReplicaSet trực tiếp?

ReplicaSet không hỗ trợ rolling update. Khi cần update image, phải xóa toàn bộ Pod cũ rồi tạo mới — gây downtime. Deployment bao gồm ReplicaSet và bổ sung rolling update, rollback. Trong thực tế, hầu như luôn dùng Deployment thay vì ReplicaSet trực tiếp.

Phần 6: Deployment — Workload phổ biến nhất

Deployment là workload resource phổ biến nhất trong Kubernetes. Nó quản lý ReplicaSet, cung cấp rolling update, rollback và nhiều tính năng khác cho stateless application.

text – Cấu trúc phân cấp Deployment
Deployment: hello-world
  └── ReplicaSet: hello-world-7d9b4c8f6  (version hiện tại)
      ├── Pod: hello-world-7d9b4c8f6-abcde
      ├── Pod: hello-world-7d9b4c8f6-fghij
      └── Pod: hello-world-7d9b4c8f6-klmno
  └── ReplicaSet: hello-world-5c6b3a2e1  (version cũ, scale=0 sau update)

Khi update image:
  Deployment tạo ReplicaSet MỚI → scale up từ từ
  Đồng thời scale down ReplicaSet CŨ
  → Zero downtime rolling update

6.1 Tạo Deployment

Manifest Deployment đầy đủ với cấu hình strategy rolling update. maxSurge là số Pod có thể tạo thêm tạm thời khi update. maxUnavailable là số Pod tối đa có thể không available trong quá trình update. resources giới hạn CPU/RAM của container:

yaml – deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
  labels:
    app: hello-world
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-world          # ReplicaSet dùng label này để tìm Pod
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1               # Tối đa 1 Pod tạo thêm khi update
      maxUnavailable: 0         # Không cho phép Pod nào unavailable
  template:                     # Đây là Pod template
    metadata:
      labels:
        app: hello-world        # Label phải khớp với selector ở trên
    spec:
      containers:
        - name: hello-world
          image: gcr.io/google-samples/hello-app:1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "100m"       # 0.1 CPU core (tối thiểu cần)
              memory: "128Mi"   # 128 MB RAM (tối thiểu cần)
            limits:
              cpu: "200m"       # 0.2 CPU core (tối đa cho phép)
              memory: "256Mi"   # 256 MB RAM (tối đa cho phép)

Tạo Deployment và quan sát quá trình tạo ReplicaSet → Pod. Lệnh rollout status chờ cho đến khi Deployment hoàn tất:

bash
kubectl apply -f deployment.yaml

# Theo dõi quá trình rollout
kubectl rollout status deployment hello-world

# Xem Deployment, ReplicaSet và Pod
kubectl get deployment hello-world
kubectl get replicaset
kubectl get pods -l app=hello-world -o wide

6.2 Scale Deployment

Scale deployment bằng lệnh kubectl scale. Quan sát trong events terminal — khi scale up sẽ thấy ReplicaSet tạo thêm Pod, khi scale down sẽ thấy Pod bị terminate:

bash
# Scale lên 5 replica
kubectl scale deployment hello-world --replicas=5
kubectl get pods -l app=hello-world

# Scale xuống 2 replica
kubectl scale deployment hello-world --replicas=2
kubectl get pods -l app=hello-world

# Xem lịch sử scale trong events
kubectl describe deployment hello-world

6.3 Rolling Update và Rollback

Update image từ v1.0 lên v2.0. Kubernetes tạo ReplicaSet mới, scale up dần Pod mới trong khi scale down Pod cũ — đảm bảo zero downtime. --record lưu lệnh vào rollout history:

bash
# Cập nhật image lên version 2.0
kubectl set image deployment hello-world hello-world=gcr.io/google-samples/hello-app:2.0

# Theo dõi tiến trình rolling update
kubectl rollout status deployment hello-world

# Xem ReplicaSet — sẽ thấy RS cũ (replicas=0) và RS mới (replicas=3)
kubectl get replicaset

# Xem lịch sử rollout
kubectl rollout history deployment hello-world

# Rollback về version trước nếu có vấn đề
kubectl rollout undo deployment hello-world

# Xác nhận đã rollback thành công
kubectl rollout status deployment hello-world
kubectl describe deployment hello-world | grep Image
💡 Tại sao Deployment giữ lại ReplicaSet cũ?

Deployment không xóa ReplicaSet cũ ngay sau khi update — nó chỉ scale về 0. Điều này cho phép rollback ngay lập tức mà không cần pull lại image. Số lượng RS cũ được giữ lại kiểm soát bởi spec.revisionHistoryLimit (mặc định là 10).

Phần 7: DaemonSet — Một Pod trên mỗi Node

DaemonSet đảm bảo một bản sao của Pod chạy trên tất cả (hoặc một nhóm) node trong cluster. Khi node mới được thêm vào cluster, Pod tự động được tạo trên node đó. Khi node bị xóa, Pod tự động bị dọn dẹp.

Use caseVí dụ
Log collection agentFluentd, Logstash, Filebeat — thu thập log từ mỗi node
Metrics/monitoring agentPrometheus node-exporter — đọc metrics của OS từng node
Network pluginCalico-node, Cilium — CNI plugin chạy trên mỗi node
Storage pluginCeph, GlusterFS node daemon
Security agentFalco, Sysdig — giám sát syscall trên từng node

Manifest DaemonSet chạy Fluentd log agent trên mỗi node. tolerations cho phép Pod chạy trên cả master node (vốn có taint node-role.kubernetes.io/control-plane). hostPath volume mount thư mục log của OS vào container:

yaml – daemonset-fluentd.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule        # Cho phép chạy trên master node
      containers:
        - name: fluentd
          image: fluent/fluentd:v1.16
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 200Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log   # Mount log directory từ node
      volumes:
        - name: varlog
          hostPath:
            path: /var/log          # Thư mục log thực tế trên node OS

Tạo DaemonSet và quan sát — số lượng Pod bằng số node trong cluster. Nếu cluster có 3 node thì sẽ có 3 Pod, mỗi Pod chạy trên 1 node:

bash
kubectl apply -f daemonset-fluentd.yaml

# Số Pod = số node trong cluster (kể cả master nếu có toleration)
kubectl get pods -n kube-system -l app=fluentd -o wide

# Mô tả DaemonSet: thấy số Desired, Current, Ready
kubectl describe daemonset fluentd -n kube-system

# Dọn dẹp
kubectl delete daemonset fluentd -n kube-system

Phần 8: StatefulSet — Workload Stateful

StatefulSet dành cho các ứng dụng cần stable identity — tên Pod, hostname, và persistent storage không thay đổi khi Pod được recreate. Điều này cần thiết cho database, message queue, và các ứng dụng distributed cần nhận biết nhau.

8.1 Deployment vs StatefulSet

Đặc điểmDeploymentStatefulSet
Tên PodRandom suffix: app-7d9b4c-xk2lpThứ tự: mysql-0, mysql-1, mysql-2
Khởi độngTất cả Pod start song songTuần tự: mysql-0 → mysql-1 → mysql-2
XóaNgẫu nhiênNgược thứ tự: mysql-2 → mysql-1 → mysql-0
StorageDùng chung hoặc không cầnMỗi Pod có PVC riêng, tồn tại qua restart
NetworkClusterIP ServiceHeadless Service — DNS riêng cho từng Pod
Dùng khi nàoStateless: web, APIStateful: MySQL, PostgreSQL, Kafka

8.2 DNS Headless Service trong StatefulSet

text – DNS pattern của StatefulSet Pod
StatefulSet tên: mysql
Headless Service tên: mysql-svc
Namespace: default

Mỗi Pod có DNS riêng và ổn định:
  mysql-0.mysql-svc.default.svc.cluster.local
  mysql-1.mysql-svc.default.svc.cluster.local
  mysql-2.mysql-svc.default.svc.cluster.local

→ mysql-1 có thể luôn liên lạc với mysql-0 qua DNS
→ Kể cả khi mysql-0 bị restart, DNS vẫn trỏ đúng Pod mới

8.3 Manifest StatefulSet

Manifest StatefulSet MySQL với Headless Service. clusterIP: None tạo Headless Service — không có ClusterIP, CoreDNS trả về IP của Pod trực tiếp. volumeClaimTemplates tự động tạo PVC riêng cho từng Pod:

yaml – statefulset-mysql.yaml
# Headless Service — bắt buộc phải có trước StatefulSet
apiVersion: v1
kind: Service
metadata:
  name: mysql-svc
spec:
  clusterIP: None           # Headless: không có VIP, DNS trả về Pod IP
  selector:
    app: mysql
  ports:
    - port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-svc    # Tên Headless Service ở trên
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          ports:
            - containerPort: 3306
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "password123"
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql  # Mỗi Pod mount PVC riêng của mình
  volumeClaimTemplates:          # Template tự tạo PVC cho từng Pod
    - metadata:
        name: mysql-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

Tạo StatefulSet và quan sát thứ tự tạo Pod tuần tự — mysql-0 phải Running trước khi mysql-1 được tạo. Mỗi Pod có PVC riêng tự động được tạo:

bash
kubectl apply -f statefulset-mysql.yaml

# Quan sát Pod tạo tuần tự: mysql-0 → mysql-1 → mysql-2
kubectl get pods -l app=mysql --watch

# Xem PVC được tạo tự động cho từng Pod
kubectl get pvc

# Pod có tên ổn định và thứ tự
kubectl get pods -l app=mysql -o wide

Phần 9: Job — Tác vụ chạy một lần

Job tạo một hoặc nhiều Pod để thực hiện tác vụ đến khi hoàn thành thành công. Khác với Deployment — khi container kết thúc thành công, Job coi là hoàn tất và không restart thêm.

9.1 Các mode chạy Job

ModecompletionsparallelismHành vi
Single Job1 (mặc định)1 (mặc định)Chạy 1 Pod duy nhất đến khi xong
Parallel fixedNPChạy tổng N Pod, tối đa P Pod cùng lúc
Work queuekhông đặtPPod tự lấy việc từ queue, dừng khi queue rỗng

Manifest Job tính Pi đến 2000 chữ số. backoffLimit là số lần retry tối đa nếu Job fail. activeDeadlineSeconds timeout toàn bộ Job — tránh Job chạy vô hạn:

yaml – job-pi.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: pi-calculator
spec:
  completions: 1            # Cần 1 lần chạy thành công
  parallelism: 1            # Chạy 1 Pod tại một thời điểm
  backoffLimit: 4           # Retry tối đa 4 lần nếu fail
  activeDeadlineSeconds: 120 # Timeout 2 phút — Job bị hủy nếu quá
  template:
    spec:
      restartPolicy: OnFailure  # Job phải dùng OnFailure hoặc Never
      containers:
        - name: pi
          image: perl:5.34
          command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]

Chạy Job và xem log kết quả. Sau khi Job Complete, Pod vẫn còn để đọc log — không bị xóa ngay:

bash
kubectl apply -f job-pi.yaml

# Theo dõi Job cho đến khi Complete
kubectl get jobs --watch

# Xem Pod do Job tạo ra
kubectl get pods -l job-name=pi-calculator

# Xem kết quả tính toán trong log
kubectl logs -l job-name=pi-calculator

# Xem chi tiết Job: Completions, Duration
kubectl describe job pi-calculator

# Dọn dẹp
kubectl delete job pi-calculator

Phần 10: CronJob — Tác vụ định kỳ

CronJob tạo Job theo lịch định kỳ dùng cú pháp cron của Linux. CronJob sẽ tự động tạo Job mới theo lịch đã định, và mỗi Job tạo Pod để thực hiện công việc.

10.1 Cú pháp Cron Schedule

text – Cú pháp cron trong Kubernetes
Cú pháp: "Phút  Giờ  Ngày  Tháng  Thứ"

┌───── Phút (0-59)
│ ┌───── Giờ (0-23)
│ │ ┌───── Ngày trong tháng (1-31)
│ │ │ ┌───── Tháng (1-12)
│ │ │ │ ┌───── Thứ trong tuần (0-6, 0=Chủ nhật)
│ │ │ │ │
* * * * *

Ví dụ:
"*/5 * * * *"      → Mỗi 5 phút
"0 * * * *"        → Mỗi giờ (vào đầu giờ)
"0 2 * * *"        → Hàng ngày lúc 2:00 AM
"0 2 * * 0"        → Hàng tuần vào CN lúc 2:00 AM
"0 2 1 * *"        → Ngày 1 hàng tháng lúc 2:00 AM
"0 9-18 * * 1-5"   → Mỗi giờ từ 9AM-6PM, thứ 2 đến thứ 6

10.2 Manifest CronJob

Manifest CronJob chạy backup mỗi ngày lúc 2AM. successfulJobsHistoryLimitfailedJobsHistoryLimit kiểm soát bao nhiêu Job cũ được giữ lại để debug. concurrencyPolicy quyết định xử lý thế nào khi lịch chạy trùng nhau:

yaml – cronjob-backup.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-backup
spec:
  schedule: "0 2 * * *"          # Mỗi ngày lúc 2:00 AM
  concurrencyPolicy: Forbid       # Không cho phép chạy song song nếu trùng lịch
  successfulJobsHistoryLimit: 3   # Giữ lại 3 Job thành công gần nhất
  failedJobsHistoryLimit: 1       # Giữ lại 1 Job thất bại gần nhất
  startingDeadlineSeconds: 300    # Nếu trễ quá 5 phút thì bỏ qua lần này
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: backup
              image: alpine
              command:
                - /bin/sh
                - -c
                - |
                  echo "Starting backup at $(date)"
                  echo "Backing up database..."
                  sleep 30
                  echo "Backup completed successfully at $(date)"

Tạo CronJob và test ngay bằng cách trigger thủ công — không cần chờ đến 2AM. kubectl create job --from tạo Job ngay từ template của CronJob:

bash
kubectl apply -f cronjob-backup.yaml

# Xem CronJob — cột SCHEDULE và LAST SCHEDULE
kubectl get cronjob daily-backup

# Trigger thủ công để test ngay (không cần chờ đến 2AM)
kubectl create job backup-manual-test --from=cronjob/daily-backup

# Xem Job và Pod được tạo
kubectl get jobs
kubectl get pods -l job-name=backup-manual-test

# Xem log của Job
kubectl logs -l job-name=backup-manual-test

# Dọn dẹp
kubectl delete cronjob daily-backup
kubectl delete job backup-manual-test

🧪 Câu hỏi kiểm tra cuối buổi

1. Bạn cần chạy Prometheus node-exporter để thu thập metrics từ mỗi node trong cluster. Workload nào phù hợp nhất?

2. Sự khác biệt chính giữa livenessProbe và readinessProbe là gì?

3. Tại sao Deployment giữ lại ReplicaSet cũ (với replicas=0) sau khi rolling update?

4. Bạn cần chạy MySQL với 3 replica, mỗi instance cần volume riêng và có tên ổn định để các instance nhận ra nhau. Workload nào phù hợp?

5. RestartPolicy nào phù hợp cho một Job batch processing cần chạy lại khi gặp lỗi nhưng không chạy lại khi thành công?

6. Trong CronJob, concurrencyPolicy: Forbid có nghĩa là gì?

7. Init Container khác Sidecar Container ở điểm nào?

8. Lệnh nào dùng để trigger thủ công 1 lần chạy của CronJob mà không cần chờ đến đúng lịch?

Bài 04

Kubernetes Service & Networking

Hiểu mô hình mạng trong K8S, các loại Service và DNS nội bộ — nền tảng để kết nối ứng dụng trong cluster

CNICalico
DNSCoreDNS
Proxykube-proxy / iptables

🎯 Mục tiêu buổi học

  • Hiểu mô hình mạng phẳng (flat network) của Kubernetes — mọi Pod đều có thể nói chuyện với nhau
  • Hiểu cách CNI plugin (Calico) cấp IP và định tuyến giữa các Pod trên nhiều node
  • Phân biệt 4 loại Service: ClusterIP, NodePort, LoadBalancer, ExternalName và Headless
  • Hiểu cơ chế kube-proxy và iptables/IPVS trong việc forward traffic đến Service
  • Hiểu CoreDNS — cơ chế DNS discovery nội bộ trong cluster
  • Thực hành tạo, test từng loại Service và quan sát DNS resolution
  • Hiểu NetworkPolicy — kiểm soát lưu lượng mạng giữa Pod

Phần 1: Mô hình mạng Kubernetes

Kubernetes có một mô hình mạng đơn giản nhưng rất mạnh mẽ: mọi Pod có thể giao tiếp trực tiếp với mọi Pod khác trong cluster, bất kể chúng đang chạy trên node nào. Không cần NAT, không cần port mapping thủ công.

1.1 Ba lớp mạng trong Kubernetes

text – Ba lớp địa chỉ IP
Lớp 1: Node Network (vật lý / VM)
  master-01:   10.236.0.10/24
  worker-01:   10.236.0.11/24
  worker-02:   10.236.0.12/24
  → Đây là IP thực của máy chủ, do hạ tầng cấp

Lớp 2: Pod Network (do CNI Plugin quản lý — Calico)
  Pod A trên worker-01:  192.168.154.243/32
  Pod B trên worker-01:  192.168.154.244/32
  Pod C trên worker-02:  192.168.44.215/32
  → Mỗi Pod nhận 1 IP duy nhất, định tuyến qua Calico

Lớp 3: Service Network (virtual IP — do kube-proxy quản lý)
  ClusterIP Service:     10.96.x.x/12  (ví dụ: 10.102.161.178)
  kube-dns Service:      10.96.0.10
  → IP ảo, không gắn với interface thật, xử lý bởi iptables/IPVS

1.2 Quy tắc mạng Kubernetes

Quy tắcÝ nghĩa thực tế
Pod ↔ Pod (cùng node)Giao tiếp qua virtual ethernet (veth pair) và Linux bridge
Pod ↔ Pod (khác node)Giao tiếp qua CNI overlay (Calico VXLAN/BGP), không cần NAT
Pod → Serviceiptables/IPVS trên node chặn và DNAT đến Pod phía sau
External → PodPhải qua Service (NodePort/LoadBalancer) hoặc Ingress
Không có NATIP nguồn của Pod giữ nguyên khi đến Pod đích

1.3 Quan sát mạng thực tế

Xem IP của tất cả node trong cluster. Cột INTERNAL-IP là IP vật lý của node — đây là lớp mạng node network. Trong môi trường cloud, EXTERNAL-IP là IP public:

bash
kubectl get nodes -o wide

Deploy workload với 3 replica và quan sát IP của từng Pod. Chú ý: các Pod trên cùng node có IP trong cùng subnet (192.168.154.x), Pod trên node khác có IP thuộc subnet khác (192.168.44.x) — đây là cách Calico phân chia IP block theo node:

bash
kubectl apply -f Deployment.yaml
kubectl get pods -o wide

# Output mẫu:
# NAME                           READY   IP                NODE
# hello-world-75d6d8cfd7-bxqj7   1/1     192.168.154.243   k8s-node-01
# hello-world-75d6d8cfd7-p2559   1/1     192.168.44.215    k8s-node-02
# hello-world-75d6d8cfd7-qlzjw   1/1     192.168.154.244   k8s-node-01

Vào trong Pod và kiểm tra cấu hình mạng. Interface eth0 là virtual ethernet pair kết nối Pod vào network namespace của node. IP /32 (không phải /24) là đặc trưng của Calico — mỗi Pod là một host riêng biệt trong routing table:

bash
# Vào Pod và kiểm tra network interface
kubectl exec -it <POD_NAME> -- ip addr

# Kết quả: eth0 với IP /32 — đặc trưng của Calico
# 3: eth0@if57: inet 192.168.154.243/32

# Test Pod-to-Pod connectivity (thay IP bằng IP của Pod khác)
kubectl exec -it <POD_NAME> -- ping 192.168.44.215 -c 3

SSH vào worker node và xem routing table của OS. Calico tạo các route cho từng Pod (cali* interface) và route cho Pod subnet của node khác (qua k8s-node-02 gateway). Đây là cơ chế BGP routing của Calico:

bash
# SSH vào worker node
ssh worker-01

# Xem routing table — Calico thêm route cho từng Pod
route -n
# 192.168.154.243  0.0.0.0  255.255.255.255  UH  0  0  0  cali6833ab5953b
# 192.168.154.244  0.0.0.0  255.255.255.255  UH  0  0  0  cali72039afe81f
# 192.168.44.192   10.236.0.12  255.255.255.192  UG  0  0  0  ens160
#                  ↑ Route đến Pod subnet trên node-02 qua node-02 IP

# Xem Calico interfaces
ip link show | grep cali

Phần 2: Calico CNI — Cơ chế hoạt động chi tiết

Calico là CNI plugin phổ biến, dùng BGP (Border Gateway Protocol) hoặc VXLAN để định tuyến traffic giữa Pod trên các node khác nhau. Lab này dùng Calico với VXLAN overlay.

2.1 Luồng gói tin Pod A → Pod B (khác node)

text – Luồng packet cross-node
Pod A (192.168.154.243) trên worker-01
  │  Gói tin gửi đến 192.168.44.215 (Pod B)
  ▼
veth pair: cali6833ab5953b ← kết nối Pod A vào node network
  │  Kernel tra routing table trên worker-01
  │  Route: 192.168.44.192/26 via 10.236.0.12 (worker-02)
  ▼
vxlan.calico interface (IP: 192.168.154.192)
  │  VXLAN encapsulation: bọc gói tin gốc vào UDP/VXLAN frame
  │  Outer IP: src=10.236.0.11 dst=10.236.0.12 (node IPs)
  ▼
ens160 (10.236.0.11) → ens160 (10.236.0.12) qua node network
  │  VXLAN decapsulation tại worker-02
  ▼
cali interface trên worker-02 → Pod B (192.168.44.215)

Kết quả: Pod A thấy mình giao tiếp trực tiếp với Pod B
         Không có NAT — IP nguồn giữ nguyên 192.168.154.243

2.2 Vấn đề khi chỉ dùng Pod IP trực tiếp

⚠️ Tại sao không dùng Pod IP để giao tiếp?

Pod IP là ephemeral (tạm thời) — khi Pod bị xóa và tạo lại (crash, rolling update, reschedule), IP sẽ thay đổi hoàn toàn. Không có cơ chế nào đảm bảo Pod giữ nguyên IP sau khi recreate. Đây là lý do cần Service — một địa chỉ IP ổn định đứng trước một nhóm Pod và tự động load balance traffic đến các Pod đang sống.

Phần 3: Kubernetes Service — Địa chỉ ổn định cho Pod

Service là resource cung cấp một địa chỉ IP và DNS name ổn định cho một nhóm Pod. Service dùng Label Selector để tìm Pod phía sau, và kube-proxy để forward traffic đến đúng Pod.

3.1 Cơ chế hoạt động của Service

text – Cơ chế Service + Endpoints
Service: hello-world (ClusterIP: 10.102.161.178, port: 8080)
  │  Label Selector: app=hello-world
  │
  ▼
Endpoints object (tự động cập nhật)
  ├── 192.168.154.243:80  (Pod A — Running, Healthy)
  ├── 192.168.44.215:80   (Pod B — Running, Healthy)
  └── 192.168.154.244:80  (Pod C — Running, Healthy)
  [Pod D đang Terminating → bị xóa khỏi Endpoints ngay]

kube-proxy trên mỗi node (watch API Server liên tục)
  → Khi Endpoints thay đổi, cập nhật iptables rules ngay
  → iptables DNAT: 10.102.161.178:8080 → random Pod IP:80

Traffic flow:
  Client → 10.102.161.178:8080 (ClusterIP)
         → iptables DNAT → 192.168.154.243:80 (Pod A)

3.2 Tổng quan 5 loại Service

Loại ServiceTruy cập từ đâuDùng khi nào
ClusterIPChỉ bên trong clusterGiao tiếp nội bộ giữa các service
NodePortBên ngoài qua IP:Port của nodeDev/test, không có Load Balancer
LoadBalancerBên ngoài qua External IPProduction trên cloud (AWS ELB, GCP LB...)
ExternalNameDNS alias đến external hostAlias cho service bên ngoài cluster
HeadlessDNS trả về Pod IP trực tiếpStatefulSet, service discovery tùy chỉnh

Phần 4: ClusterIP — Service mặc định

ClusterIP là loại Service mặc định. Kubernetes cấp một IP ảo (Virtual IP) chỉ có thể truy cập từ bên trong cluster. Đây là loại dùng phổ biến nhất để kết nối giữa các microservice.

text – Cấu trúc ClusterIP
Bên ngoài cluster
      ✗ Không thể truy cập ClusterIP từ bên ngoài

Bên trong cluster (Pod, node)
      ✓ Pod A → ClusterIP 10.102.161.178:8080 → kube-proxy → Pod B/C/D

ClusterIP flow:
  1. Nguồn: Pod A gửi đến 10.102.161.178:8080
  2. iptables PREROUTING: DNAT → Pod B IP (random, round-robin)
  3. Packet đến Pod B:80
  4. Reply đi ngược lại (conntrack tự track session)

Manifest ClusterIP Service. port là port Service lắng nghe, targetPort là port thực tế của container. Selector robusta: web-app xác định nhóm Pod phía sau Service:

yaml – 1-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
  name: robusta-svc-01
spec:
  type: ClusterIP          # Mặc định — có thể bỏ qua field này
  ports:
    - port: 8080           # Port Service lắng nghe (client kết nối vào đây)
      targetPort: 80       # Port container thực sự đang chạy
      protocol: TCP
  selector:
    robusta: web-app       # Chỉ forward đến Pod có label này

Tạo Service và kiểm tra. Lệnh get endpoints cho thấy danh sách Pod IP thực sự đằng sau Service. describe xem selector, port mapping và endpoint details:

bash
kubectl apply -f 1-clusterip.yaml

# Xem Service — cột CLUSTER-IP, PORT(S)
kubectl get service robusta-svc-01

# Xem Endpoints — danh sách Pod IP thực sự phía sau Service
kubectl get endpoints robusta-svc-01

# Chi tiết Service: Selector, TargetPort, Endpoints
kubectl describe service robusta-svc-01

# Test từ bên trong cluster (chạy busybox Pod tạm)
kubectl run -it --rm test --image=busybox -- wget -qO- http://robusta-svc-01:8080

Phần 5: NodePort — Expose ra bên ngoài qua Node

NodePort mở một port cố định trên tất cả node trong cluster (kể cả master). Traffic đến <NodeIP>:<nodePort> sẽ được forward đến Service, sau đó đến Pod. NodePort bao gồm ClusterIP — có nghĩa là vẫn có thể truy cập nội bộ qua ClusterIP.

text – NodePort traffic flow
Bên ngoài cluster (browser, curl, external tool)
      ↓  http://10.236.0.11:30080  (worker-01 IP : nodePort)
      ↓  http://10.236.0.12:30080  (worker-02 IP : nodePort) — cả hai đều hoạt động!
      ↓  http://10.236.0.10:30080  (master IP : nodePort)    — kể cả master

kube-proxy trên node nhận request
      → iptables forward đến ClusterIP:8080
      → ClusterIP DNAT đến Pod IP:80

Port ranges:
  nodePort: 30000-32767   ← range mặc định, có thể cấu hình lại
  Không được dùng port ngoài range này

Manifest NodePort Service. Ba port cần phân biệt: port (ClusterIP port), targetPort (container port), nodePort (port mở trên mỗi node). Nếu không chỉ định nodePort, Kubernetes sẽ tự chọn ngẫu nhiên trong range 30000-32767:

yaml – 2-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: robusta-svc-02
spec:
  type: NodePort
  ports:
    - port: 8080          # ClusterIP port (giao tiếp nội bộ)
      targetPort: 80      # Container port
      nodePort: 30080     # Port mở trên mỗi node (30000-32767)
      protocol: TCP
  selector:
    robusta: web-app

Tạo NodePort Service và test từ bên ngoài cluster. Output của get service sẽ hiện PORT(S): 8080:30080/TCP — nghĩa là ClusterIP port 8080 map với nodePort 30080:

bash
kubectl apply -f 2-nodeport.yaml

# Xem Service — PORT(S) hiện dạng 8080:30080/TCP
kubectl get service robusta-svc-02

# Test từ bên ngoài cluster (thay NODE_IP bằng IP thực của node)
curl http://NODE_IP:30080

# Test từ bất kỳ node nào trong cluster
curl http://10.236.0.11:30080  # worker-01
curl http://10.236.0.12:30080  # worker-02
curl http://10.236.0.10:30080  # master — cũng hoạt động!
⚠️ Hạn chế của NodePort trong production

NodePort phụ thuộc vào IP của node — nếu node down, những ai đang dùng IP đó sẽ mất kết nối. Không có tính năng health check ở lớp external. Trong production trên cloud, dùng LoadBalancer thay thế. Trong on-premise không có cloud LB, có thể dùng MetalLB + LoadBalancer, hoặc Ingress Controller (sẽ học ở buổi sau).

Phần 6: LoadBalancer — External IP từ Cloud

LoadBalancer là loại Service yêu cầu cloud provider tạo một load balancer thật sự (AWS ELB, GCP Load Balancer, Azure LB). Kubernetes gọi cloud provider API và nhận lại External IP. Traffic từ internet → External IP → NodePort → ClusterIP → Pod.

text – LoadBalancer traffic flow
Internet
      ↓  http://203.0.113.10:80  (External IP do cloud cấp)
      ↓
Cloud Load Balancer (AWS ELB / GCP LB / Azure LB)
      ↓  health check node, round-robin giữa các healthy node
      ↓  → 10.236.0.11:30080  (worker-01)
      ↓    10.236.0.12:30080  (worker-02)
      ↓
kube-proxy → ClusterIP → Pod

LoadBalancer tự động tạo NodePort phía sau:
  LoadBalancer (External IP) → NodePort (30000-32767) → ClusterIP → Pod

Trong on-premise không có cloud:
  type: LoadBalancer → Service sẽ pending EXTERNAL-IP mãi
  Giải pháp: dùng MetalLB để giả lập cloud LB trong on-premise

Manifest LoadBalancer Service — chỉ thay đổi type so với NodePort. Trên cloud, Kubernetes sẽ tự động tạo External Load Balancer và điền EXTERNAL-IP. Trên on-premise lab sẽ thấy <pending>:

yaml – loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
  name: robusta-svc-lb
spec:
  type: LoadBalancer        # Cloud provider sẽ tạo LB và cấp External IP
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
  selector:
    robusta: web-app

Tạo Service và quan sát trạng thái EXTERNAL-IP. Trên cloud sẽ thấy IP xuất hiện sau vài phút. Trên on-premise lab sẽ hiện <pending> vì không có cloud provider:

bash
kubectl apply -f loadbalancer.yaml

# Watch cho đến khi EXTERNAL-IP xuất hiện (trên cloud)
kubectl get service robusta-svc-lb --watch

# Trên on-premise: EXTERNAL-IP sẽ <pending> mãi
# Cần cài MetalLB để giải quyết

# Nếu có EXTERNAL-IP, test từ internet:
# curl http://EXTERNAL_IP:80

Phần 7: Headless Service — DNS trực tiếp đến Pod

Headless Service được tạo bằng cách set clusterIP: None. Khi query DNS của Headless Service, CoreDNS trả về danh sách IP của tất cả Pod phía sau, không phải ClusterIP. Không có load balancing qua iptables — client tự chọn Pod.

text – Headless vs ClusterIP DNS
=== ClusterIP Service ===
DNS query: robusta-svc-01.default.svc.cluster.local
DNS response: 10.102.161.178  ← 1 IP duy nhất (ClusterIP)
kube-proxy load balance đến Pod phía sau

=== Headless Service ===
DNS query: robusta-svc-03.default.svc.cluster.local
DNS response:
  192.168.154.243   ← Pod A IP
  192.168.44.215    ← Pod B IP
  192.168.154.244   ← Pod C IP
Client nhận tất cả IP và tự quyết định kết nối đến Pod nào

=== StatefulSet với Headless ===
DNS query: mysql-0.mysql-svc.default.svc.cluster.local → IP của mysql-0
DNS query: mysql-1.mysql-svc.default.svc.cluster.local → IP của mysql-1
Mỗi Pod có DNS riêng và ổn định → critical cho distributed systems

Manifest Headless Service — chỉ cần set clusterIP: None. Không có type field và không có ClusterIP được cấp. Vẫn dùng selector để Kubernetes biết Pod nào thuộc về Service này:

yaml – 3-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: robusta-svc-03
spec:
  clusterIP: None      # Đây là điều duy nhất làm cho Service trở thành Headless
  selector:
    robusta: web-app   # CoreDNS sẽ trả về IP của tất cả Pod có label này

Tạo Headless Service và so sánh DNS response với ClusterIP Service. Dùng busybox để chạy nslookup và thấy sự khác biệt — Headless trả về nhiều A record, ClusterIP trả về 1 record:

bash
kubectl apply -f 3-headless.yaml

# Headless Service không có CLUSTER-IP — hiện 'None'
kubectl get service robusta-svc-03

# So sánh DNS response trong busybox Pod
kubectl run -it --rm dnstest --image=busybox -- /bin/sh

# Trong busybox shell:
# ClusterIP → trả về 1 IP (ClusterIP)
nslookup robusta-svc-01
# Headless → trả về nhiều IP (Pod IPs trực tiếp)
nslookup robusta-svc-03

exit

Phần 8: ExternalName — Alias DNS cho service bên ngoài

ExternalName Service không forward traffic bằng IP/iptables mà tạo một DNS CNAME alias cho một hostname bên ngoài cluster. Khi Pod query DNS tên Service này, CoreDNS trả về CNAME trỏ đến hostname bên ngoài.

text – ExternalName DNS flow
Use case: App trong cluster cần gọi external database
         hoặc API bên ngoài cluster

Cách thông thường (hard-code):
  App → http://robusta.vn/api  ← hard-coded URL

Cách dùng ExternalName:
  App → http://external-01/api  ← dùng tên Service nội bộ
  CoreDNS: external-01.default.svc.cluster.local → CNAME → robusta.vn
  Sau đó DNS client resolve robusta.vn → IP thực của server bên ngoài

Lợi ích:
  → Khi endpoint bên ngoài thay đổi, chỉ cần đổi ExternalName Service
  → App không cần biết hostname thực — giảm coupling
  → Dễ dàng swap sang internal service sau này (chỉ đổi Service type)

Manifest ExternalName Service. Không có selector, không có ports — chỉ có externalName là hostname đích. Kubernetes không forward traffic qua iptables mà hoàn toàn qua DNS CNAME:

yaml – 4-externalName.yaml
apiVersion: v1
kind: Service
metadata:
  name: external-01
spec:
  type: ExternalName
  externalName: robusta.vn   # Hostname bên ngoài — phải là FQDN hợp lệ
  # Không có selector, không có ports, không có clusterIP
  # Hoàn toàn là DNS CNAME alias

Tạo ExternalName Service và kiểm tra DNS. nslookup external-01 trong cluster sẽ trả về CNAME trỏ đến robusta.vn. Sau đó robusta.vn được resolve thành IP thực:

bash
kubectl apply -f 4-externalName.yaml

# ExternalName không có CLUSTER-IP
kubectl get service external-01

# Kiểm tra DNS CNAME từ trong cluster
kubectl run -it --rm dnstest --image=busybox -- nslookup external-01
# Kết quả mẫu:
# Name: external-01.default.svc.cluster.local
# Address: CNAME → robusta.vn → [external IP]

Phần 9: CoreDNS — DNS nội bộ của cluster

CoreDNS là DNS server mặc định của Kubernetes. Tất cả Pod trong cluster đều được cấu hình tự động để dùng CoreDNS làm DNS server. CoreDNS chạy như Deployment trong namespace kube-system.

9.1 Cơ chế DNS resolution trong cluster

text – DNS resolution flow
Pod /etc/resolv.conf (tự động inject bởi kubelet):
  nameserver 10.96.0.10          ← ClusterIP của kube-dns Service
  search default.svc.cluster.local svc.cluster.local cluster.local
  options ndots:5

Pod query: nslookup hello-world
  → search list: hello-world.default.svc.cluster.local  ← thử đầu tiên
                 hello-world.svc.cluster.local
                 hello-world.cluster.local
  → Tìm thấy ở: hello-world.default.svc.cluster.local → 10.102.161.178

Pod query: nslookup hello-world.default.svc.cluster.local  ← FQDN
  → CoreDNS lookup ngay, không cần search list
  → Trả về: 10.102.161.178

Từ namespace khác (myns):
  nslookup hello-world            → NXDOMAIN (tìm trong myns, không thấy)
  nslookup hello-world.default    → NXDOMAIN
  nslookup hello-world.default.svc.cluster.local → 10.102.161.178 ✓

9.2 Kiểm tra CoreDNS

Xem CoreDNS deployment và ConfigMap. ConfigMap coredns chứa cấu hình Corefile — bao gồm upstream DNS forwarder (mặc định là /etc/resolv.conf của node). Có thể custom để dùng DNS server riêng:

bash
# Xem CoreDNS pods và service
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get service -n kube-system kube-dns

# Xem chi tiết CoreDNS deployment
kubectl describe deployment coredns -n kube-system

# Xem cấu hình CoreDNS (Corefile)
kubectl get configmap coredns -n kube-system -o yaml

9.3 Thực hành DNS discovery

Kiểm tra file /etc/resolv.conf trong Pod để thấy nameserver trỏ về CoreDNS ClusterIP. Sau đó chạy busybox để test DNS từ cùng namespace và từ namespace khác:

bash
# Xem DNS config trong Pod bình thường
kubectl exec -it <POD_NAME> -- cat /etc/resolv.conf
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local

# Test DNS từ cùng namespace (default)
kubectl run -it --rm bb --image=busybox -- /bin/sh
/ # nslookup hello-world
# → Tìm được: hello-world.default.svc.cluster.local → ClusterIP
/ # nslookup hello-world.default.svc.cluster.local
# → Trực tiếp, không qua search list
exit

# Tạo namespace mới và test cross-namespace DNS
kubectl create ns myns
kubectl run -n myns -it --rm bb1 --image=busybox -- /bin/sh
/ # nslookup hello-world
# → NXDOMAIN (Service không có trong myns)
/ # nslookup hello-world.default.svc.cluster.local
# → Thành công! FQDN hoạt động cross-namespace
exit

9.4 Custom DNS cho Pod

Có thể override DNS server cho Pod cụ thể bằng dnsConfig trong PodSpec. Ví dụ dùng Quad9 (9.9.9.9) thay vì CoreDNS — sau khi apply, /etc/resolv.conf trong Pod sẽ hiện nameserver 9.9.9.9:

yaml – custom-dns-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: custom-dns-pod
spec:
  dnsPolicy: None            # Tắt DNS mặc định của cluster
  dnsConfig:
    nameservers:
      - 9.9.9.9              # Dùng Quad9 thay vì CoreDNS
    searches:
      - my-custom-domain.local
    options:
      - name: ndots
        value: '5'
  containers:
    - name: app
      image: nginx

Phần 10: kube-proxy — Cơ chế forward traffic Service

kube-proxy chạy trên mỗi node (DaemonSet) và chịu trách nhiệm duy trì network rules để traffic đến Service ClusterIP/NodePort được forward đến đúng Pod. Kube-proxy có 3 chế độ hoạt động.

ModeCơ chếĐặc điểm
iptables (mặc định)DNAT rules trong iptables kernelHiệu năng tốt, không cần userspace. Random load balance. Giới hạn ở ~10k Services
IPVSLinux IPVS (kernel space load balancer)Hiệu năng cao hơn, nhiều thuật toán LB (round-robin, least-conn, sh...). Phù hợp cluster lớn
userspace (cũ)Proxy trong userspaceDeprecated, chậm, không dùng nữa
text – iptables rules kube-proxy tạo ra
# Khi tạo ClusterIP Service 10.102.161.178:8080 với 3 Pod phía sau:

# PREROUTING chain — bắt traffic đến ClusterIP
-A KUBE-SERVICES -d 10.102.161.178/32 -p tcp --dport 8080 \
   -j KUBE-SVC-XXXX

# Load balance — random 33% cho mỗi Pod
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.33 \
   -j KUBE-SEP-POD-A
-A KUBE-SVC-XXXX -m statistic --mode random --probability 0.5 \
   -j KUBE-SEP-POD-B
-A KUBE-SVC-XXXX -j KUBE-SEP-POD-C

# DNAT đến Pod thực
-A KUBE-SEP-POD-A -p tcp -j DNAT --to-destination 192.168.154.243:80
-A KUBE-SEP-POD-B -p tcp -j DNAT --to-destination 192.168.44.215:80
-A KUBE-SEP-POD-C -p tcp -j DNAT --to-destination 192.168.154.244:80

# Khi Pod bị xóa → kube-proxy ngay lập tức xóa rule tương ứng

Quan sát iptables rules trực tiếp trên node. Cần SSH vào node và dùng quyền root để xem các chain KUBE-SERVICES, KUBE-SVC-*, KUBE-SEP-* mà kube-proxy tạo:

bash
# SSH vào worker node
ssh worker-01

# Xem các chain KUBE-* trong iptables
sudo iptables -t nat -L KUBE-SERVICES --line-numbers
sudo iptables -t nat -L | grep -A5 KUBE-SVC

# Xem tất cả rule liên quan đến Service IP
sudo iptables -t nat -L -n | grep 10.102.161.178

Phần 11: NetworkPolicy — Kiểm soát traffic giữa Pod

Mặc định, mọi Pod trong Kubernetes có thể giao tiếp với mọi Pod khác (không có tường lửa). NetworkPolicy cho phép định nghĩa rules kiểm soát traffic ingress (vào Pod) và egress (ra khỏi Pod).

⚠️ Yêu cầu CNI hỗ trợ NetworkPolicy

NetworkPolicy chỉ hoạt động nếu CNI plugin hỗ trợ. Calico hỗ trợ đầy đủ. Flannel mặc định không hỗ trợ. NetworkPolicy là khai báo rules — CNI plugin thực thi bằng iptables/eBPF.

11.1 Default behavior và Deny-all

text – Mô hình NetworkPolicy
Mặc định (không có NetworkPolicy):
  Pod A → Pod B  ✓ (bất kỳ Pod nào cũng giao tiếp được)
  Pod A → Pod C  ✓
  Internet → Pod ✓ (qua NodePort)

Sau khi áp dụng NetworkPolicy cho Pod B (deny-all):
  Pod A → Pod B  ✗ (bị block)
  Pod C → Pod B  ✗ (bị block)
  Chỉ những source được whitelist mới vào được

Nguyên tắc:
  → Khi có ít nhất 1 NetworkPolicy select Pod
    Tất cả traffic không được allow sẽ bị DENY
  → Nhiều NetworkPolicy cùng select 1 Pod → rules được union (OR)

11.2 Ví dụ NetworkPolicy thực tế

Manifest NetworkPolicy cho phép chỉ Pod có label role=frontend mới được kết nối vào database Pod (port 3306). Tất cả traffic khác đến database bị deny. Đây là pattern zero-trust trong microservices:

yaml – networkpolicy-db.yaml
# Deny tất cả ingress traffic đến database Pod
# Chỉ cho phép traffic từ frontend Pod
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-db
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: database         # Policy này áp dụng cho Pod có label role=database
  policyTypes:
    - Ingress                # Chỉ kiểm soát traffic vào
  ingress:
    - from:
        - podSelector:
            matchLabels:
              role: frontend # Chỉ cho phép từ Pod có label role=frontend
      ports:
        - protocol: TCP
          port: 3306         # Chỉ cho phép port MySQL
---
# Deny tất cả ingress traffic đến namespace production
# (áp dụng cho mọi Pod trong namespace)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}            # {} = select tất cả Pod trong namespace
  policyTypes:
    - Ingress
    - Egress                 # Block cả traffic ra ngoài

🧪 Câu hỏi kiểm tra cuối buổi

1. Pod A (IP: 192.168.154.243) trên worker-01 muốn giao tiếp với Pod B (IP: 192.168.44.215) trên worker-02. Thành phần nào xử lý việc định tuyến cross-node?

2. ClusterIP Service có địa chỉ IP 10.102.161.178. Loại địa chỉ này là gì và ai quản lý nó?

3. Điều gì xảy ra khi Pod query DNS "hello-world" từ namespace "myns" trong khi Service "hello-world" tồn tại trong namespace "default"?

4. Sự khác biệt chính giữa Headless Service và ClusterIP Service khi query DNS là gì?

5. NodePort Service mở port 30080 trên cluster có 3 node. Có thể truy cập Service qua URL nào sau đây?

6. ExternalName Service khác các loại Service khác ở điểm nào?

7. Tại sao không nên dùng Pod IP trực tiếp để giao tiếp giữa các microservice?

8. NetworkPolicy với podSelector: {} (selector rỗng) và policyTypes: [Ingress] có tác dụng gì?

✓ Đã sao chép!