Introduction to Istio, Part 8

·

11 min read

Điều Hướng Lưu Lượng Nâng Cao

Trước đây, chúng ta đã học cách điều hướng lưu lượng giữa các subsets khác nhau bằng cách sử dụng tỷ lệ lưu lượng (trường weight). Trong một số trường hợp, việc điều hướng hoặc phân chia lưu lượng chỉ dựa trên trọng số là đủ. Tuy nhiên, cũng có những tình huống và trường hợp mà chúng ta cần kiểm soát chi tiết hơn về cách lưu lượng được phân chia và chuyển tiếp tới các dịch vụ đích.

Istio cho phép chúng ta sử dụng các phần của yêu cầu đến và khớp chúng với các giá trị đã được định nghĩa sẵn. Ví dụ, chúng ta có thể kiểm tra tiền tố URI của yêu cầu đến và điều hướng lưu lượng dựa trên điều đó.

Dưới đây là bảng các thuộc tính khác nhau mà chúng ta có thể sử dụng để khớp:

Thuộc tínhMô tả
uriKhớp URI của yêu cầu với giá trị đã chỉ định
schemeKhớp sơ đồ yêu cầu (HTTP, HTTPS, ...)
methodKhớp phương thức yêu cầu (GET, POST, ...)
authorityKhớp header authority của yêu cầu
headersKhớp với header của yêu cầu. Headers phải là chữ thường và phân cách bằng dấu gạch nối (ví dụ: x-my-request-id). Lưu ý: nếu chúng ta sử dụng header để khớp, các thuộc tính khác (uri, scheme, method, authority) sẽ bị bỏ qua.

Mỗi thuộc tính trên có thể được khớp bằng một trong các phương thức sau:

  • Khớp chính xác: Ví dụ, exact: "value" khớp với chuỗi chính xác.

  • Khớp tiền tố: Ví dụ, prefix: "value" chỉ khớp với tiền tố.

  • Khớp biểu thức chính quy (Regex): Ví dụ, regex: "value" khớp với biểu thức chính quy theo kiểu ECMAScript.

Ví dụ, giả sử URI của yêu cầu như sau:

https://dev.example.com/v1/api

Để khớp với URI của yêu cầu, chúng ta có thể viết cấu hình như sau:

http:
- match:
  - uri:
      prefix: /v1

Đoạn cấu hình trên sẽ khớp với yêu cầu đến và điều hướng yêu cầu đến dịch vụ được định nghĩa trong route đó.

Một ví dụ khác là sử dụng Regex và khớp với header:

http:
- match:
  - headers:
      user-agent:
        regex: '.*Firefox.*'

Cấu hình trên sẽ khớp với bất kỳ yêu cầu nào có header User-Agent khớp với biểu thức chính quy (Regex).

Viết lại và Chuyển hướng Lưu Lượng

Ngoài việc khớp các thuộc tính và điều hướng lưu lượng đến một dịch vụ đích, đôi khi chúng ta cũng cần phải viết lại URI của yêu cầu đến hoặc sửa đổi các header.

Ví dụ, giả sử chúng ta có một tình huống nơi yêu cầu đến được khớp với đường dẫn /v1/api. Sau khi khớp, chúng ta muốn chuyển tiếp các yêu cầu này đến một URI khác, chẳng hạn như /v2/api. Điều này có thể thực hiện bằng cách sử dụng tính năng rewrite.

http:
  - match:
    - uri:
        prefix: /v1/api
    rewrite:
      uri: /v2/api
    route:
      - destination:
          host: customers.default.svc.cluster.local

Đoạn cấu hình trên sẽ khớp với tiền tố /v1/api và sau đó viết lại phần tiền tố đã khớp với URI mà chúng ta cung cấp trong trường rewrite. Mặc dù dịch vụ đích không cung cấp hoặc lắng nghe tại endpoint /v1/api, chúng ta có thể viết lại các yêu cầu đó sang một đường dẫn khác, trong trường hợp này là /v2/api.

Chúng ta cũng có thể chuyển hướng hoặc chuyển tiếp yêu cầu đến một dịch vụ hoàn toàn khác. Đây là cách chúng ta có thể khớp một header và sau đó chuyển hướng yêu cầu đến một dịch vụ khác:

http:
  - match:
    - headers:
        my-header:
          exact: hello
    redirect:
      uri: /hello
      authority: my-service.default.svc.cluster.local:8000

Lưu ý rằng các trường redirectdestination là độc lập với nhau. Nếu chúng ta sử dụng redirect, không cần phải thiết lập trường destination.

Manipulating Headers (Sửa Đổi Header)

Khi chuyển hướng hoặc viết lại các yêu cầu, đôi khi chúng ta cũng cần thêm hoặc sửa đổi các header của yêu cầu (hoặc phản hồi). Các header có thể được sửa đổi cho từng điểm đến cụ thể hoặc cho tất cả các điểm đến trong VirtualService.

Hãy xem xét ví dụ dưới đây để thấy được cả hai tình huống:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: customers
spec:
  hosts:
  - customers.default.svc.cluster.local
  http:
  - headers:
      request:
        set:
          debug: "true"
    route:
    - destination:
        host: customers.default.svc.cluster.local
        subset: v2
      weight: 20
    - destination:
        host: customers.default.svc.cluster.local
        subset: v1
      headers:
        response:
          remove:
          - x-api-key
      weight: 80

Trong ví dụ trên, chúng ta đã thiết lập một header yêu cầu debug: true cho tất cả lưu lượng gửi đến host. Thêm vào đó, chúng ta chia lưu lượng giữa hai subsets bằng trọng số (weight), và ở một điểm đến, chúng ta đang xóa một header phản hồi có tên là x-api-key. Vì vậy, mỗi khi lưu lượng đến subset v1, phản hồi từ dịch vụ sẽ không bao gồm header x-api-key.

Ngoài việc thêm và xóa các header, chúng ta cũng có thể sử dụng trường set để ghi đè các giá trị header hiện tại.

Semantics của AND và OR

Dù chúng ta đang so khớp trên thuộc tính nào, chúng ta có thể sử dụng semantics AND hoặc OR. Hãy cùng xem xét đoạn code sau:

http:
  - match:
    - uri:
        prefix: /v1
      headers:
        my-header:
          exact: hello

Đoạn mã trên sử dụng semantics AND. Điều này có nghĩa là cả hai điều kiện cần phải đúng: tiền tố URI phải khớp với /v1 header my-header phải khớp với giá trị hello. Khi cả hai điều kiện này đều đúng, lưu lượng sẽ được chuyển hướng đến điểm đến.

Để sử dụng semantics OR, chúng ta có thể thêm một mục match khác, như sau:

http:
  - match:
    - uri:
        prefix: /v1
  - match:
    - headers:
        my-header:
          exact: hello

Trong đoạn code trên, quá trình so khớp sẽ thực hiện với tiền tố URI trước. Nếu khớp, yêu cầu sẽ được chuyển đến điểm đến. Nếu so khớp đầu tiên không đúng, thuật toán sẽ tiếp tục kiểm tra điều kiện thứ hai với header.

Nếu chúng ta bỏ qua trường match trong route, nó sẽ luôn được đánh giá là đúng.

Khi sử dụng một trong hai tùy chọn trên, hãy chắc chắn cung cấp một route dự phòng nếu cần. Bằng cách đó, nếu lưu lượng không khớp với bất kỳ điều kiện nào, nó vẫn có thể được chuyển đến một "route mặc định".

Trong bài lab này, chúng ta sẽ học cách sử dụng các thuộc tính của yêu cầu để điều hướng lưu lượng giữa nhiều phiên bản dịch vụ.

Bước đầu tiên là triển khai Gateway:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: gateway
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - '*'
  • apiVersion: Chỉ định phiên bản của Istio API, ở đây sử dụng v1beta1 cho Gateway.

  • kind: Loại tài nguyên, ở đây là Gateway.

  • metadata: Cung cấp thông tin về tài nguyên như tên (name: gateway).

  • spec:

    • selector: Lọc các pods sử dụng nhãn istio: ingressgateway.

    • servers: Xác định các cổng và giao thức mà Gateway sẽ lắng nghe, trong trường hợp này là cổng 80 và giao thức HTTP.

    • hosts: Cung cấp danh sách các hosts mà Gateway sẽ xử lý, ở đây sử dụng * để tiếp nhận tất cả các yêu cầu.

Lưu YAML trên vào tệp gateway.yaml và triển khai Gateway bằng lệnh sau:

kubectl apply -f gateway.yaml

Sau khi triển khai Gateway, bạn sẽ có thể điều hướng lưu lượng HTTP từ bên ngoài vào dịch vụ của mình thông qua Istio ingress gateway.

Triển khai các Ứng Dụng (1)

Tiếp theo, chúng ta sẽ triển khai các ứng dụng web-frontend, customers-v1, customers-v2, cùng với các VirtualServicesDestinationRule tương ứng.

Triển khai web-frontend và dịch vụ:

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:

    • name: web-frontend: Tên của ứng dụng frontend.

    • replicas: 1: Chỉ tạo một bản sao của pod.

    • Container:

  • Service:

    • Dịch vụ web-frontend sẽ trỏ đến các pod có nhãn app: web-frontend và ánh xạ cổng 80 đến cổng 8080 trong container.
  • VirtualService:

    • VirtualService web-frontend chỉ định rằng tất cả các yêu cầu HTTP sẽ được chuyển đến dịch vụ web-frontend qua gateway Istio.

    • Truyền tải yêu cầu qua cổng HTTP 80.

Lưu tệp YAML trên thành web-frontend.yaml và tạo Deployment và Service với lệnh sau:

kubectl apply -f web-frontend.yaml

Sau khi lệnh được thực thi, ứng dụng web-frontend và dịch vụ tương ứng sẽ được triển khai.

Triển khai các Ứng Dụng (2)

Tiếp theo, chúng ta sẽ triển khai dịch vụ customers-v1customers-v2, cùng với các VirtualServiceDestinationRule tương ứng.

Triển khai customers-v1, customers-v2 và dịch vụ:

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

  • Deployment cho customers-v1:

  • Deployment cho customers-v2:

  • Service:

    • Tạo một dịch vụ customers trỏ đến các pod customers-v1customers-v2, mở cổng 80 và ánh xạ đến cổng 3000 trong container.
  • VirtualService:

    • Chỉ định rằng tất cả lưu lượng đến dịch vụ customers sẽ được chuyển đến subset v1.
  • DestinationRule:

    • Định nghĩa hai subset v1v2 cho dịch vụ customers, phân chia theo nhãn version.

Lưu tệp YAML trên thành customers.yaml và tạo các tài nguyên với lệnh:

kubectl apply -f customers.yaml

Sau khi triển khai xong, tất cả lưu lượng sẽ được chuyển đến dịch vụ customers-v1. Để kiểm tra xem mọi thứ có hoạt động đúng không, bạn có thể mở GATEWAY_IP trong trình duyệt web và đảm bảo rằng bạn nhận được phản hồi từ dịch vụ customers-v1. Bạn chỉ nên thấy cột NAME trên trang kết quả.

Bạn có thể thiết lập biến môi trường GATEWAY_IP như sau:

export GATEWAY_IP=$(kubectl get svc -n istio-system istio-ingressgateway -ojsonpath=

Routing Dựa Trên Headers

Chúng ta sẽ cập nhật VirtualService của dịch vụ customers sao cho lưu lượng được chuyển hướng giữa hai phiên bản của dịch vụ customers.

Dưới đây là ví dụ YAML để chuyển hướng lưu lượng đến dịch vụ customers-v2 nếu yêu cầu chứa header user: debug. Nếu header này không được thiết lập, chúng ta sẽ chuyển hướng lưu lượng đến dịch vụ customers-v1.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: customers
spec:
  hosts:
    - 'customers.default.svc.cluster.local'
  http:
  - match:
    - headers:
        user:
          exact: debug
    route:
    - destination:
        host: customers.default.svc.cluster.local
        port:
          number: 80
        subset: v2
  - route:
      - destination:
          host: customers.default.svc.cluster.local
          port:
            number: 80
          subset: v1

Giải thích:

  • match: Chúng ta sử dụng điều kiện headers để kiểm tra xem yêu cầu có chứa header user với giá trị debug hay không.

  • route: Nếu điều kiện trên đúng, lưu lượng sẽ được chuyển đến customers-v2. Nếu không, lưu lượng sẽ được chuyển đến customers-v1.

Lưu tệp YAML trên thành customers-vs-headers.yaml và cập nhật VirtualService bằng lệnh sau:

kubectl apply -f customers-vs-headers.yaml

Kiểm tra:

  • Khi mở GATEWAY_IP trong trình duyệt, bạn sẽ vẫn nhận được phản hồi từ customers-v1.

  • Nếu thêm header user: debug vào yêu cầu, bạn sẽ thấy phản hồi từ customers-v2.

Bạn có thể sử dụng tiện ích mở rộng ModHeader để thay đổi headers từ trình duyệt. Hoặc, bạn có thể sử dụng cURL và thêm header vào yêu cầu như sau:

curl -H "user: debug" http://$GATEWAY_IP/ | grep -E 'CITY|NAME'

Kết quả sẽ chứa hai cột CITYNAME, điều này cho thấy phản hồi đến từ dịch vụ customers-v2.

Tương tự, nếu mở GATEWAY_IP trong trình duyệt, phản hồi sẽ đến từ customers-v1. Tuy nhiên, nếu bạn thêm header user: debug (sử dụng ModHeader hoặc các tiện ích tương tự) và làm mới trang, phản hồi sẽ được gửi từ dịch vụ customers-v2.