通过前几篇文章的学习与实践,我们对 Gateway、VirtualService 和 Destinationrule 的概念和原理有了初步的认知,本篇将对这几个对象资源的配置文件进行深度地解析,具体细节将会深入到每一个配置项与 Envoy 配置项的映射关系。
在开始之前,需要先搞清楚我们创建的这些对象资源最后都交给谁来处理了,负责处理这些资源的就是 pilot。
pilot总体架构#
首先我们回顾一下 pilot 总体架构,上面是
官方关于pilot的架构图,因为是 old_pilot_repo 目录下,可能与最新架构有出入,仅供参考。所谓的 pilot 包含两个组件:pilot-agent 和 pilot-discovery。图里的 agent 对应 pilot-agent 二进制,proxy 对应 Envoy 二进制,它们两个在同一个容器中,discovery service 对应 pilot-discovery 二进制,在另外一个跟应用分开部署的单独的 Deployment 中。
- discovery service : 从 Kubernetes apiserver list/watch
service、endpoint、pod、node等资源信息,监听 istio 控制平面配置信息(如VirtualService、DestinationRule等), 翻译为 Envoy 可以直接理解的配置格式。 - proxy : 也就是 Envoy,直接连接 discovery service,间接地从 Kubernetes 等服务注册中心获取集群中微服务的注册情况。
- agent : 生成 Envoy 配置文件,管理 Envoy 生命周期。
- service A/B : 使用了 Istio 的应用,如 Service A/B,的进出网络流量会被 proxy 接管。
简单来说 Istio 做为管理面,集合了配置中心和服务中心两个功能,并把配置发现和服务发现以一组统一的 xDS 接口提供出来,数据面的 Envoy 通过 xDS 获取需要的信息来做服务间通信和服务治理。
pilot-discovery 为 Envoy 提供的 xds 服务#
所谓 xds#
pilot-discovery 为数据面(运行在 sidecar 中的 Envoy 等 proxy 组件)提供控制信息服务,也就是所谓的 discovery service 或者 xds 服务。这里的 x 是一个代词,类似云计算里的 XaaS 可以指代 IaaS、PaaS、SaaS 等。在 Istio 中,xds 包括 cds(cluster discovery service)、lds(listener discovery service)、rds(route discovery service)、eds(endpoint discovery service),而 ads(aggregated discovery service) 是对这些服务的一个统一封装。
以上 cluster、endpoint、route 等概念的详细介绍和实现细节可以参考 Envoy 在社区推广的 data plane api( github.com/envoyproxy/data-plane-api),这里只做简单介绍:
- endpoint : 一个具体的“应用实例”,对应 ip 和端口号,类似 Kubernetes 中的一个 Pod。
- cluster : 一个
cluster是一个“应用集群”,它对应提供相同服务的一个或多个endpoint。cluster 类似 Kubernetes 中Service的概念,即一个 Kubernetes Service 对应一个或多个用同一镜像启动,提供相同服务的 Pod。 - route : 当我们做灰度发布、金丝雀发布时,同一个服务会同时运行多个版本,每个版本对应一个 cluster。这时需要通过
route规则规定请求如何路由到其中的某个版本的 cluster 上。
以上这些内容实际上都是对 Envoy 等 proxy 的配置信息,而所谓的 cluster discovery service、route discovery service 等 xxx discovery service 就是 Envoy 等从 pilot-discovery 动态获取 endpoint、cluster 等配置信息的协议和实现。为什么要做动态配置加载,自然是为了使用 istioctl 等工具统一、灵活地配置 service mesh。至于如何通过 istioctl 来查看 xds 信息,下文将会详细介绍。
而为什么要用 ads 来“聚合”一系列 xds,并非仅为了在同一个 gRPC 连接上实现多种 xds 来省下几个网络连接,ads 还有一个非常重要的作用是解决 cds、rds 信息更新顺序依赖的问题,从而保证以一定的顺序同步各类配置信息,这方面的讨论可以详见
Envoy官网。
如何查看 xds#
pilot-discovery 在初始化阶段依次 init 了各种模块,其中 discovery service 就是 xDS 相关实现。
envoy API reference 可以查到 v1 和 v2 两个版本的 API 文档。
envoy control plane 给了 v2 grpc 接口相关的数据结构和接口。
那么如何查看 xds 的信息呢?虽然 v2 是 grpc 的接口,但是 pilot 提供了 InitDebug,可以通过 debug 接口查询服务和 routes 等服务和配置信息。
查看 eds
首先找到 Service istio-pilot 的 Cluster IP:
$ export PILOT_SVC_IP=$(kubectl -n istio-system get svc istio-pilot -o go-template='{{.spec.clusterIP}}')
然后查看 eds:
$ curl http://$PILOT_SVC_IP:8080/debug/edsz
[{
"clusterName": "outbound|9080||reviews.nino.svc.cluster.local",
"endpoints": [{
"lbEndpoints": [{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.244.0.56",
"portValue": 9080
}
}
}
}, {
"endpoint": {
"address": {
"socketAddress": {
"address": "10.244.0.58",
"portValue": 9080
}
}
}
}, {
"endpoint": {
"address": {
"socketAddress": {
"address": "10.244.2.25",
"portValue": 9080
}
}
}
}]
}]
}, {
"clusterName": "outbound|9080|v3|reviews.nino.svc.cluster.local",
"endpoints": [{
"lbEndpoints": [{
"endpoint": {
"address": {
"socketAddress": {
"address": "10.244.0.58",
"portValue": 9080
}
}
}
}]
}]
}]
查看 cds
$ curl http://$PILOT_SVC_IP:8080/debug/cdsz
[{"node": "sidecar~172.30.104.45~fortio-deploy-56dcc85457-b2pkc.default~default.svc.cluster.local-10", "addr": "172.30.104.45:43876", "connect": "2018-08-07 06:31:08.161483005 +0000 UTC m=+54.337448884","Clusters":[{
"name": "outbound|9080||details.default.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
},
"serviceName": "outbound|9080||details.default.svc.cluster.local"
},
"connectTimeout": "1.000s",
"circuitBreakers": {
"thresholds": [
{
}
]
}
},
...
{
"name": "outbound|9090||prometheus-k8s.monitoring.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {
}
},
"serviceName": "outbound|9090||prometheus-k8s.monitoring.svc.cluster.local"
},
"connectTimeout": "1.000s",
"circuitBreakers": {
"thresholds": [
{
}
]
}
},
{
"name": "BlackHoleCluster",
"connectTimeout": "5.000s"
}]}
]
查看 ads
$ curl http://$PILOT_SVC_IP:8080/debug/adsz
Envoy 基本术语回顾#
为了让大家更容易理解后面所讲的内容,先来回顾一下 Envoy 的基本术语。
- Listener : 监听器(listener)是服务(程序)监听者,就是真正干活的。 它是可以由下游客户端连接的命名网络位置(例如,端口、unix域套接字等)。Envoy 公开一个或多个下游主机连接的侦听器。一般是每台主机运行一个 Envoy,使用单进程运行,但是每个进程中可以启动任意数量的 Listener(监听器),目前只监听 TCP,每个监听器都独立配置一定数量的(L3/L4)网络过滤器。Listenter 也可以通过 Listener Discovery Service(LDS)动态获取。
- Listener filter : Listener 使用 listener filter(监听器过滤器)来操作链接的元数据。它的作用是在不更改 Envoy 的核心功能的情况下添加更多的集成功能。Listener filter 的 API 相对简单,因为这些过滤器最终是在新接受的套接字上运行。在链中可以互相衔接以支持更复杂的场景,例如调用速率限制。Envoy 已经包含了多个监听器过滤器。
- Http Route Table : HTTP 的路由规则,例如请求的域名,Path 符合什么规则,转发给哪个 Cluster。
- Cluster : 集群(cluster)是 Envoy 连接到的一组逻辑上相似的上游主机。Envoy 通过服务发现发现集群中的成员。Envoy 可以通过主动运行状况检查来确定集群成员的健康状况。Envoy 如何将请求路由到集群成员由负载均衡策略确定。
更多详细信息可以参考
Envoy 的架构与基本术语,本文重点突出 Listener、Route 和 Cluster 这三个基本术语,同时需要注意流量经过这些术语的先后顺序,请求首先到达 Listener,然后通过 Http Route Table 转到具体的 Cluster,最后由具体的 Cluster 对请求做出响应。
Gateway 和 VirtualService 配置解析#
还是拿之前
Istio 流量管理 这篇文章中的例子来解析吧,首先创建了一个 Gateway,配置文件如下:
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
然后又创建了一个 VirtualService:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway
http:
- match:
- uri:
exact: /productpage
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
VirtualService 映射的就是 Envoy 中的 Http Route Table,大家可以注意到上面的 VirtualService 配置文件中有一个 gateways 字段,如果有这个字段,就表示这个 Http Route Table 是绑在 ingressgateway 的 Listener 中的;如果没有这个字段,就表示这个 Http Route Table 是绑在 Istio 所管理的所有微服务应用的 Pod 上的。
显而易见,上面这个 VirtualService 映射的 Http Route Table 是被绑在 ingressgateway 中的,可以通过 istioctl 来查看,istioctl 的具体用法请参考:
调试 Envoy 和 Pilot。
首先查看 Listener 的配置项:
$ istioctl -n istio-system pc listeners istio-ingressgateway-b6db8c46f-qcfks --port 80 -o json
[
{
"name": "0.0.0.0_80",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 80
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.http_connection_manager",
"config": {
...
"rds": {
"config_source": {
"ads": {}
},
"route_config_name": "http.80"
},
...
}
}
]
}
]
}
]
通过 rds 配置项的 route_config_name 字段可以知道该 Listener 使用的 Http Route Table 的名字是 http.80。
查看 Http Route Table 配置项:
$ istioctl -n istio-system pc routes istio-ingressgateway-b6db8c46f-qcfks --name http.80 -o json
[
{
"name": "http.80",
"virtualHosts": [
{
"name": "bookinfo:80",
"domains": [
"*"
],
"routes": [
{
"match": {
"path": "/productpage"
},
"route": {
"cluster": "outbound|9080||productpage.default.svc.cluster.local",
"timeout": "0.000s",
"maxGrpcTimeout": "0.000s"
},
...
},
...
{
"match": {
"prefix": "/api/v1/products"
},
"route": {
"cluster": "outbound|9080||productpage.default.svc.cluster.local",
"timeout": "0.000s",
"maxGrpcTimeout": "0.000s"
},
...
},
...
]
}
],
"validateClusters": false
}
]
- VirtualService 中的
hosts字段对应 Http Route Table 中virtualHosts配置项的domains字段。这里表示可以使用任何域名来通过 ingressgateway 访问服务(也可以直接通过 IP 来访问)。 - VirtualService 中的
exact字段对应 Http Route Table 中routes.match配置项的path字段。 - VirtualService 中的
prefix字段对应 Http Route Table 中routes.match配置项的prefix字段。 - VirtualService 中的
route.destination配置项对应 Http Route Table 中routes.route配置项的cluster字段。
关于 Envoy 中的 HTTP 路由解析可以参考我之前的文章: HTTP 路由解析。
查看 Cluster 配置项:
$ istioctl -n istio-system pc clusters istio-ingressgateway-b6db8c46f-qcfks --fqdn productpage.default.svc.cluster.local --port 9080 -o json
[
{
"name": "outbound|9080||productpage.default.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {}
},
"serviceName": "outbound|9080||productpage.default.svc.cluster.local"
},
"connectTimeout": "1.000s",
"circuitBreakers": {
"thresholds": [
{}
]
}
}
]
可以看到,Cluster 最终将集群外通过 ingressgateway 发起的请求转发给实际的 endpoint,也就是 Kubernetes 集群中的 Service productpage 下面的 Pod(由 serviceName 字段指定)。
好了,现在请求已经转交给 productpage 了,那么接下来这个请求将会如何走完整个旅程呢?请听下回分解!
参考#






