Introduction to Istio, Part 7
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 VirtualService và Gateway 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: v1
và version: 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:
Điều hướng dựa trên trọng số
Khớp và điều hướng lưu lượng
Chuyển hướng lưu lượng (HTTP 301)
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 Deployment và Service 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ả Deployment và Service 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 Deployment và Service 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-v1 và web-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 v1 và v2. 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 v1 và v2:
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ãnversion: 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 v1 và v2.
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 v1 và v2 với tỷ lệ 50% cho mỗi phiên bản.
subset: Mỗi destination có một subset riêng (v1 và v2) đượ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ụ v1 và v2. 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.