原文连接:请求都去哪了?(续)json
书接前文,上文咱们经过跟踪集群外经过 ingressgateway 发起的请求来探寻流量在 Istio 服务网格之间的流动方向,先部署 bookinfo 示例应用,而后建立一个监听在 ingressgateway
上的 GateWay 和 VirtualService,经过分析咱们追踪到请求最后转交给了 productpage
。api
在继续追踪请求以前,先对以前的内容作一个补充说明。浏览器
你们都知道,在 Istio 还没有出现以前,Kubernetes 集群内部 Pod 之间是经过 ClusterIP
来进行通讯的,那么经过 Istio 在 Pod 内部插入了 Sidecar
以后,微服务应用之间是否仍然仍是经过 ClusterIP 来通讯呢?咱们来一探究竟!bash
继续拿上文的步骤举例子,来看一下 ingressgateway 和 productpage 之间如何通讯,请求经过 ingressgateway 到达了 endpoint
,那么这个 endpoint 究竟是 ClusterIP
+ Port 仍是 PodIP
+ Port 呢?因为 istioctl 没有提供 eds 的查看参数,能够经过 pilot 的 xds debug 接口来查看:app
# 获取 istio-pilot 的 ClusterIP
$ export PILOT_SVC_IP=$(kubectl -n istio-system get svc -l app=istio-pilot -o go-template='{{range .items}}{{.spec.clusterIP}}{{end}}')
# 查看 eds
$ curl http://$PILOT_SVC_IP:8080/debug/edsz|grep "outbound|9080||productpage.default.svc.cluster.local" -A 27 -B 1
复制代码
{
"clusterName": "outbound|9080||productpage.default.svc.cluster.local",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "172.30.135.40",
"portValue": 9080
}
}
},
"metadata": {
"filterMetadata": {
"istio": {
"uid": "kubernetes://productpage-v1-76474f6fb7-pmglr.default"
}
}
}
}
]
}
]
},
复制代码
从这里能够看出,各个微服务之间是直接经过 PodIP + Port
来通讯的,Service 只是作一个逻辑关联用来定位 Pod,实际通讯的时候并无经过 Service。dom
经过 Istio 来部署 bookinfo 示例应用时,Istio 会向应用程序的全部 Pod 中注入 Envoy 容器。可是咱们仍然还不清楚注入的 Envoy 容器的配置文件里都有哪些东西,这时候就是 istioctl 命令行工具发挥强大功效的时候了,能够经过 proxy-config
参数来深度解析 Envoy 的配置文件(上一节咱们已经使用过了)。curl
咱们先把目光锁定在某一个固定的 Pod 上,以 productpage
为例。先查看 productpage 的 Pod Name:socket
$ kubectl get pod -l app=productpage
NAME READY STATUS RESTARTS AGE
productpage-v1-76474f6fb7-pmglr 2/2 Running 0 7h
复制代码
1. 查看 productpage 的监听器的基本基本摘要tcp
$ istioctl proxy-config listeners productpage-v1-76474f6fb7-pmglr
ADDRESS PORT TYPE
172.30.135.40 9080 HTTP // ③ Receives all inbound traffic on 9080 from listener `0.0.0.0_15001`
10.254.223.255 15011 TCP <---+
10.254.85.22 20001 TCP |
10.254.149.167 443 TCP |
10.254.14.157 42422 TCP |
10.254.238.17 9090 TCP | ② Receives outbound non-HTTP traffic for relevant IP:PORT pair from listener `0.0.0.0_15001`
10.254.184.32 5556 TCP |
10.254.0.1 443 TCP |
10.254.52.199 8080 TCP |
10.254.118.224 443 TCP <---+
0.0.0.0 15031 HTTP <--+
0.0.0.0 15004 HTTP |
0.0.0.0 9093 HTTP |
0.0.0.0 15030 HTTP |
0.0.0.0 8080 HTTP | ④ Receives outbound HTTP traffic for relevant port from listener `0.0.0.0_15001`
0.0.0.0 8086 HTTP |
0.0.0.0 9080 HTTP |
0.0.0.0 15010 HTTP <--+
0.0.0.0 15001 TCP // ① Receives all inbound and outbound traffic to the pod from IP tables and hands over to virtual listener
复制代码
Istio 会生成如下的监听器:ide
0.0.0.0:15001
上的监听器接收进出 Pod 的全部流量,而后将请求移交给虚拟监听器。0.0.0.0
端口配置一个虚拟监听器。上一节提到服务网格之间的应用是直接经过 PodIP 来进行通讯的,但还不知道服务网格内的应用与服务网格外的应用是如何通讯的。你们应该能够猜到,这个秘密就隐藏在 Service IP 的虚拟监听器中,以 kube-dns
为例,查看 productpage 如何与 kube-dns 进行通讯:
$ istioctl proxy-config listeners productpage-v1-76474f6fb7-pmglr --address 10.254.0.2 --port 53 -o json
复制代码
[
{
"name": "10.254.0.2_53",
"address": {
"socketAddress": {
"address": "10.254.0.2",
"portValue": 53
}
},
"filterChains": [
{
"filters": [
...
{
"name": "envoy.tcp_proxy",
"config": {
"cluster": "outbound|53||kube-dns.kube-system.svc.cluster.local",
"stat_prefix": "outbound|53||kube-dns.kube-system.svc.cluster.local"
}
}
]
}
],
"deprecatedV1": {
"bindToPort": false
}
}
]
复制代码
# 查看 eds
$ curl http://$PILOT_SVC_IP:8080/debug/edsz|grep "outbound|53||kube-dns.kube-system.svc.cluster.local" -A 27 -B 1
复制代码
{
"clusterName": "outbound|53||kube-dns.kube-system.svc.cluster.local",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "172.30.135.21",
"portValue": 53
}
}
},
"metadata": {
"filterMetadata": {
"istio": {
"uid": "kubernetes://coredns-64b597b598-4rstj.kube-system"
}
}
}
}
]
},
复制代码
能够看出,服务网格内的应用仍然经过 ClusterIP 与网格外的应用通讯,但有一点须要注意:这里并无 kube-proxy
的参与!Envoy 本身实现了一套流量转发机制,当你访问 ClusterIP 时,Envoy 就把流量转发到具体的 Pod 上去,不须要借助 kube-proxy 的 iptables
或 ipvs
规则。
2. 从上面的摘要中能够看出,每一个 Sidecar 都有一个绑定到 0.0.0.0:15001
的监听器,IP tables 将 pod 的全部入站和出站流量路由到这里。此监听器把 useOriginalDst
设置为 true,这意味着它将请求交给最符合请求原始目标的监听器。若是找不到任何匹配的虚拟监听器,它会将请求发送给返回 404 的 BlackHoleCluster
。
$ istioctl proxy-config listeners productpage-v1-76474f6fb7-pmglr --port 15001 -o json
复制代码
[
{
"name": "virtual",
"address": {
"socketAddress": {
"address": "0.0.0.0",
"portValue": 15001
}
},
"filterChains": [
{
"filters": [
{
"name": "envoy.tcp_proxy",
"config": {
"cluster": "BlackHoleCluster",
"stat_prefix": "BlackHoleCluster"
}
}
]
}
],
"useOriginalDst": true
}
]
复制代码
3. 咱们的请求是到 9080
端口的 HTTP 出站请求,这意味着它被切换到 0.0.0.0:9080
虚拟监听器。而后,此监听器在其配置的 RDS 中查找路由配置。在这种状况下,它将查找由 Pilot 配置的 RDS 中的路由 9080
(经过 ADS)。
$ istioctl proxy-config listeners productpage-v1-76474f6fb7-pmglr --address 0.0.0.0 --port 9080 -o json
复制代码
...
"rds": {
"config_source": {
"ads": {}
},
"route_config_name": "9080"
}
...
复制代码
4. 9080
路由配置仅为每一个服务提供虚拟主机。咱们的请求正在前往 reviews 服务,所以 Envoy 将选择咱们的请求与域匹配的虚拟主机。一旦在域上匹配,Envoy 会查找与请求匹配的第一条路径。在这种状况下,咱们没有任何高级路由,所以只有一条路由匹配全部内容。这条路由告诉 Envoy 将请求发送到 outbound|9080||reviews.default.svc.cluster.local
集群。
$ istioctl proxy-config routes productpage-v1-76474f6fb7-pmglr --name 9080 -o json
复制代码
[
{
"name": "9080",
"virtualHosts": [
{
"name": "reviews.default.svc.cluster.local:9080",
"domains": [
"reviews.default.svc.cluster.local",
"reviews.default.svc.cluster.local:9080",
"reviews",
"reviews:9080",
"reviews.default.svc.cluster",
"reviews.default.svc.cluster:9080",
"reviews.default.svc",
"reviews.default.svc:9080",
"reviews.default",
"reviews.default:9080",
"172.21.152.34",
"172.21.152.34:9080"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|9080||reviews.default.svc.cluster.local",
"timeout": "0.000s"
},
...
复制代码
5. 此集群配置为从 Pilot(经过 ADS)检索关联的端点。所以,Envoy 将使用 serviceName
字段做为密钥来查找端点列表并将请求代理到其中一个端点。
$ istioctl proxy-config clusters productpage-v1-76474f6fb7-pmglr --fqdn reviews.default.svc.cluster.local -o json
复制代码
[
{
"name": "outbound|9080||reviews.default.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {}
},
"serviceName": "outbound|9080||reviews.default.svc.cluster.local"
},
"connectTimeout": "1.000s",
"circuitBreakers": {
"thresholds": [
{}
]
}
}
]
复制代码
上面的整个过程就是在不建立任何规则的状况下请求从 productpage
到 reviews
的过程,从 reviews 到网格内其余应用的流量与上面相似,就不展开讨论了。接下来分析建立规则以后的请求转发过程。
首先建立一个 VirtualService
。
$ cat <<EOF | istioctl create -f - apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
EOF
复制代码
上一篇文章已经介绍过,VirtualService
映射的就是 Envoy 中的 Http Route Table
,仍是将目标锁定在 productpage 上,咱们来查看一下路由配置:
$ istioctl proxy-config routes productpage-v1-76474f6fb7-pmglr --name 9080 -o json
复制代码
[
{
"name": "9080",
"virtualHosts": [
{
"name": "reviews.default.svc.cluster.local:9080",
"domains": [
"reviews.default.svc.cluster.local",
"reviews.default.svc.cluster.local:9080",
"reviews",
"reviews:9080",
"reviews.default.svc.cluster",
"reviews.default.svc.cluster:9080",
"reviews.default.svc",
"reviews.default.svc:9080",
"reviews.default",
"reviews.default:9080",
"172.21.152.34",
"172.21.152.34:9080"
],
"routes": [
{
"match": {
"prefix": "/"
},
"route": {
"cluster": "outbound|9080|v1|reviews.default.svc.cluster.local",
"timeout": "0.000s"
},
...
复制代码
注意对比一下没建立 VirtualService 以前的路由,如今路由的 cluster
字段的值已经从以前的 outbound|9080|reviews.default.svc.cluster.local
变为 outbound|9080|v1|reviews.default.svc.cluster.local
。
请注意:咱们如今尚未建立 DestinationRule!
你能够尝试搜索一下有没有 outbound|9080|v1|reviews.default.svc.cluster.local
这个集群,若是不出意外,你将找不到 SUBSET=v1
的集群。
因为找不到这个集群,因此该路由不可达,这就是为何你打开 productpage 的页面会出现以下的报错:
为了使上面建立的路由可达,咱们须要建立一个 DestinationRule
:
$ cat <<EOF | istioctl create -f - apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews
subsets:
- name: v1
labels:
version: v1
EOF
复制代码
其实 DestinationRule
映射到 Envoy 的配置文件中就是 Cluster
。如今你应该能看到 SUBSET=v1
的 Cluster 了:
$ istioctl proxy-config clusters productpage-v1-76474f6fb7-pmglr --fqdn reviews.default.svc.cluster.local --subset=v1 -o json
复制代码
[
{
"name": "outbound|9080|v1|reviews.default.svc.cluster.local",
"type": "EDS",
"edsClusterConfig": {
"edsConfig": {
"ads": {}
},
"serviceName": "outbound|9080|v1|reviews.default.svc.cluster.local"
},
"connectTimeout": "1.000s",
"circuitBreakers": {
"thresholds": [
{}
]
}
}
]
复制代码
到了这一步,一切皆明了,后面的事情就跟以前的套路同样了,具体的 Endpoint 对应打了标签 version=v1
的 Pod:
$ kubectl get pod -l app=reviews,version=v1 -o wide
NAME READY STATUS RESTARTS AGE IP NODE
reviews-v1-5b487cc689-njx5t 2/2 Running 0 11h 172.30.104.38 192.168.123.248
复制代码
$ curl http://$PILOT_SVC_IP:8080/debug/edsz|grep "outbound|9080|v1|reviews.default.svc.cluster.local" -A 27 -B 2
复制代码
{
"clusterName": "outbound|9080|v1|reviews.default.svc.cluster.local",
"endpoints": [
{
"lbEndpoints": [
{
"endpoint": {
"address": {
"socketAddress": {
"address": "172.30.104.38",
"portValue": 9080
}
}
},
"metadata": {
"filterMetadata": {
"istio": {
"uid": "kubernetes://reviews-v1-5b487cc689-njx5t.default"
}
}
}
}
]
}
]
},
复制代码
如今再次用浏览器访问 productpage,你会发现报错已经消失了。