服務網格內部的 VirtualService 和 DestinationRule 配置深度解析

原文連接:請求都去哪了?(續)json

書接前文,上文咱們經過跟蹤集羣外經過 ingressgateway 發起的請求來探尋流量在 Istio 服務網格之間的流動方向,先部署 bookinfo 示例應用,而後建立一個監聽在 ingressgateway 上的 GateWay 和 VirtualService,經過分析咱們追蹤到請求最後轉交給了 productpageapi

在繼續追蹤請求以前,先對以前的內容作一個補充說明。瀏覽器

1. Pod 在服務網格之間如何通訊?

你們都知道,在 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

2. 部署 bookinfo 應用的時候發生了什麼?

經過 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 的全部流量,而後將請求移交給虛擬監聽器。
  • ② 每一個 Service IP 配置一個虛擬監聽器,每一個出站 TCP/HTTPS 流量一個非 HTTP 監聽器。
  • ③ 每一個 Pod 入站流量暴露的端口配置一個虛擬監聽器。
  • ④ 每一個出站 HTTP 流量的 HTTP 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 的 iptablesipvs 規則。

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": [
                {}
            ]
        }
    }
]
複製代碼

上面的整個過程就是在不建立任何規則的狀況下請求從 productpagereviews 的過程,從 reviews 到網格內其餘應用的流量與上面相似,就不展開討論了。接下來分析建立規則以後的請求轉發過程。

3. VirtualService 和 DestinationRule 配置解析

VirtualService

首先建立一個 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

爲了使上面建立的路由可達,咱們須要建立一個 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,你會發現報錯已經消失了。

4. 參考

相關文章
相關標籤/搜索