Kubernetes Production checklist -Checklist khi triển khai ứng dụng trên Kubernetes
"Chạy được trên Kubernetes" và "sẵn sàng lên production trên Kubernetes" là hai chuyện hoàn toàn khác nhau. Một Deployment với replicas: 3 vẫn có thể sập toàn bộ khi mất 1 node, một Pod "healthy" vẫn có thể đang trả 500 cho user vì readiness probe không phản ánh đúng trạng thái thật.
Bài này mình tổng hợp lại checklist mình dùng khi review một workload trước khi cho lên production, chia làm 2 tầng:
- Cluster Level: những thứ thuộc về hạ tầng/platform team, gồm control plane, node, network, security policy ở cấp cluster.
- Application Level: những thứ nằm trong tầm kiểm soát của dev/DevOps khi viết manifest cho từng workload.
Mỗi mục mình sẽ giải thích ngắn gọn "tại sao cần" và kèm ví dụ Bad/Good để dễ hình dung.
Lưu ý: Đây là checklist tổng hợp từ kinh nghiệm thực tế và nhiều nguồn tham khảo khác nhau, không phải mục nào cũng bắt buộc áp dụng 100%, tùy quy mô hệ thống mà chọn lọc cho phù hợp.
Bắt đầu thôi 🚀
Phần 1: Cluster Level
1. Control Plane High Availability
Control plane gồm API server, scheduler, controller-manager, etcd. Nếu chỉ chạy 1 control plane node, cả cluster sẽ "đứng hình" (không scale, không deploy, không tạo pod mới) khi node đó chết, dù các Pod đang chạy vẫn tiếp tục phục vụ traffic bình thường (đây là điểm hay bị hiểu nhầm: mất control plane không đồng nghĩa mất traffic ngay lập tức, nhưng bạn sẽ không tự hồi phục được khi có sự cố tiếp theo).
- Bad: 1 control plane node duy nhất, không có load balancer phía trước API server.
- Good: Tối thiểu 3 control plane node (số lẻ để đảm bảo quorum), đặt sau 1 Load Balancer.
# kubeadm init config - ví dụ trỏ API server qua LB
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
controlPlaneEndpoint: "k8s-api.internal:6443" # LB endpoint, không trỏ thẳng vào 1 node
etcd:
local:
dataDir: /var/lib/etcd
Nếu dùng managed control plane (EKS/GKE/AKS), phần này cloud lo cho bạn, nhưng vẫn nên hiểu rõ để biết điều gì cloud provider không cover (ví dụ: backup etcd thường vẫn là trách nhiệm của bạn trên self-managed cluster).
2. etcd Quorum & Backup/Restore
etcd dùng thuật toán Raft, cần quorum (đa số thành viên còn sống) để hoạt động. Với số node lẻ, khả năng chịu lỗi như sau:
| Số etcd member | Chịu được mất tối đa |
|---|---|
| 3 | 1 |
| 5 | 2 |
| 7 | 3 |
etcd lưu gì? Toàn bộ object bạn kubectl get được (Pod, Deployment, Service, ConfigMap, Secret, RBAC, PV/PVC...) dưới dạng key-value. Không lưu: image container, log Pod, dữ liệu thật trong PV/PVC, metrics. Chỉ API server nói chuyện trực tiếp với etcd, nên mất quorum không làm sập Pod đang chạy, chỉ chặn thao tác ghi state mới (deploy, scale...).
- Bad: Chạy 2 hoặc 4 etcd member (số chẵn không tăng khả năng chịu lỗi so với số lẻ liền trước, chỉ tốn thêm tài nguyên).
- Good: 3 hoặc 5 member, và luôn có backup định kỳ. HA không thay thế được backup, mất quorum vẫn có thể phục hồi từ snapshot.
# Backup etcd snapshot
etcdctl snapshot save /backup/etcd-$(date +%Y%m%d).db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Restore khi cần
etcdctl snapshot restore /backup/etcd-20260718.db \
--data-dir=/var/lib/etcd-restore
3. Worker Node Phân Bổ Theo Zone
Nếu toàn bộ worker node nằm trong 1 zone (availability zone), một sự cố zone đó là mất luôn cả ứng dụng, bất kể bạn có bao nhiêu replicas.
- Bad: Tất cả node trong
ap-southeast-1a. - Good: Node pool trải đều qua nhiều zone, có label
topology.kubernetes.io/zoneđể scheduler biết mà spread Pod ra.
kubectl get nodes -L topology.kubernetes.io/zone
# NAME STATUS ZONE
# node-1 Ready ap-southeast-1a
# node-2 Ready ap-southeast-1b
# node-3 Ready ap-southeast-1c
Chỉ có label zone thôi chưa đủ. Phần Application Level (mục 4) sẽ nói cách thực sự tận dụng nó bằng Topology Spread Constraints.
4. Namespace Separation & Resource Quota
Không giới hạn tài nguyên theo namespace, một team/app "ăn tham" (memory leak, load test quên tắt) có thể chiếm hết resource của cả cluster, kéo theo app của team khác bị OOM hoặc Pending vô thời hạn.
- Bad: Namespace dùng chung, không
ResourceQuota, khôngLimitRange. - Good: Mỗi team/môi trường một namespace riêng, có quota rõ ràng.
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-a-quota
namespace: team-a
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "50"
---
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-a
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
type: Container
LimitRange đảm bảo container nào quên khai báo resources vẫn có giá trị mặc định, tránh trường hợp 1 Pod không giới hạn "nuốt" hết node.
5. RBAC: Nguyên Tắc Least Privilege
Mỗi workload chỉ nên có đúng quyền nó cần, không hơn. cluster-admin cho mọi ServiceAccount là cách nhanh nhất để 1 lỗ hổng nhỏ trong app biến thành chiếm quyền cả cluster.
- Bad: Gán
ClusterRoleBindingvớicluster-admincho ServiceAccount của app, "cho chắc khỏi lỗi quyền". - Good: Tạo
Role/RoleBindingriêng, giới hạn đúng namespace và đúng resource cần dùng.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: shop-api-role
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: shop-api-binding
namespace: production
subjects:
- kind: ServiceAccount
name: shop-api-sa
namespace: production
roleRef:
kind: Role
name: shop-api-role
apiGroup: rbac.authorization.k8s.io
Nhớ audit định kỳ, RBAC hay bị "phình" dần theo thời gian vì không ai dọn quyền cũ khi không còn dùng.
6. NetworkPolicy
Mặc định, mọi Pod trong cluster đều nói chuyện được với nhau, kể cả Pod của 2 team hoàn toàn không liên quan. Nếu 1 Pod bị compromise, attacker có thể "đi ngang" (lateral movement) sang toàn bộ cluster.
- Bad: Không có
NetworkPolicynào, mọi traffic đông-tây (east-west) đều được phép. - Good: Default-deny, chỉ mở đúng luồng traffic cần thiết.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-shop-api-from-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: shop-api
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: 8080
policyTypes: ["Ingress"]
7. Pod Security Standards
Namespace nào cũng nên bắt buộc Pod chạy đúng chuẩn bảo mật tối thiểu, không container nào được chạy privileged, chạy root, hay mount host path tùy tiện.
- Bad: Không enforce gì, Pod nào cũng deploy được kể cả
privileged: true. - Good: Gắn label
pod-security.kubernetes.ioở namespace để enforce chuẩnrestrictedhoặcbaseline.
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
8. Image Supply Chain & Admission Control
Một image dính CVE nghiêm trọng hoặc kéo từ registry không đáng tin là cửa ngõ tấn công phổ biến nhất vào production.
- Bad: Pull image trực tiếp từ Docker Hub public, không scan, không kiểm soát nguồn gốc.
- Good: CI/CD scan image (Trivy, Grype...) trước khi push, chỉ cho phép pull từ registry nội bộ đã whitelist, dùng admission controller (Kyverno, OPA Gatekeeper) để chặn deploy image chưa qua scan.
# Scan trong CI trước khi push
trivy image --severity HIGH,CRITICAL --exit-code 1 myregistry.io/shop-api:1.4.0
# Ví dụ policy Kyverno: chỉ cho phép image từ registry nội bộ
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: Enforce
rules:
- name: allowed-registries
match:
resources:
kinds: ["Pod"]
validate:
message: "Chỉ được dùng image từ myregistry.io"
pattern:
spec:
containers:
- image: "myregistry.io/*"
9. Cluster/Node Autoscaling
Application autoscale (HPA) mà node không đủ chỗ chứa Pod mới thì HPA cũng vô nghĩa, Pod sẽ kẹt ở trạng thái Pending.
- Bad: Node pool cố định, HPA scale lên 10 replicas nhưng cluster chỉ đủ chỗ cho 6.
- Good: Bật Cluster Autoscaler hoặc Karpenter, có headroom (overprovisioning bằng pause pod priority thấp) để giảm độ trễ khi cần scale gấp.
# Pause pod overprovisioning: chiếm chỗ trước, nhường ngay khi cần
apiVersion: apps/v1
kind: Deployment
metadata:
name: overprovisioning
spec:
replicas: 3
selector:
matchLabels: { app: overprovisioning }
template:
metadata:
labels: { app: overprovisioning }
spec:
priorityClassName: overprovisioning-low-priority
containers:
- name: pause
image: registry.k8s.io/pause:3.9
resources:
requests: { cpu: 1, memory: 2Gi }
Lưu ý: scale-down của Cluster Autoscaler phải tôn trọng PodDisruptionBudget. Nếu không cấu hình đúng, việc rút node có thể bị "kẹt" vô thời hạn (liên quan tới mục Application Level #3).
10. Ingress HA + TLS
Ingress controller là single point of failure kinh điển nếu chỉ chạy 1 replica, chết 1 pod là toàn bộ traffic từ ngoài vào không tới được cluster.
- Bad: Ingress controller 1 replica, không TLS (traffic đi HTTP thuần).
- Good: Ingress controller ≥ 2 replicas với anti-affinity + PDB, TLS bắt buộc qua cert-manager.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: shop-api
namespace: production
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts: ["api.example.com"]
secretName: shop-api-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: shop-api
port: { number: 80 }
11. Observability Stack
Không có metrics/log tập trung, bạn sẽ không biết hệ thống "ốm" cho tới khi user report, lúc đó đã quá trễ.
- Bad:
kubectl logsthủ công từng Pod mỗi khi có sự cố, không có alert nào. - Good: Prometheus + Grafana (hoặc managed alternative) thu thập metrics, logging tập trung (Loki/ELK), alert dựa trên SLO burn rate chứ không chỉ CPU/memory thô.
# ServiceMonitor để Prometheus tự discover metrics endpoint
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: shop-api
namespace: production
spec:
selector:
matchLabels: { app: shop-api }
endpoints:
- port: metrics
interval: 30s
path: /metrics
12. StorageClass & Backup
Với stateful workload, mất dữ liệu vì reclaim policy sai hoặc chưa test restore lần nào là rủi ro production nghiêm trọng nhất, nghiêm trọng hơn cả downtime.
- Bad:
reclaimPolicy: Deletecho volume chứa dữ liệu quan trọng, backup "có cấu hình" nhưng chưa restore thử lần nào. - Good:
reclaimPolicy: Retaincho volume quan trọng, backup định kỳ bằng Velero và restore thử định kỳ để xác nhận backup thật sự dùng được.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: production-ssd
provisioner: ebs.csi.aws.com
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
# Velero: backup và test restore
velero backup create db-backup-$(date +%Y%m%d) --include-namespaces production
velero restore create --from-backup db-backup-20260718 --namespace-mappings production:production-restore-test
Phần 2: Application Level
1. Readiness / Liveness / Startup Probes
Đây là bộ 3 quan trọng nhất và cũng dễ cấu hình sai nhất:
-
Readiness: "Đừng gửi traffic cho tôi lúc này". Không đạt thì Pod bị rút khỏi Endpoints, không nhận traffic mới, nhưng không bị restart.
-
Liveness: "Tôi bị treo rồi, restart tôi đi". Không đạt thì container bị kill và tạo lại.
-
Startup: Dành cho app khởi động chậm. Trong lúc startup probe chưa pass, liveness/readiness probe tạm thời bị vô hiệu hoá, tránh app bị kill oan khi đang boot.
-
Bad: Liveness probe cấu hình quá gắt (
failureThreshold: 1,timeoutSeconds: 1) trên endpoint nặng. App đang xử lý load cao bị hiểu nhầm là "chết", Kubernetes restart liên tục tạo thành restart storm, càng làm tải nặng hơn. -
Good: Liveness đơn giản, chỉ check process còn sống (không gọi DB, không gọi service ngoài). Readiness mới là nơi check các dependency thật sự (DB, cache...). Startup probe riêng cho app khởi động lâu.
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # cho phép tối đa 30 x 10s = 5 phút để app boot xong
periodSeconds: 10
Note quan trọng cho app khởi động chậm: ngoài
startupProbe, đừng quênminReadySecondsở cấpDeployment(không phải field của probe, mà nằm ởspec.minReadySeconds). Mặc định giá trị này là0, nghĩa là ngay khi readiness probe pass lần đầu, Pod được tính là "available" và rollout tiếp tục ngay lập tức sang Pod kế tiếp. Với app khởi động chậm hoặc cần thời gian "warm up" thật sự ổn định (nạp cache, kết nối connection pool...) sau khi readiness đã pass, việc rollout đi quá nhanh có thể khiến bạn thay hết cả 3 Pod cùng lúc trước khi phát hiện ra Pod mới có vấn đề ẩn (ví dụ leak connection sau vài chục giây). SetminReadySecondsmột khoảng hợp lý (ví dụ 30-60s) để rollout "chờ thêm" và quan sát Pod ổn định thật sự trước khi coi là thành công, giảm rủi ro rollout cả loạt Pod lỗi liên tiếp.
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
spec:
replicas: 3
minReadySeconds: 30 # chờ 30s sau khi Ready mới tính là "available", hữu ích với app boot chậm
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
spec:
containers:
- name: shop-api
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30
periodSeconds: 10
2. Resource Requests & Limits
Không khai báo resources, Pod của bạn thuộc QoS class BestEffort, là ứng viên đầu tiên bị evict khi node thiếu tài nguyên, bất kể app có quan trọng cỡ nào.
- Bad: Không set
requests/limits. - Good: Set cả hai, dựa trên số liệu sử dụng thực tế (không đoán mò) để đạt QoS class
Guaranteedcho workload quan trọng.
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Mẹo: dùng Vertical Pod Autoscaler ở chế độ Off (chỉ recommend, không tự apply) trong vài ngày để lấy số liệu request/limit hợp lý thay vì đoán.
3. Pod Disruption Budget
PodDisruptionBudget (PDB) bảo vệ bạn khỏi voluntary disruption: node drain khi upgrade, cluster autoscaler scale-down. Nó không bảo vệ bạn khỏi kubectl delete thủ công hay node chết đột ngột (involuntary disruption).
- Bad: Không có PDB, team hạ tầng drain node để patch, vô tình đá bay hết Pod của bạn cùng lúc.
- Bad khác (cấu hình sai theo hướng ngược lại):
minAvailable: 3trong khireplicas: 3. Về lý thuyết đúng nhưng khiếnkubectl drainbị treo vĩnh viễn, không node nào có thể maintenance được. - Good: Set số dư hợp lý, ví dụ cho phép mất tối đa 1 Pod cùng lúc.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: shop-api-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: shop-api
4. Pod (Anti-)Affinity & Topology Spread Constraints
Có 3 replicas nhưng cả 3 đều bị scheduler xếp chung 1 node/1 zone thì khác gì có 1 replica, node/zone đó chết là mất sạch.
- Bad: Không khai báo gì, để scheduler tự quyết định (thường sẽ dồn Pod vào node còn nhiều tài nguyên nhất, dễ trùng node).
- Good: Dùng
podAntiAffinityđể tránh 2 Pod cùng app nằm chung node, vàtopologySpreadConstraintsđể trải Pod đều qua các zone.
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels: { app: shop-api }
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels: { app: shop-api }
5. HPA / VPA
HPA scale theo số lượng Pod (horizontal), VPA điều chỉnh request/limit của từng Pod (vertical). Chạy cả hai cùng lúc trên cùng 1 metric (thường là CPU) sẽ khiến chúng "đá" nhau: VPA đổi request, HPA lại tính toán lại dựa trên request mới, tạo vòng lặp không ổn định.
- Bad: Bật cả HPA và VPA ở chế độ
Auto, cùng theo dõi CPU. - Good: HPA quản lý CPU/số replicas, VPA chỉ chạy chế độ
Off(Initial hoặc chỉ recommend) để tham khảo, hoặc VPA quản lý các resource khác (vd: memory) tách biệt với HPA.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: shop-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: shop-api
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 60 }
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300 # scale down chậm, tránh flapping
policies:
- type: Pods
value: 1
periodSeconds: 60
6. Graceful Shutdown
Nguyên nhân số 1 gây mất request khi rollout: việc gỡ Pod khỏi Endpoints và gửi SIGTERM xảy ra song song, không tuần tự. Có một khoảng thời gian ngắn Pod đã nhận SIGTERM (và có thể đã bắt đầu tắt) nhưng vẫn còn nhận traffic vì chưa kịp gỡ khỏi Endpoints ở phía kube-proxy/load balancer.
- Bad: App thoát ngay khi nhận SIGTERM, không có
preStop, mỗi lần rollout đều rớt vài request. - Good: App xử lý SIGTERM để drain kết nối đang dở, kèm
preStopsleep để "câu giờ" cho việc gỡ Endpoints kịp lan truyền.
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
terminationGracePeriodSeconds: 30
// Ví dụ xử lý SIGTERM trong app (Go)
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM)
<-sigCh
server.Shutdown(context.Background()) // drain connection đang xử lý dở
os.Exit(0)
7. Deployment Strategy
Chiến lược rollout mặc định (RollingUpdate) không phải lúc nào cũng an toàn nếu để nguyên giá trị mặc định, và với thay đổi rủi ro cao, rolling update thuần cũng không đủ để phát hiện sớm.
- Bad: Dùng mặc định
maxUnavailable: 25%cho service quan trọng. Có thể mất tới 1/4 capacity trong lúc rollout ở giờ cao điểm. - Good: Với service quan trọng, set
maxUnavailable: 0để không bao giờ giảm capacity, dùngmaxSurgeđể tạo Pod mới trước khi tắt Pod cũ. Với thay đổi rủi ro cao, cân nhắc canary/blue-green.
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
# Canary đơn giản bằng cách chạy song song 2 Deployment, chia traffic qua Service weight
# (hoặc dùng Argo Rollouts / Flagger cho canary tự động theo % traffic)
8. Secrets Handling
Secret dạng biến môi trường (env) dễ bị lộ qua kubectl describe pod, log crash dump, hay child process kế thừa toàn bộ biến môi trường của tiến trình cha.
- Bad:
env:
- name: DB_PASSWORD
value: "supersecret123" # hardcode thẳng trong manifest
- Good: Mount secret dưới dạng volume (không lộ khi liệt kê biến môi trường), và với hệ thống lớn, dùng external secret store (Vault, AWS Secrets Manager) qua External Secrets Operator thay vì Kubernetes Secret thuần (Secret mặc định chỉ base64, không mã hoá thật sự nếu chưa bật encryption at rest).
volumes:
- name: db-secret
secret:
secretName: db-credentials
containers:
- name: shop-api
volumeMounts:
- name: db-secret
mountPath: /etc/secrets
readOnly: true
9. Labels & Metadata
Không có labeling nhất quán, việc troubleshoot, tính cost theo team, hay viết selector chính xác đều trở nên khó khăn khi cluster lớn dần.
- Bad: Label tùy hứng, mỗi service một kiểu đặt tên.
- Good: Theo chuẩn recommended labels của Kubernetes.
metadata:
labels:
app.kubernetes.io/name: shop-api
app.kubernetes.io/instance: shop-api-production
app.kubernetes.io/version: "1.4.0"
app.kubernetes.io/component: backend
app.kubernetes.io/part-of: shop-platform
app.kubernetes.io/managed-by: helm
10. Image Tag & Pull Policy
:latest là "tag không có nghĩa": không ai biết chắc chắn phiên bản nào đang chạy, rollback gần như bất khả thi vì không biết "phiên bản trước" là gì.
- Bad:
image: myregistry.io/shop-api:latest - Good: Pin tag cụ thể (bản thân CI/CD build ra), với hệ thống yêu cầu cao hơn có thể pin theo digest sha256 để đảm bảo bất biến.
containers:
- name: shop-api
image: myregistry.io/shop-api:1.4.0
imagePullPolicy: IfNotPresent # hoặc Always nếu tag có thể bị ghi đè (không nên, nhưng nếu có thì bắt buộc Always)
11. ConfigMap & Reload Strategy
Sửa ConfigMap không tự động khiến Pod đang chạy nhận giá trị mới. Nếu app chỉ đọc config lúc khởi động, bạn sẽ "sửa xong tưởng xong" nhưng thực tế Pod vẫn chạy config cũ.
- Bad: Sửa ConfigMap, tưởng là xong, không biết Pod cần restart mới nhận giá trị mới.
- Good: Có chiến lược rõ ràng: hoặc app tự watch file config và reload, hoặc dùng annotation checksum để buộc rollout mới mỗi khi ConfigMap đổi (cách phổ biến khi dùng Helm).
# Helm: checksum annotation buộc Pod restart khi ConfigMap thay đổi
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
Vì sao annotation phải nằm ở spec.template.metadata? Deployment có 2 khối metadata: một ở cấp Deployment, một trong spec.template (mô tả Pod). Controller chỉ rollout khi spec.template đổi, vì đó là phần được hash thành pod-template-hash. Đặt annotation ở metadata ngoài cùng sẽ không kích hoạt gì cả. checksum/config không có ý nghĩa đặc biệt với Kubernetes, chỉ là quy ước Helm để buộc giá trị annotation đổi theo nội dung ConfigMap.
Gợi ý: dùng Stakater Reloader thay vì tự viết checksum vào từng chart. Reloader tự theo dõi ConfigMap/Secret và patch annotation tương tự khi phát hiện thay đổi, không cần sửa chart:
metadata:
annotations:
reloader.stakater.com/auto: "true"
Lưu ý: nếu dùng ArgoCD với selfHeal: true, patch trực tiếp của Reloader có thể bị ArgoCD revert vì coi là drift so với Git. Trường hợp đó nên ưu tiên cách checksum annotation ở trên.
Phần 3: Going Live - Kiểm Tra Cuối Cùng
Trước khi bấm nút release thật sự, review nhanh 4 câu hỏi sau:
- Đã load-test đường scale chưa?: Cấu hình HPA/Cluster Autoscaler xong không có nghĩa là nó hoạt động đúng như kỳ vọng dưới tải thật. Test thử với traffic tăng đột biến 5-10 lần.
- Rollback hay roll-forward?: Quyết định trước, đừng để tới lúc sự cố mới họp bàn.
kubectl rollout undocó sẵn sàng dùng ngay không, hay quy trình là fix-forward? - Runbook đã có chưa?: Khi Pod crash loop, khi node chết, on-call biết làm gì đầu tiên không cần đoán?
- Đã review cost & right-size chưa?:
requestsquá cao so với usage thực tế là lãng phí âm thầm, cộng dồn theo tháng có thể là con số không nhỏ.
Kết luận
Checklist này không phải để áp dụng máy móc 100% mọi mục cho mọi hệ thống, một side project cá nhân không cần etcd 5-member hay canary deployment. Nhưng càng gần production thật, càng nhiều user thật, thì càng nên rà lại từng mục một cách có chủ đích thay vì "chạy được là xong".
Hy vọng checklist này giúp bạn tự tin hơn khi đưa workload lên Kubernetes production. Lưu lại và áp dụng dần cho hệ thống của bạn nhé!
Happy Coding! 👨💻
Nếu bạn đang gặp khó khăn về hệ thống, cần hỗ trợ về DevOps tools, hoặc định hướng sự nghiệp, mình tin là có thể giúp được. Liên hệ mình qua hoangviet.io.vn, rất vui được trò chuyện và hợp tác cùng bạn.
All rights reserved