在 1.8
的版本中,istio
悄悄的上了一个预览版的功能 DNS Proxy ,这个功能极为的强大,让我们一起来看看吧。
为何诞生 从官方的文档中,DNS Proxy
的功能主要为了 减少KubeDNS的查询
,这个很好理解
除此之外,还要解决一个很重大的问题:VM 和 Kubernetes
的集成问题。
当我们将 虚拟机
接入集群的时候,虚拟机
中的应用如何访问 Kubernates
中的应用一直是一个困难的事情。还顺带解决了多集群访问的问题。让我们看看他是怎没做的。
DNS Proxy 启用此功能需要在 pilot-agent
的环境变量增加 ISTIO_META_DNS_CAPTURE=true
DNS 请求拦截 当Pod
启动的时候,我们会为 Pod
指定域名解析服务。
1 2 3 4 bash-5.0 nameserver 10.96.0.10 search test-istio.svc.cluster.local svc.cluster.local cluster.local byted.org options ndots:5
这里的 nameserver
指向的就是 kube-dns
的地址
1 2 $ kubectl get svc --all-namespaces | grep '10.96.0.10' kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 39d
istio
在会将此请求拦截,转发至 pilot-agent
,这里并没有通过 envoy
,是通过 iptables
1 2 3 4 5 6 $ nsenter -t1963097 -n iptables-save -L ..... -A OUTPUT -p udp -m udp --dport 53 -m owner --uid-owner 1337 -j RETURN -A OUTPUT -p udp -m udp --dport 53 -m owner --gid-owner 1337 -j RETURN -A OUTPUT -p udp -m udp --dport 53 -j DNAT --to-destination 127.0.0.1:15053 ....
在 isito
的 pilot/pkg/dns/proxy.go:62
中,我们可以很轻松的看到
1 2 3 4 5 6 7 func (p *dnsProxy) start() { log.Infof("Starting local %s DNS server at 0.0.0.0:15053" , p.protocol) err := p.downstreamServer.ActivateAndServe() if err != nil { log.Errorf("Local %s DNS server terminated: %v" , p.protocol, err) } }
DNS Agent 对于 DNS Agent
的运行逻辑也相对的不算很复杂。主要集中在 pilot/pkg/dns/dns.go
的 ServeDNS
函数中。
ServeDNS github 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 func (h *LocalDNSServer) ServeDNS(proxy *dnsProxy, w dns.ResponseWriter, req *dns.Msg) { var response *dns.Msg lp := h.lookupTable.Load() if lp == nil { response = new (dns.Msg) response.SetReply(req) response.Rcode = dns.RcodeNameError _ = w.WriteMsg(response) return } lookupTable := lp.(*LookupTable) var answers []dns.RR hostname := strings.ToLower(req.Question[0 ].Name) answers, hostFound := lookupTable.lookupHost(req.Question[0 ].Qtype, hostname) if hostFound { response = new (dns.Msg) response.SetReply(req) response.Answer = answers if len (answers) == 0 { response.Rcode = dns.RcodeNameError } } else { response = h.queryUpstream(proxy.upstreamClient, req) } _ = w.WriteMsg(response) }
处理的逻辑并不算复杂,而比较复杂的显然是 lookupTable
从何而来,这个答案都指向了 pilot-discovery
组件
HandleUpstream github 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (p *XdsProxy) HandleUpstream(ctx context.Context, con *ProxyConnection, xds discovery.AggregatedDiscoveryServiceClient) error {for { select { case resp, ok := <-con.responsesChan: switch resp.TypeUrl { case v3.NameTableType: if p.localDNSServer != nil && len (resp.Resources) > 0 { var nt nds.NameTable if err = ptypes.UnmarshalAny(resp.Resources[0 ], &nt); err != nil { log.Errorf("failed to unmarshall name table: %v" , err) } p.localDNSServer.UpdateLookupTable(&nt) } } }
可见 pilot-discovery
将这些数据 push
到了我们的 pilot-agent
上。
Pilot-Discovery 对于 XDS
来说,我们熟系的有 LDS
RDS
RSDS
等,对于 DNS
的数据,istio
拓展了一种 NDS
协议
DNS Generate github 1 2 3 4 5 6 7 8 9 10 11 func (n NdsGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource, req *model.PushRequest) model.Resources { if !ndsNeedsPush(req) { return nil } nt := n.Server.ConfigGenerator.BuildNameTable(proxy, push) if nt == nil { return nil } resources := model.Resources{util.MessageToAny(nt)} return resources }
在代码的生成中,我们可以发现那些东西会被 Push
到 Agent
中
BuildNameTable github 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 func (configgen *ConfigGeneratorImpl) BuildNameTable(node *model.Proxy, push *model.PushContext) *nds.NameTable { out := &nds.NameTable{ Table: map [string ]*nds.NameTable_NameInfo{}, } for _, svc := range push.Services(node) { if svc.Hostname.IsWildCarded() { continue } svcAddress := svc.GetServiceAddressForProxy(node, push) var addressList []string if svcAddress == constants.UnspecifiedIP { if svc.Attributes.ServiceRegistry == string (serviceregistry.Kubernetes) && svc.Resolution == model.Passthrough && len (svc.Ports) > 0 { for _, instance := range push.ServiceInstancesByPort(svc, svc.Ports[0 ].Port, nil ) { addressList = append (addressList, instance.Endpoint.Address) } } if len (addressList) == 0 { continue } } else { addressList = append (addressList, svcAddress) } nameInfo := &nds.NameTable_NameInfo{ Ips: addressList, Registry: svc.Attributes.ServiceRegistry, } } return out }
抛开 headless
的服务来说,大部分的服务地址都可以通过 GetServiceAddressForProxy
获得。
GetServiceAddressForProxy github 1 2 3 4 5 6 7 8 9 10 11 12 func (s *Service) GetServiceAddressForProxy(node *Proxy, push *PushContext) string { if node.Metadata != nil && node.Metadata.ClusterID != "" && push.ServiceIndex.ClusterVIPs[s][node.Metadata.ClusterID] != "" { return push.ServiceIndex.ClusterVIPs[s][node.Metadata.ClusterID] } if node.Metadata != nil && node.Metadata.DNSCapture != "" && s.Address == constants.UnspecifiedIP && s.AutoAllocatedAddress != "" { return s.AutoAllocatedAddress } return s.Address }
而生产 IP
的逻辑在 autoAllocateIPs 不做过多的展开。
在 PushContext
中,获得所有的 Service
的时候,会自动的生成这些 IP
1 2 3 4 5 6 7 8 func (s *ServiceEntryStore) Services() ([]*model.Service, error ) { services := make ([]*model.Service, 0 ) for _, cfg := range s.store.ServiceEntries() { services = append (services, convertServices(cfg)...) } return autoAllocateIPs(services), nil }
DNS Proxy Works 说了那么多的原理,让我们看看,这个功能究竟会给我们带来什么。
VM 访问 Kube 内资源
当我们让 VM
访问 Kube
内资源的时候,会经过
VM 内应用查询 DNS 解析 demo.demo.srv.cluster.local
DNS 解析返回缓存的地址,实际上对应的 srv
地址
访问此地址,被 Envoy
拦截
Envoy
将请求转发至 Ingress Gateway
Ingress Gateway
通过 SNI
转发至 Service
跨集群访问 在以前的跨集群访问中,如果某个服务仅仅在某一个集群中,此时我们在另外一个不包含此服务的集群中访问
1 2 / curl: (6) Could not resolve host: something.demo.srv.cluster.local
这里错误是因为 KubeDNS
并不能成功的了解析其他集群中的服务地址。现在基于 DNS PROXY
和 #VM 访问 Kube 内资源 一样解决了这样的问题。
ServiceEntry 一键接入外部服务 以前我们想要将一个 vm
的服务单方向的接入系统,我们需要如下配置
1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: details-svc spec: hosts: - notexist.foo.cluster.local location: MESH_EXTERNAL ports: - number: 80 name: http protocol: HTTP resolution: STATIC
此时我们访问 notexist.foo.cluster.local
依然会出现 Could not resolve host
的问题,解决办法是创建一个 headless
的 service
资源。
而现在不需要了,ServiceEntry
的对象会分配一个 E Class IP
,因此我们进行解析的时候会获得一个 IP
1 2 3 4 5 6 7 8 9 / Server: 10.233.0.3 Address: 10.233.0.3:53 ** server can't find notexist.foo.cluster.local: NXDOMAIN Non-authoritative answer: Name: notexist.foo.cluster.local Address: 240.240.12.17