Table of contents
1 Istio 簡介
Istio 係個喺 Kubernetes cluster 裡運行既服務,佢提供左一啲 K8s custom resource definitions(CRDs),主要用黎管理喺 K8s cluster 運行既 microservices 既 traffic。
功能 | 描述 |
---|
管理 traffic | 提供 VirtualService 、DestinationRule 、Gateway 呢啲 CRDs 畀我地管理 K8s cluster 裡面 microservices 既 traffic。 |
安全性 | 提供左 PeerAuthentication ,畀我地設定 service-to-service 之間既 mutual TLS(mTLS)。 |
Logging | Istio 幫我地喺 K8s Pod inject 既 sidecar proxy container 會自動 log 低 inbound、outbound traffic。 |
2 功能
2.1 Traffic management
Istio VirtualService
以及 DestinationRule
可以畀我地做到以下功能:
- 根據 request 既 host、endpoint path 或者 headers 做 routing。
- 只需要一個
VirtualService
,就可以 route 到 traffic 去唔同既 K8s Service
。
- 畀我地做到 A/B testing。
- 根據 weight(%)將 requests 隨機 route 去唔同既 destination subsets。
- 畀我地做到 microservice 既 canary rollout。
- 畀我地做到 A/B testing。
- 唔同情況下 retry request,例如當 response status 係
4xx
或者 5xx
。
- Request 以及 retries 既 timeout。
- Load balancing。
- 複製 request 畀另一個 K8s
Service
處理,我地仲可以控制複製幾多 % 既 requests。
3 準備工作
3.1 安裝 K8s
我地可以安裝 Docker Desktop,然後開啟內置既 Kubernetes 功能。
3.2 喺 K8s cluster 安裝 Istio
- 去 Istio 既 GitHub releases 頁面下載:GitHub - istio/istio - Releases。
- 解壓縮 ZIP 檔。
- 將
istioctl.exe
註冊落系統既 PATH variable。
- 對 K8s cluster 安裝 Istio,執行:
istioctl install --set profile=default
,然後輸入 y
。
之後我地會觀察到:
- 喺我地對 K8s cluster 安裝完 Istio 之後,我地既 K8s cluster 多左一個叫
istio-system
既 namespace。
- 執行
kubectl get pod,deployment,service -n istio-system
會見到 istio-ingressgateway
以及 istiod
。
成功安裝 Istio 既 log:
1 |\
2 | \
3 | \
4 | \
5 /|| \
6 / || \
7 / || \
8 / || \
9 / || \
10 / || \
11/______||__________\
12____________________
13 \__ _____/
14 \_____/
15
16This will install the Istio 1.25.0 profile "default" into the cluster. Proceed? (y/N) y
17✔ Istio core installed ⛵️
18✔ Istiod installed 🧠
19✔ Ingress gateways installed 🛬
20✔ Installation complete
執行 kubectl get pod,deployment,service -n istio-system
既結果:
1NAME READY STATUS RESTARTS AGE
2pod/istio-ingressgateway-6874f4bf5c-kzvfg 1/1 Running 0 2m56s
3pod/istiod-578ddc4648-nsbzq 1/1 Running 0 3m8s
4
5NAME READY UP-TO-DATE AVAILABLE AGE
6deployment.apps/istio-ingressgateway 1/1 1 1 2m56s
7deployment.apps/istiod 1/1 1 1 3m8s
8
9NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
10service/istio-ingressgateway LoadBalancer 10.104.206.222 localhost 15021:30566/TCP,80:31416/TCP,443:32239/TCP 2m56s
11service/istiod ClusterIP 10.96.11.46 <none> 15010/TCP,15012/TCP,443/TCP,15014/TCP 3m8s
3.3 對 K8s namespace 啟用 Istio
我地要對每個 K8s namespace 做以下既步驟:
- 執行:
kubectl label namespace <namespace> istio-injection=enabled
。
例如,對 default
namespace 啟用 Istio:
kubectl label namespace default istio-injection=enabled
之後我地會觀察到:
- 喺我地對一個 K8s namespace 啟用 Istio,再部署 K8s
Deployment
或者 Pod
之後,新既 K8s Pod
既 READY
會顯示 2/2
,表示有 2
個 containers,一個係我地個 Spring Boot app,一個係 Istio 既 sidecar proxy container。
3.4 配置 Istio
3.4.1 部署 Gateway
Gateway.yml
:
1kind: Gateway
2apiVersion: networking.istio.io/v1
3metadata:
4 name: my-default-gateway
5 namespace: default
6spec:
7 selector:
8 istio: ingressgateway
9 servers:
10 - hosts:
11 - "*"
12 port:
13 number: 80
14 name: http
15 protocol: HTTP
之後用 kubectl
部署:
kubectl apply -f Gateway.yml
驗證:
kubectl get gateway -o yaml -n default
註:
- 我地呢個 Istio
Gateway
用既係 Istio 既 built-in ingress gateway istio-ingressgateway
。
spec.selector
既 istio: ingressgateway
係 istio-ingressgateway
既 K8s Pod
既 label。
- Istio 既 built-in ingress gateway
istio-ingressgateway
既 K8s Service
會將 port 80
既 traffic forward 去 port 8080
。
- 所以我地呢個 Istio
Gateway
用 port 80
就得。
3.4.2 部署 PeerAuthentication
Istio PeerAuthentication
係用黎設定 mutual TLS(mTLS),令 service-to-service communication 更加安全。
PeerAuthentication.yml
:
1kind: PeerAuthentication
2apiVersion: security.istio.io/v1
3metadata:
4 name: pa-mtls
5 namespace: istio-system
6spec:
7 mtls:
8 mode: STRICT
之後用 kubectl
部署:
kubectl apply -f PeerAuthentication.yml
驗證:
kubectl get peerauthentication -o yaml -n istio-system
3.4.3 部署 Telemetry
Istio Telemetry
可以令 sidecar proxy container 提供 logging 功能。
Telemetry.yml
:
1kind: Telemetry
2apiVersion: telemetry.istio.io/v1
3metadata:
4 name: my-default-telemetry
5 namespace: istio-system
6spec:
7 accessLogging:
8 - providers:
9 - name: envoy
之後用 kubectl
部署:
kubectl apply -f Telemetry.yml
驗證:
kubectl get telemetry -o yaml -n istio-system
之後所有受到 Istio inject 既 K8s Pod
既 inbound、outbound traffic 都會被 log 低喺個 Istio sidecar proxy container 既 log 裡面,可以用以下 command 檢查:
kubectl logs -f -c istio-proxy <pod name>
4 動手寫
4.1 寫 Spring Boot web apps
我地需要部署 3
個 microservices:
- Client microservice,名叫
my-client
。
- Server 1 microservice,名叫
my-server-1
。
- Server 2 microservice,名叫
my-server-2
。
而:
- 呢幾個 web apps 需要 expose port
8080
。
- 呢幾個 web apps 需要用 Spring Boot Actuator(expose HTTP endpoints 畀 K8s probes 用)。
- 呢幾個 web apps 需要 expose HTTP requests,用
RestClient
做 service-to-service communication,實現呢個 request flow:
- 我地 call
my-client
既 GET /client/server1
endpoint。
my-client
call my-server-1
既 GET /server1/server2
endpoint。
my-server-1
call my-server-2
既 GET /server2/end
endpoint。
my-server-2
返回 status code 200
、一個簡單既 JSON object 作為 HTTP response body。
我地至少需要做以下既野:
- 打包呢幾個 microservices 既 web app Docker images,app 既版本係
1.2.3
。
- 部署呢幾個 microservices 既 K8s
Deployment
:
my-client
my-server-1
my-server-2
- 部署呢幾個 microservices 既 K8s
Service
:
svc-my-client
svc-my-server-1
svc-my-server-2
因為呢啲 projects 都係啲簡單 Spring Boot 野,所以呢篇文章就省略 implementation。
4.1.1 Request URLs
my-client
可以用以下其中一個 URL 去 call my-server-1
:
http://svc-my-server-1:8080
(必須部署喺相同既 K8s namespace)
http://svc-my-server-1.default:8080
http://svc-my-server-1.default.svc.cluster.local:8080
(FQDN)
my-server-1
可以用以下其中一個 URL 去 call my-server-2
:
http://svc-my-server-2:8080
(必須部署喺相同既 K8s namespace)
http://svc-my-server-2.default:8080
http://svc-my-server-2.default.svc.cluster.local:8080
(FQDN)
註:
default
係我地部署 microservices 既 K8s namespace 既名。
- 當 microservice A 用 Spring Boot 既
RestClient
call microservice B 既 URL,RestClient
會自動加入 microservice B URL 既 domain name 落個 HTTP request 既 Host
header。
- K8s 會自動 resolve URL 既 short domain name 做 fully qualified domain name(FQDN),之後做 network routing。
- Istio sidecar proxy 會 intercept traffic,而且根據
Host
request header 黎決定有冇需要 apply VirtualService
既 rules。
- 如果
Host
request header 係 short domain name,Istio 會用 VirtualService
所在既 K8s namespace 黎 resolve 成 FQDN。
-
Note for Kubernetes users: When short names are used (e.g. “reviews” instead of “reviews.default.svc.cluster.local”), Istio will interpret the short name based on the namespace of the rule, not the service. A rule in the “default” namespace containing a host “reviews” will be interpreted as “reviews.default.svc.cluster.local”, irrespective of the actual namespace associated with the reviews service. To avoid potential misconfigurations, it is recommended to always use fully qualified domain names over short names.
4.1.2 寫 HTTP request
用 Spring 6.1
既 RestClient
寫 HTTP request,例如:
1return RestClient.builder()
2 .baseUrl(serverUrl).build()
3 .get()
4 .uri("/server1/server2")
5 .retrieve()
6 .toEntity(Map.class)
7 .getBody();
⚠️ 注意:唔好直接 return toEntity()
返回既 ResponseEntity
value:
1// 錯誤示範
2return RestClient.builder()
3 .baseUrl(serverUrl).build()
4 .get()
5 .uri("/server1/server2")
6 .retrieve()
7 .toEntity(Map.class);
因為咁做會帶埋原來既 HTTP response headers,導致 istio-envoy
server respond 以下既 502 Bad Gateway
error:
upstream connect error or disconnect/reset before headers. reset reason: protocol error
4.2 部署 apps 既 VirtualService
以及 DestinationRule
以下既 YAML 檔案所有 microservices 都通用,只需要將 my-client
改成 my-server-1
或者 my-server-2
就用到落其餘兩個 microservices。
VirtualService.yaml
:
1kind: VirtualService
2apiVersion: networking.istio.io/v1
3metadata:
4 name: vs-my-client
5 namespace: default
6spec:
7 gateways:
8 - mesh
9 - default/my-default-gateway
10 hosts:
11 - my-client.default
12 - svc-my-client.default.svc.cluster.local
13 http:
14 - timeout: 5s
15 route:
16 - weight: 100
17 destination:
18 subset: 1-2-3
19 host: svc-my-client.default.svc.cluster.local
20 port:
21 number: 8080
註:
- 關於
spec.gateways
:
- 指定
mesh
就會 apply 呢個 VirtualService
既 rules 落 mesh 內既所有 sidecar proxies。
- 指定
<gateway namespace>/<gateway name>
就會 apply 呢個 VirtualService
既 rules 落去所有黎自 Istio Gateway
既 traffic。
- 例如
default/my-default-gateway
(我地既 Istio Gateway
)。
-
The reserved word mesh
is used to imply all the sidecars in the mesh. When this field is omitted, the default gateway (mesh
) will be used, which would apply the rule to all sidecars in the mesh. If a list of gateway names is provided, the rules will apply only to the gateways. To apply the rules to both gateways and sidecars, specify mesh
as one of the gateway names.
- 關於
spec.hosts
既 my-server.default
:
- 當我地 call gateway URL 既時候,個
Host
request header 一定要 match 到呢個 value,否則 istio-envoy
server 就會 respond 404 Not Found
。
- 一定要係
xxx.yyy
既格式,淨係 xxx
係唔 work。
- 唔同既 Istio
VirtualService
資源唔好用埋同一個 value,否則就會有衝突。
- 只要滿足到以上既要求,其實用咩 value 都得,例如
foo.bar
(cURL 就要用 -H "Host: foo.bar"
)。
- 我地可以用
spec.http.timeout
黎控制每個 HTTP request 最多可以花幾多時間。
- 如果個 microservice 用左
5.5
秒黎處理個 request(測試用 Thread.sleep(5500);
),但 spec.http.timeout
配置左 5s
,咁個 request 就係超時,istio-envoy
server 就會 respond 504 Gateway Timeout
。
spec.http.route.destination.subset
係對應 Istio DestinationRule
既 spec.subsets.name
。
spec.http.route.weight
可以控制幾多 % 既 traffic flow 去一個 destination 既 subset。
DestinationRule.yml
:
1kind: DestinationRule
2apiVersion: networking.istio.io/v1
3metadata:
4 name: dr-my-client
5spec:
6 host: svc-my-client.default.svc.cluster.local
7 trafficPolicy:
8 loadBalancer:
9 simple: LEAST_REQUEST
10 subsets:
11 - name: 1-2-3
12 labels:
13 version: 1.2.3
14 trafficPolicy:
15 loadBalancer:
16 simple: LEAST_REQUEST
之後用 kubectl
部署:
kubectl apply -f VirtualService.yml
kubectl apply -f DestinationRule.yml
驗證:
kubectl get virtualservice,destinationrule -n default
註:
spec.subsets.name
唔可以有 .
,所以我地用 1-2-3
黎表示版本 1.2.3
。
spec.subsets.labels
係對應 K8s Pod
labels。
- 所以我地既 microservices 既 K8s
Deployment
既 spec.template.metadata.labels
一定要有 version: 1.2.3
既 label,否則個 Istio DestinationRule
就會 match 唔到。
5 測試
5.1 經 gateway URL 測試
執行以下 commands(全部用獨立既 console windows):
kubectl logs -f svc/istio-ingressgateway -n istio-system --tail=0
kubectl logs -f -c istio-proxy svc/svc-my-client --tail=0
kubectl logs -f -c istio-proxy svc/svc-my-server-1 --tail=0
kubectl logs -f -c istio-proxy svc/svc-my-server-2 --tail=0
執行以下 cURL command 測試 traffic:
curl http://localhost/client/server1 -H "Host: my-client.default" -v
預期結果:
- HTTP response status 係
200 OK
。
- HTTP response body 係 exactly
my-server-2
respond 既 JSON object。
詳解:
localhost
係我地既 gateway URL。
- HTTP 既默認 port 係
80
,所以 URL 等於 http://localhost:80/client/server1
。
- Istio 既 built-in ingress gateway
istio-ingressgateway
既 K8s Service
會將 port 80
既 traffic forward 去 port 8080
。
- Istio 會對
my-client
、my-server-1
、my-server-2
apply 佢地既 VirtualService
、DestinationRule
既 rules。
- Request flow:
- 呢個 HTTP request 會經過 Istio 既 built-in ingress gateway,進入 K8s cluster。
- 我地既 Istio
Gateway
「my-default-gateway
」會 apply。
my-client
會收到 GET /client/server1
。
Host
request header 會 match 到 my-client
既 Istio VirtualService
定義既 host my-client.default
。
- Gateway 會 match 到
my-client
既 Istio VirtualService
定義既 gateway default/my-default-gateway
。
- 所以呢個 request 就由
my-client
既 K8s Service
處理。
my-client
會 call svc-my-server-1:8080
既 GET /server1/server2
。
my-server-1
會收到 GET /server1/server2
。
Host
request header 會 match 到 my-server-1
既 Istio VirtualService
定義既 host svc-my-server-1.default.svc.cluster.local
。
- Gateway 會 match 到
my-server-1
既 Istio VirtualService
定義既 gateway mesh
。
- 所以呢個 request 就由
my-server-1
既 K8s Service
處理。
my-server-1
會 call svc-my-server-2:8080
既 GET /server2/end
。
my-server-2
會收到 GET /server2/end
。
Host
request header 會 match 到 my-server-2
既 Istio VirtualService
定義既 host svc-my-server-2.default.svc.cluster.local
。
- Gateway 會 match 到
my-server-2
既 Istio VirtualService
定義既 gateway mesh
。
- 所以呢個 request 就由
my-server-2
既 K8s Service
處理。
my-server-2
會 respond 200
以及 JSON object。
註:
- 以下既 cURL command 都會有一樣既結果:
cURL command 既 verbose log:
1* Host localhost:80 was resolved.
2* IPv6: ::1
3* IPv4: 127.0.0.1
4* Trying [::1]:80...
5* Connected to localhost (::1) port 80
6* using HTTP/1.x
7> GET /client/server1 HTTP/1.1
8> Host: my-client.default
9> User-Agent: curl/8.10.1
10> Accept: */*
11>
12* Request completely sent off
13< HTTP/1.1 200 OK
14< content-type: application/json
15< date: Sat, 08 Mar 2025 07:55:12 GMT
16< x-envoy-upstream-service-time: 4022
17< server: istio-envoy
18< transfer-encoding: chunked
19<
20{"firstName":"Mick","lastName":"Chung"}* Connection #0 to host localhost left intact
Istio ingress gateway 既 log:
[2025-03-08T07:55:08.884Z] "GET /client/server1 HTTP/1.1" 200 - via_upstream - "-" 0 39 4022 4022 "192.168.65.3" "curl/8.10.1" "132b42a9-a517-46de-a876-d87e4fcd9ee7" "my-client.default" "10.1.0.103:8080" outbound|8080|1-2-3|svc-my-client.default.svc.cluster.local 10.1.0.90:43448 10.1.0.90:8080 192.168.65.3:43428 - -
my-client
既 istio-proxy
log:
[2025-03-08T07:55:08.890Z] "GET /server1/server2 HTTP/1.1" 200 - via_upstream - "-" 0 39 4013 4012 "-" "Java-http-client/21.0.6" "46caae46-9521-4ff7-bbb1-f788cbd9c4f7" "svc-my-server-1:8080" "10.1.0.107:8080" outbound|8080|1-2-3|svc-my-server-1.default.svc.cluster.local 10.1.0.103:47266 10.108.66.87:8080 10.1.0.103:53404 - -
[2025-03-08T07:55:08.884Z] "GET /client/server1 HTTP/1.1" 200 - via_upstream - "-" 0 39 4022 4021 "192.168.65.3" "curl/8.10.1" "132b42a9-a517-46de-a876-d87e4fcd9ee7" "my-client.default" "10.1.0.103:8080" inbound|8080|| 127.0.0.6:36863 10.1.0.103:8080 192.168.65.3:0 outbound_.8080_.1-2-3_.svc-my-client.default.svc.cluster.local default
my-server-1
既 istio-proxy
log:
[2025-03-08T07:55:08.897Z] "GET /server2/end HTTP/1.1" 200 - via_upstream - "-" 0 39 4003 4003 "-" "Java-http-client/21.0.6" "51caa0bd-a8aa-45d7-86de-4b20c286f40a" "svc-my-server-2:8080" "10.1.0.111:8080" outbound|8080|1-2-3|svc-my-server-2.default.svc.cluster.local 10.1.0.107:54452 10.100.60.231:8080 10.1.0.107:48234 - -
[2025-03-08T07:55:08.891Z] "GET /server1/server2 HTTP/1.1" 200 - via_upstream - "-" 0 39 4012 4012 "-" "Java-http-client/21.0.6" "46caae46-9521-4ff7-bbb1-f788cbd9c4f7" "svc-my-server-1:8080" "10.1.0.107:8080" inbound|8080|| 127.0.0.6:53355 10.1.0.107:8080 10.1.0.103:47266 outbound_.8080_.1-2-3_.svc-my-server-1.default.svc.cluster.local default
my-server-2
既 istio-proxy
log:
[2025-03-08T07:55:08.897Z] "GET /server2/end HTTP/1.1" 200 - via_upstream - "-" 0 39 4002 4002 "-" "Java-http-client/21.0.6" "51caa0bd-a8aa-45d7-86de-4b20c286f40a" "svc-my-server-2:8080" "10.1.0.111:8080" inbound|8080|| 127.0.0.6:53865 10.1.0.111:8080 10.1.0.107:54452 outbound_.8080_.1-2-3_.svc-my-server-2.default.svc.cluster.local default
6 參考資料