Introduction to Istio, Part 7

·

14 min read

Traffic Routing in Istio

Istio cung cấp một số tài nguyên mà chúng ta có thể sử dụng để cấu hình cách lưu lượng truy cập được điều hướng trong mesh. Trước đó, chúng ta đã đề cập đến tài nguyên VirtualServiceGateway trong phần Gateway.

Chúng ta có thể sử dụng tài nguyên VirtualService để cấu hình các quy tắc điều hướng cho các dịch vụ trong Istio service mesh. Ví dụ, trong tài nguyên VirtualService, chúng ta có thể so khớp lưu lượng truy cập đầu vào dựa trên các thuộc tính của yêu cầu, sau đó điều hướng lưu lượng đó đến một hoặc nhiều điểm đến. Chẳng hạn, sau khi so khớp lưu lượng, chúng ta có thể phân chia nó theo trọng số, tiêm lỗi và/hoặc độ trễ, sao chép lưu lượng, v.v.

Tài nguyên DestinationRule chứa các quy tắc được áp dụng sau khi quyết định điều hướng (từ VirtualService) đã được thực hiện. Với DestinationRule, chúng ta có thể cấu hình cách thức tiếp cận dịch vụ đích. Ví dụ, chúng ta có thể cấu hình phát hiện dịch vụ ngoại lệ, thiết lập cân bằng tải, cấu hình kết nối và các thiết lập TLS cho dịch vụ đích.

Tài nguyên cuối cùng chúng ta cần đề cập là ServiceEntry. Tài nguyên này cho phép chúng ta đưa một dịch vụ bên ngoài hoặc API vào hệ thống, khiến nó trở thành một phần của mesh. Tài nguyên này thêm dịch vụ bên ngoài vào bảng đăng ký dịch vụ nội bộ, giúp chúng ta sử dụng các tính năng của Istio như điều hướng lưu lượng, tiêm lỗi và các tính năng khác đối với dịch vụ bên ngoài.

Điều Hướng Lưu Lượng Truy Cập Đến Đâu?

Trước khi proxy Envoy có thể quyết định điều hướng các yêu cầu, chúng ta cần có một cách để mô tả hệ thống và dịch vụ của mình.

Hãy cùng xem xét một ví dụ, trong đó chúng ta có một dịch vụ web-frontend và hai phiên bản (v1 và v2) của dịch vụ khách hàng chạy trong cluster. Về mặt tài nguyên, chúng ta có các triển khai sau trong cluster:

  • Kubernetes deployments: customers-v1, customers-v2 và web-frontend.

  • Kubernetes services: web-frontend và customers.

Để mô tả các phiên bản dịch vụ khác nhau, chúng ta sử dụng khái niệm labels trong Kubernetes. Các pod được tạo từ hai triển khai khách hàng có các label version: v1version: v2.

Lưu ý rằng chúng ta chỉ có một dịch vụ khách hàng duy nhất, dịch vụ này sẽ phân phối tải giữa tất cả các pod của dịch vụ khách hàng (dù chúng được tạo từ triển khai nào). Vậy làm thế nào Istio có thể biết và phân biệt giữa các phiên bản khác nhau của dịch vụ?

Chúng ta có thể thiết lập các label khác nhau trong mẫu spec của pod trong mỗi triển khai phiên bản, sau đó sử dụng các label này để Istio nhận biết hai phiên bản khác nhau hoặc các điểm đến lưu lượng. Các label này được sử dụng trong một cấu trúc gọi là subset, có thể được định nghĩa trong DestinationRule.

Để mô tả hai phiên bản của dịch vụ, chúng ta sẽ tạo một DestinationRule như sau:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: customers
spec:
  host: customers.default.svc.cluster.local
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

Khi Istio chuyển các tài nguyên này thành cấu hình cho Envoy proxy, các Envoy clusters riêng biệt sẽ được tạo ra để tương ứng với các phiên bản khác nhau. Istio sử dụng các endpoint của dịch vụ Kubernetes và áp dụng các label đã định nghĩa trong các subsets để tạo ra các bộ sưu tập endpoint riêng biệt. Bộ sưu tập logic của các endpoint này trong Envoy được gọi là cluster.

Các Envoy clusters được đặt tên bằng cách kết hợp hướng lưu lượng, cổng, tên subset và tên dịch vụ.

Ví dụ:

outbound|80|v1|customers.default.svc.cluster.local
outbound|80|v2|customers.default.svc.cluster.local

Bây giờ, khi Envoy đã có một nhóm endpoint có thể định tuyến, chúng ta có thể quyết định cách thức điều hướng lưu lượng đến các điểm đến này. Đây chính là lúc tài nguyên VirtualService phát huy tác dụng.

Cách Điều Hướng Lưu Lượng Truy Cập trong Istio

Trong VirtualService, chúng ta có thể chỉ định các quy tắc điều hướng và khớp lưu lượng để quyết định điểm đến của lưu lượng truy cập.

Chúng ta có nhiều lựa chọn khi quyết định cách điều hướng lưu lượng:

  1. Điều hướng dựa trên trọng số

  2. Khớp và điều hướng lưu lượng

  3. Chuyển hướng lưu lượng (HTTP 301)

  4. Sao chép lưu lượng đến một điểm đến khác

Các tùy chọn trên có thể được áp dụng và sử dụng riêng lẻ hoặc kết hợp trong cùng một tài nguyên VirtualService. Ngoài ra, chúng ta có thể thêm, thiết lập hoặc xóa các header yêu cầu và phản hồi, cấu hình cài đặt CORS, thời gian chờ, thử lại và tiêm lỗi.

Hãy cùng xem một vài ví dụ:

Điều Hướng Dựa Trên Trọng Số

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: customers-route
spec:
  hosts:
  - customers.default.svc.cluster.local
  http:
  - name: customers-v1-routes
    route:
    - destination:
        host: customers.default.svc.cluster.local
        subset: v1
      weight: 70
  - name: customers-v2-routes
    route:
    - destination:
        host: customers.default.svc.cluster.local
        subset: v2
      weight: 30

Trong ví dụ này, chúng ta phân chia lưu lượng dựa trên trọng số cho hai subset của cùng một dịch vụ, với 70% lưu lượng được điều hướng đến subset v1 và 30% đến subset v2.

Khớp và Điều Hướng Lưu Lượng

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: customers-route
spec:
  hosts:
  - customers.default.svc.cluster.local
  http:
  - match:
    - headers:
        user-agent:
          regex: ".*Firefox.*"
    route:
    - destination:
        host: customers.default.svc.cluster.local
        subset: v1
  - route:
    - destination:
        host: customers.default.svc.cluster.local
        subset: v2

Trong ví dụ này, chúng ta cung cấp một biểu thức chính quy để khớp giá trị User-Agent header. Nếu giá trị này khớp, lưu lượng sẽ được điều hướng đến subset v1. Ngược lại, nếu giá trị User-Agent không khớp, lưu lượng sẽ được điều hướng đến subset v2.

Chuyển Hướng Lưu Lượng

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: customers-route
spec:
  hosts:
  - customers.default.svc.cluster.local
  http:
  - match:
    - uri:
        exact: /api/v1/helloWorld
    redirect:
      uri: /v1/hello
      authority: hello-world.default.svc.cluster.local

Trong ví dụ này, chúng ta kết hợp việc khớp với URI và sau đó chuyển hướng lưu lượng đến một URI khác và một dịch vụ khác.

Sao Chép Lưu Lượng

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: customers-route
spec:
  hosts:
    - customers.default.svc.cluster.local
  http:
  - route:
    - destination:
        host: customers.default.svc.cluster.local
        subset: v1
      weight: 100
    mirror:
      host: customers.default.svc.cluster.local
      subset: v2
    mirrorPercentage:
      value: 100.0

Trong ví dụ này, chúng ta sao chép 100% lưu lượng đến subset v2. Việc sao chép lưu lượng sẽ gửi cùng một yêu cầu đến subset v1 và “sao chép” nó đến subset v2. Yêu cầu sẽ được thực hiện theo cơ chế “fire and forget”. Việc sao chép lưu lượng có thể được sử dụng để kiểm thử và gỡ lỗi các yêu cầu bằng cách sao chép lưu lượng sản xuất và gửi nó đến phiên bản dịch vụ mà chúng ta chọn.

Để bắt đầu, chúng ta sẽ triển khai một Gateway. Gateway sẽ cho phép chúng ta điều hướng lưu lượng từ bên ngoài vào hệ thống của chúng ta qua Istio.

Dưới đây là cấu hình YAML cho tài nguyên Gateway:

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 ý:

  • istio: ingressgateway: Chúng ta chọn IngressGateway của Istio để xử lý lưu lượng.

  • port: 80: Cổng HTTP được sử dụng để nhận lưu lượng từ bên ngoài.

  • hosts: '*': Dấu * cho phép lưu lượng truy cập từ bất kỳ hostname nào.

Sau khi lưu tệp cấu hình trên vào file gateway.yaml, chúng ta sẽ triển khai Gateway vào trong Kubernetes với câu lệnh sau:

kubectl apply -f gateway.yaml

Sau khi triển khai, Gateway sẽ được tạo ra, nhưng lưu ý rằng hiện tại chưa có bất kỳ VirtualService nào liên kết với nó, vì vậy, lưu lượng sẽ không được định tuyến đến dịch vụ cụ thể nào.

Tiếp theo, chúng ta sẽ triển khai ứng dụng web-frontend cùng với dịch vụ Kubernetes tương ứng. Dưới đây là cấu hình YAML cho tài nguyên DeploymentService của ứng dụng web-frontend:

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

Giải thích:

  • CUSTOMER_SERVICE_URL: Biến môi trường này được thiết lập với URL của dịch vụ customers, mà ứng dụng web-frontend sẽ gọi đến.

  • web-frontend sử dụng dịch vụ customers thông qua URL http://customers.default.svc.cluster.local.

  • Cổng 8080 được mở trong container, và dịch vụ Kubernetes được cấu hình để chuyển tiếp lưu lượng từ cổng 80 đến cổng 8080 trong container.

Sau khi lưu file trên vào web-frontend.yaml, chúng ta sẽ triển khai web-frontend vào Kubernetes bằng câu lệnh sau:

kubectl apply -f web-frontend.yaml

Lệnh này sẽ triển khai cả DeploymentService cho ứng dụng web-frontend. Sau khi triển khai, web-frontend sẽ có thể giao tiếp với dịch vụ customers thông qua URL mà ta đã thiết lập.

Tiếp theo, chúng ta sẽ triển khai ứng dụng customers phiên bản v1. Dưới đây là cấu hình YAML cho tài nguyên DeploymentService của ứng dụng customers-v1.

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

Giải thích:

  • version: v1: Đây là nhãn (label) được sử dụng để chỉ định phiên bản của ứng dụng customers. Chúng ta gán nhãn này vào cả Deployment và Pod template.

  • customers service: Dịch vụ này có selector chỉ sử dụng nhãn app: customers, vì vậy sẽ áp dụng cho tất cả các Pod thuộc ứng dụng customers (bất kể phiên bản của chúng).

  • Service sẽ chuyển tiếp lưu lượng từ cổng 80 đến cổng 3000 trong các container.

Sau khi lưu cấu hình trên vào file customers-v1.yaml, chúng ta có thể triển khai ứng dụng bằng câu lệnh sau:

kubectl apply -f customers-v1.yaml

Sau khi chạy lệnh trên, hai ứng dụng customers-v1web-frontend sẽ được triển khai và chạy trong Kubernetes. Để kiểm tra trạng thái của các Pod, bạn có thể sử dụng lệnh:

kubectl get po

Lệnh trên sẽ hiển thị trạng thái của các Pod trong cụm Kubernetes của bạn, ví dụ:

NAME                           READY  STATUS   RESTARTS  AGE
customers-v1-7857944975-5lxc8  2/2    Running  0         36s
web-frontend-659f65f49-jz58r   2/2    Running  0         3m38s

Cấu hình VirtualService

Chúng ta sẽ tạo một VirtualService cho ứng dụng web-frontend và liên kết nó với tài nguyên Gateway.

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:

  • hosts: Chúng ta sử dụng * trong trường này để chỉ định rằng tất cả các tên miền sẽ được phép truy cập vào VirtualService này.

  • gateways: Liên kết với Gateway tên là gateway, mà chúng ta đã triển khai trước đó.

  • http.route: Cấu hình chỉ định rằng tất cả lưu lượng HTTP sẽ được chuyển đến dịch vụ web-frontend tại cổng 80.

Bước tiếp theo:

Lưu file trên vào web-frontend-vs.yaml và áp dụng nó bằng cách sử dụng lệnh:

kubectl apply -f web-frontend-vs.yaml

Thiết lập địa chỉ IP của Gateway:

Tiếp theo, chúng ta cần lấy địa chỉ IP của Istio IngressGateway và thiết lập nó làm biến môi trường GATEWAY_IP:

export GATEWAY_IP=$(kubectl get svc -n istio-system istio-ingressgateway -ojsonpath='{.status.loadBalancer.ingress[0].ip}')

Sau đó, bạn có thể mở trình duyệt và truy cập vào http://$GATEWAY_IP. Web-frontend sẽ hiển thị danh sách khách hàng từ dịch vụ customers.

Vấn đề với dịch vụ customers khi triển khai phiên bản v2:

Nếu chúng ta triển khai phiên bản v2 của dịch vụ customers, các phản hồi trả về khi gọi http://customers.default.svc.cluster.local sẽ là ngẫu nhiên. Lý do là trong dịch vụ Kubernetes của chúng ta, nhãn version không được đặt trong selector, vì vậy Kubernetes không phân biệt giữa các phiên bản khác nhau của dịch vụ customers.

Định nghĩa Service Subsets

Để định tuyến lưu lượng đến các phiên bản khác nhau của dịch vụ customers, chúng ta cần tạo DestinationRule cho dịch vụ này và xác định hai subsets đại diện cho các phiên bản v1v2. Sau đó, chúng ta sẽ tạo một VirtualService và định tuyến tất cả lưu lượng đến subset v1. Điều này cho phép chúng ta triển khai phiên bản v2 của dịch vụ mà không làm ảnh hưởng đến các dịch vụ hoặc lưu lượng hiện tại.

Bước 1: Tạo DestinationRule với hai subsets

Dưới đây là cấu hình DestinationRule với hai subsets v1v2:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: customers
spec:
  host: customers.default.svc.cluster.local
  subsets:
    - name: v1
      labels:
        version: v1
    - name: v2
      labels:
        version: v2

Giải thích:

  • host: Định nghĩa tên dịch vụ customers với domain đầy đủ customers.default.svc.cluster.local.

  • subsets: Chúng ta định nghĩa hai subsets:

    • v1 với nhãn version: v1.

    • v2 với nhãn version: v2.

Lưu cấu hình trên vào file customers-dr.yaml và áp dụng nó bằng cách sử dụng lệnh sau:

kubectl apply -f customers-dr.yaml

Bước 2: Tạo VirtualService và định tuyến lưu lượng đến subset v1

Dưới đây là cấu hình VirtualService cho dịch vụ customers, trong đó định tuyến tất cả lưu lượng đến subset 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:

  • route.destination.subset: Đảm bảo rằng lưu lượng sẽ được định tuyến đến subset v1 của dịch vụ customers.

Lưu cấu hình trên vào file customers-vs.yaml và áp dụng nó bằng cách sử dụng lệnh sau:

kubectl apply -f customers-vs.yaml

Sau khi thực hiện các bước trên, lưu lượng gửi đến dịch vụ customers sẽ luôn được chuyển đến subset v1, cho phép chúng ta triển khai phiên bản v2 mà không làm ảnh hưởng đến lưu lượng hiện tại.

Bây giờ chúng ta đã sẵn sàng để triển khai phiên bản v2 của dịch vụ customers. Phiên bản v2 này trả về danh sách khách hàng tương tự như phiên bản trước, với bổ sung tên City.

Cấu hình Deployment cho customers v2

Chúng ta không cần phải triển khai lại Kubernetes Service vì dịch vụ này đã được triển khai từ trước với phiên bản v1.

Dưới đây là cấu hình Deployment cho customers v2:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: customers-v2
  labels:
    app: customers
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: customers
      version: v2
  template:
    metadata:
      labels:
        app: customers
        version: v2
    spec:
      containers:
        - image: gcr.io/tetratelabs/customers:2.0.0
          imagePullPolicy: Always
          name: svc
          ports:
            - containerPort: 3000

Giải thích:

  • name: Tên deployment là customers-v2.

  • version: Phiên bản v2 được xác định thông qua nhãn version: v2 trong labels.

  • image: Đặt Docker image cho phiên bản mới là gcr.io/tetratelabs/customers:2.0.0.

  • port: Dịch vụ vẫn sử dụng cổng 3000, giống như phiên bản v1.

Lưu cấu hình trên vào file customers-v2.yaml và triển khai nó bằng lệnh sau:

kubectl apply -f customers-v2.yaml

Sau khi thực hiện, dịch vụ customers v2 sẽ được triển khai và chạy. Tuy nhiên, tất cả lưu lượng đến dịch vụ customers vẫn sẽ được định tuyến đến subset v1 (vì cấu hình VirtualService vẫn chỉ định subset v1).

Chúng ta sẽ cần điều chỉnh cấu hình VirtualService để bắt đầu phân phối lưu lượng đến cả hai phiên bản của dịch vụ.

Phân Chia Lưu Lượng Giữa Các Phiên Bản

Vì chúng ta đã tạo VirtualService trước đó, tất cả lưu lượng hiện tại sẽ đi đến subset v1. Bây giờ, chúng ta sẽ sử dụng trường weight để thay đổi VirtualService và chia đều lưu lượng giữa v1v2.

Cập Nhật VirtualService

Để làm điều đó, chúng ta sẽ tạo thêm một điểm đến với cùng tên máy chủ nhưng có subset khác. Cả hai điểm đến này sẽ có trọng số 50 để chia đều lưu lượng giữa các phiên bản.

Dưới đây là cấu hình VirtualService mới:

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
          weight: 50
        - destination:
            host: customers.default.svc.cluster.local
            port:
              number: 80
            subset: v2
          weight: 50

Giải thích:

  • weight: 50: Lưu lượng sẽ được chia đều giữa v1v2 với tỷ lệ 50% cho mỗi phiên bản.

  • subset: Mỗi destination có một subset riêng (v1v2) được định nghĩa trong DestinationRule.

Lưu cấu hình trên vào file customers-50-50.yaml và cập nhật VirtualService với lệnh sau:

=kubectl apply -f customers-50-50.yaml

Kiểm Tra Lưu Lượng:

Mở GATEWAY_IP trong trình duyệt và làm mới trang nhiều lần để xem các phản hồi khác nhau từ hai phiên bản dịch vụ v1v2. Do lưu lượng được phân phối đều, bạn sẽ nhận được phản hồi từ một trong hai phiên bản với xác suất 50/50.