icon

Sealos,在云桌面中运行分布式应用程序,像使用个人电脑一样使用云

icon

去看看👀

icon

扫码加入微信群,和云原生大佬们一起探讨云原生和不可描述的事情!

wechat qr code
 
今日天气
 
舔狗日记

数据包在 Istio 网格中的生命周期(下)
  1. 博客/

数据包在 Istio 网格中的生命周期(下)

·2535 字·6 分钟· · ·
服务网格 Istio Kubernetes
米开朗基杨
作者
米开朗基杨
云原生搬砖师 & Sealos 开发者布道师 & FastGPT 熟练工
Table of Contents
gptgod
FastGPT
Laf
Contact me

书接前文,上文我们通过跟踪集群外通过 ingressgateway 发起的请求来探寻流量在 Istio 服务网格之间的流动方向,先部署 bookinfo 示例应用,然后创建一个监听在 ingressgateway 上的 GateWay 和 VirtualService,通过分析我们追踪到请求最后转交给了 productpage

在继续追踪请求之前,先对之前的内容做一个补充说明。

Pod 在服务网格之间如何通信?
#


大家都知道,在 Istio 尚未出现之前,Kubernetes 集群内部 Pod 之间是通过 ClusterIP 来进行通信的,那么通过 Istio 在 Pod 内部插入了 Sidecar 之后,微服务应用之间是否仍然还是通过 ClusterIP 来通信呢?我们来一探究竟!

继续拿上文的步骤举例子,来看一下 ingressgateway 和 productpage 之间如何通信,请求通过 ingressgateway 到达了 endpoint ,那么这个 endpoint 到底是 ClusterIP + Port 还是 PodIP + Port 呢?由于 istioctl 没有提供 eds 的查看参数,可以通过 pilot 的 xds debug 接口来查看:

# 获取 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。

部署 bookinfo 应用的时候发生了什么?
#


通过 Istio 来部署 bookinfo 示例应用时,Istio 会向应用程序的所有 Pod 中注入 Envoy 容器。但是我们仍然还不清楚注入的 Envoy 容器的配置文件里都有哪些东西,这时候就是 istioctl 命令行工具发挥强大功效的时候了,可以通过 proxy-config 参数来深度解析 Envoy 的配置文件(上一节我们已经使用过了)。

我们先把目光锁定在某一个固定的 Pod 上,以 productpage 为例。先查看 productpage 的 Pod Name:

$ kubectl get pod -l app=productpage

NAME                              READY     STATUS    RESTARTS   AGE
productpage-v1-76474f6fb7-pmglr   2/2       Running   0          7h

1. 查看 productpage 的监听器的基本基本摘要

$ 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 会生成以下的监听器:

  • 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 到网格内其他应用的流量与上面类似,就不展开讨论了。接下来分析创建规则之后的请求转发过程。

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 的集群。

图片描述: IbLzV0.jpg

由于找不到这个集群,所以该路由不可达,这就是为什么你打开 productpage 的页面会出现如下的报错:

图片描述: IifCY8.jpg

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,你会发现报错已经消失了。

图片描述: PahU33.jpg

参考
#



图片描述: wechat.gif

扫一扫关注微信公众号
-------他日江湖相逢 再当杯酒言欢-------

相关文章

数据包在 Istio 网格中的生命周期(上)
·3332 字·7 分钟·
服务网格 Istio Kubernetes
Istio 流量管理
·3385 字·7 分钟·
服务网格 Istio
Istio 服务网格中的网关
·2044 字·5 分钟·
服务网格 Istio
Istio 1.0 部署
·1135 字·3 分钟·
服务网格 Istio
Vistio—使用 Netflix 的 Vizceral 可视化 Istio service mesh
·1797 字·4 分钟·
服务网格 Istio
Kubernetes 中 Pod 的生命周期管理
·1732 字·4 分钟·
云原生 Kubernetes

公众号二维码