Istio 中有個 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 編寫: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 的效果更炫也更加直觀,具有更多選項。
例如使用 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)是 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 {
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 的程式碼自己研究。
參考
- Kiali.io
- QUERY EXAMPLES - prometheus.io
- replace Service Graph with Kiali #9066 - github.com
- rootsongjc/kubernetes-vagrant-centos-cluster - github.com
ServiceMesher社群資訊
微信群:聯絡我入群
Slack:servicemesher.slack.com 需要邀請才能加入
Twitter: twitter.com/servicemesh…
GitHub:github.com/
更多Service Mesh諮詢請掃碼關注微信公眾號ServiceMesher。