Introduction to Istio, Part 9

·

12 min read

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ỗi reset (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

  1. 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.

  2. 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.

  3. 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.

  4. 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:

  1. Deployment:

    • Tạo một bản sao ứng dụng web-frontend sử dụng container image gcr.io/tetratelabs/web-frontend:1.0.0.

    • Biến môi trường CUSTOMER_SERVICE_URL trỏ đến dịch vụ customers trong namespace default.

  2. Service:

    • Expose web-frontend trên cổng 80, chuyển hướng lưu lượng đến cổng container 8080.
  3. VirtualService:

    • Định tuyến lưu lượng HTTP từ Gateway đến dịch vụ web-frontend.
  • 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

  1. Deployment:

  2. Service:

    • Service expose dịch vụ customers trên cổng 80 và chuyển lưu lượng đến cổng container 3000.
  3. DestinationRule:

    • Cấu hình DestinationRule để hỗ trợ subsets (tập con) dựa trên nhãn version: v1.
  4. VirtualService:

    • Định tuyến lưu lượng HTTP đến subset v1 của dịch vụ customers.

Áp Dụng Cấu Hình

  1. Lưu YAML trên vào tệp customers.yaml.

  2. Á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

  1. Lưu cấu hình trên vào tệp customers-delay.yaml.

  2. 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.
  • 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.

Kiểm Tra

  1. 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

  1. Lưu cấu hình trên vào tệp customers-fault.yaml.

  2. 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.

Kiểm Tra

  1. 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.