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
Node
Vai trò
Mô tả
master-01
Control Plane
Chạy các thành phần điều khiển chính
worker-01
Worker Node
Chạy workload ứng dụng
worker-02
Worker Node
Chạ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 Node và Worker 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 Plane và Worker 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:
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):
Controller
Chức năng
Node Controller
Theo 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 Controller
Quản lý rolling update, rollback cho Deployment
Job Controller
Đảm bảo Job chạy đến khi hoàn thành
EndpointSlice Controller
Duy 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-01 và worker-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).
Layer
Thành phần
Vai trò
High-level runtime
containerd
Quản lý vòng đời container, pull image, lưu image
Low-level runtime
runc
Tạo container process thực sự theo chuẩn OCI
Interface
CRI
Giao 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
Calico
Phổ biến, hỗ trợ NetworkPolicy, dùng BGP routing. Lab này sử dụng Calico.
Cilium
Hiệ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 Net
Dễ 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ểm
Giải thích
Mỗi Pod có 1 IP duy nhất
IP đượ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 network
Giao tiếp nhau qua localhost, không cần IP riêng
Pod chạy trên đúng 1 node
Tất cả container trong Pod luôn chạy trên cùng một node
Pod không tự heal khi bị xóa
Cầ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.
Namespace
Mục đích
Nội dung thường có
default
Namespace mặc định cho workload
Pod, Deployment người dùng tạo nếu không chỉ định namespace
ConfigMap cluster-info, thông tin có thể đọc công khai
kube-node-lease
Heartbeat của node
Lease 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?
✅ Đáp án B: kube-apiserver expose Kubernetes API và là điểm giao tiếp trung tâm giữa người dùng và mọi thành phần nội bộ. Không thành phần nào giao tiếp trực tiếp với etcd ngoại trừ API Server.
2. Thành phần nào lưu toàn bộ trạng thái của Kubernetes cluster?
✅ Đáp án C: etcd là cơ sở dữ liệu key-value lưu toàn bộ state của cluster — node, Pod, Deployment, Service... Nếu etcd mất dữ liệu không có backup, cluster mất toàn bộ state.
3. Khi tạo Pod, thành phần nào quyết định Pod sẽ chạy trên node nào?
✅ Đáp án A: kube-scheduler quan sát Pod chưa được gán node, thực hiện Filtering và Scoring để chọn node phù hợp nhất. Sau đó kubelet trên node được chọn mới thực sự chạy container.
4. Các container trong cùng một Pod giao tiếp với nhau như thế nào?
✅ Đáp án B: Các container trong cùng Pod dùng chung network namespace (nhờ Pause container). Chúng giao tiếp với nhau qua localhost và phân biệt bởi port number.
5. Static Pod trong mô hình kubeadm được quản lý bởi thành phần nào?
✅ Đáp án C: kubelet tự scan thư mục /etc/kubernetes/manifests/ và quản lý trực tiếp các Static Pod. Đây là lý do control plane có thể khởi động ngay cả trước khi API Server sẵn sàng.
6. Điều gì xảy ra nếu chưa cài CNI plugin sau khi chạy kubeadm init?
✅ Đáp án B: Không có CNI → Pod không được cấp IP → CoreDNS và các Pod cần network sẽ ở trạng thái Pending hoặc ContainerCreating. API Server vẫn hoạt động nhưng Pod networking trong cluster không hoàn chỉnh.
7. kubelet chạy dưới dạng gì trên mỗi node?
✅ Đáp án C: kubelet là thành phần duy nhất trong Kubernetes không chạy như một Pod. Nó chạy như systemd service trực tiếp trên OS của node. Đây là lý do kubelet có thể quản lý Static Pod và bootstrap cluster.
8. Thành phần nào thực sự tạo và chạy container bên dưới kubelet?
✅ Đáp án C: kubelet giao tiếp với containerd qua CRI interface. containerd kéo image, tạo snapshot, cấu hình mạng rồi gọi runc để tạo container process thực sự theo chuẩn OCI.
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
(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:
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
Workload
Dùng khi nào
Ví dụ thực tế
Deployment
App stateless, cần scale, rolling update
React frontend, REST API, Nginx
StatefulSet
App cần stable network ID, persistent storage
MySQL, PostgreSQL, Kafka, Zookeeper
DaemonSet
Cần 1 instance trên mỗi node
Fluentd, Prometheus node-exporter, Calico
Job
Tác vụ chạy 1 lần đến hoàn thành
DB migration, data import, batch processing
CronJob
Tác vụ lặp lại theo lịch
Backup 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: v1 và kind: Pod xác định loại object. spec.containers liệt kê danh sách container — ở đây chỉ có 1 container:
Apply manifest để tạo Pod. Quan sát events terminal: sẽ thấy tuần tự — Scheduled (scheduler chọn node) → Pulling (pull image) → Created → Started:
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:
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ĩa
Nguyên nhân thường gặp
Pending
Pod đã được tạo, chưa được schedule hoặc đang pull image
Không đủ tài nguyên, image đang pull, chưa có CNI
Running
Pod đang chạy, ít nhất 1 container đang hoạt động
Trạng thái bình thường
Succeeded
Tấ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
CrashLoopBackOff
Container liên tục crash và restart với backoff delay
App lỗi khi khởi động, thiếu config, lỗi ENV
ImagePullBackOff
Không thể pull container image
Image 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.
Policy
Hành vi
Dùng khi nào
Always
Luôn restart khi container kết thúc (mặc định)
Web server, API — phải luôn chạy
OnFailure
Chỉ 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
Never
Không bao giờ restart
One-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:
Tạo 2 Pod và thực nghiệm kill main process để quan sát sự khác biệt giữa Never và OnFailure. Chú ý cột RESTARTS và STATUS 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.
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
Probe
Mục đích
Hành động khi fail
livenessProbe
Container có còn sống không? Có bị deadlock không?
Restart container
readinessProbe
Container đã sẵn sàng nhận traffic chưa?
Xóa Pod khỏi Service endpoint — không restart
startupProbe
App khởi động chậm, cần thêm thời gian trước khi kiểm tra
Restart 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
httpGet
HTTP GET đến endpoint — thành công nếu status 200-399
Web app có health check endpoint
tcpSocket
Kiểm tra TCP connection đến port — thành công nếu kết nối được
Database, message broker
exec
Chạy lệnh trong container — thành công nếu exit code 0
Custom check, kiểm tra file tồn tại
grpc
gRPC health check protocol
App 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 case
Ví dụ
Log collection agent
Fluentd, Logstash, Filebeat — thu thập log từ mỗi node
Metrics/monitoring agent
Prometheus node-exporter — đọc metrics của OS từng node
Network plugin
Calico-node, Cilium — CNI plugin chạy trên mỗi node
Storage plugin
Ceph, GlusterFS node daemon
Security agent
Falco, 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ểm
Deployment
StatefulSet
Tên Pod
Random suffix: app-7d9b4c-xk2lp
Thứ tự: mysql-0, mysql-1, mysql-2
Khởi động
Tất cả Pod start song song
Tuần tự: mysql-0 → mysql-1 → mysql-2
Xóa
Ngẫu nhiên
Ngược thứ tự: mysql-2 → mysql-1 → mysql-0
Storage
Dùng chung hoặc không cần
Mỗi Pod có PVC riêng, tồn tại qua restart
Network
ClusterIP Service
Headless Service — DNS riêng cho từng Pod
Dùng khi nào
Stateless: web, API
Stateful: 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
Mode
completions
parallelism
Hành vi
Single Job
1 (mặc định)
1 (mặc định)
Chạy 1 Pod duy nhất đến khi xong
Parallel fixed
N
P
Chạy tổng N Pod, tối đa P Pod cùng lúc
Work queue
không đặt
P
Pod 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. successfulJobsHistoryLimit và failedJobsHistoryLimit 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?
✅ Đáp án B: DaemonSet đảm bảo đúng 1 Pod trên mỗi node. Khi thêm node mới, Pod tự động được tạo. Deployment không đảm bảo mỗi node đều có Pod vì scheduler có thể đặt nhiều Pod trên cùng 1 node.
2. Sự khác biệt chính giữa livenessProbe và readinessProbe là gì?
✅ Đáp án C: livenessProbe xác định container có còn sống không — fail thì restart. readinessProbe xác định container có sẵn sàng nhận traffic không — fail thì xóa Pod ra khỏi Service endpoints nhưng không restart container.
3. Tại sao Deployment giữ lại ReplicaSet cũ (với replicas=0) sau khi rolling update?
✅ Đáp án B: ReplicaSet cũ vẫn tồn tại (scale=0) cho phép kubectl rollout undo hoạt động ngay lập tức — chỉ cần scale up RS cũ và scale down RS mới, không cần pull image lại.
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?
✅ Đáp án C: StatefulSet cung cấp: tên Pod ổn định (mysql-0, mysql-1, mysql-2), PVC riêng cho từng Pod qua volumeClaimTemplates, DNS riêng qua Headless Service, và thứ tự khởi động đảm bảo primary khởi động trước replica.
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?
✅ Đáp án B: OnFailure chỉ restart khi container exit với code ≠ 0. Khi thành công (exit 0), container không restart và Job được đánh dấu Complete. Always sẽ restart kể cả khi thành công — không phù hợp cho Job.
6. Trong CronJob, concurrencyPolicy: Forbid có nghĩa là gì?
✅ Đáp án C: Forbid ngăn chạy đồng thời — nếu đến lịch mà Job cũ chưa xong thì lịch mới bị skip. Allow (mặc định) cho phép chạy song song. Replace hủy Job cũ để chạy Job mới.
7. Init Container khác Sidecar Container ở điểm nào?
✅ Đáp án B: Init Container chạy tuần tự trước container chính, phải hoàn thành thành công rồi mới kết thúc (exit 0). Dùng cho setup, chờ dependency. Sidecar Container chạy song song với container chính suốt vòng đời Pod, dùng cho logging, proxy.
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?
✅ Đáp án C:kubectl create job <name> --from=cronjob/<cronjob-name> tạo ngay 1 Job từ jobTemplate của CronJob chỉ định, không cần chờ đến lịch. Rất hữu ích để test CronJob.
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 → Service
iptables/IPVS trên node chặn và DNAT đến Pod phía sau
External → Pod
Phải qua Service (NodePort/LoadBalancer) hoặc Ingress
Không có NAT
IP 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:
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 Service
Truy cập từ đâu
Dùng khi nào
ClusterIP
Chỉ bên trong cluster
Giao tiếp nội bộ giữa các service
NodePort
Bên ngoài qua IP:Port của node
Dev/test, không có Load Balancer
LoadBalancer
Bên ngoài qua External IP
Production trên cloud (AWS ELB, GCP LB...)
ExternalName
DNS alias đến external host
Alias cho service bên ngoài cluster
Headless
DNS trả về Pod IP trực tiếp
StatefulSet, 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.
Mode
Cơ chế
Đặc điểm
iptables (mặc định)
DNAT rules trong iptables kernel
Hiệu năng tốt, không cần userspace. Random load balance. Giới hạn ở ~10k Services
IPVS
Linux 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 userspace
Deprecated, 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?
✅ Đáp án C: CNI Plugin (Calico) chịu trách nhiệm định tuyến Pod-to-Pod traffic, kể cả cross-node. Calico tạo VXLAN tunnel hoặc dùng BGP routing để đưa gói tin từ node này sang node khác mà không cần NAT. kube-proxy chỉ xử lý traffic đến Service VIP.
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ó?
✅ Đáp án B: ClusterIP là Virtual IP — không gắn với bất kỳ network interface nào. kube-proxy tạo iptables DNAT rules để bắt traffic đến VIP và chuyển hướng đến Pod IP thực. Đây là lý do khi ping ClusterIP từ ngoài cluster sẽ không có route.
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"?
✅ Đáp án B: DNS search list của Pod trong myns ưu tiên tìm "hello-world.myns.svc.cluster.local" trước. Service không tồn tại trong myns → NXDOMAIN. Để giao tiếp cross-namespace phải dùng FQDN đầy đủ: "hello-world.default.svc.cluster.local".
4. Sự khác biệt chính giữa Headless Service và ClusterIP Service khi query DNS là gì?
✅ Đáp án B: ClusterIP Service → DNS trả về 1 IP ảo (VIP), kube-proxy load balance. Headless Service (clusterIP: None) → DNS trả về A record cho từng Pod IP. Client tự chọn Pod. Cần thiết cho StatefulSet vì mỗi Pod cần có DNS ổn định riêng (mysql-0.svc, mysql-1.svc...).
5. NodePort Service mở port 30080 trên cluster có 3 node. Có thể truy cập Service qua URL nào sau đây?
✅ Đáp án C: NodePort mở port trên TẤT CẢ node trong cluster — kể cả master, kể cả node không chạy Pod. kube-proxy trên mỗi node nhận traffic và forward đến Pod dù Pod đang chạy trên node khác. Đây là lý do traffic luôn đến được Pod dù client kết nối vào node nào.
6. ExternalName Service khác các loại Service khác ở điểm nào?
✅ Đáp án B: ExternalName không có selector, không có Endpoints, không có ClusterIP, không có iptables rules. Hoàn toàn là DNS level: CoreDNS trả về CNAME record trỏ đến hostname bên ngoài. Client tự resolve CNAME đó thành IP và kết nối trực tiếp.
7. Tại sao không nên dùng Pod IP trực tiếp để giao tiếp giữa các microservice?
✅ Đáp án C: Pod là ephemeral — bị xóa và tạo lại khi crash, rolling update, reschedule. Mỗi lần recreate, Pod nhận IP mới hoàn toàn. Service cung cấp địa chỉ ổn định (ClusterIP + DNS name) đứng trước nhóm Pod, tự động cập nhật khi Pod thay đổi.
8. NetworkPolicy với podSelector: {} (selector rỗng) và policyTypes: [Ingress] có tác dụng gì?
✅ Đáp án C: podSelector rỗng ({}) select TẤT CẢ Pod trong namespace. Khi có NetworkPolicy select Pod nhưng không có ingress rule nào được define → toàn bộ ingress traffic bị deny. Đây là pattern "default-deny" thường dùng để khởi đầu zero-trust model rồi thêm whitelist rules sau.
🚧
Bài học đang được chuẩn bị
Nội dung bài học này sẽ sớm được ra mắt. Hãy theo dõi để không bỏ lỡ!