Introduction to Istio, Part 9
Service Resiliency & Failure Injection
(Resiliency) là khả năng cung cấp và duy trì một mức độ dịch vụ chấp nhận được khi gặp phải các sự cố và thách thức trong quá trình hoạt động. Mục tiêu của tính Resiliency không phải là tránh khỏi thất bại mà là phản ứng lại với các thất bại đó để không xảy ra thời gian ngừng hoạt động hoặc mất mát dữ liệu. Mục tiêu cuối cùng là đưa dịch vụ trở lại trạng thái hoạt động đầy đủ sau khi xảy ra sự cố.
Một yếu tố quan trọng trong việc đảm bảo dịch vụ luôn sẵn sàng là việc sử dụng các chính sách timeout và retry khi thực hiện các yêu cầu đến dịch vụ. Chúng ta có thể cấu hình cả hai chính sách này trong tài nguyên VirtualService.
Timeout
Sử dụng trường timeout, chúng ta có thể định nghĩa thời gian giới hạn cho các yêu cầu HTTP. Nếu yêu cầu mất quá nhiều thời gian so với giá trị đã được chỉ định trong trường timeout, proxy Envoy sẽ hủy bỏ yêu cầu và đánh dấu là yêu cầu đã hết thời gian (trả về HTTP 408 cho ứng dụng). Các kết nối vẫn sẽ được giữ mở trừ khi phát hiện sự cố (outlier detection).
Dưới đây là ví dụ về cách thiết lập timeout cho một route:
- route:
- destination:
host: customers.default.svc.cluster.local
subset: v1
timeout: 10s
Trong ví dụ trên, nếu một yêu cầu đến dịch vụ customers-v1
mất hơn 10 giây, proxy Envoy sẽ hủy bỏ yêu cầu và trả về mã lỗi HTTP 408.
Retry Policy
Ngoài các timeout, chúng ta cũng có thể cấu hình một chính sách retry chi tiết hơn. Chính sách này cho phép chúng ta kiểm soát số lần thử lại cho mỗi yêu cầu, thời gian timeout cho mỗi lần thử và các điều kiện cụ thể để kích hoạt một lần thử lại. Cả retry và timeout đều được xử lý ở phía client.
Ví dụ, chúng ta có thể chỉ retry yêu cầu nếu máy chủ upstream trả về mã lỗi 5xx, hoặc chỉ retry khi gặp phải lỗi cổng (HTTP 502, 503, hoặc 504). Chúng ta cũng có thể chỉ định các mã trạng thái có thể retry trong headers của yêu cầu. Khi Envoy thử lại một yêu cầu bị lỗi, endpoint ban đầu sẽ không còn nằm trong nhóm cân bằng tải nữa.
Giả sử dịch vụ Kubernetes có ba endpoint (Pods) và một trong số đó gặp lỗi có thể retry. Khi Envoy thử lại yêu cầu, nó sẽ không gửi lại yêu cầu đến endpoint ban đầu mà thay vào đó sẽ gửi yêu cầu đến một trong hai endpoint còn lại chưa gặp lỗi.
Dưới đây là ví dụ về cách thiết lập một chính sách retry cho một route cụ thể:
- route:
- destination:
host: customers.default.svc.cluster.local
subset: v1
retries:
attempts: 10
perTryTimeout: 2s
retryOn: connect-failure,reset
Trong chính sách retry trên:
attempts: Đặt số lần thử lại là 10.
perTryTimeout: Thời gian timeout cho mỗi lần thử là 2 giây.
retryOn: Retry sẽ xảy ra khi gặp phải lỗi
connect-failure
(thất bại khi kết nối) hoặc lỗireset
(khi server không phản hồi).
Lưu ý rằng nếu chúng ta thiết lập cả retry và timeout, giá trị timeout sẽ là thời gian tối đa mà yêu cầu có thể chờ đợi. Nếu trong ví dụ trên, chúng ta thiết lập timeout là 10 giây, thì dù còn bao nhiêu lần thử lại, yêu cầu cũng chỉ chờ tối đa 10 giây.
Circuit Breaking với Outlier Detection
Một mẫu thiết kế khác để tạo ra các ứng dụng dẻo dài là circuit breaking (ngắt mạch). Đây là một phương pháp giúp giới hạn tác động của các lỗi, đỉnh trễ, và các sự cố mạng khác đến dịch vụ.
Outlier detection (phát hiện ngoại lệ) là một cách triển khai của ngắt mạch và là một dạng kiểm tra sức khỏe thụ động. Đây là kiểm tra thụ động vì Envoy không chủ động gửi yêu cầu để xác định trạng thái sức khỏe của các endpoints. Thay vào đó, Envoy quan sát hiệu suất của các pod khác nhau để xác định liệu chúng có khỏe hay không. Nếu các pod bị đánh giá là không khỏe, chúng sẽ bị loại khỏi nhóm cân bằng tải khỏe mạnh.
Việc kiểm tra sức khỏe của các pod được thực hiện thông qua các yếu tố như số lần lỗi liên tiếp, tỷ lệ thành công theo thời gian, độ trễ, v.v.
Cấu hình Outlier Detection trong Istio
Outlier detection trong Istio được cấu hình trong tài nguyên DestinationRule. Dưới đây là một ví dụ cấu hình outlier detection:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: customers
spec:
host: customers
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutive5xxErrors: 1
interval: 1s
baseEjectionTime: 3m
maxEjectionPercent: 100
Giải thích các trường cấu hình:
connectionPool: Đây là phần cấu hình cho các kết nối TCP và HTTP. Cấu hình này giới hạn số kết nối TCP và số yêu cầu HTTP có thể chờ đợi cùng một lúc. Nếu có bất kỳ điều kiện nào vượt quá giới hạn, circuit breaker sẽ được kích hoạt và dịch vụ sẽ trả về lỗi HTTP 503 (dịch vụ không khả dụng).
maxConnections
: Giới hạn số kết nối TCP tối đa cho mỗi pod.http1MaxPendingRequests
: Giới hạn số yêu cầu HTTP 1.x có thể chờ xử lý cùng một lúc.maxRequestsPerConnection
: Giới hạn số yêu cầu HTTP được xử lý trên mỗi kết nối.
outlierDetection: Cấu hình này kiểm tra các pod có lỗi liên tục và xác định nếu chúng nên bị loại khỏi nhóm cân bằng tải.
consecutive5xxErrors
: Số lỗi 5xx liên tiếp mà một pod có thể gặp phải trước khi bị coi là một outlier.interval
: Thời gian giữa các lần kiểm tra sức khỏe.baseEjectionTime
: Thời gian một pod bị loại khỏi nhóm cân bằng tải. Nếu pod tiếp tục gặp lỗi, thời gian này sẽ được kéo dài.maxEjectionPercent
: Tỷ lệ tối đa phần trăm pod có thể bị loại khỏi nhóm cân bằng tải khi gặp phải lỗi vượt ngưỡng.
Quá Trình Hoạt Động
Kiểm tra sức khỏe pod: Envoy sẽ kiểm tra sức khỏe của các pod định kỳ theo khoảng thời gian được chỉ định trong trường
interval
.Phát hiện outlier: Khi một pod gặp phải số lượng lỗi 5xx liên tiếp vượt qua ngưỡng (
consecutive5xxErrors
), pod đó sẽ bị xác định là một outlier.Ejection của pod: Nếu pod bị phát hiện là outlier, Envoy sẽ loại bỏ nó khỏi nhóm cân bằng tải. Sự loại bỏ này sẽ kéo dài trong khoảng thời gian được chỉ định bởi
baseEjectionTime
và sẽ được gia tăng nếu pod tiếp tục thất bại.Trở lại nhóm khỏe mạnh: Sau khi hết thời gian ejection và nếu pod phục hồi (không còn lỗi), pod sẽ quay lại nhóm cân bằng tải khỏe mạnh.
Failure Injection
Một tính năng khác giúp chúng ta đảm bảo tính dẻo dài cho dịch vụ là “tiêm lỗi”. Chúng ta có thể áp dụng các chính sách tiêm lỗi đối với lưu lượng HTTP và chỉ định một hoặc nhiều lỗi để tiêm vào khi chuyển tiếp yêu cầu tới đích.
Có hai loại tiêm lỗi. Chúng ta có thể trì hoãn yêu cầu trước khi chuyển tiếp, mô phỏng một mạng chậm hoặc dịch vụ quá tải, hoặc hủy yêu cầu HTTP và trả về một mã lỗi HTTP cụ thể cho người gọi. Với việc hủy yêu cầu, chúng ta có thể mô phỏng một dịch vụ upstream bị lỗi.
Dưới đây là một ví dụ về việc hủy yêu cầu HTTP và trả về mã lỗi HTTP 404 cho 30% số yêu cầu đến:
- route:
- destination:
host: customers.default.svc.cluster.local
subset: v1
fault:
abort:
percentage:
value: 30
httpStatus: 404
Proxy Envoy sẽ hủy tất cả các yêu cầu nếu chúng ta không chỉ định tỷ lệ phần trăm. Lưu ý rằng tiêm lỗi chỉ ảnh hưởng đến các dịch vụ sử dụng VirtualService đó. Nó không ảnh hưởng đến tất cả các người tiêu dùng của dịch vụ. Ví dụ, nếu chúng ta cấu hình một lỗi cho một tên máy chủ cụ thể (ví dụ: example.com), thì bất kỳ yêu cầu nào sử dụng tên máy chủ hello.com sẽ không bị tiêm lỗi.
Tương tự, chúng ta có thể áp dụng một độ trễ tùy chọn đối với các yêu cầu bằng cách sử dụng trường fixedDelay:
- route:
- destination:
host: customers.default.svc.cluster.local
subset: v1
fault:
delay:
percentage:
value: 5
fixedDelay: 3s
Cấu hình trên sẽ áp dụng độ trễ 3 giây cho 5% số yêu cầu đến.
Lưu ý rằng việc tiêm lỗi sẽ không kích hoạt bất kỳ chính sách retry (thử lại) nào mà chúng ta đã cấu hình trên các route. Ví dụ, nếu chúng ta tiêm lỗi HTTP 500, chính sách retry được cấu hình để thử lại khi gặp lỗi HTTP 500 sẽ không được kích hoạt.
Triển Khai Gateway và Ứng Dụng Web-Frontend
1. Triển khai Gateway
Đầu tiên, triển khai một Gateway để quản lý lưu lượng HTTP đến các dịch vụ trong cluster. Gateway này sẽ lắng nghe trên cổng 80 và chấp nhận lưu lượng từ mọi tên miền ('*'
).
Cấu hình YAML:
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- '*'
Lưu YAML trên vào tệp
gateway.yaml
, sau đó chạy lệnh sau để áp dụng Gateway:kubectl apply -f gateway.yaml
2. Triển khai Ứng Dụng Web-Frontend
Ứng dụng web-frontend
bao gồm một Deployment, một Service, và một VirtualService để định tuyến lưu lượng từ Gateway đến dịch vụ.
YAML cấu hình:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
replicas: 1
selector:
matchLabels:
app: web-frontend
template:
metadata:
labels:
app: web-frontend
version: v1
spec:
containers:
- image: gcr.io/tetratelabs/web-frontend:1.0.0
imagePullPolicy: Always
name: web
ports:
- containerPort: 8080
env:
- name: CUSTOMER_SERVICE_URL
value: 'http://customers.default.svc.cluster.local'
---
kind: Service
apiVersion: v1
metadata:
name: web-frontend
labels:
app: web-frontend
spec:
selector:
app: web-frontend
ports:
- port: 80
name: http
targetPort: 8080
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: web-frontend
spec:
hosts:
- '*'
gateways:
- gateway
http:
- route:
- destination:
host: web-frontend.default.svc.cluster.local
port:
number: 80
Giải thích:
Deployment:
Tạo một bản sao ứng dụng
web-frontend
sử dụng container imagegcr.io/tetratelabs/web-frontend:1.0.0
.Biến môi trường
CUSTOMER_SERVICE_URL
trỏ đến dịch vụcustomers
trong namespacedefault
.
Service:
- Expose
web-frontend
trên cổng 80, chuyển hướng lưu lượng đến cổng container 8080.
- Expose
VirtualService:
- Định tuyến lưu lượng HTTP từ Gateway đến dịch vụ
web-frontend
.
- Định tuyến lưu lượng HTTP từ Gateway đến dịch vụ
Lưu YAML trên vào tệp
web-frontend.yaml
, sau đó chạy lệnh sau để tạo các tài nguyên:kubectl apply -f web-frontend.yaml
3. Kiểm Tra
Truy cập ứng dụng: Lấy địa chỉ IP của Gateway để kiểm tra ứng dụng:
export GATEWAY_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo "http://$GATEWAY_IP"
Mở địa chỉ này trong trình duyệt để xác nhận ứng dụng
web-frontend
hoạt động.
Tiếp theo, bạn có thể triển khai dịch vụ customers-v1
, cấu hình lỗi (fault injection) và độ trễ (delay) để quan sát chúng trong Jaeger, Kiali, và Grafana.
Để hoàn thiện hệ thống, chúng ta sẽ triển khai dịch vụ customers-v1
cùng với các tài nguyên liên quan như Service, DestinationRule, và VirtualService.
Cấu Hình YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: customers-v1
labels:
app: customers
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: customers
version: v1
template:
metadata:
labels:
app: customers
version: v1
spec:
containers:
- image: gcr.io/tetratelabs/customers:1.0.0
imagePullPolicy: Always
name: svc
ports:
- containerPort: 3000
---
kind: Service
apiVersion: v1
metadata:
name: customers
labels:
app: customers
spec:
selector:
app: customers
ports:
- port: 80
name: http
targetPort: 3000
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: customers
spec:
host: customers.default.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
subset: v1
Giải Thích
Deployment:
Tạo một Deployment cho phiên bản
v1
của ứng dụngcustomers
.Container image
gcr.io/tetratelabs/customers:1.0.0
lắng nghe trên cổng 3000.
Service:
- Service expose dịch vụ
customers
trên cổng 80 và chuyển lưu lượng đến cổng container 3000.
- Service expose dịch vụ
DestinationRule:
- Cấu hình DestinationRule để hỗ trợ subsets (tập con) dựa trên nhãn
version: v1
.
- Cấu hình DestinationRule để hỗ trợ subsets (tập con) dựa trên nhãn
VirtualService:
- Định tuyến lưu lượng HTTP đến subset
v1
của dịch vụcustomers
.
- Định tuyến lưu lượng HTTP đến subset
Áp Dụng Cấu Hình
Lưu YAML trên vào tệp
customers.yaml
.Áp dụng cấu hình bằng lệnh sau:
kubectl apply -f customers.yaml
Kiểm Tra
Xác nhận Pod:
kubectl get pods
Kiểm tra dịch vụ:
kubectl get svc customers
Gửi yêu cầu thử nghiệm (sử dụng Gateway IP từ bước trước):
export GATEWAY_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') curl http://$GATEWAY_IP/customers
Sau khi triển khai thành công, bạn có thể tiếp tục thử nghiệm các tính năng như Fault Injection và quan sát trong Jaeger, Kiali, và Grafana
Cấu Hình Injecting Delay
Chúng ta sẽ cấu hình một độ trễ cố định (fixedDelay) là 5 giây cho 50% số request đến dịch vụ customers-v1
. Cấu hình này được thực hiện trên tài nguyên VirtualService của dịch vụ customers
.
Cấu Hình YAML
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
subset: v1
fault:
delay:
percentage:
value: 50
fixedDelay: 5s
Áp Dụng Cấu Hình
Lưu cấu hình trên vào tệp
customers-delay.yaml
.Cập nhật VirtualService bằng lệnh sau:
kubectl apply -f customers-delay.yaml
Giải Thích
fault.delay.fixedDelay
:- Cấu hình độ trễ cố định 5 giây cho các yêu cầu HTTP đến dịch vụ
customers-v1
.
- Cấu hình độ trễ cố định 5 giây cho các yêu cầu HTTP đến dịch vụ
percentage.value
:- Chỉ định tỷ lệ phần trăm request bị ảnh hưởng. Trong trường hợp này, 50% số request sẽ gặp độ trễ.
Ảnh hưởng:
- Các dịch vụ khác gọi đến
customers-v1
(qua VirtualService) sẽ chịu độ trễ này.
- Các dịch vụ khác gọi đến
Kiểm Tra
Gửi yêu cầu thử nghiệm:
export GATEWAY_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') curl http://$GATEWAY_IP/customers
- Gửi nhiều yêu cầu để quan sát hiện tượng delay.
Cấu Hình Injecting Failure
Bây giờ, chúng ta sẽ cấu hình để tiêm lỗi (fault injection) vào dịch vụ customers-v1
. Cấu hình này sẽ trả về HTTP 500 cho 50% số request đến dịch vụ.
Cấu Hình YAML
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: customers
spec:
hosts:
- 'customers.default.svc.cluster.local'
http:
- route:
- destination:
host: customers.default.svc.cluster.local
port:
number: 80
subset: v1
fault:
abort:
httpStatus: 500
percentage:
value: 50
Áp Dụng Cấu Hình
Lưu cấu hình trên vào tệp
customers-fault.yaml
.Cập nhật VirtualService bằng lệnh sau:
kubectl apply -f customers-fault.yaml
Giải Thích
fault.abort.httpStatus
:- Trả về mã lỗi HTTP 500 cho các yêu cầu bị ảnh hưởng.
percentage.value
:- Tỷ lệ phần trăm yêu cầu bị ảnh hưởng. Trong ví dụ này, 50% số request sẽ trả về lỗi HTTP 500.
Ảnh hưởng:
- Các yêu cầu HTTP đến dịch vụ
customers-v1
qua VirtualService sẽ chịu lỗi này.
- Các yêu cầu HTTP đến dịch vụ
Kiểm Tra
Gửi yêu cầu thử nghiệm:
export GATEWAY_IP=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}') curl http://$GATEWAY_IP/customers
- Gửi nhiều yêu cầu và quan sát, 50% yêu cầu sẽ trả về mã HTTP 500.