Đảm bảo khả năng sẵn sàng cao cho ứng dụng trên Kubernetes - High Availability for application on Kubernetes
Mở đầu
Chúng ta đã nghe rất nhiều đến những lợi ích khi sử dụng Kuberntes để chạy ứng dụng dụng microservices, điều này vô tình làm mọi người tin rằng ứng dụng cứ chạy trên Kubernetes là sẽ có độ sẵn sàng cao, khả năng chịu lỗi tốt, scale nhanh,...
Tuy nhiên thực tế thì để đạt được độ sẵn sàng cao (High Availability) cho ứng dụng trên K8s yêu cầu nhiều cấu hình cụ thể phù hợp với từng ứng dụng khác nhau. Trong bài viết ngày hôm nay mình sẽ đề cập đến các cấu hình mà chúng ta cần quan tâm nếu muốn đảm bảo ứng dụng chạy ít lỗi, không có downtime,... gọi chung là tính sẵn sàng cao.
Deployment Strategy
Deployment Strategy là chiến lược triển khai ứng dụng giúp bạn kiểm soát cách thức triển khai và cập nhật ứng dụng trong Kubernetes. Có hai chiến lược phổ biến là Rolling Update (Phương thức mặc định trong Kubernetes) và Recreate.
Rolling Update cho phép triển khai từng phiên bản mới theo cách tuần tự, cập nhật một pod tại một thời điểm. Điều này giúp đảm bảo rằng phiên bản cũ vẫn hoạt động trong khi phiên bản mới được triển khai dần dần và không gây ra downtime.
Recreate, ngược lại, tắt tất cả các pod cũ trước khi triển khai phiên bản mới – phù hợp cho các ứng dụng không cần duy trì tính liên tục. Phương thức này sẽ gây ra downtime trong quá trình đợi phiên bản mới sẵn sàng.
Bạn cũng có thể kết hợp các chiến lược canary hoặc blue-green deployment để kiểm thử phiên bản mới một cách an toàn. Lựa chọn chiến lược triển khai phù hợp giúp bạn duy trì dịch vụ ổn định trong quá trình cập nhật và giảm thiểu rủi ro trong suốt vòng đời của ứng dụng.
Sau đây là ví dụ cho việc cấu hình sử dụng chiến lược Rolling Update cho một deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
strategy:
type: RollingUpdate # Deployment strategy config
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: myapp-image:v2
Readiness & Liveness
Readiness Probe và Liveness Probe là hai loại healthcheck quan trọng cho các pod trong Kubernetes. 2 healthcheck này giúp đảm bảo dịch vụ của bạn luôn trong trạng thái sẵn sàng và ổn định.
Readiness Probe xác định thời điểm một container có thể nhận yêu cầu mới, tức là nếu một container đang tải dữ liệu hoặc kết nối đến các dịch vụ phụ thuộc thì nó sẽ không được truy cập cho đến khi hoàn toàn sẵn sàng. Điều này ngăn chặn người dùng khỏi việc tiếp cận dịch vụ khi nó chưa sẵn sàng hoạt động, giúp nâng cao trải nghiệm. Nếu người dùng truy cập vào một dịch vụ web khi pod chưa sẵn sàng thì sẽ gặp lỗi HTTP Code 503
Trong khi đó, Liveness Probe kiểm tra xem container có đang hoạt động bình thường hay không. Nếu một container bị lỗi hoặc rơi vào trạng thái không ổn định, Kubernetes sẽ khởi động lại pod để tự động khắc phục. Sự kết hợp của hai cơ chế này giúp nâng cao tính sẵn sàng và độ tin cậy của hệ thống bằng cách đảm bảo chỉ các pod khỏe mạnh mới xử lý yêu cầu. Việc cấu hình thời gian kiểm tra như thế nào cũng rất quan trọng, mình đã đề cập đến Best Practice để cấu hình các Probes trong K8s trong bài viết trước đây.
⚠️ Cấu hình Startup, Readiness, Liveness Probe cho ứng dụng chạy trên Kubernetes
Cấu hình probe cho Deployment
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
containers:
- name: myapp-container
image: myapp-image
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
Resource Requests & Limits
Resource Requests và Limits là cài đặt cho phép bạn kiểm soát việc sử dụng tài nguyên của mỗi container để tối ưu hóa hiệu suất và tránh tình trạng quá tải hệ thống.
Requests là mức tài nguyên tối thiểu mà container yêu cầu để chạy ổn định, đảm bảo nó luôn có đủ tài nguyên khi cần.
Limits là mức tối đa tài nguyên mà container được phép sử dụng, giúp tránh tình trạng container chiếm hết tài nguyên của các ứng dụng khác.
Nếu không đặt các giới hạn này, một số container có thể chiếm nhiều tài nguyên hơn dự kiến, gây ra thiếu tài nguyên cho các container khác và ảnh hưởng đến độ ổn định của toàn bộ cluster. Việc cấu hình Requests và Limits hợp lý giúp bạn tối ưu hóa việc phân phối tài nguyên trong Kubernetes và tăng cường tính ổn định của hệ thống. Nếu cấu hình chỉ số quá cao sẽ gây ra lãng phí tài nguyên hệ thống, nếu cấu hình quá thấp thì ứng dụng có thể sẽ bị kill gây ra gián đoạn. Mình cũng đã có viết một bài chỉ các mình chọn resource limit và request cho ứng dụng, bạn có thể tham khảo ở đây nhé
🦾 Kubernetes resource management - Quản trị tài nguyên trong Kubernetes
Sau đây là một cấu hình mẫu resource request và limit cho K8s deployment
apiVersion: v1
kind: Pod
metadata:
name: resource-demo
spec:
containers:
- name: resource-demo-container
image: nginx
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
Horizontal Pod Autoscaler (HPA)
Horizontal Pod Autoscaler (HPA) là một tính năng tự động tăng hoặc giảm số lượng pod của ứng dụng dựa trên các chỉ số như CPU hoặc bộ nhớ. Điều này giúp dịch vụ của bạn có thể tự động mở rộng để đáp ứng nhu cầu tải cao và thu nhỏ lại khi tải giảm, tiết kiệm tài nguyên và chi phí vận hành. HPA dựa vào các chỉ số mà bạn đặt ra, chẳng hạn như sử dụng CPU đạt 70%, HPA sẽ tự động thêm pod để giảm tải cho các pod hiện tại. HPA không chỉ giúp duy trì hiệu suất và tính ổn định của dịch vụ, mà còn là một công cụ hữu ích để quản lý chi phí trong môi trường cloud khi tài nguyên được cấp phát dựa trên nhu cầu thực tế.
Đa số các trường hợp ta có thể scale dựa trên chỉ số CPU, tuy nhiên trong 1 số ứng dụng đặc biệt ta cần scale dựa trên memory hay số lượng request đến chằng hạn thì K8s không có hỗ trợ các chỉ số đó. Lúc này bạn cần cài các công cụ bổ sung để cung cấp các chỉ số (metrics) này, Keda là một trong số đó. Để tìm hiểu thêm về Keda bạn có thể đọc bài viết mình đã viết về công cụ này
Sau đây là cấu hình HPA ví dụ cho ứng dụng trên K8s
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
PostStart & PreStop Hook
PostStart Hook được kích hoạt ngay sau khi container khởi động, giúp bạn thực hiện các hành động cần thiết để thiết lập container hoặc chuẩn bị môi trường làm việc trước khi container chính thức nhận yêu cầu. Chẳng hạn, bạn có thể sử dụng PostStart để tải các file cấu hình cần thiết, khởi động các kết nối cần thiết, hoặc thực hiện các thao tác xác thực, cấu hình bổ sung.
Cấu hình PostStart hay PreStop sử dụng cú pháp exec để chạy các lệnh hoặc httpGet để gọi một API ngay sau khi container khởi động.
PostStart không đóng vai trò lớn trong việc duy trì khả năng sẵn sàng cho hệ thống nhưng cũng là một tính năng rất tiện lợi có thể sử dụng thay để initContainer
PreStop Hook là một hook quan trọng để đảm bảo quá trình ngừng hoạt động của container diễn ra mượt mà. Khi container chuẩn bị bị hủy, PreStop cho phép container thực hiện một số công việc dọn dẹp hoặc lưu trữ dữ liệu. Bạn có thể dùng PreStop để đóng các kết nối mạng, lưu dữ liệu đang xử lý, hoặc thông báo cho các dịch vụ khác rằng container sắp ngừng hoạt động. Điều này giúp tránh mất dữ liệu và giảm thiểu ảnh hưởng đến trải nghiệm người dùng.
Trong thực tế, rất nhiều ứng dụng có thời gian tắt khá lâu và lớn hơn 30 giây mặc định. Vậy nên các DevOps Engineer thường cấu hình câu lệnh sleep 30s
để kéo dài hơn quá trình shutdown 1 pod, giúp ứng dụng có thể đạt graceful shutdown.
Dưới đây là cấu hình mẫu khi sử dụng PostStart và PreStop để dừng dịch vụ nginx:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
Pod Disruption Budget (PDB)
Pod Disruption Budget (PDB) là một cơ chế cho phép bạn thiết lập số lượng tối đa các pod có thể ngừng hoạt động tại bất kỳ thời điểm nào để duy trì dịch vụ. Khi bạn thực hiện nâng cấp hệ thống hoặc có một sự cố nào đó, PDB đảm bảo rằng một số pod nhất định vẫn tiếp tục hoạt động để không ảnh hưởng đến trải nghiệm người dùng. PDB đặc biệt hữu ích khi kết hợp với các chiến lược cập nhật như Rolling Update, cho phép bạn bảo trì mà không làm gián đoạn dịch vụ. Pod Disruption Budget mình cho rằng là cấu hình luôn luôn phải có ở trong môi trường Production.
Ví dụ, nếu bạn có 5 pod, bạn có thể thiết lập PDB để chỉ cho phép 1 pod ngừng hoạt động tại một thời điểm. Điều này đảm bảo rằng ngay cả trong quá trình cập nhật hoặc sự cố, dịch vụ vẫn luôn duy trì tính sẵn sàng ở mức tối thiểu nhất định.
Cấu hình mẫu cho Pod Disruption Budget với số lượng pod sẵn sàng ít nhất là 2 :
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: myapp
Pod Anti-Affinity
Pod Anti-Affinity là một thiết lập giúp bạn định hướng vị trí triển khai các pod trong cluster để tránh việc chúng bị gom vào một node hay một zone duy nhất. Điều này rất quan trọng để đảm bảo độ sẵn sàng của dịch vụ, vì nếu các pod của cùng một ứng dụng đều nằm trên một node và node đó gặp sự cố, toàn bộ dịch vụ sẽ bị gián đoạn.
Bằng cách sử dụng Anti-Affinity, bạn có thể chỉ định rằng các pod cần được phân tán trên nhiều node khác nhau, giúp giảm thiểu rủi ro khi có sự cố node. Anti-Affinity thường được áp dụng cho các ứng dụng cần độ sẵn sàng cao và chịu tải lớn. Thiết lập này giúp tăng cường khả năng chịu lỗi và cải thiện độ sẵn sàng tổng thể của hệ thống.
Cấu hình mẫu Pod Anti-Affinity cho một deployment nginx:
apiVersion: apps/v1
kind: Deployment
metadata:
name: anti-affinity-demo
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- myapp
topologyKey: "kubernetes.io/hostname" # Yêu cầu các pod không nằm trên cùng 1 node
containers:
- name: nginx
image: nginx
Kết
Như vậy thông qua bài viết mình đã đưa đến các bạn tính năng trong Kubernetes giúp ta tăng và đảm bảo tính sẵn sàng cao của ứng dụng là:
- Readiness & Liveness Probes
- Resource request & limit
- Pod Anti-affinity
- Pod Disruption Budget
- Horizontal Pod Autoscaling
- PostStart & PreStop Hook
- Deployment Strategy
Ngoài ra còn rất nhiều các yếu tố khác ảnh hưởng đến độ sẵn sàng của ứng dụng như codebase, hạ tầng vật lý,... mà chúng ta cũng cần quan tâm đến. Tuy nhiên về phía DevOps quản trị tầng application thì đảm bảo cấu hình hợp lý các yếu tố trên đã đóng góp 80% trong khả năng sẵn sàng của ứng dụng rồi.
Hy vọng bài viết đã đem lại một vài ý tưởng mới cho bạn trong công việc! Nếu thấy bài viết hay hãy Upvote bài viết ở góc trên bên trái và Follow mình để hóng thêm các bài viết khác nữa nhé Have a nice day!
All rights reserved