注:阅读DNS源码前,可以阅读DNS原理入门增加对DNS的认识。

架构图

这是我简单画的架构图,希望能帮助大家理解。

代码结构

k8s.io

| dns

| cmd // 三大组件的入口

| dnsmasq-nanny // DNS缓存

| kube-dns // dns主项目

| sidecar // 附加组件

| pkg 组件代码库,主要实现代码在该目录下

| dns // kube-dns代码库, 监听service、pod等资源,动态更新DNS记录

| dnsmasq // 内部封装dnsmasq程序用于缓存,并可从dns服务器获取dns监控指标

| sidecar 用于监控和健康检查

主要的代码都集中在上述树形结构中,下面依次讲解。

kube-dns

kube-dns是提供DNS功能的组件,我们重点关注。

首先看main方法:

  1. func main() {
  2. config := options.NewKubeDNSConfig()
  3. config.AddFlags(pflag.CommandLine)
  4. flag.InitFlags()
  5. // Convinces goflags that we have called Parse() to avoid noisy logs.
  6. // OSS Issue: kubernetes/kubernetes#17162.
  7. goflag.CommandLine.Parse([]string{}) // 解析参数
  8. logs.InitLogs()
  9. defer logs.FlushLogs() // 初始化日志
  10. version.PrintAndExitIfRequested()
  11. glog.V(0).Infof("version: %+v", version.VERSION)
  12. // 实例化KubeDNSServer并运行
  13. server := app.NewKubeDNSServerDefault(config)
  14. server.Run()
  15. }

下面我们分析app包里的server.go文件

  1. type KubeDNSServer struct {
  2. // DNS domain name.
  3. domain string
  4. healthzPort int
  5. dnsBindAddress string
  6. dnsPort int
  7. nameServers string
  8. kd *dns.KubeDNS
  9. } // KubeDNSServer类里前几个变量都是main函数里传递过来的参数直接赋值,没啥可讲的。kd是pkg/dns包里的KubeDNS类的实例,我们后续再讲。
  10. func NewKubeDNSServerDefault(config *options.KubeDNSConfig) *KubeDNSServer {} // 根据参数,填充KubeDNSServer对象
  11. func newKubeClient(dnsConfig *options.KubeDNSConfig) (kubernetes.Interface, error) {} // 根据参数,实例化一个与apiserver通信的client,有两种模式的client可以使用。集群内client(通过serviceAccount认证)以及集群外client(当前Kubernetes集群配置的其他认证方式进行认证)
  12. // main函数中的server.Run()调用的函数
  13. func (server *KubeDNSServer) Run() {
  14. pflag.VisitAll(func(flag *pflag.Flag) {
  15. glog.V(0).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
  16. })
  17. setupSignalHandlers() // 监听系统事件,主要作用是等待日志处理完成
  18. server.startSkyDNSServer() // 配置SkyDNS服务并启动服务,此处使用了SkyDNS的相关代码,这篇文章就不赘述了(我没看代码,囧)
  19. server.kd.Start() // 启动KubeDNS,后续再深入
  20. server.setupHandlers() // 添加两个http方法,/readiness用于健康检查,/cache返回当前dns中缓存的dns记录JSON
  21. glog.V(0).Infof("Status HTTP port %v", server.healthzPort)
  22. if server.nameServers != "" {
  23. glog.V(0).Infof("Upstream nameservers: %s", server.nameServers)
  24. }
  25. glog.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", server.healthzPort), nil)) // 监听http服务
  26. }

app/options包中还有个options.go文件,这里主要包含app需要的配置信息,不再赘述。下面我们跳入pkg/dns包中探一探究竟,首先入场(也是唯一入场)的是pkg/dns/dns.go

  1. type KubeDNS struct {
  2. // 与apiserver通信的client
  3. kubeClient clientset.Interface
  4. // 域名,默认为cluster.local.
  5. domain string
  6. // configMap的名称,默认为空,使用命令行参数
  7. configMap string
  8. // 存储集群中所有的endpoints
  9. endpointsStore kcache.Store
  10. // 存储集群中所有的services
  11. servicesStore kcache.Store
  12. // 存储集群中所有的nodes
  13. nodesStore kcache.Store
  14. // dns缓存
  15. cache treecache.TreeCache
  16. // PTR记录 ip --> skymsg.Service
  17. reverseRecordMap map[string]*skymsg.Service
  18. // 集群服务列表 ip --> v1.Service
  19. clusterIPServiceMap map[string]*v1.Service
  20. // 缓存锁,更新上述三者的数据时,需加锁
  21. cacheLock sync.RWMutex
  22. // 域名路径,是域名反向分割后的列表,比如默认的为[]string{"local", "cluster"}
  23. domainPath []string
  24. // endpointsController 管理endpoints的变更
  25. endpointsController *kcache.Controller
  26. // serviceController 管理services的变更
  27. serviceController *kcache.Controller
  28. // 配置对象
  29. config *config.Config
  30. // 配置更新锁
  31. configLock sync.RWMutex
  32. // 配置更新管理对象
  33. configSync config.Sync
  34. // 初始化同步endpoints和services的过期时间
  35. initialSyncTimeout time.Duration
  36. }
  37. func NewKubeDNS(client clientset.Interface, clusterDomain string, timeout time.Duration, configSync config.Sync) *KubeDNS {} // 根据参数配置KubeDNS对象,设置endpoint和service相关store和controller。
  38. func (kd *KubeDNS) Start() {} // 启动KudeDNS,其实也就是启动在NewKubeDNS中设置的endpointsController和serviceController,并且监听配置文件的变化
  39. func (kd *KubeDNS) GetCacheAsJSON() (string, error) {} // 获取dns缓存对象JSON,在cmd/app/server.go中被http方法/cache使用
  40. func (kd *KubeDNS) setServicesStore() {} // 在NewKubeDNS中调用。监听service的变化,针对不同的操作(新增,删除,更新)执行不同的callback
  41. func (kd *KubeDNS) setEndpointsStore() {} // 在NewKubeDNS中调用。监听endpoint的变化,针对不同的操作(新增,删除,更新)执行不同的callback
  42. func (kd *KubeDNS) newService(obj interface{}) {} // 根据service的类型,生成不同类型的dns记录。简单的说,ExternalName类型的service是CNAME,只在dns缓存(KubeDNS.cache)中存储该记录;Headless(无ClusterIp)类型的service不在KubeDNS.clusterIPServiceMap中记录;而其他类型的service则在cache,reverseRecordMap,clusterIPServiceMap中一并存储
  43. func (kd *KubeDNS) removeService(obj interface{}) {} // 删除service,也即删除在cache,reverseRecordMap,clusterIPServiceMap中的相关记录
  44. func (kd *KubeDNS)updateService(oldObj,newObjinterface{}){}// 更新=删除+新增func(kd*KubeDNS)handleEndpointAdd(objinterface{}){}// 新增endpointsfunc(kd*KubeDNS)handleEndpointUpdate(oldObj,newObjinterface{}){}// 更新endpoints,删除新endpoints子网里跟老endpoints子网里一样的PTR记录(KubeDNS.reverseRecordMap),即删除相同ip的endpoint,然后调用handleEndpointAdd新增endpointsfunc(kd*KubeDNS)handleEndpointDelete(objinterface{}){}// 删除相关的PTR记录(KubeDNS.reverseRecordMap)即可func(kd*KubeDNS)addDNSUsingEndpoints(e*v1.Endpoints)error{}// 新增endpoints,如果endpoints对应的service是Headless service,则生成相关记录。如果不是,什么也不做。func(kd*KubeDNS)getServiceFromEndpoints(e*v1.Endpoints)(*v1.Service,error){}// 根据endpoints返回servicefunc(kd*KubeDNS)fqdn(service*v1.Service,subpaths...string)string{}// 生成一个完整网域名称(Fully qualified domain name)func(kd*KubeDNS)newPortalService(service*v1.Service){}// 生成portalService,我的理解是一般类型的service,同时在cache,reverseRecordMap,clusterIPServiceMap中存储该service的记录func(kd*KubeDNS)generateRecordsForHeadlessService(e*v1.Endpoints,svc*v1.Service)error{}// 生成headlessService,同时在cache,reverseRecordMap中存储该service的记录func(kd*KubeDNS)newExternalNameService(service*v1.Service){}// 生成ExternalNameService,只在cache中记录该条信息func(kd*KubeDNS)Records(namestring,exactbool)(retval[]skymsg.Service,errerror){}// 查询DNS记录,参数中的exact标识是否精确匹配。其中federation相关的东西我还不是太明白func(kd*KubeDNS)ReverseRecord(namestring)(*skymsg.Service,error){}// 查询PTR记录

Records 和 ReverseRecord 两个方法是实现了skydns的Backend接口,这样的话,KubeDNS就可以作为skydns的后端存储提供dns查询服务了,二者怎么关联起来的呢?回看cmd/kube-dns/app/server.go文件里的startSkyDNSServer方法,你会找到汇合点的,试试看吧。

至此,我们对kubeDNS组件有了初步大概的认识。接下来,我们再接着看dnsmasq组件。

dnsmasq

cmd/dnsmasq-nanny没有什么可以讲的,也就是解析命令行参数然后调用pkg/dnsmasq/nanny.go的RunNanny方法。或者可以这么说,整个dnsmasq就没什么事,就是在其中内嵌了dnsmasql应用程序,通过代码启动并管理该程序的生命进程

  1. // RunNanny runs the nanny and handles configuration updates.
  2. func RunNanny(sync config.Sync, opts RunNannyOpts) {
  3. defer glog.Flush()
  4. currentConfig, err := sync.Once()
  5. if err != nil {
  6. glog.Errorf("Error getting initial config, using default: %v", err)
  7. currentConfig = config.NewDefaultConfig()
  8. } // 解析配置
  9. nanny := &Nanny{Exec: opts.DnsmasqExec}
  10. nanny.Configure(opts.DnsmasqArgs, currentConfig)
  11. if err := nanny.Start(); err != nil {
  12. glog.Fatalf("Could not start dnsmasq with initial configuration: %v", err)
  13. } // 启动dnsmasq应用程序
  14. configChan := sync.Periodic()
  15. for {
  16. select {
  17. case status := <-nanny.ExitChannel:
  18. glog.Flush()
  19. glog.Fatalf("dnsmasq exited: %v", status)
  20. break
  21. case currentConfig = <-configChan:
  22. if opts.RestartOnChange {
  23. glog.V(0).Infof("Restarting dnsmasq with new configuration")
  24. nanny.Kill()
  25. nanny = &Nanny{Exec: opts.DnsmasqExec}
  26. nanny.Configure(opts.DnsmasqArgs, currentConfig)
  27. nanny.Start()
  28. } else {
  29. glog.V(2).Infof("Not restarting dnsmasq (--restartDnsmasq=false)")
  30. }
  31. break
  32. }
  33. } // 持续监听退出状态和配置更新
  34. }
  35. func (n *Nanny) Kill() error {} // 杀掉运行中的dnsmasq进程
  36. func (n *Nanny) Start()error{}// 启动dnsmasq进程,并将日志信息输出到glogfunc(n*Nanny)Configure(args[]string,config*config.Config){}// 解析配置,必须在Start方法前调用

dnsmasq的另一部分的作用是从dnsmasq应用程序读取监控信息,包括缓存命中数量,缓存未命中数量,缓存删除数量,缓存插入数量,缓存大小等信息。

sidecar

sidecar组件的主要作用是提供kube-dns和dnsmasq的健康检查和dns的监控。我们略过cmd/sidecar/main.go,他的主要作用也无非是解析参数。我们重点关注pkg/sidecar/server.go

  1. func (s *server) Run(options *Options) {
  2. s.options = options
  3. glog.Infof("Starting server (options %+v)", *s.options)
  4. // 之前说sidecar监控kube-dns和dnsmasq,其实是通过参数传递进来的,具体的参数解析可以参考cmd/sidecar/main.go
  5. for _, probeOption := range options.Probes {
  6. probe := &dnsProbe{DNSProbeOption: probeOption}
  7. s.probes = append(s.probes, probe)
  8. probe.Start(options) // 启动组件健康检查
  9. }
  10. s.runMetrics(options) // dns监控信息
  11. }

容器编排Kubernetes之kube-dns源码解读的更多相关文章

  1. Java中的容器(集合)之HashMap源码解析

    1.HashMap源码解析(JDK8) 基础原理: 对比上一篇<Java中的容器(集合)之ArrayList源码解析>而言,本篇只解析HashMap常用的核心方法的源码. HashMap是 ...

  2. Kubernetes client-go Indexer / ThreadSafeStore 源码分析

    Kubernetes client-go Indexer / ThreadSafeStore 源码分析   请阅读原文:原文地址   Contents 概述 Indexer 接口 ThreadSafe ...

  3. Prometheus 源码解读(一)

    Prometheus 源码解读(一) Prometheus 是云原生监控领域的事实标准,越来越来的开源项目开始支持 Prometheus 监控数据格式.从本篇开始,我将和大家一起阅读分析 Promet ...

  4. swoft 源码解读【转】

      官网: https://www.swoft.org/ 源码解读: http://naotu.baidu.com/file/814e81c9781b733e04218ac7a0494e2a?toke ...

  5. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  6. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  7. AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization

    本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...

  8. YYModel 源码解读(二)之NSObject+YYModel.h (1)

    本篇文章主要介绍 _YYModelPropertyMeta 前边的内容 首先先解释一下前边的辅助函数和枚举变量,在写一个功能的时候,这些辅助的东西可能不是一开始就能想出来的,应该是在后续的编码过程中 ...

  9. 源码解读—HashTable

    在上一篇学习过HashMap(源码解读—HashMap)之后对hashTable也产生了兴趣,随即便把hashTable的源码看了一下.和hashMap类似,但是也有不同之处. public clas ...

随机推荐

  1. 深入理解Mysql索与事务隔离级别

    1. 概述 1.1 定义 锁是计算机协调多个进程或线程并发访问某一资源的机制. 在数据库中,除了传统的计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供需要用户共享的资源.如何保证数据并 ...

  2. Redis与Memcached的比较(转)

    原文:http://blog.nosqlfan.com/html/3729.html 这两年Redis火得可以,Redis也常常被当作Memcached的挑战者被提到桌面上来.关于Redis与Memc ...

  3. Python 实现获取【昨天】【今天】【明天】日期

    昨天 from datetime import date, timedelta yesterday = (date.today() + timedelta(days=-1)).strftime(&qu ...

  4. Docker Libnetwork Bridge插件实现代码分析----创建网络部分

    // drivers/bridge/bridge.go // Create a new network using bridge plugin 1.func (d *driver) CreateNet ...

  5. 我的Android进阶之旅------>RxJava学习资料汇总

    在响应式编程中,应该牢记以下两点: everything is a stream(一切皆流) don't break the chain(不要打断链式结构) 记住,可观测序列就像一条河,它们是流动的. ...

  6. [动态规划]UVA10285 - Longest Run on a Snowboard

    Problem C Longest Run on a Snowboard Input: standard input Output: standard output Time Limit: 5 sec ...

  7. 【Java编程】写入、读取、遍历Properties文件

    在Java开发中通常我们会存储配置參数信息到属性文件.这种属性文件能够是拥有键值对的属性文件,也能够是XML文件.关于XML文件的操作,请參考博文[Java编程]DOM XML Parser 解析.遍 ...

  8. 关于shared pool的深入探讨(五)

    Oracle使用两种数据结构来进行shared pool的并发控制:lock 和 pin.Lock比pin具有更高的级别. Lock在handle上获得,在pin一个对象之前,必须首先获得该handl ...

  9. python s12

    logging模块 很多程序都有记录日志的需求,并且日志中包含的信息即有正常的程序访问日志,还可能有错误.警告等信息输出,python的logging模块提供了标准的日志接口,你可以通过它存储各种格式 ...

  10. Oracle 在64位机器上使用plSQL连接Oracle的问题(SQL*Net not properly installed)

    问题: 在64位机器上了64位的oracle客户端. 然后装上PL/SQL Developer,但是连接oracle老报这个错: Initialization error      SQL*Net n ...