DevOps: Docker + Kubernetes Core Concepts (Intermediate++)
Bài này dành cho mức intermediate++: bạn đã quen với Docker và Kubernetes cơ bản, nhưng còn nhiều câu hỏi dạng "tại sao container bị OOMKill?", "iptables liên quan gì đến Service?", "tại sao pod Pending dù node còn memory?".
Mục tiêu: hiểu đủ sâu để vận hành hệ thống production, debug sự cố thực tế, và giải thích các quyết định thiết kế khi phỏng vấn senior level.
Mental model: Docker là công nghệ đóng gói — biến app và dependencies thành một đơn vị di động, chạy nhất quán mọi môi trường. Kubernetes là hệ thống điều phối — quản lý vòng đời các container đó ở quy mô lớn, tự động hoá recovery, scaling, và rollout.
1. Docker internals sâu hơn
1.1 Container không phải mini-VM — kernel namespaces là cốt lõi
Container không virtualise hardware. Chúng dùng tính năng của Linux kernel để tạo môi trường ảo hoá nhẹ:
flowchart TB
subgraph VM["VM"]
V1["Hardware"] --> V2["Hypervisor"] --> V3["Guest OS (kernel riêng)"] --> V4["App"]
V4 -.-> V5["Overhead: ~100–500ms boot, ~128MB RAM/VM"]
end
subgraph CT["Container"]
C1["Hardware"] --> C2["Host OS kernel"] --> C3["containerd/runc"] --> C4["App (process biệt lập)"]
C4 -.-> C5["Overhead: ~10–50ms start, ~1–5MB"]
endVM:
Hardware ──► Hypervisor ──► Guest OS (kernel riêng) ──► App
Tốn: ~100–500ms boot, ~128MB RAM/VM overhead
Container:
Hardware ──► Host OS kernel ──► containerd/runc ──► App (process biệt lập)
Tốn: ~10–50ms start, ~1–5MB overhead
Linux namespaces — 7 loại cách ly môi trường:
| Namespace | Cách ly cái gì | Ý nghĩa thực tế |
|---|---|---|
pid |
Process ID tree | Container chỉ thấy process của mình, PID 1 là process chính |
net |
Network stack | Interface, routing table, firewall riêng |
mnt |
Filesystem mounts | Cây thư mục riêng, không thấy /proc host |
ipc |
System V IPC, POSIX MQ | Shared memory riêng biệt |
uts |
Hostname, domain name | hostname trong container độc lập |
user |
UID/GID mapping | Root trong container ≠ root trên host |
cgroup |
cgroup tree | Container thấy giới hạn resource của mình |
cgroups (control groups) — giới hạn và đo lường tài nguyên:
# Xem cgroup của container đang chạy
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.limit_in_bytes
# Nếu không set limit → unlimited → OOM killer sẽ ngẫu nhiên kill process trên host
Hai phiên bản: cgroups v1 (systemd/docker cũ) và cgroups v2 (unified hierarchy, mặc định từ kernel 5.x, Docker 20.10+). cgroups v2 quan trọng vì:
- Memory accounting chính xác hơn (tính cả page cache).
- PSI (Pressure Stall Information) — đo áp lực memory/CPU/IO.
- Hỗ trợ tốt hơn cho Kubernetes QoS classes.
1.2 Union filesystem và overlay2
Image Docker là stack các layer read-only. Khi container chạy, engine thêm một writable layer (container layer) ở trên cùng. Đây là Union Filesystem — nhiều layer hợp thành một filesystem duy nhất.
Container writable layer ← ghi ở đây, bị xoá khi container chết
─────────────────────────
Layer 3: COPY . . (code app)
Layer 2: RUN npm ci (node_modules)
Layer 1: COPY package.json .
Layer 0: node:20-alpine (base image)
─────────────────────────
Host filesystem
Overlay2 (driver mặc định trên Linux) dùng cơ chế Copy-on-Write (CoW):
- Đọc file từ layer thấp hơn → không copy.
- Ghi file từ layer thấp hơn lần đầu → copy lên writable layer rồi ghi.
- Hệ quả: write-heavy workload (database) không nên chạy trên writable layer — dùng volume thay thế.
Tối ưu Dockerfile để tận dụng layer cache:
# ❌ Tệ: thay đổi code → rebuild toàn bộ (npm ci lại từ đầu)
FROM node:20-alpine
WORKDIR /app
COPY . . # copy tất cả — cache miss mọi lúc
RUN npm ci
RUN npm run build
# ✅ Tốt: layer ổn định ở dưới, layer hay thay đổi ở trên
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./ # Layer này cache đến khi package.json đổi
RUN npm ci # Chỉ chạy lại khi dependencies đổi
COPY . . # Code thay đổi thường xuyên → đặt sau
RUN npm run build
# Multi-stage: final image không chứa build tools
FROM nginx:1.27-alpine
COPY /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
BuildKit (bật từ Docker 23+, mặc định) mở thêm:
--mount=type=cache: cache npm/go module giữa các build, không copy vào layer.- Build song song các stage.
- Không build stage không cần thiết (lazy evaluation).
# BuildKit cache mount — npm cache không đi vào image
RUN \
npm ci --prefer-offline
1.3 OCI, containerd, và runc
Chuỗi runtime thực tế trên một K8s node:
flowchart LR K["kubelet"] --> CD["containerd (CRI endpoint)"] CD --> SH["containerd-shim-runc-v2"] SH --> R["runc (clone, pivot_root, ...)"]
kubelet
└── containerd (CRI endpoint: /run/containerd/containerd.sock)
└── containerd-shim-runc-v2
└── runc (tạo container thực sự bằng syscall clone, pivot_root, ...)
- OCI (Open Container Initiative): chuẩn image format và runtime spec — đảm bảo image build bằng Docker chạy được trên Podman, containerd, hay bất kỳ runtime nào.
- containerd: daemon quản lý lifecycle container, image pull, snapshot.
- runc: công cụ CLI tạo container theo OCI runtime spec — gọi
clone(),pivot_root(),seccomp,capabilitiescủa kernel.
Vì sao quan trọng: khi debug ở node level, bạn dùng crictl thay vì docker:
# Liệt kê container đang chạy (trên node K8s)
crictl ps
# Xem logs
crictl logs <container_id>
# Inspect container
crictl inspect <container_id>
2. Kubernetes architecture — sâu hơn một lớp
2.1 Control plane: cách các component phối hợp
┌─────────────────────────────────────────────────────────────────┐
│ CONTROL PLANE │
│ │
│ kubectl / API ──► kube-apiserver ──► etcd (source of truth) │
│ │ │
│ ┌─────┴──────┐ │
│ │ │ │
│ scheduler controller-manager │
│ (chọn node) (reconciliation loops) │
└─────────────────────────────────────────────────────────────────┘
│ watch & notify │ watch & notify
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Worker Node 1 │ │ Worker Node 2 │
│ kubelet │ │ kubelet │
│ kube-proxy │ │ kube-proxy │
│ containerd │ │ containerd │
└────────────────┘ └────────────────┘
etcd — distributed key-value store dùng Raft consensus:
- Toàn bộ trạng thái cluster lưu ở đây: pod specs, secrets, configmaps, service endpoints.
- etcd yếu → toàn bộ control plane tê liệt (worker nodes vẫn chạy pod cũ, nhưng không có gì mới được schedule).
- Khuyến nghị production: 3 hoặc 5 etcd member (chịu được 1 hoặc 2 node failure).
kube-apiserver — mọi thứ đi qua đây:
- Authenticate → Authorize (RBAC) → Admission (Mutating → Validating) → Persist vào etcd.
- Admission controllers: OPA Gatekeeper, Kyverno, ImagePolicyWebhook... chạy ở đây.
kube-scheduler — quy trình chọn node:
- Filtering (predicates): loại bỏ node không đủ điều kiện (insufficient CPU, taint không match, affinity rules).
- Scoring (priorities): chấm điểm các node còn lại (spread score, resource balance, topology spread).
- Chọn node điểm cao nhất, ghi vào PodSpec.
controller-manager — chứa hàng chục controller chạy song song:
ReplicaSet controller: đếm pod đang chạy, tạo/xoá để matchreplicas.Deployment controller: quản lý ReplicaSet, thực hiện rolling update.EndpointSlice controller: cập nhật danh sách IP:port của pods vào Service.Job controller,CronJob controller,HPA controller...
2.2 Reconciliation loop — trái tim của K8s
Pattern này là lý do K8s self-healing:
while true:
desired_state = etcd.read(resource)
current_state = observe_real_world()
if current_state != desired_state:
take_action_to_converge()
sleep(a_bit)
Khi bạn kubectl apply -f deployment.yaml:
- apiserver lưu desired state vào etcd.
- Deployment controller detect thay đổi → tạo ReplicaSet mới.
- ReplicaSet controller detect pods thiếu → tạo Pod objects.
- Scheduler detect pod chưa có
nodeName→ gắn node. - kubelet trên node detect pod được gán cho mình → pull image, chạy container.
2.3 kubelet — agent trên worker node
kubelet làm nhiệm vụ:
- Watch apiserver, lấy pod spec được gán cho node mình.
- Giao tiếp với containerd qua CRI (Container Runtime Interface).
- Chạy probe (liveness, readiness, startup) theo schedule.
- Báo cáo node status, pod status lên apiserver.
- Evict pod khi node bị áp lực memory (dựa trên
requests, không phảilimits).
3. Resource management — chi tiết production
3.1 Requests, Limits và QoS Classes
resources:
requests:
cpu: "250m" # 0.25 core — scheduler dùng để chọn node
memory: "256Mi" # kubelet dùng để tính eviction priority
limits:
cpu: "1000m" # throttled nếu vượt (CPU compressible)
memory: "1Gi" # OOMKilled nếu vượt (memory incompressible)
Kubernetes chia pod thành 3 QoS class dựa vào cách set requests/limits:
| QoS Class | Điều kiện | Eviction priority |
|---|---|---|
| Guaranteed | requests == limits (cả CPU và memory) | Bị evict sau cùng |
| Burstable | requests < limits hoặc chỉ set một trong hai | Trung gian |
| BestEffort | Không set requests lẫn limits | Bị evict đầu tiên |
Bài học thực chiến: Pod không set resources = BestEffort = bị kill đầu tiên khi node áp lực. Đây là lý do hay gặp pod bị terminate bất ngờ trong cluster dùng chung.
CPU throttling — khác với OOMKill:
- Khi container dùng CPU vượt
limits.cpu, CFS (Completely Fair Scheduler) throttle process. - Container không bị kill, nhưng mọi syscall bị làm chậm → latency tăng đột biến theo chu kỳ.
- Dấu hiệu:
container_cpu_cfs_throttled_seconds_totaltăng trong Prometheus. - Fix: tăng
limits.cpuhoặc giảmrequests.cpuđể phân bổ công bằng hơn.
Memory và OOMKill:
- Khi container vượt
limits.memory, kernel OOM killer kill process ngay lập tức. - K8s ghi lại lý do trong pod events:
OOMKilled. - Nguyên nhân phổ biến: memory leak, JVM heap không được giới hạn, load spike đột ngột.
3.2 Scheduler: node affinity và topology spread
Node Affinity — ràng buộc cứng và mềm:
affinity:
nodeAffinity:
# Cứng: phải chạy trên node có SSD
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk-type
operator: In
values: ["ssd"]
# Mềm: ưu tiên node ở zone us-east-1a
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a"]
Topology Spread Constraints — phân tán pod đều các zone (K8s 1.19+):
topologySpreadConstraints:
- maxSkew: 1 # Zone A và B chênh nhau tối đa 1 pod
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule # Không schedule nếu vi phạm
labelSelector:
matchLabels:
app: payment-api
Đây là cách đúng để đảm bảo HA đa-zone — tốt hơn podAntiAffinity vì hoạt động theo số lượng, không chỉ theo "không trùng node".
Taints & Tolerations — dùng để tách workload:
# Taint node: chỉ pod có toleration mới vào được
kubectl taint nodes node1 dedicated=gpu:NoSchedule
# Toleration trong pod spec
tolerations:
- key: "dedicated"
operator: "Equal"
value: "gpu"
effect: "NoSchedule"
Dùng phổ biến: node GPU cho ML workload, node spot/preemptible, node cho system daemonsets.
4. Networking — hiểu tường tận
4.1 Mô hình mạng K8s và CNI
K8s yêu cầu:
- Mọi Pod có thể nói chuyện với mọi Pod khác (không NAT) — kể cả khác node.
- Node có thể nói chuyện với Pod.
- Pod thấy IP của chính mình đúng như IP mà node khác thấy.
CNI plugin implement model này. Cách hoạt động:
Pod A (node 1, 10.244.1.5) ──► veth pair ──► bridge cbr0 ──► node routing ──► Pod B (node 2, 10.244.2.8)
- Calico: dùng BGP để quảng bá route giữa các node. Performance tốt, hỗ trợ NetworkPolicy đầy đủ.
- Cilium: dùng eBPF thay vì iptables — performance cao hơn, observability tốt hơn (L7 metrics), native mTLS.
- Flannel: đơn giản, chỉ overlay, không hỗ trợ NetworkPolicy.
4.2 Service và kube-proxy — traffic routing thực sự
Khi bạn tạo Service, K8s không tạo một process riêng để proxy traffic. Thay vào đó, kube-proxy cài iptables rules (hoặc ipvs/eBPF) trên mọi node:
Client Pod ──► iptables PREROUTING ──► DNAT đến một pod IP ngẫu nhiên (load balance)
Ví dụ chain iptables cho Service:
KUBE-SERVICES → KUBE-SVC-XXXXX (selector: app=payment)
→ KUBE-SEP-AAA (50%) → 10.244.1.5:8080
→ KUBE-SEP-BBB (50%) → 10.244.2.8:8080
Hệ quả quan trọng:
- Service IP (ClusterIP) là virtual IP — không route được mà không qua iptables.
- Khi pod mới join hoặc rời, EndpointSlice controller cập nhật, kube-proxy sync lại rules.
- Delay giữa pod ready và traffic được route:
readinessProbe+terminationGracePeriodSecondsphải được set đúng để tránh connection drop khi deploy.
DNS trong cluster — CoreDNS:
# Service "payment-api" trong namespace "payments":
payment-api.payments.svc.cluster.local → ClusterIP của service
# Từ cùng namespace: có thể dùng short form
payment-api → resolve đến payment-api.payments.svc.cluster.local
kube-proxy modes (theo thứ tự hiệu năng tăng dần):
iptables(mặc định): O(n) lookup, chậm khi cluster > vài nghìn services.ipvs: O(1) hash-based, hỗ trợ nhiều load balancing algorithm.- eBPF (Cilium): bypass iptables hoàn toàn, highest performance.
4.3 NetworkPolicy — zero-trust networking
Mặc định K8s cho phép all-to-all. NetworkPolicy là cách enforce least-privilege:
# Chỉ cho phép payment-api gọi vào postgres, port 5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-ingress
namespace: payments
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: payment-api
ports:
- protocol: TCP
port: 5432
Nguyên tắc áp dụng:
- Default deny all: tạo NetworkPolicy rỗng cho namespace → block tất cả, rồi mở từng rule.
- Ingress và Egress là độc lập — phải khai báo cả hai chiều nếu cần.
- NetworkPolicy chỉ hiệu lực khi CNI plugin hỗ trợ (Calico, Cilium có; Flannel không có).
5. Health checks — semantic đúng, không phải đặt cho có
5.1 Ba loại probe và khi nào dùng gì
spec:
containers:
- name: payment-api
# startupProbe: chỉ chạy khi container start
# Tránh liveness kill container đang khởi động
startupProbe:
httpGet:
path: /healthz/startup
port: 8080
failureThreshold: 30 # Cho phép fail 30 lần × 10s = 5 phút để start
periodSeconds: 10
# livenessProbe: container có đang "sống" không?
# Fail → kubelet RESTART container (không xoá pod)
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3 # Fail 3 lần liên tiếp → restart
# readinessProbe: container có sẵn sàng nhận traffic không?
# Fail → pod bị rút khỏi Service endpoints (không restart)
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
periodSeconds: 5
failureThreshold: 2 # Fail nhanh hơn liveness
Semantics của từng probe — quan trọng để implement đúng:
| Probe | Câu hỏi | Nên check gì | KHÔNG nên check gì |
|---|---|---|---|
liveness |
Process còn "sống" không? | Deadlock, goroutine leak | DB connection (DB down → không nên restart container) |
readiness |
Sẵn sàng nhận request? | DB connection, cache warm | Dependency ngoài tầm kiểm soát của pod |
startup |
Đã khởi động xong chưa? | Chỉ cần return 200 khi init xong | — |
Anti-pattern phổ biến: liveness và readiness cùng endpoint /health check DB connection → DB flap → toàn bộ pod bị restart liên tục → thundering herd.
5.2 Deployment strategy nâng cao
Rolling Update (mặc định):
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0 # Không giảm capacity (zero-downtime)
maxSurge: 1 # Tạo thêm 1 pod mới trước khi kill pod cũ
Blue/Green qua cách đổi selector:
# Đang chạy version blue
kubectl patch service payment-api -p '{"spec":{"selector":{"version":"blue"}}}'
# Deploy green
kubectl apply -f deployment-green.yaml
# Kiểm tra green ổn
kubectl patch service payment-api -p '{"spec":{"selector":{"version":"green"}}}'
# Rollback tức thì nếu lỗi
kubectl patch service payment-api -p '{"spec":{"selector":{"version":"blue"}}}'
Canary bằng cách điều chỉnh replica ratio:
# deployment-stable.yaml: replicas: 9
# deployment-canary.yaml: replicas: 1
# → 10% traffic đến canary (vì Service load balance đều theo số pod)
PodDisruptionBudget — đảm bảo không down quá nhiều pod khi drain node:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-api-pdb
spec:
minAvailable: 2 # Phải còn ít nhất 2 pod "Available" mọi lúc
selector:
matchLabels:
app: payment-api
6. Stateful workloads — StatefulSet, PV/PVC, StorageClass
6.1 StatefulSet — khi identity quan trọng
StatefulSet đảm bảo:
- Pod name ổn định:
postgres-0,postgres-1,postgres-2. - Thứ tự scale up/down: deploy từ 0 → n, scale down từ n → 0.
- Mỗi pod có PVC riêng (qua
volumeClaimTemplates), PVC không bị xoá khi pod restart.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres-headless # Headless service cho DNS per-pod
replicas: 3
selector:
matchLabels:
app: postgres
template:
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # Mỗi pod tự động tạo PVC riêng
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-ssd
resources:
requests:
storage: 100Gi
Headless Service (clusterIP: None) cho phép DNS resolution per-pod:
postgres-0.postgres-headless.default.svc.cluster.local → IP của postgres-0
postgres-1.postgres-headless.default.svc.cluster.local → IP của postgres-1
Đây là cách replica set của PostgreSQL/Redis/Elasticsearch biết địa chỉ của nhau.
6.2 PersistentVolume, StorageClass và dynamic provisioning
StorageClass (template)
└── PVC (yêu cầu storage)
└── PV (storage thực tế — EBS, GCE PD, NFS...)
└── Mount vào Pod
Dynamic provisioning — PVC tự động tạo PV:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com # AWS EBS CSI driver
parameters:
type: gp3
iops: "3000"
throughput: "125"
reclaimPolicy: Retain # Giữ EBS volume khi PVC xoá (an toàn hơn Delete)
volumeBindingMode: WaitForFirstConsumer # Tạo EBS cùng AZ với pod
Access Modes:
ReadWriteOnce(RWO): mount bởi một node (EBS, GCE PD).ReadOnlyMany(ROX): nhiều node đọc (NFS, EFS).ReadWriteMany(RWX): nhiều node đọc và ghi (NFS, EFS, CephFS).
7. Security — production-grade
7.1 securityContext — hardening container
spec:
securityContext: # Pod-level
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000 # Files tạo bởi process có group 2000
seccompProfile:
type: RuntimeDefault # Chặn syscall nguy hiểm theo default profile
containers:
- name: app
securityContext: # Container-level (override pod-level)
allowPrivilegeEscalation: false # Chặn setuid, sudo
readOnlyRootFilesystem: true # / không ghi được → attacker không thể modify files
capabilities:
drop: ["ALL"] # Drop tất cả Linux capabilities
add: ["NET_BIND_SERVICE"] # Chỉ thêm lại cái thực sự cần
7.2 RBAC — phân quyền đúng
# ServiceAccount cho pod
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-api
namespace: payments
---
# Role: chỉ đọc secrets trong namespace payments
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: payments
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["payment-api-secret"] # Chỉ secret cụ thể, không phải tất cả
verbs: ["get"]
---
# Bind role vào service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payment-api-secret-reader
namespace: payments
subjects:
- kind: ServiceAccount
name: payment-api
namespace: payments
roleRef:
kind: Role
name: secret-reader
apiGroup: rbac.authorization.k8s.io
Kiểm tra permission:
kubectl auth can-i get secrets --as=system:serviceaccount:payments:payment-api -n payments
7.3 Pod Security Standards (PSS)
Thay thế PodSecurityPolicy (deprecated K8s 1.21, removed 1.25). Áp dụng qua namespace label:
# Enforce: vi phạm → pod bị reject
# Warn: vi phạm → cảnh báo nhưng vẫn cho qua
# Audit: vi phạm → ghi vào audit log
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted # Chế độ chặt nhất
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
Levels: privileged (không giới hạn) → baseline (ngăn escalation rõ ràng) → restricted (best practice đầy đủ).
7.4 Secrets management
Kubernetes Secret mặc định chỉ base64 encoded, không encrypted. Production cần thêm:
- Encryption at rest: cấu hình
EncryptionConfigurationvới KMS provider (AWS KMS, GCP KMS). - External secret store: External Secrets Operator đồng bộ từ AWS Secrets Manager / GCP Secret Manager / HashiCorp Vault.
- Không dùng env vars cho secrets (xuất hiện trong
ps aux,/proc, logs): dùng file mount từ secret volume +readOnlyRootFilesystem.
8. Autoscaling
8.1 HPA — Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payment-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-api
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60 # Scale khi CPU > 60% của requests
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 800Mi
- type: Pods
pods:
metric:
name: http_requests_per_second # Custom metric từ Prometheus
target:
type: AverageValue
averageValue: "1000"
HPA + PDB phối hợp: HPA scale down có thể vi phạm PDB → K8s sẽ throttle scale-down để đảm bảo minAvailable.
8.2 VPA và Cluster Autoscaler
VPA (Vertical Pod Autoscaler) — tự động điều chỉnh requests/limits:
- Mode
Off: chỉ recommend (dùng để calibrate giá trị ban đầu). - Mode
Auto: tự update requests và restart pod. - Chú ý: VPA restart pod để apply resource thay đổi → không nên dùng cùng HPA trên cùng metric.
Cluster Autoscaler — thêm/xoá node tự động:
- Scale up: khi pod Pending vì không đủ resource trên node hiện tại.
- Scale down: khi node utilization thấp và tất cả pod có thể chạy trên node khác.
- Yêu cầu
requestsđược set đúng — CA dùngrequestsđể tính, không phải actual usage.
9. Observability — biết hệ thống đang làm gì
9.1 Golden signals (Google SRE)
| Signal | Metric ví dụ | Alert khi nào |
|---|---|---|
| Latency | http_request_duration_seconds p99 |
> SLO threshold |
| Traffic | http_requests_total rate |
Anomaly (quá cao hoặc quá thấp) |
| Errors | http_request_errors_total rate |
Error rate > 1% |
| Saturation | CPU throttling, memory usage, queue depth | Approaching limits |
9.2 Structured logging và distributed tracing
// Structured log — machine-readable, dễ filter
log.Info("payment processed",
"order_id", orderID,
"amount", amount,
"duration_ms", elapsed.Milliseconds(),
"trace_id", span.SpanContext().TraceID(),
)
# Fluentd/Fluent Bit DaemonSet: collect logs từ mọi node, forward đến Elasticsearch/Loki
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentbit
spec:
selector:
matchLabels:
app: fluentbit
template:
spec:
containers:
- name: fluentbit
image: fluent/fluent-bit:2.2
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/log
10. Anti-patterns phổ biến ở production
| Anti-pattern | Hệ quả | Cách đúng |
|---|---|---|
Dùng latest tag |
Không reproducible, deploy không biết version gì | Tag bằng git SHA hoặc semver |
| Không set resource requests | Pod là BestEffort, bị evict đầu tiên; CA không scale up đúng | Luôn set requests |
| Requests == Limits | Guaranteed nhưng lãng phí; JVM/Node không thể burst | Burstable: requests < limits |
| Liveness probe check DB | DB flap → toàn bộ pod restart liên tục | Liveness chỉ check internal health |
| Không có PDB | Node drain kill > 1 pod cùng lúc → downtime | Tạo PDB cho tất cả production service |
| Secrets trong env vars | Lộ qua /proc, logs, kubectl describe |
Volume mount secrets |
| Không có NetworkPolicy | Blast radius không giới hạn khi container bị compromise | Default-deny, mở từng rule |
| Không test restore | Backup có nhưng restore lỗi thì backup vô nghĩa | Quarterly restore drill |
Bước tiếp theo
- Helm, Kustomize và GitOps với Argo CD — quản lý config ở scale.
- CI/CD Pipeline production-grade cho Docker + K8s — từ commit đến production.
- K8s Troubleshooting thực chiến — debug các sự cố hay gặp nhất.