➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
JPA/Hibernate 使用方式(二)Helm chart 入門

Istio 入門

Continued from previous post:
Spring Boot + Docker + K8s

Table of contents

1 Istio 簡介

Istio 係個喺 Kubernetes cluster 裡運行既服務,佢提供左一啲 K8s custom resource definitions(CRDs),主要用黎管理喺 K8s cluster 運行既 microservices 既 traffic。
功能描述
管理 traffic提供 VirtualServiceDestinationRuleGateway 呢啲 CRDs 畀我地管理 K8s cluster 裡面 microservices 既 traffic。
安全性提供左 PeerAuthentication,畀我地設定 service-to-service 之間既 mutual TLS(mTLS)。
LoggingIstio 幫我地喺 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

  1. 去 Istio 既 GitHub releases 頁面下載:GitHub - istio/istio - Releases
  2. 解壓縮 ZIP 檔。
  3. istioctl.exe 註冊落系統既 PATH variable。
  4. 對 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 PodREADY 會顯示 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.selectoristio: ingressgatewayistio-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

可以參考返呢篇:Spring Boot + Docker + K8s
我地需要部署 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:
    1. 我地 call my-clientGET /client/server1 endpoint。
    2. my-client call my-server-1GET /server1/server2 endpoint。
    3. my-server-1 call my-server-2GET /server2/end endpoint。
    4. 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.1RestClient 寫 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.hostsmy-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 DestinationRulespec.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 Deploymentspec.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-clientmy-server-1my-server-2 apply 佢地既 VirtualServiceDestinationRule 既 rules。
  • Request flow:
    1. 呢個 HTTP request 會經過 Istio 既 built-in ingress gateway,進入 K8s cluster。
      • 我地既 Istio Gatewaymy-default-gateway」會 apply。
    2. 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 處理。
    3. my-client 會 call svc-my-server-1:8080GET /server1/server2
    4. 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 處理。
    5. my-server-1 會 call svc-my-server-2:8080GET /server2/end
    6. 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 處理。
    7. my-server-2 會 respond 200 以及 JSON object。
註:
  • 以下既 cURL command 都會有一樣既結果:
    • curl http://localhost/client/server1 -H "Host: svc-my-client.default.svc.cluster.local" -v
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-clientistio-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-1istio-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-2istio-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 參考資料