coredns
# 1.简介
# 1.1.coredns
coredns用于监听apiserver获取service资源配置,维护及动态更新缓存的dns记录。coredns server收到dns query请求,会根据请求的域名查找本地indexer缓存对象,组装及返回域名解析记录。--- 插件化 基于caddy服务器框架,大量应用端逻辑抽象为plugin,以预配置方式将不同的plugin串为链按序执行 --- 配置简单 引入表达力更强的DSL,配置文件以corefile形式存在 --- 一体化方案 区别于kube-dns,coredns编译的可执行文件内置cache、backend storage、healthy check能力1
2
3
4
5
6
7
8
补充
coredns提供多种插件,可定义corefile进行plugin chain组合实现顺序执行,不同来源的dns记录会保存到etcd进行存取
# 1.2.plugin
coredns基于caddy框架实现,很多核心功能依赖caddy插件功能注册。本质上,plugin是实现特定接口的对象,通过init()注册到全局插件列表,供dns server启动期间加载及构造plugin chain,达到按序执行解析domain目的。// Handler is like dns.Handler except ServeDNS may return an rcode and/or error. Handler interface { ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error) Name() string } // plugin注册到caddy,供启动期间回调注册 func init() { plugin.Register("log", setup) } // caddy plugin注册回调 func setup(c *caddy.Controller) error { // corefile日志规则解析 rules, err := logParse(c) ... // pluginFunc注册到关联server的回调 dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler { // 真正的plugin创建,由makeServer阶段触发构造pluginChain return Logger{Next: next, Rules: rules, repl: replacer.New()} }) return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
补充
plugin本质上是懒加载,setup()回调先注册到caddy plugin,根据corefile定义执行将pluginFunc注册到关联server,最后关联server启动前执行创建及初始化plugin chain,以按序解析dns请求
# 2.主流程
# 2.1.入口
coredns基于caddy框架构建及启动,必要功能均以plugin形式外包,因此其入口很简单,看起来仅执行coremain.Run(),然而内部会加载配置,根据corefile定义初始化server及关联plugin,最后以dns服务启动。func main() { coremain.Run() } // Run is CoreDNS's main() function. func Run() { ... // Get Corefile input corefile, err := caddy.LoadCaddyfile(serverType) ... // Start your engines instance, err := caddy.Start(corefile) ... // Twiddle your thumbs instance.Wait() } // loads a Caddyfile. func LoadCaddyfile(serverType string) (Input, error) { // upgrade, obtain the Caddyfile from parent process, regardless of configured loaders. if IsUpgrade() { err := gob.NewDecoder(os.Stdin).Decode(&loadedGob) ... return loadedGob.Caddyfile, nil } // 基于注册的loader(os.stdin/file)或defaultloader(file)读取corefile cdyfile, err := loadCaddyfileInput(serverType) ... // 基于注册的default server获取默认corefile if cdyfile == nil { cdyfile = DefaultInput(serverType) } // 生成空corefile if cdyfile == nil { cdyfile = CaddyfileInput{ServerTypeName: serverType} } return cdyfile, nil } // Start starts Caddy with the given Caddyfile. // This function blocks until all the servers are listening. func Start(cdyfile Input) (*Instance, error) { // 初始化服务实例 inst := &Instance{serverType: cdyfile.ServerType(), wg: new(sync.WaitGroup), Storage: make(map[interface{}]interface{})} // 启动所有server err := startWithListenerFds(cdyfile, inst, nil) ... // 当前进程pid写入文件 writePidFile() ... return inst, nil } // 初始化及启动所有服务 func startWithListenerFds(cdyfile Input, inst *Instance, restartFds map[string]restartTriple) error { ... // 校验corefile模块及生成serverConfig(插件回调触发) err = ValidateAndExecuteDirectives(cdyfile, inst, false) ... // 创建https/grpc/dns/tls服务列表(插件创建回调) slist, err := inst.context.MakeServers() ... // 执行启动前回调 if !IsUpgrade() && restartFds == nil { // first startup means not a restart or upgrade for _, firstStartupFunc := range inst.OnFirstStartup { firstStartupFunc() ... } } for _, startupFunc := range inst.OnStartup { startupFunc() ... } // 启动服务列表 err = startServers(slist, inst, restartFds) ... // 执行启动后回调 if restartFds == nil { for _, srvln := range inst.servers { if srv, ok := srvln.server.(AfterStartup); ok { srv.OnStartupComplete() } } ... } ... return nil }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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
注意
1.
serverList构造完成后,会依次启动udp/tcp服务,监听请求及读取dns报文,调用serveDNS()进行dns解析2.
corefile定义的block都对应一个server
# 2.2.服务启动
plugin相关准备工作(启动回调)完成后,会执行startServers()启动coredns服务,根据serverList列表启动tcp和udp服务,用于接收及处理外部dns请求。func startServers(serverList []Server, inst *Instance, restartFds map[string]restartTriple) error { ... // 遍历server列表 for _, s := range serverList { ... // 平滑升级fd继承 if IsUpgrade() { // 实现平滑重启 if gs, ok := s.(GracefulServer); ok { addr := gs.Address() // 父进程tcp.sockFD if fdIndex, ok := loadedGob.ListenerFds["tcp"+addr]; ok { // tcp文件描述符包装为file对象 file := os.NewFile(fdIndex, "") // 文件描述符还原为net.Listener ln, err = net.FileListener(file) ... file.Close() ... } // 父进程udp.sockFD if fdIndex, ok := loadedGob.ListenerFds["udp"+addr]; ok { // udp文件描述符包装为file对象 file := os.NewFile(fdIndex, "") // 文件描述符还原为net.Packetconn pc, err = net.FilePacketConn(file) ... file.Close() ... } // listener包装(TLS/限流) ln = gs.WrapListener(ln) } } // 重加载过程fd复用 if gs, ok := s.(GracefulServer); ok && restartFds != nil { addr := gs.Address() // addr是旧的 if old, ok := restartFds[addr]; ok { // listener if old.listener != nil { // 旧tcp文件描述符 file, err := old.listener.File() ... // 文件描述符还原为net.Listener ln, err = net.FileListener(file) ... file.Close() ... } // packetconn if old.packet != nil { // 旧udp文件描述符 file, err := old.packet.File() ... // 文件描述符还原为net.PacketConn pc, err = net.FilePacketConn(file) ... file.Close() ... } ln = gs.WrapListener(ln) } } // 初次启动 if ln == nil { ln, err = s.Listen() ... } if pc == nil { pc, err = s.ListenPacket() ... } inst.servers = append(inst.servers, ServerListener{server: s, listener: ln, packet: pc}) } // 启动server for _, s := range inst.servers { ... func(s Server, ln net.Listener, pc net.PacketConn, inst *Instance) { go func() { ... // 启动tcp服务 errChan <- s.Serve(ln) }() go func() { ... // 启动udp服务 errChan <- s.ServePacket(pc) }() }(s.server, s.listener, s.packet, inst) } ... return nil } // Serve starts the server with an existing listener. It blocks until the server stops. func (s *Server) Serve(l net.Listener) error { ... s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { ... // 执行dns解析 s.ServeDNS(ctx, w, r) }), TsigSecret: s.tsigSecret} ... // 启动tcp服务 return s.server[tcp].ActivateAndServe() } // ServePacket starts the server with an existing packetconn. It blocks until the server stops. func (s *Server) ServePacket(p net.PacketConn) error { ... s.server[udp] = &dns.Server{PacketConn: p, Net: "udp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) { ... // 执行dns解析 s.ServeDNS(ctx, w, r) }), TsigSecret: s.tsigSecret} ... // 启动udp服务 return s.server[udp].ActivateAndServe() } // tcp/udp服务处理dns请求 func (srv *Server) serveDNS(m []byte, w *response) { // 解析报文头(固定12字节) dh, off, err := unpackMsgHdr(m, 0) ... // 报文头放到req req := new(Msg) req.setHdr(dh) // 检查dns请求合法性 switch action := srv.MsgAcceptFunc(dh); action { // 合法请求 case MsgAccept: // 报文内容解析到req if req.unpack(dh, m, off) == nil { break } fallthrough // 请求拒绝 case MsgReject, MsgRejectNotImplemented: // 构造错误响应报文 opcode := req.Opcode req.SetRcodeFormatError(req) req.Zero = false if action == MsgRejectNotImplemented { req.Opcode = opcode req.Rcode = RcodeNotImplemented } // Are we allowed to delete any OPT records here? req.Ns, req.Answer, req.Extra = nil, nil, nil // 响应 w.WriteMsg(req) fallthrough // 请求丢弃 case MsgIgnore: // 回收udp缓冲区m if w.udp != nil && cap(m) == srv.UDPSize { srv.udpPool.Put(m[:srv.UDPSize]) } // 结束 return } ... // 回收udp缓冲区m if w.udp != nil && cap(m) == srv.UDPSize { srv.udpPool.Put(m[:srv.UDPSize]) } // 根据构造请求执行回调 // Msg{ // Id: 0x1234, // Opcode: QUERY, // Question: [{Name: "www.example.com.", Qtype: A, Qclass: IN}], // } srv.Handler.ServeDNS(w, req) // Writes back to the client }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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
注意
1.
dnsServer启动时,升级或重启场景会对tcp/udp监听的连接复用,避免客户端断开,尽可能保证平滑切换2.
dnsServer启动tcp/udp服务,监听到连接就调用serveDNS()读取dns请求报文,分发给tcp/udp服务注册的handlerFunc解析处理3.
handlerFunc本质上是服务列表server实现的.serveDNS(),会依次调用plugin.serveDNS()解析dns请求
# 2.3.请求解析
srv.serveDNS()会遍历执行plugin chain插件链的所有plugin.serveDNS(),类似将dns请求任务按序分发给不同插件,直到解析dns请求或无法解析退出。// ServeDNS is the entry point for every request to the address that is bound to. func (s *Server) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) { ... // 检查edns版本合法性 // edns是个很厉害的功能,edns出现前,gslb只能拿到dns server地址,拿不到用户地址 // 使用edns开放协议,gslb可以拿到用户地址,确保流量调度更准确 if m, err := edns.Version(r); err != nil { // Wrong EDNS version, return at once. w.WriteMsg(m) return } // writer包装,确保响应报文不会超出客户端缓冲区 w = request.NewScrubWriter(r, w) // 需要解析的域名 q := strings.ToLower(r.Question[0].Name) ... for { // 检查域匹配的zone((www.baidu.com.-->baidu.com.-->com.) if z, ok := s.zones[q[off:]]; ok { for _, h := range z { ... // plugin实现Viewer接口,执行FilterFunc // view example1 { expr incidr(client_ip(), '192.168.48.0/24') // } if passAllFilterFuncs(ctx, h.FilterFuncs, &request.Request{Req: r, W: w}) { ... // 非DS记录 // DS用于信任链衔接,查询子域在父域登记的签名 if r.Question[0].Qtype != dns.TypeDS { // 调用plugin chain的解析 rcode, _ := h.pluginChain.ServeDNS(ctx, w, r) // 响应解析失败 if !plugin.ClientWrite(rcode) { errorFunc(s.Addr, w, r, rcode) } return } // DS查询,暂存当前handler,继续向后找父zone dshandler = h } } } // 下一级域 off, end = dns.NextLabel(q, off) if end { break } } // DS查询,找到上级handler(父zone/爷爷zone) if r.Question[0].Qtype == dns.TypeDS && dshandler != nil && dshandler.pluginChain != nil { // 执行plugin chain解析 rcode, _ := dshandler.pluginChain.ServeDNS(ctx, w, r) if !plugin.ClientWrite(rcode) { errorFunc(s.Addr, w, r, rcode) } return } // 未匹配任何zone,走根zone if z, ok := s.zones["."]; ok { for _, h := range z { ... // 执行继承Viewer接口的插件FilterFunc if passAllFilterFuncs(ctx, h.FilterFuncs, &request.Request{Req: r, W: w}) { ... // 直接执行plugin chain解析(DS也可处理,.是根zone) rcode, _ := h.pluginChain.ServeDNS(ctx, w, r) if !plugin.ClientWrite(rcode) { errorFunc(s.Addr, w, r, rcode) } return } } } // 无法解析,拒绝dns请求 errorAndMetricsFunc(s.Addr, w, r, dns.RcodeRefused) }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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
注意
1.针对域名解析,先逐层拆解域进行精确匹配,根据匹配到的
zoneBlock配置的plugin按序解析处理2.针对
DS域名解析请求,先遍历保存父zone的block配置,根据父zone配置的plugin按序解析处理3.仅配置
根zone(.),按照根zone的plugin按序解析处理
# 2.4.解析过程
pluginChain本质是plugin,基于plugin.next指向后面的plugin,serveDNS()解析请求会依次执行plugin.serveDNS(),解析成功返回给客户端,解析失败调用plugin.next继续解析。
# 3.补充
# 3.1.dns配置
pod调度执行createPodSandbox()会生成pause容器所需配置,生成的dns配置包含三部分:search、nameserver、options,指定支持的搜索域及上游的dns服务器,基于grpc交互containerd设置到pause容器,供其它容器共享。func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string,string,error) { // 生成pause的配置 podSandboxConfig, err := m.generatePodSandboxConfig(pod, attempt) ... // 创建pod日志目录(/var/log/podxxxx/containerxxxx) m.osInterface.MkdirAll(podSandboxConfig.LogDirectory, 0755) ... // 启动pause容器 podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig, runtimeHandler) ... return podSandBoxID, "", nil } func (m *kubeGenericRuntimeManager) generatePodSandboxConfig(pod *v1.Pod, attempt uint32) (*runtimeapi.PodSandboxConfig, error) { ... // 获取dns dnsConfig, err := m.runtimeHelper.GetPodDNS(pod) ... podSandboxConfig.DnsConfig = dnsConfig ... return podSandboxConfig, nil } func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) { // 获取当前host的dns配置,其实就是resolv.conf内容 dnsConfig, err := c.getHostDNSConfig() ... // 获取pod的dns策略 dnsType, err := getPodDNSType(pod) if err != nil { // 默认使用clusterFirst策略 dnsType = podDNSCluster } switch dnsType { case podDNSNone: // None策略创建dnsConfig,后边会合并pod的dnsConfig dnsConfig = &runtimeapi.DNSConfig{} case podDNSCluster: // clusterFirst模式 if len(c.clusterDNS) != 0 { // 把kubelet启动时的参数--cluster-dns地址加入nameserver(基本是kubeadm初始化service-cidr时第一个地址) dnsConfig.Servers = []string{} for _, ip := range c.clusterDNS { dnsConfig.Servers = append(dnsConfig.Servers, ip.String()) } // 把dns search domain加入到search dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod) // 配置dns options,默认配置ndots:5 dnsConfig.Options = defaultDNSOptions break } fallthrough case podDNSHost: // 如果dns配置默认模式,当前host的dns配置为空,使用127.0.0.1作为server if c.ResolverConfig == "" { for _, nodeIP := range c.nodeIPs { if utilnet.IsIPv6(nodeIP) { dnsConfig.Servers = append(dnsConfig.Servers, "::1") } else { dnsConfig.Servers = append(dnsConfig.Servers, "127.0.0.1") } } if len(dnsConfig.Servers) == 0 { dnsConfig.Servers = append(dnsConfig.Servers, "127.0.0.1") } dnsConfig.Searches = []string{"."} } } // pod配置dnsConfig,合并到上述初始化的dnsConfig对象 if pod.Spec.DNSConfig != nil { dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig) } return c.formDNSConfigFitsLimits(dnsConfig, pod), nil }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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
注意
1.
clusterFirst模式,pod调度默认注入.cluster.local域及coredns作为上游,ndots默认为5,同时会合并pod.spec.dns配置2.
clusterHost模式,127.0.0.1作为dns上游3.
hostnetwork的pod策略必须为clusterFirstWithHostNet,否则只能使用node dns解析配置,不走coredns4.
kubelet配置resolv-conf外部配置,调度期间会将外部dns配置注入到pod
# 3.2.dns解析
pod基于svc/pod发起域名请求,会转发到coredns进行解析,可能需要折腾多次才能响应,这是由于pod内部的ndots默认为5,域名.的数量小于5时会依次遍历拼接search后缀进行dns查询。# cat /etc/resolv.conf search default.svc.cluster.local svc.cluster.local cluster.local nameserver 10.254.0.10 options ndots:51
2
3
4此外,集群
dns解析转到coredns可能造成较大压力,可以配合nodelocal dnscache进行dns缓存代理提高集群dns解析性能,集群内dns解析继续转到中心coredns,外部域名解析直接转到global dns。
注意
ndots设置建议为2,以减少集群无用dns请求
# 3.3.流量处理
coredns负责域名解析,以将svc域名转为clusterIP,之后的clusterIP流量代理工作由kubeproxy完成,基于iptables/ipvs规则进行DNAT负载为podIP,podIP--podIP的流量最终交给CNI处理。
注意
1.
coredns作为域名解析服务2.
kubeproxy负载svc clusterIP流量,DNAT到podIP3.
cni处理podIP流量,实现同节点或跨节点通信