Introduction to Istio, Part 8
Đ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ính | Mô tả |
uri | Khớp URI của yêu cầu với giá trị đã chỉ định |
scheme | Khớp sơ đồ yêu cầu (HTTP, HTTPS, ...) |
method | Khớp phương thức yêu cầu (GET, POST, ...) |
authority | Khớp header authority của yêu cầu |
headers | Khớ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 redirect và destination 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
và 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 VirtualServices
và DestinationRule
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:
Chạy image
gcr.io/tetratelabs/web-frontend:1.0.0
và mở cổng 8080.Môi trường
CUSTOMER_SERVICE_URL
được cấu hình với URL của dịch vụ khách hàng.
Service:
- Dịch vụ
web-frontend
sẽ trỏ đến các pod có nhãnapp: web-frontend
và ánh xạ cổng 80 đến cổng 8080 trong container.
- Dịch vụ
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-v1
và customers-v2
, cùng với các VirtualService
và DestinationRule
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:
Tạo một pod
customers-v1
với nhãnversion: v1
.Sử dụng ảnh
gcr.io/tetratelabs/customers:1.0.0
và mở cổng 3000.
Deployment cho customers-v2:
Tạo một pod
customers-v2
với nhãnversion: v2
.Sử dụng ảnh
gcr.io/tetratelabs/customers:2.0.0
và mở cổng 3000.
Service:
- Tạo một dịch vụ
customers
trỏ đến các podcustomers-v1
vàcustomers-v2
, mở cổng 80 và ánh xạ đến cổng 3000 trong container.
- Tạo một dịch vụ
VirtualService:
- Chỉ định rằng tất cả lưu lượng đến dịch vụ
customers
sẽ được chuyển đến subsetv1
.
- Chỉ định rằng tất cả lưu lượng đến dịch vụ
DestinationRule:
- Định nghĩa hai subset
v1
vàv2
cho dịch vụcustomers
, phân chia theo nhãnversion
.
- Định nghĩa hai subset
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 headeruser
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 đếncustomers-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 CITY
và NAME
, đ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
.