Istio 入門(三):體驗 Istio、微服務部署、可觀測性

2023-07-11 12:01:22

本教學已加入 Istio 系列:https://istio.whuanle.cn

3,快速入門

在本章中,我們正式邁入學習 Istio 的第一步。因為 Istio 的知識體系是較為龐大的,因此我們可以先通過本章的入門教學快速瞭解如何使用 Istio 部署一套微服務,以及 Istio 核心功能的使用方法,瞭解 Istio 可以為微服務解決什麼問題。

在本章中,我們將會學習到如何部署一套微服務、如何使用 Istio 暴露服務到叢集外,並且如何使用可觀測性元件監測流量和系統指標。

在後面的章節中,筆者會針對每個 Istio 元件做單獨講解,而在本章中,我們只需要大概瞭解使用方法即可。

書店微服務

本章教學範例使用的是 Istio 官方的一套微服務,這套微服務是一個線上書店,開啟頁面之後會看到一個分類、書的資訊以及書的評論,頁面的內容由不同的子服務提供。

書店微服務分為四個單獨的微服務,在上圖中已經使用紅色方框畫出來了。這四個微服務分別是:

  • productpage: 彙集所有服務的資料,生成瀏覽頁面。
  • details:儲存了書籍的資訊,如描述、作者、出版社等。
  • reviews:儲存了書籍相關的評論,但是不包含評分打星。
  • ratings:儲存評論中的評分打星。

在這個微服務中,Productpage 服務對外提供 Web 存取頁面,而且其它的三個服務只能在叢集內部存取。四個服務分別採用了不同的語言開發,Productpage 聚合其它三個服務的資訊生成一個頁面。

在微服務設計中,我們不要每個子服務都暴露埠到叢集外部,應該通過一些應用集中資料後給外部顯示。我們可以使用 API 閘道器,代理子服務一部分介面,然後在 API 閘道器中實現基於使用者端或第三方呼叫的身份驗證。

productpage、details、ratings 都只有一個 v1 版本,而 reviews 有三個版本。

ratings 負責給出使用者打分的資料,例如一星、兩星。而 reviews 三個版本分別對 ratings 的資料做以下處理:

  • reviews v1:遮蔽星級,不顯示打分;
  • reviews v2:顯示星級,使用灰色星星表示,★★★★☆;
  • reviews v3:顯示星級,使用紅色星星表示,★★★★☆

【圖源 istio 官網

服務依賴圖如下所示:

接下來我們將會使用 Kuubernetes Deployment 部署這些服務,這跟常規的 Kubernetes 部署並無差別。

預先準備

給這些範例服務建立一個名稱空間。

kubectl create namespace bookinfo

給名稱空間新增 Istio 的標籤,指示 Istio 在部署應用(只對 Pod 起效)的時候,自動注入 Envoy Sidecar Proxy 容器:

kubectl label namespace bookinfo istio-injection=enabled

開啟讓 Istio 注入 Sidecar 有很多種方式,其中一種是給名稱空間設定下標籤,在此名稱空間下部署的 Pod,會被自動注入 Sidecar 。

你可以從本系列教學的 git 倉庫中找到這些範例,檔案位置:https://github.com/whuanle/istio_book/tree/main/3

倉庫拉取後開啟 3 目錄,執行命令進行部署:

kubectl -n bookinfo apply -f details_deploy.yaml
kubectl -n bookinfo apply -f details_svc.yaml
kubectl -n bookinfo apply -f details_sa.yaml
kubectl -n bookinfo apply -f ratings_deploy.yaml
kubectl -n bookinfo apply -f ratings_svc.yaml
kubectl -n bookinfo apply -f ratings_sa.yaml
kubectl -n bookinfo apply -f reviews_v1_deploy.yaml
kubectl -n bookinfo apply -f reviews_v2_deploy.yaml
kubectl -n bookinfo apply -f reviews_v3_deploy.yaml
kubectl -n bookinfo apply -f reviews_svc.yaml
kubectl -n bookinfo apply -f reviews_sa.yaml
kubectl -n bookinfo apply -f productpage_deploy.yaml
kubectl -n bookinfo apply -f productpage_svc.yaml
kubectl -n bookinfo apply -f productpage_sa.yaml

或者你可以參考下面的四個小節,通過手動的方式部署應用,瞭解每一個應用是如何定義的。

details 應用

儲存了書籍資訊的應用。

部署命令:

kubectl -n bookinfo apply -f details_deploy.yaml
kubectl -n bookinfo apply -f details_svc.yaml
kubectl -n bookinfo apply -f details_sa.yaml

使用 Deployment 部署 details 應用。

details_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: details-v1
  labels:
    app: details
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: details
      version: v1
  template:
    metadata:
      labels:
        app: details
        version: v1
    spec:
      serviceAccountName: bookinfo-details
      containers:
      - name: details
        image: docker.io/istio/examples-bookinfo-details-v1:1.17.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
        securityContext:
          runAsUser: 1000

部署 details。

kubectl -n bookinfo apply -f details_deploy.yaml

為 details 服務設定 Kubernetes Service 。

details_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: details
  labels:
    app: details
    service: details
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: details
kubectl -n bookinfo apply -f details_svc.yaml

接下來為 details 服務建立一個 ServiceAccount。

Istio 為服務之間的通訊提供基於雙向 TLS 的認證,這是是通過給每個 ServiceAccount 建立一個證書實現的,可以使用 ServiceAccount 驗證對方的身份,不同的應用可以共用同一個 ServiceAccount,但是為每個 Deployment 使用單獨的 ServiceAccount 可以更好地組織和管理安全設定。

details_sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-details
  labels:
    account: details
kubectl -n bookinfo apply -f details_sa.yaml

ratings 應用

提供每條評論的打星資料。

部署命令:

kubectl -n bookinfo apply -f ratings_deploy.yaml
kubectl -n bookinfo apply -f ratings_svc.yaml
kubectl -n bookinfo apply -f ratings_sa.yaml

ratings_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ratings-v1
  labels:
    app: ratings
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ratings
      version: v1
  template:
    metadata:
      labels:
        app: ratings
        version: v1
    spec:
      serviceAccountName: bookinfo-ratings
      containers:
      - name: ratings
        image: docker.io/istio/examples-bookinfo-ratings-v1:1.17.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
        securityContext:
          runAsUser: 1000

ratings_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: ratings
  labels:
    app: ratings
    service: ratings
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: ratings

ratings_sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-ratings
  labels:
    account: ratings

reviews v1/v2/v3 應用

提供書籍的評論資訊。

部署命令:

kubectl -n bookinfo apply -f reviews_v1_deploy.yaml
kubectl -n bookinfo apply -f reviews_v2_deploy.yaml
kubectl -n bookinfo apply -f reviews_v3_deploy.yaml
kubectl -n bookinfo apply -f reviews_svc.yaml
kubectl -n bookinfo apply -f reviews_sa.yaml

為三個版本的 reviews 建立三個 Deployment。

reviews_v1_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v1
  labels:
    app: reviews
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v1
  template:
    metadata:
      labels:
        app: reviews
        version: v1
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
      - name: reviews
        image: docker.io/istio/examples-bookinfo-reviews-v1:1.17.0
        imagePullPolicy: IfNotPresent
        env:
        - name: LOG_DIR
          value: "/tmp/logs"
        ports:
        - containerPort: 9080
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: wlp-output
          mountPath: /opt/ibm/wlp/output
        securityContext:
          runAsUser: 1000
      volumes:
      - name: wlp-output
        emptyDir: {}
      - name: tmp
        emptyDir: {}

reviews_v2_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v2
  labels:
    app: reviews
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v2
  template:
    metadata:
      labels:
        app: reviews
        version: v2
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
      - name: reviews
        image: docker.io/istio/examples-bookinfo-reviews-v2:1.17.0
        imagePullPolicy: IfNotPresent
        env:
        - name: LOG_DIR
          value: "/tmp/logs"
        ports:
        - containerPort: 9080
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: wlp-output
          mountPath: /opt/ibm/wlp/output
        securityContext:
          runAsUser: 1000
      volumes:
      - name: wlp-output
        emptyDir: {}
      - name: tmp
        emptyDir: {}

reviews_v3_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: reviews-v3
  labels:
    app: reviews
    version: v3
spec:
  replicas: 1
  selector:
    matchLabels:
      app: reviews
      version: v3
  template:
    metadata:
      labels:
        app: reviews
        version: v3
    spec:
      serviceAccountName: bookinfo-reviews
      containers:
      - name: reviews
        image: docker.io/istio/examples-bookinfo-reviews-v3:1.17.0
        imagePullPolicy: IfNotPresent
        env:
        - name: LOG_DIR
          value: "/tmp/logs"
        ports:
        - containerPort: 9080
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        - name: wlp-output
          mountPath: /opt/ibm/wlp/output
        securityContext:
          runAsUser: 1000
      volumes:
      - name: wlp-output
        emptyDir: {}
      - name: tmp
        emptyDir: {}

給三個 Deployment 建立一個 Service,三個相同應用的不同版本共有同一個 Service。

reviews_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: reviews
  labels:
    app: reviews
    service: reviews
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: reviews

reviews_sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-reviews
  labels:
    account: reviews

productpage 應用

頁面聚合服務,供使用者瀏覽書籍資訊。

部署命令:

kubectl -n bookinfo apply -f productpage_deploy.yaml
kubectl -n bookinfo apply -f productpage_svc.yaml
kubectl -n bookinfo apply -f productpage_sa.yaml

productpage_deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: productpage-v1
  labels:
    app: productpage
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: productpage
      version: v1
  template:
    metadata:
      labels:
        app: productpage
        version: v1
    spec:
      serviceAccountName: bookinfo-productpage
      containers:
      - name: productpage
        image: docker.io/istio/examples-bookinfo-productpage-v1:1.17.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        securityContext:
          runAsUser: 1000
      volumes:
      - name: tmp
        emptyDir: {}

productpage_svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: productpage
  labels:
    app: productpage
    service: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage

productpage_sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: bookinfo-productpage
  labels:
    account: productpage

檢查

執行命令完成後,檢視 bookinfo 名稱空間下的 Pod。

kubectl get pods -n bookinfo

可以看到,每個 Pod 的 READY 屬性都是 2/2 ,這表示該 Pod 中有兩個容器,並且當前有兩個容器已經就緒。

如果我們檢視其中一個 Pod 的組成結構,會發現有 Pod 被塞進了一個 istio-proxy 容器。

如果 Kubernetes 中沒有安裝 Dashbooard ,那麼可以使用 kubectl -n bookinfo describe pod {Pod ID} 檢視組成結構。

接著使用 kubectl -n bookinfo get svc 檢視 Service,四個微服務都已經被註冊了 Service。

然後我們存取 productpage 對應的 CLUSTER-IP:

curl 10.233.37.130:9080

預設 Istio 不會開啟零信任雙向認證模式,因此在叢集內可以自己存取應用。如果開啟了 mTLS 雙向認證模式,則只能在 Pod 中存取應用。

可以看到返回了一堆 html,說明我們的部署是正常的。

臨時存取

接著為了檢視頁面效果,我們在暫未使用 Istio-ingressgateway 之前,臨時建立一個 Service 暴露 productpage 的頁面。

productpage_tmpsvc.yaml

apiVersion: v1
kind: Service
metadata:
  name: productpagetmp
  labels:
    app: productpage
    service: productpage
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: productpage
  type: NodePort
kubectl -n bookinfo apply -f  productpage_tmpsvc.yaml

檢視所有 Service:

root@k8smain:/data/learn/book# kubectl -n bookinfo get svc
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
details          ClusterIP   10.233.63.247   <none>        9080/TCP         40m
productpage      ClusterIP   10.233.37.130   <none>        9080/TCP         23m
productpagetmp   NodePort    10.233.47.14    <none>        9080:30258/TCP   77s
ratings          ClusterIP   10.233.7.6      <none>        9080/TCP         36m
reviews          ClusterIP   10.233.58.219   <none>        9080/TCP         23m

然後在頁面中存取 30258 埠(大家的埠不一樣,按自己的來)。

接著開啟 http://192.168.3.150:30258/productpage?u=normal

因為當前使用 Service 繫結 Pod,因此會使用輪詢實現負載均衡,你可以多次重新整理 http://192.168.3.150:30258/productpage?u=normal,會查到右側的評分星星有所變化。

Istio 預設情況下使用輪詢負載均衡的方法。

頁面右側評論顯示規則是 無星星 => 黑色星星 => 紅色星星。

部署入口閘道器

什麼是 Gateway

終於來到體驗 Istio 的時刻了,在本小節中,我們將會為 productpage 建立 Istio Gateway,對外提供網頁存取。

在第二章中,我們已經部署了 istio-ingressgateway,這個元件起到了類似 nginx、apisix 的效果,對外提供埠存取,然後將流量轉發到內部服務中。

但是 istio-ingressgateway 並不能直接轉發流量到 Pod,它還需要進行一些設定。我們要為 productpage 建立一個站點,繫結對應的域名,這樣外部存取 istio-ingressgateway 的埠時,istio-ingressgateway 才知道該將流量轉發給誰。在 Istio 中,定義這種繫結關係的資源叫 Gateway

後面的章節會解釋清楚,這裡大概瞭解即可。

Gateway 類似 Nginx 需要建立一個反向代理時需要繫結的域名設定。

部署 Gateway

建立一個 Gateway,繫結域名入口。

ingress_gateway.yaml

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: ingressgateway
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

hosts 表示對外開放的存取路徑,你可以繫結域名、IP 等。這裡使用 * ,表示所有存取都可以進入此閘道器。

kubectl -n bookinfo apply -f ingress_gateway.yaml

這一步就像 nginx 的監聽設定:

server {
    listen      80;
    server_name example.org www.example.org;
    #...
}

當我們建立 Istio Gateway 之後,istio-ingressgateway 會為我們監控流量,檢測不同的域名或埠屬於哪個 Istio Gateway 。

部署 VirtualService

什麼是 VirtualService

雖然建立了 Istio Gateway,但是我們還不能直接通過閘道器存取到前面部署的微服務,我們還需要建立 Istio VirtualService 將 Istio Gateway 跟對應的 Kubernetes Service 繫結起來,然後流量才能正式流向 Pod。

請一定要注意這裡,流量實際並不會經過 Service 中,但是 VirtualService 需要通過 Service 來發現 Pod。

這裡類似 nginx 設定反向代理,設定監聽之後,還需要指向將請求對映到哪個地址。

server {
    listen      80;
    server_name example.org www.example.org;
    #...
}

location /some/path/ {
    proxy_pass http://A:9080;
}

為什麼不直接將 Gateway 跟 Service 繫結,而是中間加個 VirtualService 呢?有句話叫做,計算機領域中的問題,都可以通過增加一個層來解決。

VirtualService 的主要目標是為服務提供穩定的入口地址,並通過設定一系列的路由規則來控制流量在網格內的行為。

就以最簡單的路由區配來說,Kubernetes Service 是不支援路由規則的,而 Istio 可以通過指定路由字尾中;Service 不支援流量分析,負載均衡只有輪詢。而 Istio 利用 Service 來發現 Pod,然後直接將流量轉發到 Pod 中,可以實現各種功能。

VirtualService 可以用於實現以下功能:

請求路由:將請求路由到特定的服務或版本,例如將請求分發到不同版本的服務,以實現灰度釋出或金絲雀釋出。

請求重試:為失敗的請求設定重試策略,以提高服務的可用性。

請求超時:設定請求的超時時間,以便在特定時間內沒有得到響應時中斷請求。

請求映象:將請求的副本傳送到另一個服務,用於測試新版本的服務,而不影響實際的生產流量。

流量分割:將流量按照特定的比例分發到不同的服務或版本,以實現流量控制。

部署 VirtualService

productpage_vs.yaml

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: bookinfo
spec:
  hosts:
  - "*"
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        prefix: /static
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        host: productpage
        port:
          number: 9080
kubectl -n bookinfo apply -f productpage_vs.yaml

關於 VistualService 中各種設定的作用,在 4.1 章節中會有介紹。

這裡的 YAML 分為兩大部分,第一部分是 http.match,表示暴露了哪些 API 地址,外部存取時只能存取到這些地址。

可以通過 http.match 限制叢集外部存取此地址時所能使用的 URL。

然後通過 http.route 繫結 Kubernetes Service ,通過 Service 中的服務發現,將流量轉發到對應的 Pod 中。

host 這裡,由於 VirtualService 跟 Service/Pod 在同一個名稱空間中,所以只需要設定 Service 的名稱即可,如果要跨名稱空間存取,則需要加上完整的名稱空間名稱。

什麼是 DestinationRule

在本章中,會提前預告 DestinationRule,下一章才會使用 DestinationRule,這裡我們知道還有 DestinationRule 這個東西即可。

Istio VistualService 中可以限制外部能夠存取的路由地址,而 DestinationRule 則可以設定存取的 Pod 策略。可以為 Istio VistualService 繫結一個 Istio DestinationRule,通過 DestinationRule 我們還可以定義版本子集等,通過更加豐富的策略轉發流量。

由於只暴露了五個地址,所以外部直接存取 / ,是打不開頁面的。

檢查

為了確保閘道器沒問題,我們需要執行 Istio 命令檢視紀錄檔:

istioctl analyze

然後我們檢視為 productpage 建立的閘道器。

root@k8smain:/data/learn/book# kubectl get gw -A
NAMESPACE   NAME               AGE
bookinfo    bookinfo-gateway   26m

Kubernetes 本身也有一個 Gateway,因此不能使用 kubectl get gateway 來獲取 Istio 的 Gateway,而是使用簡寫 gw

然後檢視 VistualService。

root@k8smain:/data/learn/book# kubectl get vs -A
NAMESPACE   NAME       GATEWAYS               HOSTS   AGE
bookinfo    bookinfo   ["bookinfo-gateway"]   ["*"]   79m

在第二章中,我們通過 Helm 部署了 istio-ingressgateway,其存取埠如下:

在本節部署 bookinfo-gateway 的時候,我們使用了埠 80,因此不需要另外設定 ,直接通過 istio-ingressgateway 的 32309 埠存取即可。

存取時一定需要帶 /productpage ,因為我們並沒有放通 /

嘗試修改 Gateway 埠

如果需要更換埠,可以修改 istio-ingressgateway 的 Service,增加新的埠對映。

kubectl edit svc istio-ingressgateway -n istio-system  

然後修改前面的 ingress_gateway.yaml,將埠從 80 改成 666 。

即可通過 32666 埠存取到此微服務。

範例:http://192.168.3.150:30666/productpage