前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >istio nds下发及dns解析原理

istio nds下发及dns解析原理

作者头像
有点技术
发布2020-12-08 09:35:57
1.6K0
发布2020-12-08 09:35:57
举报
文章被收录于专栏:有点技术有点技术

简介

在istio1.8中为了支持DNS解析功能,并且实现了dns cache,不需要通过search域进行多次查询,例如解析bar.foo.svc.cluster.local可能需要依次解析bar.foo.svc.cluster.local.foo.svc.cluster.local/bar.foo.svc.cluster.local.svc.cluster.local/bar.foo.svc.cluster.local.cluster.local/bar.foo.svc.cluster.local,这是因为search域的配置影响,而istio pilot-agent dns代理则只需要一次解析,提高了解析速度。

为了支持dns cache,则需要控制面下发网格内部的现有服务的域名IP配置到agent,为了解决数据传输问题引入了NDS(NameTable Service Discovery)协议,其url定义为type.googleapis.com/istio.networking.nds.v1.NameTable

本位将探索NDS资源的下发方式及客户端的dns解析原理

pilot-agent处理DNS请求

在通过以下方式安装istio后

代码语言:javascript
复制
 istioctl install --set profile=demo  --set meshConfig.defaultConfig.proxyMetadata.ISTIO_META_DNS_CAPTURE='\"true\"'

pilot-agent的istio-iptables将在添加iptables规则时将53的dns请求重定向到pilot-agent同时启动dns server

代码语言:javascript
复制
func (sa *Agent) initLocalDNSServer(isSidecar bool) (err error) {
    if sa.cfg.DNSCapture && sa.cfg.ProxyXDSViaAgent && isSidecar {
        if sa.localDNSServer, err = dns.NewLocalDNSServer(sa.cfg.ProxyNamespace, sa.cfg.ProxyDomain); err != nil {
            return err
        }
        sa.localDNSServer.StartDNS()
    }
    return nil
}

NewLocalDNSServer

根据集群域及命名空间获取LocalDNSServer

代码语言:javascript
复制
func NewLocalDNSServer(proxyNamespace, proxyDomain string) (*LocalDNSServer, error) {
    h := &LocalDNSServer{
        proxyNamespace: proxyNamespace,
    }
    // proxyDomain可以包含使其成为冗余的名称空间。我们只需要.svc.cluster.local部分
    parts := strings.Split(proxyDomain, ".")
    if len(parts) > 0 {
        if parts[0] == proxyNamespace {
            parts = parts[1:]
        }
        h.proxyDomainParts = parts
        h.proxyDomain = strings.Join(parts, ".")
    }
    // 使用本地的resolv.conf作为dns代理配置
    dnsConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
    if err != nil {
        log.Warnf("failed to load /etc/resolv.conf: %v", err)
        return nil, err
    }
     // 与传统的DNS解析器不同,不需要将搜索名称空间附加到查询再解析。这是因为代理充当应用程序进行的DNS查询的DNS拦截器。应用程序的解析器已经向我们发送了DNS查询,每个DNS搜索名称空间都有一个。我们只需要在本地命名表中检查此名称是否存在。如果没有,我们会将查询原样转发给上游解析器。
    if dnsConfig != nil {
        for _, s := range dnsConfig.Servers {
            h.resolvConfServers = append(h.resolvConfServers, s+":53")
        }
        h.searchNamespaces = dnsConfig.Search
    }
    if h.udpDNSProxy, err = newDNSProxy("udp", h); err != nil {
        return nil, err
    }
    if h.tcpDNSProxy, err = newDNSProxy("tcp", h); err != nil {
        return nil, err
    }
    return h, nil
}

newDNSProxy

将LocalDNSServer作为resolver传递给对应的tcp和udp proxy

StartDNS

分别启动tcpDNSProxy和udpDNSProxy

ServeDNS

对于miekg/dns来说handler需要实现ServeDNS也就是LocalDNSServer的ServeDNS方法

代码语言:javascript
复制
func (h *LocalDNSServer) ServeDNS(proxy *dnsProxy, w dns.ResponseWriter, req *dns.Msg) {
    var response *dns.Msg
    if len(req.Question) == 0 {
        response = new(dns.Msg)
        response.SetReply(req)
        response.Rcode = dns.RcodeNameError
    } else {
        // 获取 LookupTable
        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)
        // 判断LookupTable中是否存在,存在则直接返回
        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 {
                // 我们在预编译的已知主机列表中找到了该主机,但是该查询类型没有有效记录.所以返回NXDOMAIN
                response.Rcode = dns.RcodeNameError
            }
        } else {
            // 我们没有在内部缓存中找到主机。向上游查询并按原样返回响应。
            response = h.queryUpstream(proxy.upstreamClient, req)
        }
    }
    _ = w.WriteMsg(response)
}

更新本地缓存

代码语言:javascript
复制
if p.localDNSServer != nil && len(resp.Resources) > 0 {
    var nt nds.NameTable
    // TODO we should probably send ACK and not update nametable here
    if err = ptypes.UnmarshalAny(resp.Resources[0], &nt); err != nil {
        log.Errorf("failed to unmarshall name table: %v", err)
    }
    p.localDNSServer.UpdateLookupTable(&nt)
}

通过UpdateLookupTable更新localDNSServer数据

pilot-discovery下发NDS

在istio的xds实现中分为以下两个channel

•reqChannel 用于处理xds请求•pushChannel 用于主动推送xds数据变更

他们最终都将调用 pushXds

pushXds 下发XDS资源

代码语言:javascript
复制
func (s *DiscoveryServer) pushXds(con *Connection, push *model.PushContext,
    currentVersion string, w *model.WatchedResource, req *model.PushRequest) error {
    if w == nil {
        return nil
    }
    gen := s.findGenerator(w.TypeUrl, con)
    if gen == nil {
        return nil
    }
    t0 := time.Now()
    cl := gen.Generate(con.proxy, push, w, req)
    if cl == nil {
        // If we have nothing to send, report that we got an ACK for this version.
        if s.StatusReporter != nil {
            s.StatusReporter.RegisterEvent(con.ConID, w.TypeUrl, push.Version)
        }
        return nil // No push needed.
    }
    defer func() { recordPushTime(w.TypeUrl, time.Since(t0)) }()
    resp := &discovery.DiscoveryResponse{
        TypeUrl:     w.TypeUrl,
        VersionInfo: currentVersion,
        Nonce:       nonce(push.Version),
        Resources:   cl,
    }
    err := con.send(resp)
    if err != nil {
        recordSendError(w.TypeUrl, con.ConID, err)
        return err
    }
    // Some types handle logs inside Generate, skip them here
    if _, f := SkipLogTypes[w.TypeUrl]; !f {
        adsLog.Infof("%s: PUSH for node:%s resources:%d", v3.GetShortType(w.TypeUrl), con.proxy.ID, len(cl))
    }
    return nil
}

通过对应的TypeUrl,获取对应的Generator

代码语言:javascript
复制
s.findGenerator(w.TypeUrl, con)

然后从根据客户端的配置生产成对应的资源

代码语言:javascript
复制
cl := gen.Generate(con.proxy, push, w, req)

对于nds服务发现类型其对应的url为type.googleapis.com/istio.networking.nds.v1.NameTable 其对应的Generate实现为

代码语言:javascript
复制
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
}

其主要逻辑为BuildNameTable,获取nametable序列化后返回给客户端

BuildNameTable 生成NameTable

BuildNameTable生成一个主机名及其关联的IP的表,然后代理可以使用该表来解析DNS。此逻辑始终处于活动状态。但是,只有在代理中启用DNS捕获后,本地DNS解析才会生效

代码语言:javascript
复制
func (configgen *ConfigGeneratorImpl) BuildNameTable(node *model.Proxy, push *model.PushContext) *nds.NameTable {
    // 只对sidecar类型的代理生效
    if node.Type != model.SidecarProxy {
        return nil
    }
    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
        //对于headless svc或者对于自动分配的serviceentry此处未指定IP,缺乏对有状态应用解析的支持
        if svcAddress == constants.UnspecifiedIP {
            // 用ep填充
            if svc.Attributes.ServiceRegist ry == 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,
        }
        if svc.Attributes.ServiceRegistry == string(serviceregistry.Kubernetes) {
            nameInfo.Namespace = svc.Attributes.Namespace
            nameInfo.Shortname = svc.Attributes.Name
        }
        out.Table[string(svc.Hostname)] = nameInfo
    }
    return out
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 有点技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
  • pilot-agent处理DNS请求
    • NewLocalDNSServer
      • newDNSProxy
        • StartDNS
          • ServeDNS
            • 更新本地缓存
            • pilot-discovery下发NDS
              • pushXds 下发XDS资源
                • BuildNameTable 生成NameTable
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档