容器编排Kubernetes之kube-dns源码解读

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 花了几天时间,研究了Kubernetes DNS插件的源代码,对其实现有了个简单的理解。这篇文章我简单梳理下代码流程。 注:阅读DNS源码前,可以阅读DNS原理入门增加对DNS的认识。 架构图 这是我简单画的架构图,希望能帮助大家理解。

花了几天时间,研究了Kubernetes DNS插件的源代码,对其实现有了个简单 的理解。这篇文章我简单梳理下代码流程。

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

架构图

20170523213042

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

代码结构

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方法:

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

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

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

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

type KubeDNS struct { // 与apiserver通信的client kubeClient clientset.Interface // 域名,默认为cluster.local. domain string // configMap的名称,默认为空,使用命令行参数 configMap string // 存储集群中所有的endpoints endpointsStore kcache.Store // 存储集群中所有的services servicesStore kcache.Store // 存储集群中所有的nodes nodesStore kcache.Store // dns缓存 cache treecache.TreeCache // PTR记录 ip --> skymsg.Service reverseRecordMap map[string]*skymsg.Service // 集群服务列表 ip --> v1.Service clusterIPServiceMap map[string]*v1.Service // 缓存锁,更新上述三者的数据时,需加锁 cacheLock sync.RWMutex // 域名路径,是域名反向分割后的列表,比如默认的为[]string{"local", "cluster"} domainPath []string // endpointsController 管理endpoints的变更 endpointsController *kcache.Controller // serviceController 管理services的变更 serviceController *kcache.Controller // 配置对象 config *config.Config // 配置更新锁 configLock sync.RWMutex // 配置更新管理对象 configSync config.Sync // 初始化同步endpoints和services的过期时间 initialSyncTimeout time.Duration } func NewKubeDNS(client clientset.Interface, clusterDomain string, timeout time.Duration, configSync config.Sync) *KubeDNS {} // 根据参数配置KubeDNS对象,设置endpoint和service相关store和controller。 func (kd *KubeDNS) Start() {} // 启动KudeDNS,其实也就是启动在NewKubeDNS中设置的endpointsController和serviceController,并且监听配置文件的变化 func (kd *KubeDNS) GetCacheAsJSON() (string, error) {} // 获取dns缓存对象JSON,在cmd/app/server.go中被http方法/cache使用 func (kd *KubeDNS) setServicesStore() {} // 在NewKubeDNS中调用。监听service的变化,针对不同的操作(新增,删除,更新)执行不同的callback func (kd *KubeDNS) setEndpointsStore() {} // 在NewKubeDNS中调用。监听endpoint的变化,针对不同的操作(新增,删除,更新)执行不同的callback func (kd *KubeDNS) newService(obj interface{}) {} // 根据service的类型,生成不同类型的dns记录。简单的说,ExternalName类型的service是CNAME,只在dns缓存(KubeDNS.cache)中存储该记录;Headless(无ClusterIp)类型的service不在KubeDNS.clusterIPServiceMap中记录;而其他类型的service则在cache,reverseRecordMap,clusterIPServiceMap中一并存储 func (kd *KubeDNS) removeService(obj interface{}) {} // 删除service,也即删除在cache,reverseRecordMap,clusterIPServiceMap中的相关记录 func (kd *KubeDNS) updateService(oldObj, newObj interface{}) {} // 更新=删除+新增 func (kd *KubeDNS) handleEndpointAdd(obj interface{}) {} // 新增endpoints func (kd *KubeDNS) handleEndpointUpdate(oldObj, newObj interface{}) {} // 更新endpoints,删除新endpoints子网里跟老endpoints子网里一样的PTR记录(KubeDNS.reverseRecordMap),即删除相同ip的endpoint,然后调用handleEndpointAdd新增endpoints func (kd *KubeDNS) handleEndpointDelete(obj interface{}) {} // 删除相关的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返回service func (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(name string, exact bool) (retval []skymsg.Service, err error) {} // 查询DNS记录,参数中的exact标识是否精确匹配。其中federation相关的东西我还不是太明白 func (kd *KubeDNS) ReverseRecord(name string) (*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应用程序,通过代码启动并管理该程序的生命进程

// RunNanny runs the nanny and handles configuration updates. func RunNanny(sync config.Sync, opts RunNannyOpts) { defer glog.Flush() currentConfig, err := sync.Once() if err != nil { glog.Errorf("Error getting initial config, using default: %v", err) currentConfig = config.NewDefaultConfig() } // 解析配置 nanny := &Nanny{Exec: opts.DnsmasqExec} nanny.Configure(opts.DnsmasqArgs, currentConfig) if err := nanny.Start(); err != nil { glog.Fatalf("Could not start dnsmasq with initial configuration: %v", err) } // 启动dnsmasq应用程序 configChan := sync.Periodic() for { select { case status := <-nanny.ExitChannel: glog.Flush() glog.Fatalf("dnsmasq exited: %v", status) break case currentConfig = <-configChan: if opts.RestartOnChange { glog.V(0).Infof("Restarting dnsmasq with new configuration") nanny.Kill() nanny = &Nanny{Exec: opts.DnsmasqExec} nanny.Configure(opts.DnsmasqArgs, currentConfig) nanny.Start() } else { glog.V(2).Infof("Not restarting dnsmasq (--restartDnsmasq=false)") } break } } // 持续监听退出状态和配置更新 } func (n *Nanny) Kill() error {} // 杀掉运行中的dnsmasq进程 func (n *Nanny) Start() error {} // 启动dnsmasq进程,并将日志信息输出到glog func (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

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

本文转自中文社区-容器编排Kubernetes之kube-dns源码解读

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
7天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
30 2
|
17天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
9天前
|
存储 Kubernetes Docker
【赵渝强老师】Kubernetes中Pod的基础容器
Pod 是 Kubernetes 中的基本单位,代表集群上运行的一个进程。它由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。基础容器负责维护 Pod 的网络空间,对用户透明。文中附有图片和视频讲解,详细介绍了 Pod 的组成结构及其在网络配置中的作用。
【赵渝强老师】Kubernetes中Pod的基础容器
|
5天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
9天前
|
运维 Kubernetes Shell
【赵渝强老师】K8s中Pod的临时容器
Pod 是 Kubernetes 中的基本调度单位,由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。临时容器用于故障排查和性能诊断,不适用于构建应用程序。当 Pod 中的容器异常退出或容器镜像不包含调试工具时,临时容器非常有用。文中通过示例展示了如何使用 `kubectl debug` 命令创建临时容器进行调试。
|
9天前
|
Kubernetes 调度 容器
【赵渝强老师】K8s中Pod中的业务容器
Pod 是 Kubernetes 中的基本调度单元,由一个或多个容器组成。除了业务容器,Pod 还包括基础容器、初始化容器和临时容器。本文通过示例介绍如何创建包含业务容器的 Pod,并提供了一个视频讲解。示例中创建了一个名为 &quot;busybox-container&quot; 的业务容器,并使用 `kubectl create -f firstpod.yaml` 命令部署 Pod。
|
9天前
|
Kubernetes 容器 Perl
【赵渝强老师】K8s中Pod中的初始化容器
Kubernetes的Pod包含业务容器、基础容器、初始化容器和临时容器。初始化容器在业务容器前运行,用于执行必要的初始化任务。本文介绍了初始化容器的作用、配置方法及优势,并提供了一个示例。
|
17天前
|
Kubernetes 负载均衡 Cloud Native
云原生应用:Kubernetes在容器编排中的实践与挑战
【10月更文挑战第27天】Kubernetes(简称K8s)是云原生应用的核心容器编排平台,提供自动化、扩展和管理容器化应用的能力。本文介绍Kubernetes的基本概念、安装配置、核心组件(如Pod和Deployment)、服务发现与负载均衡、网络配置及安全性挑战,帮助读者理解和实践Kubernetes在容器编排中的应用。
47 4
|
18天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
38 3

相关产品

  • 云解析DNS
  • 推荐镜像

    更多