Kubernetes trên AWS EKS: Triển Khai, Scaling & Giám Sát
Bạn đã từng ngồi debug một Kubernetes cluster tự dựng lúc 2 giờ sáng, khi etcd bỗng nhiên từ chối write vì disk đầy, trong khi production đang chịu tải cao nhất tuần? Mình đã từng. Và sau lần đó, mình quyết định chuyển sang EKS — không phải vì lười, mà vì nhận ra rằng vận hành control plane không phải thứ tạo ra giá trị cho sản phẩm. Bài này là tổng hợp những gì mình học được sau nhiều năm chạy EKS ở môi trường production, bao gồm cả những sai lầm tốn kém nhất.
Tại Sao EKS Thay Vì Tự Dựng Cluster?
Khi tự quản lý Kubernetes với kubeadm hay kops, bạn phải lo toàn bộ control plane: etcd replication trên 3 node, apiserver high availability phía sau load balancer, kube-controller-manager và kube-scheduler chạy active-passive. Những thứ này không tạo ra giá trị business trực tiếp, nhưng nếu hỏng thì cả hệ thống sập ngay lập tức — và thường hỏng vào những lúc tệ nhất.
AWS EKS quản lý control plane với SLA 99.95%. Mình chỉ cần lo worker nodes và workload. Với team nhỏ dưới 10 người, đây là đánh đổi hoàn toàn xứng đáng — tiết kiệm ít nhất 20–30% thời gian ops mỗi tuần. Team mình từng có một engineer dành gần 30% thời gian chỉ để vá Kubernetes version và patch CVE trên self-managed cluster. Sau khi migrate sang EKS, con số đó về gần như 0, toàn bộ thời gian đó đổ vào feature development.
EKS tích hợp native với toàn bộ AWS ecosystem. Cụ thể:
IAM + IRSA (IAM Roles for Service Accounts) — Thay vì gắn permission vào node (instance-level), mình có thể gắn IAM Role trực tiếp vào từng Service Account trong Kubernetes. Pod A chỉ đọc được S3 bucket nhất định, Pod B mới có quyền write DynamoDB. Granular hơn nhiều so với node-level credentials.
VPC CNI Plugin — Pod nhận địa chỉ IP thật từ VPC subnet, không cần overlay network như Flannel hay Calico. Điều này có nghĩa là pod-to-pod communication không có overhead của encapsulation, và security group rules áp dụng trực tiếp được lên pod. Latency thấp hơn, debug networking dễ hơn.
AWS Load Balancer Controller — Tạo ALB hoặc NLB tự động từ Kubernetes Ingress/Service annotation. Không cần tự cấu hình target groups hay listener rules.
Về chi phí: control plane tốn $0.10/giờ (~$73/tháng) per cluster. Với staging environment, nhiều team dùng chung 1 cluster và namespace isolation để tiết kiệm. Production thì nên cluster riêng — khi staging gặp sự cố không ảnh hưởng production, và ngược lại.
Tạo EKS Cluster Đúng Cách Với eksctl
Dùng eksctl — công cụ CLI chính thức được Weaveworks phát triển và AWS khuyến nghị. Đừng dùng console để tạo cluster production: không có IaC, không track được config, không reproduce được khi cần tạo cluster mới ở region khác hoặc sau disaster recovery.
File config YAML cho cluster production (khuyến nghị hơn dùng CLI flags):
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: prod-cluster
region: ap-southeast-1
version: "1.29"
availabilityZones:
- ap-southeast-1a
- ap-southeast-1b
- ap-southeast-1c
managedNodeGroups:
- name: standard-workers
instanceType: t3.medium
minSize: 2
maxSize: 6
desiredCapacity: 3
volumeSize: 50
ssh:
allow: false
iam:
withAddonPolicies:
autoScaler: true
cloudWatch: true
albIngress: true
addons:
- name: vpc-cni
- name: coredns
- name: kube-proxyTạo cluster và update kubeconfig:
eksctl create cluster -f cluster.yaml
aws eks update-kubeconfig --region ap-southeast-1 --name prod-cluster
kubectl get nodesQuá trình tạo cluster mất khoảng 15–20 phút. Trong thời gian đó, eksctl tạo: VPC với public/private subnets, Security Groups, IAM roles cho node và control plane, rồi mới tạo cluster và node group. Tất cả đều được deploy qua CloudFormation, có thể rollback nếu bất kỳ bước nào fail.
managedNodeGroups dùng EKS Managed Node Groups — AWS sẽ tự xử lý rolling update khi upgrade Kubernetes version, thay vì phải drain node thủ công từng cái. Một điểm quan trọng: Managed Node Groups không support custom user data đầy đủ như Self-managed, nhưng với 95% use case thì không cần.
Về networking, mình khuyến nghị deploy worker nodes vào private subnets và chỉ expose ra ngoài qua ALB. Không có lý do gì để node có public IP trong production. Cấu hình này trong eksctl:
managedNodeGroups:
- name: standard-workers
privateNetworking: true # Thêm dòng này
...Mẹo xương máu: Luôn deploy với node trải đều 3 AZ. Nếu một AZ die — và điều này đã xảy ra với ap-southeast-1b vào năm 2023, kéo dài hơn 2 giờ — cluster vẫn còn quorum. Đừng tiết kiệm vài chục USD/tháng mà đặt cược uptime production vào 1–2 AZ.
Cấu Hình Autoscaling: HPA, Cluster Autoscaler Và Karpenter
EKS có ba lớp scaling, mỗi lớp giải quyết bài toán khác nhau:
Horizontal Pod Autoscaler (HPA) — Scale số Pod của một Deployment/StatefulSet dựa trên CPU, memory, hoặc custom metrics từ Prometheus. Đây là lớp đầu tiên nên cấu hình, vì nó phản ứng nhanh nhất (vài chục giây).
Cluster Autoscaler (CA) — Scale số Node khi Pod không schedule được vì thiếu tài nguyên, hoặc scale down node khi underutilized. CA gọi AWS Auto Scaling Group API. Phản ứng chậm hơn HPA (1–3 phút để node join cluster).
Karpenter — Alternative mới hơn cho CA, do AWS phát triển. Provisioner trực tiếp EC2 instances thay vì thông qua Auto Scaling Group. Nhanh hơn (~30 giây), linh hoạt hơn trong việc chọn instance type, và có thể dùng Spot + On-demand trong cùng node pool. Với cluster mới từ 2024 trở đi, mình khuyến nghị dùng Karpenter thay CA.
Cài metrics-server (bắt buộc cho HPA):
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# Verify sau 30 giây
kubectl top nodesTạo HPA cho deployment:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # Đợi 5 phút trước khi scale down
policies:
- type: Percent
value: 50
periodSeconds: 60Block behavior.scaleDown quan trọng — không có nó, HPA scale down ngay khi load giảm, gây flapping nếu traffic không ổn định.
Cài Cluster Autoscaler qua Helm (với eksctl, IAM policy đã được cấp qua withAddonPolicies.autoScaler: true):
helm repo add autoscaler https://kubernetes.github.io/autoscaler
helm install cluster-autoscaler autoscaler/cluster-autoscaler \
--namespace kube-system \
--set autoDiscovery.clusterName=prod-cluster \
--set awsRegion=ap-southeast-1 \
--set extraArgs.balance-similar-node-groups=true \
--set extraArgs.skip-nodes-with-system-pods=false \
--set extraArgs.scale-down-delay-after-add=5m \
--set extraArgs.scale-down-unneeded-time=5m
Lỗi hay gặp với CA: scale-down bị block bởi PodDisruptionBudget quá strict. Nếu set maxUnavailable: 0 trên toàn bộ replicas, CA không evict được Pod nào, không drain node được, không scale down được. Kiểm tra PDB:
kubectl get pdb -A
kubectl describe pdb my-app-pdbCA scale-down chậm hơn scale-up nhiều (mặc định đợi 10 phút unused). Đây là chủ ý — tránh flapping. Tune
scale-down-delay-after-addvàscale-down-unneeded-timenếu cần scale-down nhanh hơn.
Monitoring Production-Grade: CloudWatch + Prometheus Stack
Monitoring EKS nên có hai lớp: infra-level và application-level. Dùng một lớp thôi thì luôn có blind spot.
CloudWatch Container Insights — AWS native, ít cấu hình. Dùng cho node-level metrics (CPU, memory, disk, network), container crash loops, và log aggregation qua Fluent Bit. Tích hợp sẵn với CloudWatch Alarms để gửi SNS/PagerDuty khi ngưỡng vượt.
Prometheus + Grafana — Dùng cho application-level metrics (latency, error rate, throughput), custom business metrics, và alerting phức tạp hơn (ví dụ: alert khi error rate tăng 3x so với baseline 1 giờ trước). Không lock-in AWS.
Bật CloudWatch Container Insights qua addon:
aws eks create-addon \
--cluster-name prod-cluster \
--addon-name amazon-cloudwatch-observability \
--region ap-southeast-1Addon tự cài CloudWatch agent và Fluent Bit dưới dạng DaemonSet. Logs từ container stdout/stderr sẽ được ship vào CloudWatch Logs tự động, group theo /aws/containerinsights/prod-cluster/application. Metrics xuất hiện trên CloudWatch sau 2–3 phút.
Tạo CloudWatch Alarm cho node memory:
aws cloudwatch put-metric-alarm \
--alarm-name eks-node-memory-high \
--metric-name node_memory_utilization \
--namespace ContainerInsights \
--dimensions Name=ClusterName,Value=prod-cluster \
--period 300 \
--evaluation-periods 2 \
--threshold 85 \
--comparison-operator GreaterThanThreshold \
--alarm-actions arn:aws:sns:ap-southeast-1:ACCOUNT_ID:alertsVới Prometheus + Grafana, dùng kube-prometheus-stack — bộ all-in-one tốt nhất hiện tại:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=YourStrongPassword \
--set prometheus.prometheusSpec.retention=15d \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=gp2 \
--set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50GiStack này cài luôn: Prometheus, Grafana, Alertmanager, kube-state-metrics, node-exporter, và các ServiceMonitor mặc định cho toàn bộ Kubernetes core components. Access Grafana lần đầu:
kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80
# Mở http://localhost:3000 với admin/YourStrongPasswordSau đó expose qua ALB Ingress với HTTPS và restrict access theo IP. Dashboard "Kubernetes / Compute Resources / Cluster" trong Grafana là điểm khởi đầu tốt nhất để hiểu tổng quan resource usage của cluster.
Nếu chỉ dùng CloudWatch, bạn bị locked vào AWS. Prometheus cho phép giữ nguyên alerting rules và Grafana dashboards khi chuyển platform. Với team đang cân nhắc multi-cloud hoặc migration, đây là lợi thế đáng kể.
Những Ổ Gà Mình Từng Dẫm
1. Node không join cluster sau khi launch — Hay gặp khi Security Group của node group không có outbound 443 đến control plane endpoint, hoặc VPC DNS resolution bị tắt. Cũng kiểm tra node IAM role — cần ba managed policy: AmazonEKSWorkerNodePolicy, AmazonEC2ContainerRegistryReadOnly, AmazonEKS_CNI_Policy. Thiếu một trong ba là node không join được.
2. Pod stuck ở Pending dù cluster còn resource — Nguyên nhân phổ biến nhất là taint/toleration mismatch. Kiểm tra:
kubectl describe pod my-pod-name
# Xem phần Events ở cuối output để thấy lý do scheduling failVới spot instances, node thường có taint eks.amazonaws.com/capacityType=SPOT:NoSchedule — pod cần toleration tương ứng nếu muốn schedule lên spot node. Nếu không có toleration, pod chỉ schedule được lên on-demand node, có thể gây thiếu capacity.
3. ALB Ingress không tạo được Load Balancer — Cần hai điều: AWS Load Balancer Controller được cài đúng (verify pod đang Running trong kube-system) và annotation đúng trong Ingress. Xem log controller để debug:
kubectl logs -n kube-system deployment/aws-load-balancer-controller --tail=50Annotation hay bị quên: kubernetes.io/ingress.class: alb (hoặc dùng IngressClass resource với Kubernetes 1.22+).
4. ImagePullBackOff từ ECR — Thường do node IAM role thiếu AmazonEC2ContainerRegistryReadOnly, hoặc ECR repository ở region khác mà URI không đúng format. ECR URI: ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/repo:tag. Cross-region pull thì cần thêm permission ECR của region nguồn.
5. OOMKilled liên tục — Đừng chỉ tăng memory limit ngay. Profile ứng dụng trước bằng kubectl top pod theo thời gian và Grafana memory trend. Nhiều trường hợp là memory leak trong code — tăng limit mà không fix leak chỉ là delay sự cố, không giải quyết gốc rễ.
6. CoreDNS quá tải — Khi cluster lớn (100+ pod), CoreDNS hay bị quá tải bởi DNS lookup. Giải pháp: tăng replica CoreDNS, và bật NodeLocal DNSCache để Pod resolve DNS locally thay vì phải đi qua CoreDNS mỗi lần. Giảm latency DNS đáng kể và giảm tải CoreDNS.
FAQ
Q: EKS Fargate có nên dùng cho production không?A: Phụ thuộc workload. Fargate tốt cho batch jobs, microservices stateless, CI/CD runners, và khi không muốn quản lý node groups. Không phù hợp với stateful apps cần persistent volume phức tạp, workload cần privileged containers, hoặc cần DaemonSet (Fargate không support). Cost cao hơn EC2 ~20–30% nếu utilization thấp. Mình thường dùng hybrid: EC2 managed node groups cho production traffic, Fargate cho scheduled batch jobs và CI runners.
Q: Upgrade Kubernetes version EKS như thế nào để ít rủi ro nhất?A: Quy trình chuẩn: (1) Đọc release notes của version target và Kubernetes changelog — check API deprecation; (2) Test trên staging cluster chạy workload giống production; (3) Upgrade control plane trước qua eksctl hoặc console — mất 10–15 phút; (4) Upgrade managed node groups — AWS rolling update từng node; (5) Upgrade addons (vpc-cni, coredns, kube-proxy). AWS support mỗi minor version khoảng 14 tháng — đừng để lag quá 2 version, upgrade càng nhỏ càng an toàn hơn.
Q: Làm sao để giảm chi phí EKS đáng kể mà không ảnh hưởng availability?A: Ba đòn chính có thể giảm 40–60% bill: (1) Spot Instances cho non-critical workloads — dùng mixed instance policy với ít nhất 5 instance type khác nhau để tăng availability khi Spot bị reclaim; (2) Cluster Autoscaler hoặc Karpenter bật scale-down để giảm node ban đêm và cuối tuần khi traffic thấp; (3) Set resource requests/limits chính xác — over-provisioning là kẻ thù lớn nhất của cost efficiency. Dùng Goldilocks hoặc Vertical Pod Autoscaler trong recommendation mode để biết requests/limits phù hợp với usage thực tế.
Setup Đúng Từ Đầu — Ngủ Ngon Về Sau
EKS không phải silver bullet, nhưng nó giải quyết đúng phần phức tạp nhất của Kubernetes cho team vừa và nhỏ: vận hành control plane. Phần còn lại — thiết kế workload, cấu hình networking, autoscaling, monitoring — vẫn là trách nhiệm của team, và đó cũng là phần thú vị hơn nhiều so với patch etcd lúc 2 giờ sáng.
Điều mình muốn nhấn mạnh nhất: đừng skip monitoring. Cluster không có observability là cluster đang chờ sự cố — và bạn sẽ chỉ biết khi người dùng báo lỗi, không phải khi alert bắn ra lúc còn kịp xử lý. Chi phí setup CloudWatch Container Insights và Prometheus stack nhỏ hơn nhiều so với thời gian debug blind khi production sập.
Với stack đầy đủ — eksctl với managed node groups, Cluster Autoscaler hoặc Karpenter, HPA với metrics-server, CloudWatch Container Insights, và kube-prometheus-stack — bạn sẽ thấy sự khác biệt rõ rệt khi traffic spike: cluster tự scale, alert bắn ra trước khi user phàn nàn, và tất cả những gì cần làm lúc 3 giờ sáng là check notification rồi tiếp tục ngủ.
