Istio 流量管理机制技术实践-Istio商业环境实战

849 阅读9分钟

专注于大数据及容器云核心技术解密,如有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号,或请转发邮件至1120746959@qq.com

1 总体

  • Pilot 定义了网格中服务的标准模型,这个标准模型独立于各种底层平台,Pilot 还可以从 Mesos, Cloud Foundry, Consul 等平台中获取服务信息,还可以开发适配器将其他提供服务发现的组件集成到 Pilot 中。
  • Istio 和 Envoy 项目联合制定了 Envoy V2 API, 并采用该 API 作为 Istio 控制平面和数据平面流量管理的标准接口。
  • 用户也可以根据自己的业务场景选择不同的 sidecar 和控制平面集成,如高吞吐量的,低延迟的,高安全性的等等。除 Istio 目前集成的 Envoy 外,还可以和 Linkerd, Nginmesh 等

2 Discovery Services

2.1 控制平面-pilot-discovery

  • 从 Service provider(如kubernetes或者consul)中获取服务信息
  • 从 K8S API Server 中获取流量规则(K8S CRD Resource)
  • 将服务信息和流量规则转化为数据平面可以理解的格式,通过标准的数据平面 API 下发到网格中的各个 sidecar 中
  • 对应的 docker 镜像为 gcr.io/istio-release/pilot

2.2 数据平面

  • 在数据平面有两个进程 Pilot-agent 和 envoy

2.2.1 Pilot-agent

  • 根据 K8S API Server 中的配置信息生成 Envoy 的配置文件
  • 启动 Envoy 进程(Envoy 的大部分配置信息都是通过 xDS 接口从 Pilot 中动态获取的)
  • Pilot-agent 在启动 Envoy 进程时传入了 pilot 地址和 zipkin 地址,并为 Envoy 生成了一个初始化配置文件 envoy-rev0.json

2.2.1.1 envoy-rev0.json结构说明

  • Node 包含了 Envoy 所在节点相关信息
  • Admin 配置 Envoy 的日志路径以及管理端口
  • Dynamic_resources 配置动态资源,这里配置了 ADS 服务器。
  • Static_resources 配置静态资源,包括了 xds-grpc 和 zipkin 两个 cluster。其中 xds-grpc cluster 对应前面 dynamic_resources 中 ADS 配置,指明了 Envoy 用于获取动态资源的服务器地址。
  • Tracing 配置分布式链路跟踪
  • Stats_sinks 这里配置的是和 Envoy 直连的 metrics 收集 sink,和 Mixer telemetry 没有关系。Envoy 自带 stats 格式的 metrics 上报。

2.2.2 envoy

  • 通过 K8s 的 Admission webhook 机制实现了 sidecar 的自动注入,Mesh 中的每个微服务会被加入 Envoy 相关的容器。下面是 Productpage 微服务的 Pod 内容,可见除 productpage 之外,Istio 还在该 Pod 中注入了两个容器 gcr.io/istio-release/proxy_init 和 gcr.io/istio-release/proxyv2
[root@hadoop ~]# kubectl exec productpage-v1-99b4c9954-vj6hf -c istio-proxy -- ps -ef                              
UID        PID  PPID  C STIME TTY          TIME CMD
istio-p+     1     0  0 03:26 ?        00:00:05 

/usr/local/bin/pilot-agent proxy sidecar --domain default.svc.cluster.local 
--configPath /etc/istio/proxy 
--binaryPath /usr/local/bin/envoy 
--serviceCluster productpage.default 
--drainDuration 45s --parentShutdownDuration 1m0s 
--discoveryAddress istiod.istio-system.svc:15012 
--zipkinAddress zipkin.istio-system:9411 
--proxyLogLevel=warning 
--proxyComponentLogLevel=misc:error 
--connectTimeout 10s 
--proxyAdminPort 15000 
--concurrency 2 
--controlPlaneAuthPolicy NONE 
--dnsRefreshRate 300s 
--statusPort 15020 
--trust-domain=cluster.local 
--controlPlaneBootstrap=false

istio-p+    20     1  0 03:26 ?        00:00:21 /usr/local/bin/envoy -c /etc/istio/proxy/envoy-rev0.json 
--restart-epoch 0 
--drain-time-s 45 
--parent-shutdown-time-s 60 
--service-cluster productpage.default 
--service-node sidecar~10.42.0.69~productpage-v1-99b4c9954-vj6hf.default~default.svc.cluster.local 
--max-obj-name-len 189 
--local-address-ip-version v4 
--log-format [Envoy (Epoch 0)] [%Y-%m-%d %T.%e][%t][%l][%n] %v -l warning 
--component-log-level misc:error 
--concurrency 2
istio-p+    36     0  0 06:32 ?        00:00:00 ps -ef
  • 从 Envoy 初始化配置文件中,我们可以大致看到 Istio 通过 Envoy 来实现服务发现和流量管理的基本原理。即控制平面将 xDS server 信息通过 static resource 的方式配置到 Envoy 的初始化配置文件中,Envoy 启动后通过 xDS server 获取到 dynamic resource,包括网格中的 service 信息及路由规则
  • Envoy 中实际生效的配置是由初始化配置文件中的静态配置和从 Pilot 获取的动态配置一起组成的。

3 envoy原理

Envoy 支持 静态(StaticRoute)、 动态(RDS)两种方式进行路由配置。静态路由配置,是指写死在 Envoy 启动配置文件里。动态路由配置,又称 RDS(Route Discovery Service,路由发现服务),是指 Envoy 在运行时向控制面发送请求,由控制面下发路由配置。 从架构图看出,Envoy的路由机制可粗略划分为五步:

  • ① 请求路由配置
  • ② 接收路由配置
  • ③ 收到并解析HTTP请求
  • ④ 路由匹配
  • ⑤ 转发HTTP请求

3.0 Envoy 启动配置文件的内容分析

  • 定义了一个网络监听器,名为 listener_0,监听地址 10.85.25.87:60001。
  • 监听器下面挂载了一条过滤器链(filter chain),只包含了一个过滤器 envoy.httpconnectionmanager,官方的 HTTP 连接管理器。
  • 过滤器下面配置了动态路由发现 rds,表示路由配置将在运行时从某处数据源动态获取。
  • routeconfigname:唯一标识了一项路由配置,以便向数据源获取指定的路由配置。
  • configsource: 表示路由配置的数据源,值 ads:{} 表示数据源是 adsconfig 指定的集群。
  • adsconfig: 指定了所有 xDS 资源(包括路由配置)都将从集群 xdscluster 获取,且通讯协议为 GRPC。
  • xds_cluster: 指定了集群下面的主机只有一个 10.85.25.87:61001。
 kubectl exec -it productpage-v1-54b8b9f55-bx2dq -c istio-proxy curl http://127.0.0.1:15000/config_dump > config_dump

3.1 Outbound Cluster

  • 以 details 为例,对于 Productpage 来说,details 是一个外部服务,因此其 Cluster 名称中包含 outbound 字样。
  • 从 details 服务对应的 cluster 配置中可以看到,其类型为 EDS,即表示该 Cluster 的 endpoint 来自于动态发现,动态发现中 eds_config 则指向了 ads,最终指向 static Resource 中配置的 xds-grpc cluster,即 Pilot 的地址。

3.2 Inbound Cluster

  • 该类 Cluster 对应于 Envoy 所在节点上的服务。如果该服务接收到请求,当然就是一个入站请求。对于 Productpage Pod 上的 Envoy,其对应的 Inbound Cluster 只有一个,即 productpage。该 cluster 对应的 host 为 127.0.0.1,即环回地址上 productpage 的监听端口。由于 iptables 规则中排除了 127.0.0.1,入站请求通过该 Inbound cluster 处理后将跳过 Envoy,直接发送给 Productpage 进程处理。

3.3 BlackHoleCluster

  • 这是一个特殊的 Cluster,并没有配置后端处理请求的 Host。如其名字所暗示的一样,请求进入后将被直接丢弃掉。如果一个请求没有找到其对的目的服务,则被发到 BlackHoleCluster。

3.4 Listeners

  • Envoy 采用 listener 来接收并处理 downstream 发过来的请求,listener 的处理逻辑是插件式的,可以通过配置不同的 filter 来插入不同的处理逻辑。

3.5 Virtual Listener

  • Envoy 创建了一个在 15001 端口监听的入口监听器。Iptables 将请求截取后发向 15001 端口,该监听器接收后并不进行业务处理,而是根据请求目的地分发给其他监听器处理。
  • Envoy 是如何做到按服务分发的呢? 可以看到该 Listener 的配置项 use_original_dest 设置为 true,该配置要求监听器将接收到的请求转交给和请求原目的地址关联的 listener 进行处理。

4 路程分析

  • 查看 productpage对应的所有监听器,0.0.0.0:15001 上的监听器接收进出 Pod 的所有流量,然后将请求移交给虚拟监听器。
export PILOT_SVC_IP=$(kubectl -n istio-system get svc -l app=pilot -o go-template='{{range .items}}{{.spec.clusterIP}}{{end}}')

kubectl get pod -l app=productpage

istioctl proxy-config listeners productpage-v1-99b4c9954-vj6hf

ADDRESS           PORT      TYPE
10.42.0.69        9080      HTTP
10.42.0.69        15020     TCP
10.43.113.103     31400     TCP
10.43.113.103     15443     TCP
10.43.0.1         443       TCP
10.43.86.87       15011     TCP
10.43.39.94       443       TCP
10.43.113.103     443       TCP
10.43.108.37      15012     TCP
10.43.0.10        53        TCP
10.43.86.87       15012     TCP
10.43.86.87       443       TCP
10.43.39.94       15443     TCP
10.43.108.37      443       TCP
10.43.0.10        9153      TCP
10.43.113.103     15020     TCP
10.43.113.103     15031     TCP
10.43.113.103     15032     TCP
10.43.96.17       14250     TCP
0.0.0.0           20001     TCP
0.0.0.0           80        TCP
10.43.113.103     15029     TCP
10.43.96.17       14268     TCP
0.0.0.0           9411      TCP
10.43.113.103     15030     TCP
10.43.96.17       14267     TCP
0.0.0.0           8000      TCP
10.43.34.201      80        TCP
0.0.0.0           15014     TCP
10.43.137.123     443       TCP
0.0.0.0           8080      TCP
0.0.0.0           3000      TCP
10.43.130.148     16686     TCP
0.0.0.0           9090      TCP
0.0.0.0           15010     TCP
0.0.0.0           14250     TCP
0.0.0.0           9080      TCP
0.0.0.0           15001     TCP   
0.0.0.0           15006     TCP
0.0.0.0           443       TCP
0.0.0.0           15090     HTTP

istioctl proxy-config listeners productpage-v1-99b4c9954-vj6hf --address 10.43.0.10 --port 53 -o json

 {
                        "name": "envoy.tcp_proxy",
                        "typedConfig": {
                            "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                            "statPrefix": "outbound|53||kube-dns.kube-system.svc.cluster.local",
                            "cluster": "outbound|53||kube-dns.kube-system.svc.cluster.local",
                            "accessLog": [
                                {
                                    "name": "envoy.file_access_log",
                                    "typedConfig": {
                                        "@type": "type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog",
                                        "path": "/dev/stdout",
                                        "format": "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% \"%DYNAMIC_METADATA(istio.mixer:status)%\" \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%\n"
                                    }
                                }
                            ]
                        }
                    }
                  
                  
kubectl exec productpage-v1-99b4c9954-vj6hf  -c istio-proxy --  netstat -ln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:9080            0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:15000         0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:15001           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:15006           0.0.0.0:*               LISTEN
tcp6       0      0 :::15020                :::*                    LISTEN 

9080: productpage进程对外提供的服务端口
15001: Envoy的Virtual Outbound监听器,iptable会将productpage服务发出的出向流量导入该端口中由Envoy进行处理
15006: Envoy的Virtual Inbound监听器,iptable会将发到productpage的入向流量导入该端口中由Envoy进行处理
15000: Envoy管理端口,该端口绑定在本地环回地址上,只能在Pod内访问。
15090:指向127.0.0.1:15000/stats/prometheus, 用于对外提供Envoy的性能统计指标
  • 查看 eds
# 查看 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"
                }
            }
          }
        }
      ]
    },

  • 各个微服务之间是直接通过 PodIP + Port 来通信的,Service 只是做一个逻辑关联用来定位 Pod,实际通信的时候并没有通过 Service Envoy 自己实现了一套流量转发机制, 当访问 ClusterIP 时,Envoy 就把流量转发到具体的 Pod 上去,不需要借助 kube-proxy 的 iptables 或 ipvs 规则
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": [
    {
      "locality": {

      },
      "lbEndpoints": [
        {
          "endpoint": {
            "address": {
              "socketAddress": {
                "address": "10.42.0.69",
                "portValue": 9080
              }
            }
          },
          "metadata": {
            "filterMetadata": {
              "envoy.transport_socket_match": {
                  "tlsMode": "istio"
                }
            }
          },
          "loadBalancingWeight": 1
        }
      ],
      "loadBalancingWeight": 1
    }
  • 每个 Sidecar 都有一个绑定到 0.0.0.0:15001 的监听器,IP tables 将 pod 的所有入站和出站流量路由到这里。此监听器把 useOriginalDst 设置为 true, 这意味着它将请求交给最符合请求原始目标的监听器。如果找不到任何匹配的虚拟监听器,它会将请求发送给返回 404 的 BlackHoleCluster
istioctl proxy-config listeners productpage-v1-99b4c9954-vj6hf --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
    }
]
  • 我们的请求是到 9080 端口的 HTTP 出站请求,这意味着它被切换到 0.0.0.0:9080 虚拟监听器。然后,此监听器在其配置的 RDS 中查找路由配置。在这种情况下,它将查找由 Pilot 配置的 RDS 中的路由 9080
istioctl proxy-config listeners productpage-v1-99b4c9954-vj6hf --address 0.0.0.0 --port 9080 -o json

"filters": [
    {
        "name": "envoy.http_connection_manager",
        "typedConfig": {
            "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
            "statPrefix": "outbound_0.0.0.0_9080",
            "rds": {
                "configSource": {
                    "ads": {}
                },
                "routeConfigName": "9080"
            },
  • 9080 路由配置仅为每个服务提供虚拟主机。我们的请求正在前往 reviews 服务,因此 Envoy 将选择我们的请求与域匹配的虚拟主机。一旦在域上匹配,Envoy 会查找与请求匹配的第一条路径。在这种情况下,我们没有任何高级路由,因此只有一条路由匹配所有内容。这条路由告诉 Envoy 将请求发送到 outbound|9080||reviews.default.svc.cluster.local 集群。
istioctl proxy-config routes productpage-v1-99b4c9954-vj6hf --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"
                        },
  • 此集群配置为从 Pilot(通过 ADS)检索关联的端点。因此,Envoy 将使用 serviceName 字段作为密钥来查找端点列表并将请求代理到其中一个端点。
istioctl proxy-config cluster productpage-v1-99b4c9954-vj6hf --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": [
                {}
            ]
        }
    }
]
    

5 备注

本文核心内容摘自Istio 流量管理实现机制深度解析,为了加深个人理解,结合实践,写了笔记,方便查阅,原文内容请参考:fuckcloudnative.io/posts/istio…

专注于大数据及容器云核心技术解密,如有任何学术交流,可随时联系。更多内容请关注《数据云技术社区》公众号,或请转发邮件至1120746959@qq.com