原文連接:請求都去哪了?(續)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,你會發現報錯已經消失了。