3.控制面板没有下发inbound-listener错误分析

488 阅读5分钟
(一)基础概念
①envoy的基本概念和术语
  • Host:能够进行网络通信的实体(如移动设备,服务器上的应用程序)
  • Downstream:发送请求并接受响应
  • Upstream:上游主机连接来自Envoy的连接和请求,并返回响应
  • Listener:监听器是命名网地址(例如:端口,unix domain socket等),可以被下游客户端连接。Envoy暴露一个或者多个监听器给下游主机连接。在Envoy中,listener可以绑定到端口上直接对外服务,也可以不绑定到端口上,而是接受其他listener转发的请求
  • Cluster:集群是指Envoy连接到的逻辑上相同的一组上游主机。Envoy通过服务发现来发现集群的成员。可以选择通过主动健康检查来确定集群成员的健康状态。Envoy通过负载均衡策略决定将请求路由到那个集群成员
②Listeners:Envoy采用listener来接受和处理downstream发过来的请求,listener的处理逻辑是插件式的,可以通过配置不同的filter来插入不同的处理逻辑。
Virtual Listener:
Envoy创建了一个在15001端口监听的入口监听器。Iptable将请求截取后发向15001端口,该监听器接收后不进行业务处理,而是根据请求目的地分发给其他监听器处理。该监听器取名为"virtual"监听器也是这个原因

{
    "version_info": "2019-07-26T08:48:04Z/8731",
    "listener": {
        "name": "virtual",
        "address": {
            "socket_address": {
                "address": "0.0.0.0",
                "port_value": 15001
            }
        },
        "filter_chains": [
            {
                "filters": [
                    {
                        "name": "envoy.tcp_proxy",
                        "typed_config": {
                            "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                            "stat_prefix": "PassthroughCluster",
                            "cluster": "PassthroughCluster"
                        }
                    }
                ]
            }
        ],
        "use_original_dst": true
    },
    "last_updated": "2019-07-26T08:50:55.096Z"
}

Inbound Listener:
当外部调用服务的请求到达Pod上15001的Virtual Listener上时,Virtual Listener根据请求目的地匹配到该listener,请求将被转发过来

{
    "version_info": "2019-07-26T09:28:13Z/8805",
    "listener": {
        "name": "10.131.5.135_80",
        "address": {
            "socket_address": {
                "address": "10.131.5.135",
                "port_value": 80
            }
        },
        "filter_chains": [
            {
                "filters": [
                    {
                        "name": "envoy.http_connection_manager",
                        "typed_config": {
                            "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
                            "stat_prefix": "10.131.5.135_80",
                            "route_config": {
                                "name": "inbound|80|http-80|kunkka-caodi.mservice.svc.cluster.local",
                                "virtual_hosts": [
                                    {
                                        "name": "inbound|http|80",
                                        "domains": [
                                            "*"
                                        ],
                                        "routes": [
                                            {
                                                "match": {
                                                    "prefix": "/"
                                                },
                                                "route": {
                                                    "cluster": "inbound|80|http-80|kunkka-caodi.mservice.svc.cluster.local",
                                                    "timeout": "0s",
                                                    "max_grpc_timeout": "0s"
                                                },
                                                "decorator": {
                                                    "operation": "kunkka-caodi.mservice.svc.cluster.local:80/*"
                                                }
                                            }
                                        ]
                                    }
                                ],
                                "validate_clusters": false
                            },
                            "http_filters": [
                                {
                                    "name": "envoy.cors"
                                },
                                {
                                    "name": "envoy.fault"
                                },
                                {
                                    "name": "envoy.router"
                                }
                            ],
                            "tracing": {
                                "client_sampling": {
                                    "value": 100
                                },
                                "random_sampling": {
                                    "value": 1
                                },
                                "overall_sampling": {
                                    "value": 100
                                }
                            },
                            "server_name": "istio-envoy",
                            "use_remote_address": false,
                            "generate_request_id": true,
                            "forward_client_cert_details": "APPEND_FORWARD",
                            "set_current_client_cert_details": {
                                "subject": true,
                                "dns": true,
                                "uri": true
                            },
                            "upgrade_configs": [
                                {
                                    "upgrade_type": "websocket"
                                }
                            ],
                            "stream_idle_timeout": "0s"
                        }
                    }
                ]
            }
        ],
        "deprecated_v1": {
            "bind_to_port": false
        }
    },
    "last_updated": "2019-07-26T09:28:16.046Z"
}
从上面的配置 "bind_to_port":false 可以得知该listener创建后并不会被绑定到tcp端口上直接接受网络上的数据,因此其他所有请求都转发自15001端口

Outbound Listener:
Envoy为网格中的外部服务按照端口创建多个listener,以用于处理出向请求。
首先需要区分入向请求和出向(发送给其他几个服务)请求
  • 发给服务的的入向请求,virtual listener根据其目的IP和Port首先匹配到PodIP:80这个listener上,不会进入0.0.0.0_80 listener处理。
  • 从Pod发出的出向请求,virtual listener无法找到和其目的IP完全匹配的listener,因此根据通配原则转交给0.0.0.0_80处理。
注意:出向请求的端口都是从service资源里面获取的。只有service资源里面才存储了对外暴露的端口和对内的端口信息

(二)envoy获取pilot的listener的代码流程分析
①服务端stream需要实现的方法

// AggregatedDiscoveryServiceServer is the server API for AggregatedDiscoveryService service.
type AggregatedDiscoveryServiceServer interface {
   // This is a gRPC-only API.
   StreamAggregatedResources(AggregatedDiscoveryService_StreamAggregatedResourcesServer) error
   DeltaAggregatedResources(AggregatedDiscoveryService_DeltaAggregatedResourcesServer) error
}
所以主要看pilot对于StreamAggregatedResources()方法的实现细节即可

②对于每个envoy的连接创建一个connectionNode资源,其需要根据envoy请求时的Request填充以下结构体

// Proxy contains information about an specific instance of a proxy (envoy sidecar, gateway,
// etc). The Proxy is initialized when a sidecar connects to Pilot, and populated from
// 'node' info in the protocol as well as data extracted from registries.
//
// In current Istio implementation nodes use a 4-parts '~' delimited ID.
// Type~IPAddress~ID~Domain
type Proxy struct {
   // ClusterID specifies the cluster where the proxy resides.
   ClusterID string

   // Type specifies the node type. First part of the ID.
   Type NodeType

   // IPAddresses is the IP addresses of the proxy used to identify it and its
   // co-located service instances. Example: "10.60.1.6". In some cases, the host
   // where the poxy and service instances reside may have more than one IP address
   IPAddresses []string

   // ID is the unique platform-specific sidecar proxy ID. For k8s it is the pod ID and
   // namespace.
   ID string

   // Locality is the location of where Envoy proxy runs.
   Locality *core.Locality

   // DNSDomain defines the DNS domain suffix for short hostnames (e.g.
   // "default.svc.cluster.local")
   DNSDomain string

   // ConfigNamespace defines the namespace where this proxy resides
   // for the purposes of network scoping.
   // NOTE: DO NOT USE THIS FIELD TO CONSTRUCT DNS NAMES
   ConfigNamespace string

   // TrustDomain defines the trust domain of the certificate
   TrustDomain string

   // Metadata key-value pairs extending the Node identifier
   Metadata map[string]string

   // the sidecarScope associated with the proxy
   SidecarScope *SidecarScope

   // service instances associated with the proxy (只能是同一个集群下的配置下的serviceInstance)
   ServiceInstances []*ServiceInstance
}

③问题出现在此处

// GetProxyServiceInstances returns service instances co-located with a given proxy
func (c *Controller) GetProxyServiceInstances(proxy *model.Proxy) ([]*model.ServiceInstance, error) {
   out := make([]*model.ServiceInstance, 0)

   // There is only one IP for kube registry
   proxyIP := proxy.IPAddresses[0]

   pod := c.pods.getPodByIP(proxyIP)
   if pod != nil {
      // find proxy service by label selector, if not any
      svcLister := listerv1.NewServiceLister(c.services.informer.GetIndexer())
      if services, err := svcLister.GetPodServices(pod); err == nil && len(services) > 0 {
         for _, svc := range services {
            out = append(out, c.getProxyServiceInstancesByPod(pod, svc, proxy)...)
         }
         return out, nil
      }
   }

   if len(out) == 0 {
      if c.Env != nil {
         c.Env.PushContext.Add(model.ProxyStatusNoService, proxy.ID, proxy, "")
         status := c.Env.PushContext
         if status == nil {
            log.Infof("Empty list of services for pod %s %v", proxy.ID, c.Env)
         }
      } else {
         log.Infof("Missing env, empty list of services for pod %s", proxy.ID)
      }
   }
   return out, nil
}
pod := c.pods.getPodByIP(proxyIP)
该方法是根据proxyIP获取pod相关的信息,内部的PodIP是从 ip := pod.Status.PodIP 获取到的。也就是从kubectl get pod -o wide获取到的。只要kubectl get pod -o wide获取不到对应的ip的话,pilot就拿不到这个pod对应的各种信息了,也就没有办法下发inbound listener了。导致探针检查的时候会出现404的错误