歡迎光臨
每天分享高質量文章

使用 Istio 實現基於 Kubernetes 的微服務應用

近兩年,隨著容器、Kubernetes 等技術的興起,微服務被廣泛提及並被大量使用。本文旨在讓讀者瞭解 Istio,透過它與 Kubernetes 相結合,大幅降低微服務的複雜度,以便讓開發人員更關註於程式碼本身。
Istio 的架構分析

 

Istio 介紹
Istio 被稱為 Service Mesh 架構,該開源專案由 Google 和 IBM 主導,根據 http://stackalytics.com 網站的統計,該社群程式碼 Commits 廠商排名如下: 
圖 1. Istio 各廠商程式碼貢獻量圖示
圖 2. Istio 各廠商程式碼貢獻量排名
在 GitHub 上,Istio 專案受關註的程度非常高,您可進一步瞭解。接下來,我們詳細介紹 Istio 的技術架構。
Istio 的架構
Istio 分為兩個平面:資料平面和控制平面。
資料平面:
資料平面由一組 Sidecar 的代理(Envoy)組成。這些代理調解和控制微服務之間的所有網路通訊,並且與控制平面的 Mixer 通訊,接受排程策略。
控制平面:
控制平面透過管理和配置 Envoy 來管理流量。此外,控制平面配置 Mixers 來實施路由策略並收集檢測到的監控資料。 
圖 3. Istio 的架構圖
在介紹了 Istio 的兩個平面以後,我們詳細介紹 Istio 的各個元件。
Envoy 是一個用 C ++開發的高效能代理,用於管理 Service Mesh 中所有服務的所有入站和出站流量。 Istio 利用 Envoy 的許多內建功能,例如:
  • 動態服務發現

  • 負載均衡

  • TLS 終止

  • HTTP / 2 和 gRPC 代理

  • 斷路器

  • 健康檢查

  • 流量分割

  • 故障註入

  • 監控指標

 

我們知道,在 Kubernetes 中叢集中,Pod 是最小的計算資源排程單位。一個 Pod 可以包含一個或者多個容器,但通常是一個。而使用 Istio 的時候,需要在 Pod 中主容器旁註入一個 Sidecar,也就是上面提到的代理(Envoy)。
舉一個例子,我們檢視一個被註入了 Envoy 的 Pod,從下圖結果可以看到,這個 Pod 包含兩個容器: 
圖 4. 檢視 Pod 中的 Container
在 Istio 中,每一個 Pod 中都必須要部署一個 Sidecar。
Mixer 是一個獨立於平臺的元件,負責在整個 Service Mesh 中執行訪問控制和使用策略,並從 Envoy 代理和其他服務收集監控到的資料。
Pilot 為 Envoy 提供服務發現;為高階路由(例如,A / B 測試,金絲雀部署等)提供流量管理功能;以及異常控制,如:超時,重試,斷路器等。
Citadel 透過內建身份和憑證管理,提供強大的服務到服務和終端使用者身份驗證。我們可以使用 Citadel 升級 Service Mesh 中的未加密流量。我們可以使用 Istio 的授權功能來控制誰可以訪問服務。
Istio 路由規則的實現
在 Istio 中,和路由相關的有四個概念:Virtual Services 、Destination Rules、ServiceEntry、Gateways。
Virtual Services 的作用是:定義了針對 Istio 中的一個微服務的請求的路由規則。Virtual Services 既可以將請求路由到一個應用的不同版本,也可以將請求路由到完全不同的應用。
在如下的示例配置中,發給微服務的請求,將會被路由到 Productpage,埠號為 9080。
  1. route:
  2.    - destination:
  3.        host: productpage
  4.        port:
  5.          number: 9080
清單 1. Virtual Services 規則
在下麵的示例配置中,定義了熔斷策略。
  1. spec:
  2.  host: productpage
  3.  subsets:
  4.  - labels:
  5.      version: v1
  6.    name: v1
  7.  trafficPolicy:
  8.    connectionPool:
  9.      http:
  10.        http1MaxPendingRequests: 1
  11.        maxRequestsPerConnection: 1
  12.      tcp:
  13.        maxConnections: 1
  14.    tls:
清單 2. Destination 規則
ServiceEntry 用於將 Istio 外部的服務註冊到 Istio 的內部服務登錄檔,以便 Istio 內部的服務可以訪問這些外部的服務,如 Istio 外部的 Web API。
在如下的示例配置中,定義了 Istio 外部的 Mongo Cluster 與 Istio 內部的訪問規則。
  1. apiVersion: networking.istio.io/v1alpha3
  2. kind: ServiceEntry
  3. metadata:
  4.  name: external-svc-mongocluster
  5. spec:
  6.  hosts:
  7.  - mymongodb.somedomain # not used
  8.  addresses:
  9.  - 192.192.192.192/24 # VIPs
  10.  ports:
  11.  - number: 27018
  12.    name: mongodb
  13.    protocol: MONGO
  14.  location: MESH_INTERNAL
  15.  resolution: STATIC
  16.  endpoints:
  17.  - address: 2.2.2.2
  18.  - address: 3.3.3.3
清單 3. ServiceEntry 規則
Gateway:定義了 Istio 邊緣的負載均衡器。所謂邊緣,就是 Istio 的入口和出口。這個負載均衡器用於接收傳入或傳出 Istio 的 HTTP / TCP 連線。在 Istio 中會有 Ingress Gateway 和 Egress Gateway,前者負責入口流量,後者負責出口流量。
在如下的示例配置中,定義了 Istio 的入口流量。
  1. spec:
  2.  selector:
  3.    istio: ingressgateway
  4.  servers:
  5.  - hosts:
  6.    - '*'
  7.    port:
  8.      name: http
  9.      number: 80
  10.      protocol: HTTP
清單 4. Gateway 規則
Istio 的技術實現

 

基於 Kubernetes 部署的 Istio
在本文中,我們基於 Kubernetes 1.11 部署 Istio 1.04。由於篇幅有限,具體的部署步驟可以參考 Quick Start with Kubernetes(https://istio.io/docs/setup/Kubernetes/quick-start/)。
檢視 Kubernetes 版本: 
圖 5. 檢視 Kubernetes 叢集的版本
檢視 Kubernetes 叢集: 
圖 6. 檢視 Kubernetes 叢集的節點
檢視部署好的 Istio。
Istio 以一個專案的形式部署到 Kubernetes 叢集中。我們可以看到,部署好的 Pod 中,除了有 istio-citadel、istio-egressgateway、istio-ingressgateway、istio-pilot 等 Istio 本身的功能元件,還集成了微服務相關的監控工具,如:Grafana、jaeger-agent、Kiali、Prometheus。正是這些功能豐富且強大的監控工具,幫助 Istio 實現了微服務的視覺化管理。
 
圖 7. 檢視 Kubernetes 中的 Istio
檢視 istio 版本:1.0.4: 
圖 8. 檢視 Istio 版本
接下來,我們將會對 Isito 整合的工具進行介紹。本文最後的實驗展現環節,我們將會使用這些工具進行微服務的監控和管理。
Istio 的工具集:Grafana
Grafana 是一個非常著名的開源專案。它是一個 Web 應用,可以提供豐富的監控儀錶盤。它的後端支援 Graphite、 InfluxDB 或 OpenTSDB。
透過瀏覽器訪問 Istio 中部署好的 Grafana。
登入 Grafana 後,首頁面如下: 
圖 9. Grafana 首頁面
檢視已有的 Dashboard: 
圖 10. Grafana 上的 Istio Dashboard
我們檢視 Pilot Dashboard,可以看到豐富的資源統計。 
圖 11. Grafana 上的 Istio Dashboard 檢視
Istio 的工具集:Prometheus
Prometheus 是一個開源監控系統。它具有維度資料模型;具備靈活的查詢語言、高效的時間序列資料庫,並提供靈活的警報方法。
在 Istio 中,Prometheus 收到的資料,會被彙總到 Grafana 進行統一展現。
訪問 Istio 中部署好的 Prometheus: 
圖 12. Prometheus 的 UI
我們可以看到有多達上百個監測點: 
圖 13. Prometheus 的監測點
例如我們選擇 Containermemorycache,點選 Execute。
圖 14. 執行監測點
然後可以生成圖形化介面展示,並且我們也可以調整時間間隔(圖中是 60 分鐘)。 
圖 15. 監控圖
Istio 的工具集:Kiali
Kiali 作為一個開源專案,可以為 Istio 提供視覺化服務網格拓撲、斷路器或請求率等功能。此外 Kiali 還包括 Jaeger Tracing,可以提供開箱即用的分散式跟蹤功能。
我們看一下 Istio 中部署的 Kiali: 
圖 16. Kiali 的 UI 首頁
它可以檢視在 Istio 上部署的微服務的拓撲結構: 
圖 17. Kiali 檢視微服務的拓撲
Istio 的工具集:Jaeger
Jaeger 是一個開源專案,用於微服務的分散式跟蹤。它實現的功能有:
  • 分散式事務監控

  • 服務呼叫問題根因分析

  • 服務依賴性分析

  • 效能/延遲最佳化

Jaeger 工具已經整合到 Istio 中,部署以後可以透過瀏覽器訪問。
下圖是 Jeager 追蹤 Productpage 這個服務在過去三個小時的所有呼叫: 
圖 18. Jaeger 檢視 API 呼叫
我們可以展開看詳細的呼叫層級: 
圖 19. Jaeger 檢視 API 詳細呼叫
Istio 管理微服務的實驗展現

 

在本小節中,我們將在 Istio 上部署一個名為 bookinfo 的微服務應用。為了方便讀者理解,我們先分析這個應用的原始碼。然後展示 Istio 如何管理這套微服務。
bookinfo 微服務原始碼分析
bookinfo 應用程式顯示的有關書籍的資訊,類似於線上書店的單個商品。應用頁面上顯示的是書籍的描述、書籍詳細資訊(ISBN,頁數等)以及書評。
bookinfo 應用一共包含四個微服務:Productpage、Details、Reviews、Ratings。
  • Productpage 使用 Python 開發,負責展示書籍的名稱和書籍的簡介。

  • Details 使用 Ruby 開發,負責展示書籍的詳細資訊。

  • Reviews 使用 Java 開發,負責顯示書評。

  • Ratings 使用 Node.js 開發,負責顯示書籍的評星。

其拓撲關係見下圖。 
圖 20. bookinfo 應用拓撲架構
我們看一下 bookinfo 微服務部署完畢的展示效果: 
圖 21. bookinfo 應用頁面展示效果
Productpage 微服務包含兩部分內容:
  • 書籍的名稱:”The Comedy of Errors”,翻譯成中文是《錯誤的喜劇》。

  • 書籍的簡介:Summary。翻譯成中文是:《錯誤的喜劇》是威廉·莎士比亞早期劇作之一。這是他最短的、也是他最喜歡的喜劇之一,除了雙關語和文字遊戲之外,幽默的主要部分來自於打鬧和錯誤的身份。

Details 微服務包含的內容是書籍的詳細資訊,內容如下:
  1. Type:
  2. paperback
  3. Pages:
  4. 200
  5. Publisher:
  6. PublisherA
  7. Language:
  8. English
  9. ISBN-10:
  10. 1234567890
  11. ISBN-13:
  12. 123-1234567890
清單 5. Details 微服務顯示內容
Reviews 微服務包含的資訊是書評內容:
  1. An extremely entertaining play by Shakespeare. The slapstick humour is refreshing!
  2. Absolutely fun and entertaining. The play lacks thematic depth when compared to other plays by Shakespeare.
清單 6. 書評內容
Ratings 微服務包含的內容將展示為評星部分(下圖黑框中的部分)。
圖 22. 評星圖
接下來,我們訪問 GitHub 上檢視 bookinfo 原始碼,透過原始碼分析,理解業務的實現邏輯。
首先檢視 Productpage(https://github.com/istio/istio/tree/master/samples/bookinfo/src/productpage)的原始碼(部分內容):
  1. def getProducts():
  2.        return [
  3.            {
  4.                'id': 0,
  5.                'title': 'The Comedy of Errors',
  6.                'descriptionHtml': 'Wikipedia Summary: The Comedy of Errors is one of < b>William Shakespeare\'s< /b> early plays. It is his shortest and one of his most farcical comedies, with a major part of the humour coming from slapstick and mistaken identity, in addition to puns and word play.'
  7.            }
清單 7. Productpage 原始碼
我們可以很明顯看出,以上程式碼就是 bookinfo 頁面顯示的書籍的名稱和簡介。
檢視 Productpage 的另外一部分原始碼。
  1. details = {
  2. "name" : "http://details{0}:9080".format(servicesDomain),
  3. "endpoint" : "details",
  4. "children" : []
  5. }
  6.  
  7.  
  8. ratings = {
  9. "name" : "http://ratings{0}:9080".format(servicesDomain),
  10. "endpoint" : "ratings",
  11. "children" : []
  12. }
  13.  
  14.  
  15. reviews = {
  16. "name" : "http://reviews{0}:9080".format(servicesDomain),
  17. "endpoint" : "reviews",
  18. "children" : [ratings]
  19. }
  20.  
  21.  
  22. productpage = {
  23. "name" : "http://details{0}:9080".format(servicesDomain),
  24. "endpoint" : "details",
  25. "children" : [details, reviews]
  26. }
  27.  
  28.  
  29. service_dict = {
  30. "productpage" : productpage,
  31. "details" : details,
  32. "reviews" : reviews,
  33. }
清單 8. Productpage 原始碼
上面程式碼定義了四個微服務的 name、endpoint、children。endpoint 代表這個微服務後端的 Kubernetes 叢集中 service 名稱、children 代表本微服務呼叫的 Kubernetes 叢集中的微服務 service 名稱。
以 Productpage 舉例,它的 endpoint 是 details、children 是 details 和 reviews。所以,被髮送到 Productpage 請求,將會呼叫 details、reviews 這兩個服務。
接下來,檢視 reviews 微服務的原始碼,程式碼使用 Java 編寫的。
  1. private String getJsonResponse (String productId, int starsReviewer1, int starsReviewer2) {
  2.    String result = "{";
  3.    result += "\"id\": \"" + productId + "\",";
  4.    result += "\"reviews\": [";
  5.  
  6.    // reviewer 1:
  7.    result += "{";
  8.    result += "  \"reviewer\": \"Reviewer1\",";
  9.    result += "  \"text\": \"An extremely entertaining play by Shakespeare. The slapstick humour is refreshing!\"";
  10.  if (ratings_enabled) {
  11.    if (starsReviewer1 != -1) {
  12.      result += ", \"rating\": {\"stars\": " + starsReviewer1 + ", \"color\": \"" + star_color + "\"}";
  13.    }
  14.    else {
  15.      result += ", \"rating\": {\"error\": \"Ratings service is currently unavailable\"}";
  16.    }
  17.  }
  18.    result += "},";
  19.  
  20.    // reviewer 2:
  21.    result += "{";
  22.    result += "  \"reviewer\": \"Reviewer2\",";
  23.    result += "  \"text\": \"Absolutely fun and entertaining. The play lacks thematic depth when compared to other plays by Shakespeare.\"";
  24.  if (ratings_enabled) {
  25.    if (starsReviewer2 != -1) {
  26.      result += ", \"rating\": {\"stars\": " + starsReviewer2 + ", \"color\": \"" + star_color + "\"}";
  27.    }
  28.    else {
  29.      result += ", \"rating\": {\"error\": \"Ratings service is currently unavailable\"}";
  30.    }
  31.  }
  32.    result += "}";
  33.  
  34.    result += "]";
  35.    result += "}";
  36.  
  37.    return result;
  38. }
清單 9. reviews 服務原始碼
上面的這段程式碼,定義的是兩個 Reviewer,以及書評的內容。書評的內容正是 bookinfo 頁面展示的內容。
在上面的程式碼中,我們註意到有兩個重要的變數 starcolor 和 ratingsenabled。
  • star_color 表示評星的顏色(黑色和紅色)。

  • ratings_enabled 表示是否啟用評星。

檢視 reviews 微服務的原始碼的另外一部分內容:
  1. private final static String star_color = System.getenv("STAR_COLOR") == null ? "black" : System.getenv("STAR_COLOR");
清單 10. reviews 服務原始碼
上面程式碼顯示:在應用構建時:
  • 如果不指定 STARCOLOR 變數且 ratingsenabled 為 true,那麼評星預設為黑色。

  • 如果指定 STARCOLOR 變數且 ratingsenabled 為 true,那麼評星顏色為傳入的顏色。

  • 如果不指定 ratings_enabled 為 true,那麼將不會顯示評星。

 

那麼,STAR_COLOR 這個變數,在應用構建時,有沒有傳入呢?我們檢視:build-services.sh
  1. #java build the app.
  2. docker run --rm -v "$(pwd)":/home/gradle/project -w /home/gradle/project gradle:4.8.1 gradle clean build
  3. pushd reviews-wlpcfg
  4.  #plain build -- no ratings
  5.  docker build -t "istio/examples-bookinfo-reviews-v1:${VERSION}" -t istio/examples-bookinfo-reviews-v1:latest --build-arg service_version=v1 .
  6.  #with ratings black stars
  7.  docker build -t "istio/examples-bookinfo-reviews-v2:${VERSION}" -t istio/examples-bookinfo-reviews-v2:latest --build-arg service_version=v2 \
  8.     --build-arg enable_ratings=true .
  9.  #with ratings red stars
  10.  docker build -t "istio/examples-bookinfo-reviews-v3:${VERSION}" -t istio/examples-bookinfo-reviews-v3:latest --build-arg service_version=v3 \
  11.     --build-arg enable_ratings=true --build-arg star_color=red .
清單 11. build-services.sh 內容
上面程式碼顯示,執行該指令碼是,將會構建三個版本 Reviews 的 docker image:
  • V1:沒有評星(未指定 enable_ratings=true)。

  • V2:評星為黑色(指定 enableratings=true;未指定 starcolor 的變數,程式碼中預設的顏色為黑色)。

  • V3:評星為紅色(指定 enableratings=true;指定 starcolor 的變數為 red)。

 

在 bookinfo 的原始碼中,還有兩個資料庫的定義 MongoDB 和 MySQL。
接下來,我們看這個應用中兩個資料庫的內容。
先看 MongoDB 的 script.sh,內容如下:
  1. #!/bin/sh
  2.    set -e
  3.    mongoimport --host localhost --db test --collection ratings --drop --file /app/data/ratings_data.json
清單 12. MongoDB 的 script.sh
也就是說,MongoDB 資料庫在初始化時,會將 ratings_data.json 檔案中的資訊匯入到資料庫中。
再看 ratings_data.json:
  1. {rating: 5}
  2. {rating: 4}
清單 13. ratings_data.json 檔案
也就是說,當應用部署完畢後,MongoDB 將包含五星和四星。
檢視 MySQL 的初始化檔案:mysqldb-init.sql。
  1. # Initialize a mysql db with a 'test' db and be able test productpage with it.
  2. # mysql -h 127.0.0.1 -ppassword < mysqldb-init.sql
  3.  
  4. CREATE DATABASE test;
  5. USE test;
  6.  
  7. CREATE TABLE `ratings` (
  8.  `ReviewID` INT NOT NULL,
  9.  `Rating` INT,
  10.  PRIMARY KEY (`ReviewID`)
  11. );
  12. INSERT INTO ratings (ReviewID, Rating) VALUES (1, 5);
  13. INSERT INTO ratings (ReviewID, Rating) VALUES (2, 4);
清單 14. mysqldb-init.sql 內容
我們可以看出,上面的初始化指令碼是建立一個名為 Ratings 的資料庫表,插入的資料效果如下:
ReviewID Rating
1 5
2 4
表 1. 資料庫表示意
檢視 Ratings 的原始碼,該程式碼使用 Node.JS 書寫。
  1. * We default to using mongodb, if DB_TYPE is not set to mysql.
  2. */
  3. if (process.env.SERVICE_VERSION === 'v2') {
  4.  if (process.env.DB_TYPE === 'mysql') {
  5.    var mysql = require('mysql')
  6.    var hostName = process.env.MYSQL_DB_HOST
  7.    var portNumber = process.env.MYSQL_DB_PORT
  8.    var username = process.env.MYSQL_DB_USER
  9.    var password = process.env.MYSQL_DB_PASSWORD
  10.  } else {
  11.    var MongoClient = require('mongodb').MongoClient
  12.    var url = process.env.MONGO_DB_URL
  13.  }
  14. }
  15.  
  16. dispatcher.onGet(/^\/ratings\/[0-9]*/, function (req, res) {
  17.  var productIdStr = req.url.split('/').pop()
  18.  var productId = parseInt(productIdStr)
  19.  
  20.  if (Number.isNaN(productId)) {
  21.    res.writeHead(400, {'Content-type': 'application/json'})
  22.    res.end(JSON.stringify({error: 'please provide numeric product ID'}))
  23.  } else if (process.env.SERVICE_VERSION === 'v2') {
  24.    var firstRating = 0
  25.    var secondRating = 0
  26.  
  27.    if (process.env.DB_TYPE === 'mysql') {
  28.      var connection = mysql.createConnection({
  29.        host: hostName,
  30.        port: portNumber,
  31.        user: username,
  32.        password: password,
  33.        database: 'test'
  34.      })
  35.  
  36.      connection.connect()
  37.      connection.query('SELECT Rating FROM ratings', function (err, results, fields) {
  38.        if (err) {
  39.          res.writeHead(500, {'Content-type': 'application/json'})
  40.          res.end(JSON.stringify({error: 'could not connect to ratings database'}))
  41.        } else {
  42.          if (results[0]) {
  43.            firstRating = results[0].Rating
  44.          }
  45.          if (results[1]) {
  46.            secondRating = results[1].Rating
  47.          }
  48.          var result = {
  49.            id: productId,
  50.            ratings: {
  51.              Reviewer1: firstRating,
  52.              Reviewer2: secondRating
  53.            }
  54.          }
  55.          res.writeHead(200, {'Content-type': 'application/json'})
  56.          res.end(JSON.stringify(result))
  57.        }
  58.      })
清單 15. Ratings 的原始碼
以上程式碼主要實現:如果不指定 DB_TYPE 的變數,將預設使用 MongoDB 資料庫。
當微服務 Reviews 的版本是 V2 時,將連線資料庫 MySQL 或 MongoDB(根據環境變數傳入的 DB_TYPE)。當 Reviews 的版本是 V3 時,訪問 MongoDB 資料庫。
但從上面的資料庫分析我們可以知道,無論 Reviews 連線哪個資料庫,得到的資料都是第一個評論者五星、第二個評論者四星。也就是說,只要是 Reviews 的 V2 和 V3 版本,訪問資料庫得到的評星結果是一樣的;只不過 Reviews 為 V2 時評星為黑色、Reviews 為 V3 時評星為紅色。
微服務的部署
我們在 Kubernetes 叢集中部署 bookinfo 應用。
圖 23. 在 Kubernetes 叢集部署 bookinfo
Pod 建立成功: 
圖 24. 檢視 bookinfo 的 Pod
接下來,透過瀏覽器對 bookinfo 發起多次訪問,頁面呈現三種顯示。
  • 第一種:訪問 bookinfo 時(Productpage 呼叫的 Review V1),頁面沒有評星;

  • 第二種:訪問 bookinfo 時(Productpage 呼叫的 Review V2),頁面是黑色的評星;

  • 第三種:訪問 bookinfo 時(Productpage 呼叫的 Review V3),頁面是紅色的評星。

 

圖 25. bookinfo 第一種展現
圖 26. bookinfo 第二種展現
圖 27. bookinfo 第三種展現
過了幾秒後:Kiali 收集到之前幾次對 bookinfo 的訪問流量,併進行動態展示。我們可以看到,Productpage 隨機訪問 Reviews V1、V2、V3。
圖 28. Kiali 展示微服務流量
Productpage 輪詢訪問 Review V1 和 V2 的原因是:我們沒有設定針對 Reviews 的特定策略,而 Productpage 的原始碼中,指定了 Product 服務呼叫 Reviews 服務的業務邏輯,但並未指定版本。因此,Product 服務會隨機訪問 Reviews 的三個版本。 
圖 29. 檢視 virtualservice
接下來,我們檢視 Virtualservice 的配置檔案。
圖30. 檢視 Virtualservice 配置檔案串列
檢視 virtual-service-reviews-v3.yaml 內容。該檔案定義發向 Reviews 的請求,全部到 V3 版本。
  1. [root@master networking]# cat virtual-service-reviews-v3.yaml
  2. apiVersion: networking.istio.io/v1alpha3
  3. kind: VirtualService
  4. metadata:
  5.  name: reviews
  6. spec:
  7.  hosts:
  8.    - reviews
  9.  http:
  10.  - route:
  11.    - destination:
  12.        host: reviews
  13.        subset: v3      version: v3
清單 16. 檢視 virtual-service-reviews-v3.yaml 內容
接下來,應用 Virtualservice 配置。
圖 31. 應用配置
檢視生效的 Virtualservice,reviews 的配置生效。
圖 32. 檢視生效的 Virtualservice
接下來,我們再次對 bookinfo 發起多次訪問,可以看到,頁面的評星均為紅色。 
圖 33. 訪問 bookinfo 應用
透過 Kiali 檢視流量,可以看到,Productpage 的流量全部訪問 Review V3。
圖 34. Kiali 檢視 bookinfo 應用訪問流量
接下來,我們繼續調整策略,讓 Productpage 對 Reviews 的訪問,以 V1 和 V2 按照 8:2 比率進行:
  1. [root@master networking]# cat virtual-service-reviews-80-20.yaml
  2. apiVersion: networking.istio.io/v1alpha3
  3. kind: VirtualService
  4. metadata:
  5.  name: reviews
  6. spec:
  7.  hosts:
  8.    - reviews
  9.  http:
  10.  - route:
  11.    - destination:
  12.        host: reviews
  13.        subset: v1
  14.      weight: 80
  15.    - destination:
  16.        host: reviews
  17.        subset: v2
  18.      weight: 20
清單 17. 檢視 virtual-service-reviews-80-20.yaml 內容
圖 35. 替換之前的 Virtual Services 策略
圖 36. Kiali 檢視 bookinfo 應用訪問流量
Istio 的限速
在瞭解了 Istio 微服務的路由策略後,接下來我們對微服務元件進行限速的設定。
在預設情況下,Productpage 隨機訪問 reviews 三個版本,沒有流量限制。我們編寫兩個配置檔案,限制 Productpage 到 reviews v3 的訪問速度。
speedrule.yaml 限制了從 Productpage 到 reviews v3 的訪問速度,最多每秒一個請求。
  1. [root@master ~]# cat speedrule.yaml
  2. apiVersion: "config.istio.io/v1alpha2"
  3. kind: memquota
  4. metadata:
  5.  name: handler
  6.  namespace: myproject
  7. spec:
  8.  quotas:
  9.  - name: requestcount.quota.myproject
  10.    # default rate limit is 5000qps
  11.    maxAmount: 5000
  12.    validDuration: 1s
  13.    # The first matching override is applied.
  14.    # A requestcount instance is checked against override dimensions.
  15.    overrides:
  16.    - dimensions:
  17.        destination: reviews
  18.        source: productpage
  19.        destinationVersion: v3
  20.      maxAmount: 1
  21.      validDuration: 1s
  22.  
  23. recommendation_rate_limit_handler.yml 檔案宣告了 requestcount quota。
清單 18. 檢視 speedrule.yaml
  1. [root@master ~]# cat recommendation_rate_limit_handler.yml
  2. apiVersion: "config.istio.io/v1alpha2"
  3. kind: quota
  4. metadata:
  5.  name: requestcount
  6.  namespace: myproject
  7. spec:
  8.  dimensions:
  9.    source: source.labels["app"] | source.service | "unknown"
  10.    sourceVersion: source.labels["version"] | "unknown"
  11.    destination: destination.labels["app"] | destination.service | "unknown"
  12.    destinationVersion: destination.labels["version"] | "unknown"
  13. ---
  14. apiVersion: "config.istio.io/v1alpha2"
  15. kind: rule
  16. metadata:
  17.  name: quota
  18.  namespace: myproject
  19. spec:
  20.  actions:
  21.  - handler: handler.memquota
  22.    instances:
  23.    - requestcount.quota
  24. ---
  25. apiVersion: config.istio.io/v1alpha2
  26. kind: QuotaSpec
  27. metadata:
  28.  creationTimestamp: null
  29.  name: request-count
  30.  namespace: myproject
  31. spec:
  32.  rules:
  33.  - quotas:
  34.    - charge: 1
  35.      quota: RequestCount
  36. ---
  37. apiVersion: config.istio.io/v1alpha2
  38. kind: QuotaSpecBinding
  39. metadata:
  40.  creationTimestamp: null
  41.  name: request-count
  42.  namespace: myproject
  43. spec:
  44.  quotaSpecs:
  45.  - name: request-count
  46.    namespace: myproject
  47.  services:
  48.  - name: productpage
  49.    namespace: myproject
  50.  - name: details
  51.    namespace: myproject
  52.  - name: reviews
  53.    namespace: myproject
清單 19. 檢視 recommendation_rate_limit_handler.yml
應用兩個配置:
 
圖 37. 應用配置
圖 38. 應用配置
然後,對 bookinfo 發起高頻度請求,訪問請求頻率為 10 次/秒。
  1. while true; do curl http://istio-ingressgateway-istio-system.apps.example.com/productpage; sleep .1; done
清單 20. 發起高頻度訪問請求
透過 Kiali,可以看到: 
圖 39. Kiali 顯示微服務流量
我們檢視 Grafana,可以看到這段時間內,reviews v3 的流量,遠低於同期 reviews v1 和 v2 的流量。
圖 40. Grafana 顯示微服務流量統計
Istio 的熔斷
熔斷技術,是為了避免”雪崩效應”的產生而出現的。我們都知道”雪球越滾越大”的現象。應用中的雪崩指的由於應用第一個元件的出現問題,造成呼叫這個元件的第二個元件有無無法呼叫第一個元件,無法實現業務邏輯,也出現問題;而呼叫第二個元件第三個元件因此也出現問題,問題迅速傳播,從而造成整個應用的癱瘓,我們稱之為應用的雪崩效應。
在單體應用中,多個業務的功能模組放在一個應用中,且由於各個功能模組之前是緊耦合,因此不容易出現雪崩情況。但由於微服務松耦合、各個元件呼叫關係複雜的特點,雪崩現象就較為容易出現。為了避免雪崩情況的發生,就需要有熔斷機制,採用斷路樣式。熔斷機制相當於為每個微服務前面加一個”保險絲”。當電流負載過大的時候(如服務訪出現故障問超時,並且超過設定的重試次數),保險絲燒斷,中斷客戶端對該應用的訪問,而不影響客戶端訪問其他正常執行的元件。
Spring Cloud 中熔斷的實現,需要呼叫 Hystrix。而 Istio 本身自帶熔斷的功能。下麵,我們進行實現展現。
在初始情況下,未配置熔斷。 
圖 41. 配置熔斷之前正常訪問應用流量
接下來,我們在 Productpage 的 destination rule 中配置熔斷策略(trafficPolicy):每個連結最多的請求數量是一個;最多 pending 的 request 是一個、最多的連線數是一個。
  1. spec:
  2.  host: productpage
  3.  subsets:
  4.  - labels:
  5.      version: v1
  6.    name: v1
  7.  trafficPolicy:
  8.    connectionPool:
  9.      http:
  10.        http1MaxPendingRequests: 1
  11.        maxRequestsPerConnection: 1
  12.      tcp:
  13.        maxConnections: 1
  14.    tls:
  15.      mode: ISTIO_MUTUAL
清單 21. 在 destination rule 中配置熔斷(open.yaml 配置檔案部分內容)
接下來,我們先刪除舊的配置,應用熔斷的配置。 
圖 42. 應用新配置
圖 43. 發起大併發流量後的熔斷
過一會,當問題熔斷器開啟後,業務恢復正常: 
圖 44. 熔斷後的應用訪問頁面
Istio 的訪問控制
Istio 中的訪問控制有白名單和黑名單。白名單是允許從哪個服務到哪個服務的訪問;黑名單是不允許從哪個服務到哪個服務之間的訪問。兩種實現的效果展現是一樣的,由於篇幅有限,本小節展示黑名單。
我們將在 details 服務上建立一個黑名單,從 Productpage 發往 details 請求將會傳回 403 錯誤碼。
  1. [root@master ~]# cat acl-blacklist.yml
  2. apiVersion: "config.istio.io/v1alpha2"
  3. kind: denier
  4. metadata:
  5.  name: denycustomerhandler
  6. spec:
  7.  status:
  8.    code: 7
  9.    message: Not allowed
  10. ---
  11. apiVersion: "config.istio.io/v1alpha2"
  12. kind: checknothing
  13. metadata:
  14.  name: denycustomerrequests
  15. spec:
  16. ---
  17. apiVersion: "config.istio.io/v1alpha2"
  18. kind: rule
  19. metadata:
  20.  name: denycustomer
  21. spec:
  22.  match: destination.labels["app"] == "details" && source.labels["app"]=="productpage"
  23.  actions:
  24.  - handler: denycustomerhandler.denier
  25. instances: [ denycustomerrequests.checknothing ]
清單 22. 黑名單配置檔案
圖 45. 應用黑名單
接下來,對 Producepage 發起流量訪問。從下圖可以看到,從 Productpage 到 details 之間的訪問是被拒絕的。 
圖 46. Kali 顯示應用流量訪問
此時,透過瀏覽器訪問 bookinfo,介面無法顯示產品的詳細資訊,但其他微服務顯示正常。 
圖 47. 訪問 bookinfo 應用
我們刪除黑名單,再訪問 bookinfo,對 details 微服務的訪問馬上正常。
圖 48. 刪除黑名單策略
圖 49. Kali 顯示應用流量訪問
圖 50. 訪問 bookinfo 應用
總結

 

透過本文,相信讀者對微服務的概念和 Istio 的架構有了一定程度的理解。在微服務領域,正是由於 Istio 強大的功能、豐富的介面、視覺化的監控,Istio 的使用將會越來越廣泛。
原文連結:https://www.ibm.com/developerworks/cn/cloud/library/cl-lo-implementing-kubernetes-microservice-using-istio/index.html

 

Kubernetes實戰培訓

 

Kubernetes實戰培訓將於2019年3月8日在深圳開課,3天時間帶你係統掌握Kubernetes,學習效果不好可以繼續學習本次培訓包括:雲原生介紹、微服務;Docker基礎、Docker工作原理、映象、網路、儲存、資料捲、安全;Kubernetes架構、核心元件、常用物件、網路、儲存、認證、服務發現、排程和服務質量保證、日誌、監控、告警、Helm、實踐案例等。
    閱讀原文

    贊(0)

    分享創造快樂