SkyDNS2源码分析
来源:互联网 发布:智业软件好用吗 编辑:程序博客网 时间:2024/04/27 15:37
SkyDNS2是SkyDNS Version 2.x的统称,其官方文档只有README.md,网上能找到的资料也不多,因此需要我们自行对代码进行一定的分析,才能对其有更好的理解,这就是本文的工作,通过走读SkyDNS的代码,了解其内部架构及其工作原理。
说明
- SkyDNS2的github地址: https://github.com/skynetservices/skydns
- Version: v2.5.3a
SkyDNS架构
关于SkyDNS是什么?…. 这些知识,请前往官网了解。
下面我直接把我阅读代码后理解的SkyDNS架构贴出来:
SkyDNS工作原理
SkyDNS Server的工作,依赖后端Key-Value存储的支持。当前支持etcd或etcd3作为Backend(架构图中蓝色部分),为SkyDNS提供配置和数据的管理。
通过环境变量ETCD_MACHINES
进行etcd cluster的配置,如果Backend为etcd3,还需要设置etcd中/v2/keys//skydns/config/etcd3为true。SkyDNS中有etcd client模块,负责与ETCD_MACHINES
的通信。
SkyDNS主要对应的etcd key path如下:
/v2/keys/skydns/config/v2/keys/skydns/local/skydns/east/production/rails/v2/keys/skydns/local/skydns/dns/stub/v2/keys/skydns/local/skydns/...
通过如下环境变量的配置,支持prometheus监控(架构图中棕色部分)。如果想disable prometheus监控,则配置环境变量PROMETHEUS_PORT的值为0即可。
Port = os.Getenv("PROMETHEUS_PORT")Path = envOrDefault("PROMETHEUS_PATH", "/metrics")Namespace = envOrDefault("PROMETHEUS_NAMESPACE", "skydns")Subsystem = envOrDefault("PROMETHEUS_SUBSYSTEM", "skydns")
如果/v2/keys/skydns/config/nameservers有值,则SkyDNS解析不了的Domain,会forward到对应的这些IP:Port
构成的nameservers,由它们进行解析(架构图中绿色部分)。
参考官方文档https://github.com/skynetservices/skydns/blob/master/README.md完成参数配置后,便可启动SkyDNS。
SkyDNS Server的启动过程如下:
- 创建etcd client对象;
- dns_addr 和 nameservers参数合法性检查;
- 加载启动参数到etcd,覆盖/v2/keys/skydns/config中原有数据;
- 配置SkyDNS Server参数的default值,并创建SkyDNS server对象;
- 去etcd中加载…/dns/stub//xx数据作为server的stub zones数据,并启动对…/dns/stub/的watcher,一旦有数据更新,就加载到server的stub zones数据中;
- 注册SkyDNS metrics到prometheus;
- 然后在/v2/keys/skydns/config/dns_addr配置的interface和port上开启tcp/udp监听服务并block住,由此开始提供DSN服务。
在github.com/skynetservices/skydns/server/server.go中的ServeDNS方法覆盖了miekg/dns/server中的ServeMux.ServeDNS方法,由自实现的ServeDNS提供来处理DNS client的请求。
github.com/skynetservices/skydns/server/server.go// ServeDNS is the handler for DNS requests, responsible for parsing DNS request, possibly forwarding// it to a real dns server and returning a response.func (s *server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { ... // Check cache first. m1 := s.rcache.Hit(q, dnssec, tcp, m.Id) if m1 != nil { ... // Still round-robin even with hits from the cache. // Only shuffle A and AAAA records with each other. if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { s.RoundRobin(m1.Answer) } ... return } for zone, ns := range *s.config.stub { if strings.HasSuffix(name, "." + zone) || name == zone { metrics.ReportRequestCount(req, metrics.Stub) resp := s.ServeDNSStubForward(w, req, ns) if resp != nil { s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) } metrics.ReportDuration(resp, start, metrics.Stub) metrics.ReportErrorCount(resp, metrics.Stub) return } } ... if name == s.config.Domain { if q.Qtype == dns.TypeSOA { m.Answer = []dns.RR{s.NewSOA()} return } if q.Qtype == dns.TypeDNSKEY { if s.config.PubKey != nil { m.Answer = []dns.RR{s.config.PubKey} return } } } if q.Qclass == dns.ClassCHAOS { if q.Qtype == dns.TypeTXT { switch name { case "authors.bind.": fallthrough case s.config.Domain: hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} authors := []string{"Erik St. Martin", "Brian Ketelsen", "Miek Gieben", "Michael Crosby"} for _, a := range authors { m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{a}}) } for j := 0; j < len(authors)*(int(dns.Id())%4+1); j++ { q := int(dns.Id()) % len(authors) p := int(dns.Id()) % len(authors) if q == p { p = (p + 1) % len(authors) } m.Answer[q], m.Answer[p] = m.Answer[p], m.Answer[q] } return case "version.bind.": fallthrough case "version.server.": hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{Version}}} return case "hostname.bind.": fallthrough case "id.server.": // TODO(miek): machine name to return hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{"localhost"}}} return } } // still here, fail m.SetReply(req) m.SetRcode(req, dns.RcodeServerFailure) return } switch q.Qtype { case dns.TypeNS: if name != s.config.Domain { break } // Lookup s.config.DnsDomain records, extra, err := s.NSRecords(q, s.config.dnsDomain) if isEtcdNameError(err, s) { m = s.NameError(req) return } m.Answer = append(m.Answer, records...) m.Extra = append(m.Extra, extra...) case dns.TypeA, dns.TypeAAAA: records, err := s.AddressRecords(q, name, nil, bufsize, dnssec, false) if isEtcdNameError(err, s) { m = s.NameError(req) return } m.Answer = append(m.Answer, records...) case dns.TypeTXT: records, err := s.TXTRecords(q, name) if isEtcdNameError(err, s) { m = s.NameError(req) return } m.Answer = append(m.Answer, records...) case dns.TypeCNAME: records, err := s.CNAMERecords(q, name) if isEtcdNameError(err, s) { m = s.NameError(req) return } m.Answer = append(m.Answer, records...) case dns.TypeMX: records, extra, err := s.MXRecords(q, name, bufsize, dnssec) if isEtcdNameError(err, s) { m = s.NameError(req) return } m.Answer = append(m.Answer, records...) m.Extra = append(m.Extra, extra...) default: fallthrough // also catch other types, so that they return NODATA case dns.TypeSRV: records, extra, err := s.SRVRecords(q, name, bufsize, dnssec) if err != nil { if isEtcdNameError(err, s) { m = s.NameError(req) return } logf("got error from backend: %s", err) if q.Qtype == dns.TypeSRV { // Otherwise NODATA m = s.ServerFailure(req) return } } // if we are here again, check the types, because an answer may only // be given for SRV. All other types should return NODATA, the // NXDOMAIN part is handled in the above code. TODO(miek): yes this // can be done in a more elegant manor. if q.Qtype == dns.TypeSRV { m.Answer = append(m.Answer, records...) m.Extra = append(m.Extra, extra...) } } if len(m.Answer) == 0 { // NODATA response m.Ns = []dns.RR{s.NewSOA()} m.Ns[0].Header().Ttl = s.config.MinTtl }}
上面代码逻辑比较复杂,细节上需要你慢慢去理解,简短的可以总结如下:
- 如架构图中标注的线路1:如果在SkyDNS维护的cache中找到对应Msg,则从cache中读取并返回Msg给DNS client;
- 如架构图中标注的线路2:如果在cache中没有对应的记录,并且是需要DNS forward的场景(比如name匹配到stub zones等),则将请求forward到对应的DNS servers进行处理;
- 如架构图中标注的线路3:如果在cache中没有对应的记录,并且Question Type为A/AAAA,SRV等类型时,就通过etcd client去etcd cluster中获取对应的Rule,并构造Msg返回。
总结
通过走读SkyDNS的代码,了解其内部架构及其工作原理。
Mark
SkyDNS2 Changes since version 1:
- Does away with Raft and uses etcd (which uses raft).
- Makes it possible to query arbitrary domain names.
- Is a thin layer above etcd, that translates etcd keys and values to the DNS.
- Does DNSSEC with NSEC3 instead of NSEC.
- SkyDNS2源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析:SparseArray分析
- 源码- Spark Broadcast源码分析
- Android源码/框架源码分析
- 【Android应用源码分析】HandlerThread 源码分析
- 【Android应用源码分析】IntentService 源码分析
- java源码分析01-Object源码分析
- VC++源码分析 - 中国象棋源码分析
- [Java源码分析]ArrayList源码分析
- [java源码分析]LinkedList源码分析
- 神奇的数学——三次方程
- 用三层架构来做winform程序
- 函数声明与函数表达式
- 书目
- iOS 自动布局框架学习之Masonry
- SkyDNS2源码分析
- SSH 使用密钥登录并禁止口令登录
- Ubuntu下对PyCharm-2016.3.2的长期使用
- HTML框架
- 自己不要怪谁。是你自己努力的程度不够,努力吧!
- linux下yum方式配置本地源仓库并安装软件
- 查询每班的最高分
- 《Linux设备驱动开发详解》读书笔记(6)
- mysql高效索引之覆盖索引