Kiali——Istio Service Mesh的可觀察性工具
Istio 中有個 ofollow,noindex" target="_blank">issue #9066 要求將 Istio 中預設使用的 Service Graph 替換成 Kiali 。Kiali 最初是由 Red Hat 開源的,用於解決 Service Mesh 中可觀察性即微服務的可視性問題。目前已獲得 Istio 社群的官方支援。
關於 Kiali
單體應用使用微服務架構拆分成了許多微服務的組合。服務的數量顯著增加,就對需要了解服務之間的通訊模式,例如容錯(通過超時、重試、斷路等)以及分散式跟蹤,以便能夠看到服務呼叫的去向。服務網格可以在平臺級別上提供這些服務,並使應用程式編寫者從以上繁重的通訊模式中解放出來。路由決策在網格級別完成。Kiali 與Istio 合作,視覺化服務網格拓撲、斷路器和請求率等功能。Kiali還包括 Jaeger Tracing,可以提供開箱即用的分散式跟蹤功能。
Kiali 提供的功能
Kiali 提供以下功能:
- 服務拓撲圖
- 分散式跟蹤
- 指標度量收集和圖示
- 配置校驗
- 健康檢查和顯示
- 服務發現
下圖展示了 kiali 中顯示的 Bookinfo 示例的服務拓撲圖。
你可以使用 kubernetes-vagrant-centos-cluster 來快速啟動一個執行 Kiali 的 Kubernetes 叢集。
編譯安裝與試用
Kilia pod 中執行的程序是 /opt/kiali/kiali -config /kiali-configuration/config.yaml -v 4
。
/kiali-configuration/config.yaml
是使用 ConfigMap 掛載進去的,用於配置 Kiali 的 Web 根路徑和外部服務地址。
server: port: 20001 web_root: / external_services: jaeger: url: "http://172.17.8.101:31888" grafana: url: "http://grafana.istio-system:3000"
Kiali 中的基本概念
在瞭解 Kilia 如何提供 Service Mesh 中微服務可觀察性之前,我們需要先了解下 Kilia 如何劃分監控類別的。
- Application :使用執行的工作負載,必須使用 Istio 的將 Label 標記為
app
才算。注意,如果一個應用有多個版本,只要app
標籤的值相同就是屬於同一個應用。 - Deployment :即 Kubernetes 中的 Deployment。
- Label :這個值對於 Istio 很重要,因為 Istio 要用它來標記 metrics。每個 Application 要求包括
app
和version
兩個 label。 - Namespace :通常用於區分專案和使用者。
- Service :即 Kubernetes 中的 Service,不過要求必須有
app
label。 - Workload :Kubernetes 中的所有常用資源型別如 Deployment、StatefulSet、Job 等都可以檢測到,不論這些負載是否加入到 Istio Service Mesh 中。
Application、Workload 與 Service 的關係如下圖所示。
Kilia 的詳細 API 使用說明請檢視 Swagger API 文件,在 Kiali 的根目錄下執行下面的命令可以檢視 API 文件。
make swagger-serve
Swagger UI 如下圖。
架構
Kiali 部署完成後只啟動了一個 Pod,前後端都整合在這一個 Pod 中。Kiali 也有一些依賴的元件,例如如果要在 Kiali 的頁面中獲取到監控 metric 需要使用在 istio-system
中部署 Prometheus。分散式卓總直接下圖是 Kiali 的架構,來自 Kiali 官網。
Kiali 使用傳統的前後端分離架構:
- 後端使用 Go 編寫: https://github.com/kiali/kiali ,為前端提供 API,所有訊息使用 JSON 編碼,使用 ConfigMap 和 Secret 來儲存配置。直接與 Kubernetes 和 Istio 通訊來獲取資料。
- 前端使用 Typescript 編寫: https://github.com/kiali/kiali-ui ,無狀態,除了一些證書儲存在瀏覽器中。於查詢後端 API,可以跳轉訪問 Jaeger 分散式追蹤和 Grafana 監控頁面。
Jaeger 和 Grafana 都是可選元件,使用的都是外部服務,不是由 Kiali 部署的,需要在 kiali-configmap.yaml
中配置 URL。注意該 URL 必須是從你本地瀏覽器中可以直接訪問到的地址。
注意:如果服務之間沒有任何請求就不會在 Prometheus 中儲存資料也就無法顯示服務拓撲圖,所以大家在部署完 Bookinfo
服務之後向 productpage
服務傳送一些請求用於生成服務拓撲圖。
服務拓撲圖
Kiali 中的服務拓撲圖比起 Istio 原來預設部署的 ServiceGraph 的效果更炫也更加直觀,具有更多選項。
例如使用 CURL 模擬請求。
$ curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTM5NjczOTYyfQ.6gNz4W6yA9Bih4RkTbcSvqdaiRqsyj8c8o6ictM9iDs" http://172.17.8.101:32439/api/namespaces/all/graph?duration=60s&graphType=versionedApp&injectServiceNodes=false&appenders=dead_node,sidecars_check,istio
會得到如下的返回的 JSON 返回值,為了節省篇幅其中省略了部分結果:
{ "timestamp": 1539296648, "graphType": "versionedApp", "elements": { "nodes": [ { "data": { "id": "6519157be154675342fb76c41edc731c", "nodeType": "app", "namespace": "default", "app": "reviews", "isGroup": "version" } }, ... { "data": { "id": "6249668dd0a91adb9e62994d36563365", "nodeType": "app", "namespace": "istio-system", "workload": "istio-ingressgateway", "app": "istio-ingressgateway", "version": "unknown", "rateOut": "0.691", "isOutside": true, "isRoot": true } } ], "edges": [ { "data": { "id": "d51ca2a95d721427bbe27ed209766ec5", "source": "06e488a37fc9aa5b0e0805db4f16ae69", "target": "31150e7e5adf85b63f22fbd8255803d7", "rate": "0.236", "percentRate": "17.089", "responseTime": "0.152" } }, ... { "data": { "id": "1dda06d9904bcf727d1b6a113be58556", "source": "80f71758099020586131c3565075935d", "target": "4b64bda48e5a3c7e50ab1c63836c9469", "rate": "0.236", "responseTime": "0.022" } } ] } }
該值中包含了每個 node
和 edege
的資訊,Node 即圖中的每個節點,其中包含了節點的配置資訊,Edge 即節點間的關係還有流量情況。前端可以根據該資訊繪製服務拓撲圖,我們下面將檢視下 kiali 的後端,看看它是如何生成以上格式的 JSON 資訊的。
注:詳細的 REST API 使用和欄位說明請檢視 swagger 生成的 API 文件。
程式碼解析
下面將帶大家瞭解 Kiali 的後端程式碼基本結構。
路由配置
服務拓撲圖的路由資訊儲存在 kiali/routing/routes.go
檔案中。
{ "GraphNamespace", "GET", "/api/namespaces/{namespace}/graph", handlers.GraphNamespace, true, }, { "GraphAppVersion", "GET", "/api/namespaces/{namespace}/applications/{app}/versions/{version}/graph", handlers.GraphNode, true, }, { "GraphApp", "GET", "/api/namespaces/{namespace}/applications/{app}/graph", handlers.GraphNode, true, }, { "GraphService", "GET", "/api/namespaces/{namespace}/services/{service}/graph", handlers.GraphNode, true, }, { "GraphWorkload", "GET", "/api/namespaces/{namespace}/workloads/{workload}/graph", handlers.GraphNode, true, }
直接檢視 Swagger 生成的 API 文件也可以。
PQL 查詢語句構建
kiali/handlers/graph.go
中處理 HTTP 請求,服務拓撲圖中所有的指標資訊都是從 Prometheus 中查詢得到的。
Kiali 的服務狀態拓撲是根據 namespace 來查詢的,例如 default
namespace 下的服務指標查詢 PQL:
round(sum(rate(istio_requests_total{reporter="source",source_workload_namespace="default",response_code=~"[2345][0-9][0-9]"} [600s])) by (source_workload_namespace,source_workload,source_app,source_version,destination_service_namespace,destination_service_name,destination_workload,destination_app,destination_version,response_code),0.001)
其中的引數都是通過頁面選擇傳入的(構建的 PQL 中的選項在 kiali/graph/options/options.go
中定義):
-
reporter="source"
:metric 報告來源,源服務(source)是 sary/#envoy" rel="nofollow,noindex" target="_blank">envoy 代理的下游客戶端。在 服務網格 裡,一個源服務通常是一個 工作負載 ,但是入口流量的源服務有可能包含其他客戶端,例如瀏覽器,或者一個移動應用。 -
source_workload_namespace="default"
:選擇名稱空間。 -
response_code
:返回碼區間。 -
[600s]
:查詢的資料中的時間間隔。
關於 PQL 的詳細使用方式請參考 QUERY EXAMPLES - prometheus.io 。
這裡麵包含了所有 workload 的流量資訊,做簡單的操作就可以計算出 application/service 的流量狀況。
HTTP 處理邏輯
HTTP 請求的處理邏輯入口位於 kiali/handlers/graph.go
,路徑為:
func graphNamespaces(o options.Options, client *prometheus.Client) graph.TrafficMap { switch o.Vendor { case "cytoscape": default: checkError(errors.New(fmt.Sprintf("Vendor [%s] not supported", o.Vendor))) } log.Debugf("Build [%s] graph for [%v] namespaces [%s]", o.GraphType, len(o.Namespaces), o.Namespaces) trafficMap := graph.NewTrafficMap() for _, namespace := range o.Namespaces { log.Debugf("Build traffic map for namespace [%s]", namespace) namespaceTrafficMap := buildNamespaceTrafficMap(namespace, o, client) for _, a := range o.Appenders { a.AppendGraph(namespaceTrafficMap, namespace) // Appender 用於新增 service graph } mergeTrafficMaps(trafficMap, namespaceTrafficMap) //將不同的 namespace 下的服務狀態合併 } // appender 用於新增/刪除/修改 node 資訊。操作完成後可以做出如下判斷: // - 將其標記外來者(即不在請求的 namespace 中的 node) // - 將其標記內部流量製造者(即位於 namespace 中只有向外的 edge) markOutsiders(trafficMap, o) markTrafficGenerators(trafficMap) if graph.GraphTypeService == o.GraphType { trafficMap = reduceToServiceGraph(trafficMap) } return trafficMap }
Appender 是一個介面,在 service graph 中注入詳細的資訊,它的定義如下:
// Appender 由任何程式碼提供實現,以附加具有補充資訊的 service graph。如果出錯,appender應該執行 panic 並將其作為錯誤響應處理。 type Appender interface { // AppendGraph 在提供的 traffic map 上執行 appender 工作。Map 最初可能是空的。允許 appender 新增或刪除對映條目。 AppendGraph(trafficMap graph.TrafficMap, namespace string) }
Appender 位於 kiali/graph/appender
目錄下,目前一共有如下實現:
- DeadNodeAppender :用於將不想要 node 從 service graph 中刪除。
- IstioAppender :獲取指定 namespace 下 Istio 的詳細資訊,當前版本獲取指定 namespace 下的 VirtualService 和 DestinationRule 資訊。
- ResponseTimeAppender :獲取響應時間。
- SecurityPolicyAppender :在 service graph 中新增安全性策略資訊。
- SidecarsCheckAppender :檢查 Sidecar 的配置資訊,例如 Pod 中是否有 App label。
- UnusedNodeAppender :未加入 Service Mesh 的 node。
我們再來看下在 kiali/graph/graph.go
中定義的 TrafficMap
結構。
// TrafficMap 是 App 與 Node 之間的對映,每個節點都可選擇儲存 Edge 資料。Metadata 是用於儲存任何期望的 node 或 edge 資訊的通用對映。每個 app 節點應具有唯一的 namespace + workload。請注意,在同一 namespace 中有兩個具有相同 name + version 的節點是可行的但可能並不常見。 type TrafficMap map[string]*Node type Node struct { IDstring// unique identifier for the node NodeTypestring// Node type Namespace string// Namespace Workloadstring// Workload (deployment) name Appstring// Workload app label value Versionstring// Workload version label value Servicestring// Service name Edges[]*Edge// child nodes Metadatamap[string]interface{} // app-specific data } type Edge struct { Source*Node Dest*Node Metadata map[string]interface{} // app-specific data }
以上只是對 Kiali 部分程式碼的解讀,更詳細的實現大家可以克隆 kiali 的程式碼自己研究。