Module 16 Production Operations 5 labs

Advanced Kubernetes Operations

Vận hành Kubernetes production-grade: HPA/VPA, Liveness/Readiness/Startup Probes, resource requests & limits, NetworkPolicy, Persistent Volumes, rolling upgrade zero-downtime, backup bằng Velero và security context hardening.

Công cụ thực hành kubectl, Helm, Velero, metrics-server, VS Code
Nền tảng Linux (WSL2) / macOS — Kubernetes local (kind/minikube) hoặc AKS/EKS
Thời điểm phát hành 23/05/2026
Ngày biên soạn 23/05/2026
Người biên soạn Trần Văn Hòa — Microsoft Certified Trainer (MCT)

Mục tiêu học tập

1. Lý thuyết cốt lõi

1.1. Health Probes — ba loại và cách dùng đúng

Kubernetes dùng 3 loại probe để quyết định hành động với container:

  • Liveness Probe — "Container còn sống không?" Nếu fail → kubelet restart container. Dùng cho deadlock, infinite loop.
  • Readiness Probe — "Container sẵn sàng nhận traffic chưa?" Nếu fail → kubelet xóa Pod khỏi Service endpoints (không restart). Dùng khi app đang warm-up, load config.
  • Startup Probe — Bảo vệ container khởi động chậm: tắt Liveness/Readiness cho đến khi Startup Probe pass. Quan trọng với legacy app cần vài phút khởi động.

Cơ chế probe: HTTP GET (phổ biến nhất), TCP Socket, Exec (chạy lệnh trong container), gRPC (K8s ≥ 1.24).

1.2. Resource Requests, Limits và Quality of Service

Request = lượng resource scheduler dùng để chọn Node; Limit = trần tối đa container được dùng (vượt → throttle CPU, OOMKill memory). QoS class ảnh hưởng thứ tự eviction:

QoS ClassĐiều kiệnEviction priority
Guaranteedrequest == limit cho tất cả containersEvict cuối cùng
Burstablerequest < limit hoặc chỉ set một trong haiEvict sau BestEffort
BestEffortKhông set request lẫn limitEvict đầu tiên

1.3. Horizontal Pod Autoscaler (HPA)

HPA điều chỉnh số replica dựa trên metric (CPU, memory, custom). Cần metrics-server cài trong cluster. Công thức: desiredReplicas = ceil(currentReplicas × currentMetric / targetMetric). Cooldown mặc định: scale-down 5 phút, scale-up 15 giây. VPA (Vertical Pod Autoscaler) bổ sung: tự điều chỉnh request/limit nhưng cần restart Pod.

1.4. NetworkPolicy — zero-trust networking

Mặc định Kubernetes cho phép tất cả Pod nói chuyện với nhau (flat network). NetworkPolicy (cần CNI hỗ trợ: Calico, Cilium, Antrea) dùng label selector để kiểm soát ingress/egress ở Layer 3/4. Best practice: default deny all rồi whitelist từng luồng cần thiết.

1.5. Storage — PersistentVolume và PVC

PersistentVolume (PV) là tài nguyên storage được admin provision (hoặc dynamic provisioning qua StorageClass). PersistentVolumeClaim (PVC) là yêu cầu của Pod về storage (size, accessMode). Sau khi bound, PVC mount vào Pod qua volumes. Access modes: ReadWriteOnce (1 node), ReadOnlyMany, ReadWriteMany. StatefulSet dùng volumeClaimTemplates để cấp PVC riêng cho từng replica.

1.6. Rolling Update, Rollback và Security Context

Rolling update là strategy mặc định của Deployment: thay thế Pod cũ bằng Pod mới theo từng batch, kiểm soát bởi maxSurge (Pod tăng thêm) và maxUnavailable (Pod có thể down). Đặt cả hai = 0 để zero-downtime (cần đủ Node). Rollback: kubectl rollout undo deployment/name. Security Context cứng hóa Pod: runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation: false, capabilities: drop: [ALL].

2. Thực hành (Labs)

LAB-076

Cấu hình Liveness, Readiness và Startup Probes

kubectl · YAML

🎯 Mục tiêu: Triển khai app có đủ 3 loại probe; quan sát Kubernetes tự restart container khi liveness fail và giữ traffic khi readiness fail.

🧰 Công cụ / nền tảng: kubectl, kind/minikube, VS Code.

📦 Chuẩn bị: Cluster đang chạy; namespace probes-lab.

▶️ Các bước:

# 1. Tạo namespace
kubectl create namespace probes-lab

# 2. Deploy app với đủ 3 probe
cat > probes-demo.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: probes-demo
  namespace: probes-lab
spec:
  replicas: 2
  selector:
    matchLabels:
      app: probes-demo
  template:
    metadata:
      labels:
        app: probes-demo
    spec:
      containers:
      - name: web
        image: nginx:1.27-alpine
        ports:
        - containerPort: 80
        # Startup Probe: chờ tối đa 30s (6 * 5s) cho container khởi động
        startupProbe:
          httpGet:
            path: /
            port: 80
          failureThreshold: 6
          periodSeconds: 5
        # Readiness Probe: kiểm tra mỗi 5s, fail 2 lần thì remove khỏi endpoints
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 5
          failureThreshold: 2
          successThreshold: 1
        # Liveness Probe: kiểm tra mỗi 10s, fail 3 lần thì restart
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 10
          failureThreshold: 3
          timeoutSeconds: 2
        resources:
          requests:
            cpu: "50m"
            memory: "32Mi"
          limits:
            cpu: "200m"
            memory: "64Mi"
EOF
kubectl apply -f probes-demo.yaml
kubectl rollout status deployment/probes-demo -n probes-lab

# 3. Quan sát probe status
kubectl get pods -n probes-lab -o wide
kubectl describe pod -n probes-lab -l app=probes-demo | grep -A 10 "Liveness\|Readiness\|Startup"

# 4. Tái hiện liveness failure: exec vào pod xóa file index
POD=$(kubectl get pod -n probes-lab -l app=probes-demo -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n probes-lab $POD -- rm /usr/share/nginx/html/index.html
# Quan sát restart
kubectl get pod -n probes-lab -w

# 5. Xem restart count
kubectl get pod -n probes-lab $POD -o jsonpath='{.status.containerStatuses[0].restartCount}'

# 6. Kiểm tra readiness: block traffic khi pod không ready
kubectl get endpoints -n probes-lab

✅ Kết quả mong đợi: Sau khi xóa index.html, Pod RESTARTS tăng lên (liveness kill → restart); trong lúc readiness fail, kubectl get endpoints bỏ Pod đó ra khỏi danh sách (0 IP cho service). Sau restart nginx phục hồi.

🧹 Cleanup: kubectl delete namespace probes-lab.

LAB-077

Resource Requests/Limits và HPA

kubectl · metrics-server · HPA

🎯 Mục tiêu: Cài metrics-server, đặt resource requests/limits, tạo HPA dựa trên CPU, dùng load generator để trigger autoscale.

🧰 Công cụ / nền tảng: kubectl, metrics-server, hey (hoặc ab) để tạo load.

📦 Chuẩn bị: Cluster kind/minikube đang chạy. Với minikube: minikube addons enable metrics-server.

▶️ Các bước:

# 1. Cài metrics-server (kind cluster)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Patch để bỏ TLS verify (local cluster dùng self-signed cert)
kubectl patch deployment metrics-server -n kube-system \
  --type=json \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
kubectl rollout status deployment/metrics-server -n kube-system
# Đợi vài giây rồi kiểm tra
kubectl top nodes
kubectl top pods -A

# 2. Deploy PHP app có CPU load (image chuẩn của K8s HPA doc)
kubectl create namespace hpa-lab
cat > php-apache.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
  namespace: hpa-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php-apache
  template:
    metadata:
      labels:
        app: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: "200m"
            memory: "64Mi"
          limits:
            cpu: "500m"
            memory: "128Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  namespace: hpa-lab
spec:
  selector:
    app: php-apache
  ports:
  - port: 80
EOF
kubectl apply -f php-apache.yaml

# 3. Tạo HPA: scale từ 1 đến 10 replica khi CPU > 50%
kubectl autoscale deployment php-apache \
  --namespace=hpa-lab \
  --cpu-percent=50 \
  --min=1 \
  --max=10
kubectl get hpa -n hpa-lab -w &

# 4. Tạo load (terminal riêng)
kubectl run -it load-generator --image=busybox:1.36 --rm \
  --namespace=hpa-lab \
  --restart=Never \
  -- sh -c "while sleep 0.001; do wget -q -O- http://php-apache; done"

# Quan sát sau ~1 phút
kubectl get hpa -n hpa-lab
kubectl get pods -n hpa-lab

# 5. Dừng load (Ctrl+C trong terminal load-generator)
# Quan sát scale-down sau ~5 phút
kubectl get hpa -n hpa-lab -w

# 6. Xem chi tiết HPA events
kubectl describe hpa php-apache -n hpa-lab

✅ Kết quả mong đợi: HPA tăng replica từ 1 lên 4–8 (tùy load); kubectl get hpa cột TARGETS cho thấy CPU% vượt 50%. Sau khi dừng load, replica giảm về 1 sau ~5 phút (scale-down cooldown).

🧹 Cleanup: kubectl delete namespace hpa-lab.

LAB-078

NetworkPolicy deny-by-default

kubectl · Calico/Cilium · NetworkPolicy

🎯 Mục tiêu: Áp dụng NetworkPolicy deny-all rồi whitelist đúng luồng: frontendbackenddatabase, các luồng khác bị chặn.

🧰 Công cụ / nền tảng: kubectl; cluster với CNI hỗ trợ NetworkPolicy (kind + Calico hoặc kindest/node mặc định với Cilium).

📦 Chuẩn bị: Tạo kind cluster với Calico CNI (xem bước 1). Hoặc dùng minikube với --cni=calico.

▶️ Các bước:

# 1. Tạo cluster kind với Calico
cat > kind-calico.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
  podSubnet: "192.168.0.0/16"
nodes:
- role: control-plane
- role: worker
EOF
kind create cluster --name netpol-lab --config kind-calico.yaml
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
kubectl rollout status daemonset/calico-node -n calico-system --timeout=120s

# 2. Tạo namespace và các tier app
kubectl create namespace netpol-demo

kubectl run frontend --image=nginx:alpine -n netpol-demo --labels="tier=frontend"
kubectl run backend  --image=nginx:alpine -n netpol-demo --labels="tier=backend"
kubectl run database --image=nginx:alpine -n netpol-demo --labels="tier=database"
kubectl expose pod frontend --port=80 -n netpol-demo
kubectl expose pod backend  --port=80 -n netpol-demo
kubectl expose pod database --port=80 -n netpol-demo
kubectl get pods -n netpol-demo

# 3. Kiểm tra kết nối TRƯỚC khi áp policy (all-allow mặc định)
kubectl exec -n netpol-demo frontend -- wget -qO- http://database --timeout=3 && echo "ALLOWED" || echo "BLOCKED"
# Kết quả: ALLOWED (mặc định)

# 4. Áp NetworkPolicy deny-all ingress cho namespace
cat > deny-all.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: netpol-demo
spec:
  podSelector: {}
  policyTypes:
  - Ingress
EOF
kubectl apply -f deny-all.yaml

# 5. Whitelist: chỉ frontend → backend
cat > allow-frontend-to-backend.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: netpol-demo
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 80
EOF
kubectl apply -f allow-frontend-to-backend.yaml

# 6. Whitelist: chỉ backend → database
cat > allow-backend-to-db.yaml <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-to-database
  namespace: netpol-demo
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: backend
    ports:
    - protocol: TCP
      port: 80
EOF
kubectl apply -f allow-backend-to-db.yaml

# 7. Kiểm tra sau policy
kubectl exec -n netpol-demo frontend -- wget -qO- http://backend  --timeout=3 && echo "ALLOWED" || echo "BLOCKED"
# Kết quả: ALLOWED

kubectl exec -n netpol-demo frontend -- wget -qO- http://database --timeout=3 && echo "ALLOWED" || echo "BLOCKED"
# Kết quả: BLOCKED

kubectl exec -n netpol-demo backend -- wget -qO- http://database  --timeout=3 && echo "ALLOWED" || echo "BLOCKED"
# Kết quả: ALLOWED

# 8. Liệt kê tất cả NetworkPolicy
kubectl get networkpolicy -n netpol-demo

✅ Kết quả mong đợi: frontend → backend: ALLOWED | frontend → database: BLOCKED | backend → database: ALLOWED. Mô hình zero-trust hoạt động đúng.

🧹 Cleanup: kubectl delete namespace netpol-demo && kind delete cluster --name netpol-lab.

LAB-079

Persistent Volume và PVC cho database

kubectl · StorageClass · PVC

🎯 Mục tiêu: Tạo PVC từ StorageClass mặc định, mount vào Pod PostgreSQL, ghi dữ liệu, xóa Pod rồi verify dữ liệu còn sau khi Pod mới start.

🧰 Công cụ / nền tảng: kubectl, kind (StorageClass: standard/rancher.io/local-path).

📦 Chuẩn bị: Cluster kind đang chạy (có StorageClass mặc định).

▶️ Các bước:

# 1. Xem StorageClass có sẵn
kubectl get storageclass
# kind thường có: standard (provisioner: rancher.io/local-path)

# 2. Tạo namespace
kubectl create namespace storage-lab

# 3. Tạo PersistentVolumeClaim
cat > postgres-pvc.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  namespace: storage-lab
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: standard
  resources:
    requests:
      storage: 1Gi
EOF
kubectl apply -f postgres-pvc.yaml
kubectl get pvc -n storage-lab   # Status: Bound (sau khi Pod dùng PVC)

# 4. Deploy PostgreSQL với PVC
cat > postgres-deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: storage-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16-alpine
        env:
        - name: POSTGRES_PASSWORD
          value: "labpassword"
        - name: POSTGRES_DB
          value: "labdb"
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        ports:
        - containerPort: 5432
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"
      volumes:
      - name: postgres-storage
        persistentVolumeClaim:
          claimName: postgres-data
EOF
kubectl apply -f postgres-deployment.yaml
kubectl rollout status deployment/postgres -n storage-lab

# 5. Ghi dữ liệu test
POD=$(kubectl get pod -n storage-lab -l app=postgres -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n storage-lab $POD -- psql -U postgres -d labdb -c \
  "CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT); INSERT INTO products(name) VALUES ('laptop'),('phone');"

kubectl exec -n storage-lab $POD -- psql -U postgres -d labdb -c "SELECT * FROM products;"

# 6. Xóa Pod (Deployment sẽ tự tạo Pod mới)
kubectl delete pod -n storage-lab $POD
kubectl rollout status deployment/postgres -n storage-lab

# 7. Kiểm tra dữ liệu còn sau restart
NEW_POD=$(kubectl get pod -n storage-lab -l app=postgres -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n storage-lab $NEW_POD -- psql -U postgres -d labdb -c "SELECT * FROM products;"
# Kết quả mong đợi: 2 row (laptop, phone) vẫn còn

# 8. Kiểm tra PV được tạo
kubectl get pv
kubectl describe pvc postgres-data -n storage-lab

✅ Kết quả mong đợi: Sau khi Pod bị xóa và tạo lại, SELECT * FROM products vẫn trả về 2 dòng dữ liệu — xác nhận PVC persist data qua Pod lifecycle. kubectl get pv hiển thị PV với STATUS Bound.

🧹 Cleanup: kubectl delete namespace storage-lab (PVC và PV xóa theo nếu reclaimPolicy=Delete).

LAB-080

Backup namespace bằng Velero

kubectl · Velero CLI · MinIO (local S3)

🎯 Mục tiêu: Cài Velero với backend MinIO local, backup toàn bộ namespace, simulate disaster (xóa namespace), restore từ backup.

🧰 Công cụ / nền tảng: velero CLI, kubectl, MinIO (chạy trên Docker local làm S3-compatible backend).

📦 Chuẩn bị: Cluster đang chạy; Docker Desktop; Velero CLI đã cài (brew install velero hoặc download binary từ GitHub releases).

▶️ Các bước:

# 1. Chạy MinIO local làm S3 backend (Docker)
docker run -d --name minio \
  -p 9000:9000 -p 9001:9001 \
  -e MINIO_ROOT_USER=minioadmin \
  -e MINIO_ROOT_PASSWORD=minioadmin \
  quay.io/minio/minio server /data --console-address ":9001"

# Tạo bucket "velero-backups" qua MinIO CLI
docker exec minio mc alias set local http://localhost:9000 minioadmin minioadmin
docker exec minio mc mb local/velero-backups

# 2. Tạo credentials file cho Velero
cat > velero-credentials <<'EOF'
[default]
aws_access_key_id=minioadmin
aws_secret_access_key=minioadmin
EOF

# 3. Cài Velero vào cluster (dùng AWS S3-compatible provider)
# Lấy IP của docker host (WSL2: thường là 172.x.x.x hoặc host.docker.internal)
MINIO_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' minio)

velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.9.0 \
  --bucket velero-backups \
  --secret-file ./velero-credentials \
  --use-volume-snapshots=false \
  --backup-location-config region=minio,s3ForcePathStyle=true,s3Url=http://${MINIO_IP}:9000
kubectl get pods -n velero

# 4. Tạo namespace cần backup
kubectl create namespace prod-app
kubectl create deployment webapp --image=nginx:alpine --replicas=2 -n prod-app
kubectl create configmap app-settings --from-literal=ENV=prod -n prod-app
kubectl get all -n prod-app

# 5. Chạy backup
velero backup create prod-app-backup \
  --include-namespaces prod-app \
  --wait
velero backup describe prod-app-backup
velero backup logs prod-app-backup

# 6. Simulate disaster: xóa namespace
kubectl delete namespace prod-app
kubectl get namespace prod-app 2>&1 || echo "Namespace đã bị xóa"

# 7. Restore từ backup
velero restore create --from-backup prod-app-backup --wait
kubectl get all -n prod-app
kubectl get configmap -n prod-app

# 8. Lên lịch backup tự động (mỗi ngày lúc 2AM UTC)
velero schedule create daily-prod-backup \
  --schedule="0 2 * * *" \
  --include-namespaces prod-app \
  --ttl 720h
velero schedule get

✅ Kết quả mong đợi: Sau restore, kubectl get all -n prod-app hiển thị lại Deployment + 2 Pod đang chạy; ConfigMap app-settings còn nguyên. velero backup describe hiển thị Phase: Completed.

🧹 Cleanup: velero uninstall --force; kubectl delete namespace prod-app; docker stop minio && docker rm minio.

3. Tình huống doanh nghiệp thực tế

Bối cảnh

Một e-commerce platform chạy 30+ microservices trên AKS. Black Friday sắp đến: traffic dự kiến tăng 10x. Đồng thời, audit security phát hiện nhiều Pod đang chạy root, không có resource limits và network hoàn toàn flat (mọi service nói chuyện được với nhau).

Cách xử lý của SRE team

  • HPA + Cluster Autoscaler: Tất cả Deployment critical (checkout, payment, catalog) có HPA với target CPU 60%. Cluster Autoscaler tự thêm Node khi Node hiện tại đầy. Kết quả: không cần pre-provision Node đắt tiền, chỉ trả tiền khi traffic tới.
  • Resource budgeting: Dùng LimitRange per-namespace để set default limits; dùng ResourceQuota để cap tổng CPU/memory mỗi team dùng. Prevents noisy neighbor vấn đề.
  • NetworkPolicy: Áp dụng deny-all cho toàn cluster, chỉ whitelist đúng luồng business. PCI-DSS compliance yêu cầu payment service chỉ được gọi từ checkout service.
  • Security Context hardening: OPA/Gatekeeper policy enforce: runAsNonRoot=true, readOnlyRootFilesystem=true, không mount host path, không privileged. Rollout dần qua admission webhook với warn mode trước khi enforce.
  • Velero scheduled backup: Backup toàn bộ namespace production mỗi 6 tiếng, retain 7 ngày, snapshot PV qua CSI snapshot. RTO target: 30 phút (đã test restore drill hàng quý).
  • Rolling upgrade zero-downtime: Mọi Deployment set maxUnavailable: 0, maxSurge: 1 kết hợp với Readiness Probe — traffic chỉ chuyển sang Pod mới khi probe pass. Kết quả: không có downtime trong 8 release/ngày.

📚 Nguồn tham khảo

Module 15: Kubernetes Core Module 17: CI/CD Fundamentals
Zalo