ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

K8s中CoreDNS上游DNS配置实战:内网公网域名解析全攻略

K8s中CoreDNS上游DNS配置实战:内网公网域名解析全攻略 前几周有个同事跑过来找我说集群里有个Pod一直在报域名解析失败公网域名比如baidu.com能通但公司内网的一个GitLab地址怎么都解析不了。我第一反应不是去看网络而是去查CoreDNS的配置——这种问题十有八九不是链路断了是CoreDNS不知道该把这串域名交给谁处理。是的你没看错。K8s集群里的DNS解析默认全部由集群内的CoreDNS负责。它既管集群内部服务的名字比如service名、Pod名也管集群外部的域名解析。而“外部域名”到底交给哪台DNS服务器来解完全取决于我们给CoreDNS配置的上游DNS服务器是什么。配置得当Pod内外域名通吃配置不当轻则解析超时重则集群里一堆服务莫名其妙互访失败。这篇文章就是要把“给CoreDNS配置上游DNS服务器”这件事讲透从原理到实操再到排错和一些进阶玩法把我这些年踩过的坑一次说完。1. CoreDNS在K8s里到底扮演什么角色1.1 先搞清楚Pod是怎么“问路”的在K8s集群里每个Pod的/etc/resolv.conf并不是管理员手动指定的而是由每个节点上的kubelet自动生成并写入。这里面最关键的一行是nameserver它指向的是kube-dns这个Service的ClusterIP。这里有个容易混淆的点Service名是kube-dns但真正干活的Pod是CoreDNS。kube-dns这个名称是历史遗留早期K8s用的DNS插件叫kube-dns后来被CoreDNS取代但Service名一直保留着。所以你在集群里看到kubectl get svc -n kube-system里有个叫kube-dns的Service它背后对应的Pod就是CoreDNS。当你在一个Pod里执行nslookup my-service.default.svc.cluster.local时流程是这样的Pod读取自己的/etc/resolv.conf拿到nameserver也就是kube-dns的ClusterIP通常默认是10.96.0.10。请求到达CoreDNS。CoreDNS收到查询域名后先看是不是cluster.local后缀集群内部域名是的话直接查K8s API返回Service或Pod的IP。不是集群内部域名就按配置走转发逻辑交给上游DNS服务器处理。这个“交给上游”的动作就是本文的核心所在。默认情况下CoreDNS的forward插件会把查询转发到/etc/resolv.conf里指定的DNS服务器而这个文件是容器镜像里带的通常指向宿主机所在的DNS或公共DNS。具体值取决于你的集群安装方式很多场景下它并不符合你的实际网络需求。1.2 默认配置能解决什么、不能解决什么默认配置下CoreDNS能顺利完成的事包括解析集群内Service名比如nginx.default.svc.cluster.local解析跨命名空间的Service名比如redis.middleware.svc.cluster.local解析带cluster.local后缀的Pod反向查询in-addr.arpa部分解析一些简单的公网域名比如www.baidu.com前提是CoreDNS容器里的/etc/resolv.conf指向的DNS能递归解析公网域名。但有三类常见需求默认配置是搞不定的第一类公司内网域名。很多企业有自建的DNS服务器专门解析内部系统比如gitlab.corp.local、harbor.registry.internal。这类域名在公网DNS上根本不存在记录CoreDNS默认转发给公共DNS肯定返回NXDOMAIN。第二类跨VPC或专线互联的私有域名。比如你有一个数据库服务部署在另一套IDC走专线和K8s集群互通但域名需要由IDC侧DNS解析。这个地址只对IDC网络可见公共DNS同样不认。第三类合规和审计要求。部分企业的安全策略要求所有DNS查询必须经过指定的内网DNS网关不允许Pod直接向公共DNS发起解析。这个时候也必须把上游DNS改成公司指定的服务器。所以给CoreDNS配置正确的上游DNS服务器本质上是让集群内DNS解析适配你的外部网络环境而不是让CoreDNS真的“只靠自己解决所有解析”。2. 动手前想清楚你要上游帮你解析哪些域名2.1 三种常见需求对照不少人在配CoreDNS的时候有个误区不加思考地填几个DNS IP就完事。实际上你得先明确一个问题——你希望上游DNS帮你解析哪些域名是全部域名还是只有一部分特定域名我帮你把常见需求归成三类可以直接对照你的场景来选需求类型典型现象上游配置建议仅需要解析公网域名Pod访问外网应用内网域名不需要保持默认确认CoreDNS容器能访问公网DNS即可需要全局解析内网公网域名公司自建DNS既能解析内网域名也能递归公网域名把上游指向内网DNS比如forward . 10.10.0.2内网域名和公网域名分别走不同DNS内网域名只能由内网DNS解公网域名不走内网DNS或内网DNS不支持公网递归用多个zone区分比如forward corp.local 10.10.0.2forward . 223.5.5.5大多数中大型企业属于第三种。因为内网DNS服务器通常只负责内部域名并不开放公网递归或者出于安全策略不允许把公网查询流量也送进内网DNS。2.2 关键概念Corefile里那个.代表什么CoreDNS的核心配置在一个叫Corefile的文件里它类似于Nginx的nginx.conf通过区块来定义域名和插件逻辑。我们最常见到的一行配置是这样forward . 10.10.0.2这里的.不是单纯的“句中句号”它代表根域也就是“所有未匹配的域名”。CoreDNS的匹配逻辑是从最长后缀开始匹配查询域名如果没有更具体的zone匹配就走.这个根域配置。举个例子假设你在Corefile里同时定义了corp.local:53 { forward . 10.10.0.2 } .:53 { forward . 223.5.5.5 }当你查询gitlab.corp.local时CoreDNS发现corp.local这个zone存在所以走第一个区块把请求发给10.10.0.2。当你查询www.baidu.com时没有匹配到corp.local于是走.区块把请求发给223.5.5.5。理解了这一点你就能玩出很多花样。但要注意多个zone的配置区块之间不要互相覆盖否则会出现“配了等于没配”的情况。比如你写了一个corp.local区块却在里面配了forward . 10.10.0.2又在.:53区块里也把上游指向公共DNS那corp.local域名的解析确实走了内网DNS但如果你内网DNS解析不了其他内网域名比如office.corp.local一样会失败。正确做法是把你所有内网域名后缀都在zone里表达清楚。2.3 一个容易被忽略的问题上游DNS服务器IP从哪来在动手配置之前先确认你要填进去的DNS服务器IP是否真的可连通。很多人配完之后发现不起作用结果一查上游DNS的IP写错了或者是防火墙阻断了53端口的UDP流量。我建议用下面这个顺序逐层确认在集群节点上执行nslookup gitlab.corp.local 10.10.0.2确认节点本身能通过这台DNS解析目标域名在节点上执行ping 10.10.0.2确认网络能通如果节点能通但进入CoreDNS Pod后不通检查节点安全组、Calico/Cilium网络策略是否限制了Pod网段访问该IP的53端口。第3点特别容易踩坑。K8s集群里Pod访问外部IP时流量要经过节点SNAT如果安全组只放行了节点IP访问DNS端口但没放行Pod网段虽然SNAT后会变成节点IP但有些防火墙会按源连接追踪的原始信息判断也可能失败。反正填上游IP之前先做通联测试能省后面一大半排错时间。3. 实操给CoreDNS配上真正好用的上游3.1 先摸清当前CoreDNS配置动手之前先看看CoreDNS现在是啥状态。两条命令搞定kubectl get cm coredns -n kube-system -o yaml你会看到类似这样的输出apiVersion: v1 data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } kind: ConfigMap metadata: name: coredns namespace: kube-system再确认一下CoreDNS Pod当前用的上游是什么kubectl -n kube-system get pod -l k8s-appkube-dns kubectl -n kube-system exec coredns-pod-name -- cat /etc/resolv.conf默认情况下forward . /etc/resolv.conf表示CoreDNS容器使用自身/etc/resolv.conf里的nameserver作为上游。而这个文件的内容通常继承自节点的DNS配置或者是集群安装工具写入的。在这个阶段你把/etc/resolv.conf里的内容记录下来后面无论是改还是排错都能作为对比基线。3.2 修改Corefile的两种姿势现在到了正式动手改配置的环节。有两种常见姿势我分别说下优劣。第一种直接用kubectl edit修改ConfigMapkubectl edit cm coredns -n kube-system这个方式简单直接但有个隐患如果你对YAML格式不熟改错一个缩进整个ConfigMap会出问题如果又保存了会导致CoreDNS重启失败。我见过不少同事在这个环节把集群DNS搞挂过。第二种先导出为文件编辑后用kubectl apply提交kubectl get cm coredns -n kube-system -o yaml coredns-cm.yaml vim coredns-cm.yaml kubectl apply -f coredns-cm.yaml个人推荐第二种。因为它有文件留底万一改出问题还能用文件快速回滚。另外不要把resourceVersion和uid等元数据保留在apply文件里导出来后先把这些字段清理掉参考下面的精简结构apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . 10.10.0.2 223.5.5.5 { policy random health_check 5s max_concurrent 1000 } cache 30 loop reload loadbalance }改完后直接apply。但这里有个很重要的知识点CoreDNS默认不会因为ConfigMap变化就自动重载配置。虽然上面的Corefile里有reload插件但它实现的是周期性地检查配置文件变化默认30秒一次而且它依赖CoreDNS进程直接读取文件内容。在K8s里ConfigMap更新后如果CoreDNS容器没有以subPath方式挂载配置文件reload插件不一定能可靠感知变更。为了确保生效最稳妥的办法是重启CoreDNSkubectl -n kube-system rollout restart deployment/corednsrollout restart会触发滚动更新旧Pod退出、新Pod启动新Pod启动时会重新读取ConfigMap里的最新配置。3.3 配置参数详解这些数字别随便填好现在我们把上面Corefile里的forward块单独拎出来看forward . 10.10.0.2 223.5.5.5 { policy random health_check 5s max_concurrent 1000 }这里的每个参数都是可以调的我分别说明一下。域名匹配区.表示所有未匹配的查询都转发到后面的服务器列表。如果你只想让特定域名走某个上游把.换成具体后缀即可比如forward corp.local 10.10.0.2。上游DNS列表10.10.0.2 223.5.5.5支持填多个IP。CoreDNS会按选定的策略在它们之间分配查询流量。注意这里填的IP必须能直接访问不能填需要递归才能找到的域名地址。policy random随机选择上游。可选值有random、round_robin、sequential。我一般推荐random能有效避免固定使用第一台DNS导致高负载堆积。如果你的上游有多台且性能不均衡可以用round_robin轮询。health_check 5s每隔5秒对上游做一次健康检查。具体检查方式是发送一个.的DNS查询根域查询如果上游返回响应哪怕SERVFAIL都算健康只有超时或无响应才标记为故障。被标记为故障的上游会被自动摘除直到恢复健康。这个参数的值不宜太大否则故障转移不及时也不宜太小否则健康检查流量会干扰上游。5秒是比较均衡的选择。max_concurrent 1000限制同时转发给上游的最大并发查询数。超过这个数后额外的查询会直接被丢弃并返回SERVFAIL。这其实是一个“熔断保护”机制防止上游高负载打爆CoreDNS整个进程。如果你集群规模很大比如每秒DNS QPS有几千可以把1000调大比如2000或5000但也要配合上游的实际处理能力否则你只是把队列问题向后推移。这里还得提一下时间参数CoreDNS默认的转发超时是10秒也就是说上游10秒不响应CoreDNS会返回超时失败。如果你在业务里碰到偶尔的DNS超时可以把超时设置加长。但更推荐的做法是增加健康检查让有问题的上游被提前摘除而不是拉长等待时间。3.4 常见坑配置明明改了Pod却还是解析失败配置改完、重启完最让人崩溃的是什么Pod里一测还是老样子。我来盘点几个我实际遇到过的原因。坑一nodelocaldns插件遮蔽了上游配置。很多K8s集群会安装nodelocaldns组件也叫NodeLocal DNSCache它会在每个节点上运行一个本地DNS缓存并把Pod的DNS请求拦截到本机。这种情况下真正和上游DNS打交道的是nodelocaldns而不是CoreDNS。也就是说你改了CoreDNS的上游但如果nodelocaldns自己还缓存着旧结果或者它的上游配置没有跟着变Pod端还是拿到旧结果。改这类集群的配置要同时查看kubectl get cm -n kube-system node-local-dns之类的配置。鉴别方法很简单看Pod的/etc/resolv.conf里nameserver指向的是不是169.254.20.10或者节点IP。如果是说明有nodelocaldns拦截。排错时一定要把这个组件考虑进来否则你会被“改配置没效果”这个假象折磨很久。坑二改的是ConfigMap但CoreDNS副本数不对。有些集群把CoreDNS设置成单个副本部署你在滚动重启的瞬间旧Pod停了新Pod还没就绪会导致短暂DNS中断。更糟的情况是如果你用的镜像有拉取问题新Pod一直Pending集群DNS就废了。所以重启前先看下副本数kubectl -n kube-system get deploy coredns如果是单副本建议先临时扩成两副本再重启kubectl -n kube-system scale deploy coredns --replicas2 kubectl -n kube-system rollout restart deploy/coredns kubectl -n kube-system scale deploy coredns --replicas原副本数虽然CoreDNS本身很轻量但“DNS服务重启期间断解析”这个窗口期放在生产环境完全是不可接受的。坑三上游DNS本身就不支持递归解析公网域名。前面说了内网DNS服务器不一定支持公网递归。如果你把上游全部改成内网DNS公网域名解析就会变慢甚至失败。CoreDNS会返回SERVFAIL。这类问题在配置前就该通过需求分析第2节规避掉但如果已经改了建议立即在Pod里测试一下公网域名kubectl run -it --rm dns-test --imagebusybox --restartNever -- nslookup www.baidu.com如果返回SERVFAIL或超时多半就是上游不支持公网递归。解决方法是加一个能解析公网域名的备用上游比如forward . 10.10.0.2 223.5.5.5让CoreDNS自己实现故障转移。4. 验证配置是否生效和问题排查4.1 在Pod内验证解析结果配置改了、部署重启了现在要验证。最老土也最直观的方法是起一个临时Pod进去测试kubectl run -it --rm test-dns --imagebusybox:1.36 --restartNever -- sh进去后依次跑nslookup kubernetes.default.svc.cluster.local nslookup gitlab.corp.local nslookup www.baidu.com这三条分别验证集群内部域名、内网上游域名、公网域名。三者都通说明配置基本没问题。有些人习惯用dig命令但busybox镜像里没有dig需要额外装bind-tools比较麻烦。nslookup在busybox里就有够用了。另外一个更精确的验证方法是直接在CoreDNS Pod里测试其对上游的解析能力kubectl -n kube-system exec coredns-pod-name -- nslookup gitlab.corp.local 10.10.0.2这条命令的意思是绕过CoreDNS自身的转发逻辑直接让CoreDNS容器请求10.10.0.2这台DNS。如果这里能解析说明网络通、上游DNS工作正常如果这里不行那问题出在CoreDNS配置之前——要么是上游IP写错了要么是网络策略拦截。4.2 常见问题速查表遇到问题别慌先对照下面这个表定位方向现象可能原因解决方式集群内Service名解析失败CoreDNS的kubernetes插件配置异常或API Server连接异常查看CoreDNS日志检查kubectl get ep -n kube-system kube-dns是否正常内网域名解析到公网IP内网域名前缀没匹配到corp.local zone走了默认上游检查Corefile里zone配置是否覆盖了所有内网后缀公网域名解析超时上游DNS不支持公网递归或上游IP不可达修改forward加入公网DNS作为备选比如forward . 10.10.0.2 223.5.5.5部分Pod解析正常、部分Pod超时nodelocaldns缓存或Pod网络策略差异检查是否有nodelocaldns检查Pod之间的网络策略CoreDNS Pod日志大量i/o timeoutCoreDNS到上游DNS的链路不稳定在节点上测试到上游的连通性检查安全组和MTU改了配置但长时间不生效没重启CoreDNS或reload插件不可靠执行kubectl rollout restart deployment/coredns -n kube-system4.3 排错链路三段法我给团队培训的时候习惯把CoreDNS排错拆成三段遇到问题按顺序排查效率很高。第一段Pod到CoreDNS。在问题Pod里执行cat /etc/resolv.conf nslookup kubernetes.default.svc.cluster.local如果/etc/resolv.conf里的nameserver不是kube-dns的ClusterIP可能是这个Pod用了dnsPolicy: Default或dnsPolicy: None。如果nameserver正确但解析失败可能是CoreDNS本身挂了或者网络组件Calico/Cilium的kube-proxy规则有问题。这一段的关键是确认“请求有没有到CoreDNS”。第二段CoreDNS到上游DNS。在CoreDNS Pod里直接请求上游kubectl -n kube-system exec coredns-pod-name -- nslookup gitlab.corp.local 10.10.0.2如果失败看返回的是connection timed out还是SERVFAIL。超时是网络不通SERVFAIL是上游已经响应但拒绝权威解析。这两个方向完全不同超时查网络和安全组SERVFAIL查上游DNS配置和授权情况。第三段上游DNS到最终权威源。如果上游正常但解析结果不对需要在上游DNS服务器本身上测试它解析目标域名的情况nslookup gitlab.corp.local 127.0.0.1上游DNS返回的不对有可能是它自身配置了错误记录也可能是它向上层权威DNS查询时失败。这一步通常需要和网络/基础架构团队协同处理。三段定位法不做多余猜测大多数DNS问题都能在半小时内定位到具体环节。5. 进阶多上游、分场景配置做一个稳的集群DNS5.1 分支解析内网域名和公网域名各走各路如果你的内网DNS不支持公网递归又想保留集群访问公网域名的能力那就得用多zone方案。看这个Corefile示例corp.local:53 { errors forward . 10.10.0.2 } office.example.com:53 { errors forward . 10.10.20.5 } .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance }这个配置的巧妙之处在于corp.local和office.example.com这两个内网zone各自指定了不同的内网DNS其他域名走默认的.:53区块使用公共DNS递归。看起来简单但在实际生产中非常实用。需要注意一个细节当一个内网域名既匹配corp.local又匹配更长的corp.local子域比如ops.corp.local时CoreDNS会按照最长后缀优先的规则选择最具体的zone。所以如果你有复杂的内网域名层级zone要写全避免走了错误的默认分支。5.2 缓存和DNSSEC别让上游处理太多重复请求CoreDNS本身有cache插件默认配置里一般写cache 30意思是正解析结果缓存30秒。这个值考虑的是“业务域名变更后能较快感知”和“降低上游压力”之间的平衡。如果你在调整上游DNS配置后发现DNS查询QPS很高、上游压力大可以适当调大比如cache 120。但注意如果你业务里有域名频繁变更的场景缓存时间太长会导致Pod一直拿到旧IP这比解析慢更讨厌。另外默认配置里通常不会开启DNSSEC。DNSSEC能防止DNS劫持但要求上游DNS支持且会增加解析延迟和故障复杂度。在内网环境里很多DNS服务器根本不支持DNSSEC强行开启会导致解析失败。我的建议是先确认上游支持情况再决定要不要开默认保持关闭即可。还有一个小技巧如果内网域名解析后的TTL非常短比如1秒而上游又没有做本地缓存CoreDNS的cache会让这1秒的TTL变成30秒实际效果是业务拿到的解析结果TTL被放大。这个在排查“为什么改了DNS记录Pod半天不生效”时很有用。5.3 从可观测性角度看集群DNS健康配置完上游DNS不是“能用”就行还得“看得见”。强烈建议把CoreDNS的监控指标接入Prometheus在Grafana里配个面板。CoreDNS会在9153端口暴露metrics前提是Corefile里配置了prometheus :9153。几个核心指标可以重点盯coredns_dns_request_count_total总请求量按zone和type区分能看出哪些域名查询最多coredns_forward_request_duration_seconds转发到上游的耗时如果这个值长期处于高位说明上游响应慢coredns_forward_healthcheck_failure_count_total上游健康检查失败次数如果持续增长说明上游不稳定coredns_dns_response_rcode_count_total按返回码区分重点关注SERVFAIL和NXDOMAIN占比。为什么要强调可观测因为DNS是“故障放大器”。上游慢100毫秒对单次请求来说还好但一个Pod里如果有多个域名查询并发一上去应用整体请求就会被拉垮。而这种问题在故障发生时业务方大概率报的是“接口变慢”不容易联想到DNS。有了指标和数据排查效率会高很多。我遇到过最典型的案例某个内网DNS服务器升级后开启了递归限速CoreDNS的forward请求超时率飙升但Pod内看起来只是“偶尔慢”。当时就是通过Grafana面板看到coredns_forward_request_duration_seconds从原来的10ms涨到800ms才定位到根因。结尾配置CoreDNS上游DNS这件事看着简单真到线上环境里细节还挺多的。我个人这些年比较大的体会是改Corefile之前一定要先理清自己网络环境里有哪些域名、该走哪台DNS不要想当然地填几个IP上去。特别是多网络互通、混合云部署的场景一个错误的上游配置会引发连锁故障。最后再分享一个小习惯每次改完CoreDNS配置我都会顺手建一条变更记录写明改了哪个zone、上游从什么改成什么、原因是什么。CoreDNS配置在K8s里就是个ConfigMap改起来太容易了但也因为太容易很多人改完就忘了。等到三个月后集群DNS出问题谁能记得当时为什么配了一个奇怪的上游IP记录留好排查时能省下不少时间。
返回列表