Kiali——Istio Service Mesh 的可觀察性工具

ServiceMesher發表於2018-10-22

Istio 中有個 issue #9066 要求將 Istio 中預設使用的 Service Graph 替換成 Kiali。Kiali 最初是由 Red Hat 開源的,用於解決 Service Mesh 中可觀察性即微服務的可視性問題。目前已獲得 Istio 社群的官方支援。

關於 Kiali

單體應用使用微服務架構拆分成了許多微服務的組合。服務的數量顯著增加,就對需要了解服務之間的通訊模式,例如容錯(通過超時、重試、斷路等)以及分散式跟蹤,以便能夠看到服務呼叫的去向。服務網格可以在平臺級別上提供這些服務,並使應用程式編寫者從以上繁重的通訊模式中解放出來。路由決策在網格級別完成。Kiali 與Istio 合作,視覺化服務網格拓撲、斷路器和請求率等功能。Kiali還包括 Jaeger Tracing,可以提供開箱即用的分散式跟蹤功能。

Kiali 提供的功能

Kiali 提供以下功能:

  • 服務拓撲圖
  • 分散式跟蹤
  • 指標度量收集和圖示
  • 配置校驗
  • 健康檢查和顯示
  • 服務發現

下圖展示了 kiali 中顯示的 Bookinfo 示例的服務拓撲圖。

Kiali——Istio Service Mesh 的可觀察性工具

你可以使用 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 要求包括 appversion 兩個 label。
  • Namespace:通常用於區分專案和使用者。
  • Service:即 Kubernetes 中的 Service,不過要求必須有 app label。
  • Workload:Kubernetes 中的所有常用資源型別如 Deployment、StatefulSet、Job 等都可以檢測到,不論這些負載是否加入到 Istio Service Mesh 中。

Application、Workload 與 Service 的關係如下圖所示。

Kiali——Istio Service Mesh 的可觀察性工具

Kilia 的詳細 API 使用說明請檢視 Swagger API 文件,在 Kiali 的根目錄下執行下面的命令可以檢視 API 文件。

make swagger-serve
複製程式碼

Swagger UI 如下圖。

Kiali——Istio Service Mesh 的可觀察性工具

架構

Kiali 部署完成後只啟動了一個 Pod,前後端都整合在這一個 Pod 中。Kiali 也有一些依賴的元件,例如如果要在 Kiali 的頁面中獲取到監控 metric 需要使用在 istio-system 中部署 Prometheus。分散式卓總直接下圖是 Kiali 的架構,來自 Kiali 官網。

Kiali——Istio Service Mesh 的可觀察性工具

Kiali 使用傳統的前後端分離架構:

  • 後端使用 Go 編寫:github.com/kiali/kiali,為前端提供 API,所有訊息使用 JSON 編碼,使用 ConfigMap 和 Secret 來儲存配置。直接與 Kubernetes 和 Istio 通訊來獲取資料。
  • 前端使用 Typescript 編寫:github.com/kiali/kiali…,無狀態,除了一些證書儲存在瀏覽器中。於查詢後端 API,可以跳轉訪問 Jaeger 分散式追蹤和 Grafana 監控頁面。

Jaeger 和 Grafana 都是可選元件,使用的都是外部服務,不是由 Kiali 部署的,需要在 kiali-configmap.yaml 中配置 URL。注意該 URL 必須是從你本地瀏覽器中可以直接訪問到的地址。

注意:如果服務之間沒有任何請求就不會在 Prometheus 中儲存資料也就無法顯示服務拓撲圖,所以大家在部署完 Bookinfo服務之後向 productpage 服務傳送一些請求用於生成服務拓撲圖。

服務拓撲圖

Kiali 中的服務拓撲圖比起 Istio 原來預設部署的 ServiceGraph 的效果更炫也更加直觀,具有更多選項。

Kiali——Istio Service Mesh 的可觀察性工具

例如使用 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"
        }
      }
    ]
  }
}
複製程式碼

該值中包含了每個 nodeedege 的資訊,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)是 envoy 代理的下游客戶端。在服務網格裡,一個源服務通常是一個工作負載,但是入口流量的源服務有可能包含其他客戶端,例如瀏覽器,或者一個移動應用。
  • source_workload_namespace="default":選擇名稱空間。
  • response_code:返回碼區間。
  • [600s]:查詢的資料中的時間間隔。

關於 PQL 的詳細使用方式請參考 QUERY EXAMPLES - prometheus.io

Prometheus

這裡麵包含了所有 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 {
	ID        string                 // unique identifier for the node
	NodeType  string                 // Node type
	Namespace string                 // Namespace
	Workload  string                 // Workload (deployment) name
	App       string                 // Workload app label value
	Version   string                 // Workload version label value
	Service   string                 // Service name
	Edges     []*Edge                // child nodes
	Metadata  map[string]interface{} // app-specific data
}

type Edge struct {
	Source   *Node
	Dest     *Node
	Metadata map[string]interface{} // app-specific data
}
複製程式碼

以上只是對 Kiali 部分程式碼的解讀,更詳細的實現大家可以克隆 kiali 的程式碼自己研究。

參考

ServiceMesher社群資訊

微信群:聯絡我入群

社群官網:www.servicemesher.com

Slack:servicemesher.slack.com 需要邀請才能加入

Twitter: twitter.com/servicemesh…

GitHub:github.com/

更多Service Mesh諮詢請掃碼關注微信公眾號ServiceMesher。

Kiali——Istio Service Mesh 的可觀察性工具


相關文章