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

Istio 中有个 issue #9066 要求将 Istio 中默认使用的 Service Graph 替换成 Kiali。Kiali 最初是由 Red Hat 开源的,用于解决 Service Mesh 中可观察性即微服务的可视性问题。目前已得到 Istio 社区的官方支持。前端

关于 Kiali

单体应用使用微服务架构拆分红了许多微服务的组合。服务的数量显著增长,就对须要了解服务之间的通讯模式,例如容错(经过超时、重试、断路等)以及分布式跟踪,以便可以看到服务调用的去向。服务网格能够在平台级别上提供这些服务,并使应用程序编写者从以上繁重的通讯模式中解放出来。路由决策在网格级别完成。Kiali 与Istio 合做,可视化服务网格拓扑、断路器和请求率等功能。Kiali还包括 Jaeger Tracing,能够提供开箱即用的分布式跟踪功能。node

Kiali 提供的功能

Kiali 提供如下功能:git

  • 服务拓扑图
  • 分布式跟踪
  • 指标度量收集和图标
  • 配置校验
  • 健康检查和显示
  • 服务发现

下图展现了 kiali 中显示的 Bookinfo 示例的服务拓扑图。github

你可使用 kubernetes-vagrant-centos-cluster 来快速启动一个运行 Kiali 的 Kubernetes 集群。web

编译安装与试用

Kilia pod 中运行的进程是 /opt/kiali/kiali -config /kiali-configuration/config.yaml -v 4json

/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 如何划分监控类别的。centos

  • 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 的关系以下图所示。api

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"
        }
      }
    ]
  }
}
复制代码

该值中包含了每一个 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。

相关文章
相关标签/搜索