Kubernetes服务暴露与CoreDNS域名解析:从原理到排障实战

发布时间:2026/10/20 15:01:44
Kubernetes服务暴露与CoreDNS域名解析:从原理到排障实战 Kubernetes里面跑服务最容易被低估的两块坑就是服务怎么暴露DNS怎么解析。十次排查疑难网络问题有七次跟这两件事有关。很多同学能把Deployment和Service文件写得规规矩矩集群里apply一下就发现应用跑起来了可一旦要对外提供访问或者要用域名把服务调通就开始抓瞎。服务暴露解决的是外部怎么找到你CoreDNS解决的是内部怎么按名字找到你。两者分开看都不算难但连在一起就很容易踩坑域名解析到了Service的ClusterIP却连不上后端NodePort端口明明开了外部就是访问不了Ingress配好了域名curl过去永远404。这篇文章把我自己在生产环境里调试这层链路时用到的方法、踩过的坑、以及验证过的方案整理出来适合正在做集群运维、需要调试服务访问链路的同学也适合那些被域名不通问题折磨了一天还没头绪的人。1. 服务暴露这件事为什么比想象中复杂1.1 先搞明白Service提供的四种访问入口Kubernetes里的Pod IP是动态的Pod一重建IP就变你不能让调用方一直去跟一个会变的IP打交道。Service的作用就是给一组Pod提供一个稳定的访问入口这个入口可以是一个虚拟IP也可以是Node上的一个端口或者是一个外部负载均衡器。Service类型主要有四种各自的定位和适用场景完全不同Service类型访问方式适用场景典型问题ClusterIP集群内部虚拟IP服务间调用、内部API集群外部无法直接访问NodePortNodeIP 静态端口临时对外暴露、小规模场景端口范围受限多服务难管理LoadBalancer依赖云厂商LB公有云上的生产环境绑定云资源本地环境无法使用ExternalNameDNS别名把外部服务伪装成集群内服务只能做域名映射不能做端口转发很多人上来就选NodePort因为它最简单改一行就能让外部访问。但等服务数量超过十个就发现NodePort方案几乎无法维护端口经常冲突健康检查不好做HTTPS证书也没地方挂。LoadBalancer在本地开发环境根本没法用因为本地没有LB资源可创建。所以在我的实践经验里ClusterIP是默认选择所有集群内部服务之间通信都用它需要对外暴露的业务上面加一层Ingress把域名、路径、证书都统一收口到Ingress里管理NodePort通常只在调试、应急、或者没有Ingress Controller的简单环境里用。1.2 从实际场景决定用哪种暴露方式选型不能靠感觉哪个好用要回到业务场景去分析。我见过不少团队把Ingress当成唯一解法结果内部服务之间也走Ingress绕了一圈让流量在外网打转既增加延迟又扩大暴露面。我的建议是分三类来思考。第一类是纯内部调用Pod之间、微服务之间的接口调用用ClusterIP加Service域名让CoreDNS去做服务发现第二类是面向集群外部用户的服务比如Web应用、开放API用Ingress统一入口一张域名表管所有服务第三类是调试场景临时把某个Pod端口映射出来看看效果用NodePort最直接。这里还有一个容易忽略的细节Ingress本身不是Kubernetes自带的功能它只是一个资源对象真正干活的是Ingress Controller。集群里必须有nginx这类Ingress Controller在运行Ingress规则才会生效。如果没有Controller你apply一个Ingress下去日志里不会有任何报错它就像被默默忽略了一样这也是很多初学者百思不得其解的地方。2. CoreDNS是怎么让服务名变成IP的2.1 Service域名的标准格式与解析规则Kubernetes集群内默认安装了CoreDNS它负责把Service域名解析成ClusterIP。Service域名的标准格式是service-name.namespace.svc.cluster-domain集群域名默认是cluster.local所以一个叫demo-svc、部署在default命名空间下的服务完整域名是demo-svc.default.svc.cluster.local。在同一个命名空间里你可以偷懒只写demo-svcCoreDNS能根据Pod的search域补齐。但跨命名空间访问时必须写完整域名比如从pay命名空间访问default命名空间的服务就得写demo-svc.default.svc.cluster.local。无头服务headless service的解析规则和普通Service不一样。普通Service解析返回ClusterIP无头Service直接把所有后端Pod IP返回给调用方格式是pod-podName.service-name.namespace.svc.cluster.local。如果你在业务里用到StatefulSet每个Pod都有稳定域名很多数据库集群就是靠这个特性组建的。2.2 三种DNS策略和search域的影响Kubernetes中的Pod默认使用ClusterFirst策略意思是Pod内DNS查询优先交给CoreDNS处理CoreDNS解析不了的域名再转发给上游DNS。除此之外还有Default和NoneDefault直接使用Node节点的/etc/resolv.conf配置None则完全自定义dnsConfig。实际调试时最影响理解的是Pod内/etc/resolv.conf里自动注入的search域长这样search default.svc.cluster.local svc.cluster.local cluster.local nameserver 10.96.0.10 options ndots:5这里的10.96.0.10是CoreDNS的Service ClusterIPPod内所有DNS查询都发给它。ndots:5表示域名里点的数量少于5个时会先尝试拼接search域。这意味着你访问demo-svc实际执行的是demo-svc.default.svc.cluster.local.、demo-svc.svc.cluster.local.、demo-svc.cluster.local.、demo-svc.依次查询。带来的副作用是如果你在代码里访问一个外部短域名比如api.internal.com只有3个点它也会先拿default.svc.cluster.local去拼接一次白白多几次DNS查询。所以代码里访问外部域名尽量写成带最后一个点号的FQDN形式能省掉不必要的search域尝试。2.3 用Corefile自定义域名解析CoreDNS的核心配置在一个叫Corefile的配置块里存放在kube-system命名空间下名为coredns的ConfigMap中。默认配置里有一段kubernetes cluster.local in-addr.arpa ip6.arpa说明只会解析cluster.local这个域。如果业务里需要让集群内Pod访问一个自定义域名比如内网某个中台系统platform.internal不需要改每台Pod的hosts文件直接在CoreDNS的ConfigMap里加hosts插件配置就行apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system data: Corefile: | .:53 { errors health kubernetes cluster.local in-addr.arpa ip6.arpa { pods verified fallthrough in-addr.arpa ip6.arpa } hosts { 10.0.0.5 platform.internal fallthrough } forward . /etc/resolv.conf }修改ConfigMap后需要滚动重启CoreDNS的Pod才能生效命令是kubectl -n kube-system rollout restart deployment coredns。这里注意fallthrough关键字必须写上否则自定义域名查不到后会直接返回NXDOMAIN不会继续去kubernetes插件或上游DNS查。我最初就是漏了这个导致集群原有Service域名解析全部失败。3. 从零做一遍服务暴露与域名访问完整实验3.1 准备一个示例应用和一个ClusterIP服务理论讲再多不如亲手跑一遍。我用一个最简单的Nginx应用做演示先创建Deployment再创建一个ClusterIP类型ServiceapiVersion: apps/v1 kind: Deployment metadata: name: demo namespace: default spec: replicas: 2 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: containers: - name: demo image: nginx:1.25 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: demo-svc spec: selector: app: demo ports: - port: 80 targetPort: 80 type: ClusterIP保存为demo.yaml后执行kubectl apply -f demo.yaml。Service的selector必须和Pod的标签匹配这里都是app: demo。查看Service详情时第一件事是看Endpoints列表是否有真实Pod地址kubectl get svc demo-svc kubectl get endpoints demo-svc如果Endpoints为空说明Selector没匹配上Pod这时候Service本身是正常的但你访问它一定超时。我建议养成习惯Service创建完之后顺手看一眼Endpoints能少踩很多坑。3.2 用NodePort把服务暴露到集群外部接下来把服务暴露到外部。我不建议把原有Service的类型直接改成NodePort而是新建一个独立Service这样内网访问和外部访问互不影响。NodePort端口的合法范围默认是30000-32767可以手动指定也可以让K8s随机分配apiVersion: v1 kind: Service metadata: name: demo-nodeport spec: selector: app: demo type: NodePort ports: - port: 80 targetPort: 80 nodePort: 30080apply之后外部访问地址就是任意一台Node节点的IP:30080。本地起实验环境时只有一个节点直接访问本机IP即可。多Node场景下所有Node的30080端口都会转发到Pod前提是基础网络放行了这段端口。我在云环境里遇到过几次NodePort端口明明监听正常外部就是访问不通十有八九是安全组没放行。所以配置完记得先在集群内节点上curl一下localhost:30080排除掉服务本身的问题再去找网络放行的问题。3.3 用Ingress给服务挂上域名生产环境里想让多个服务共享同一组对外IP和端口就得用Ingress。先把Ingress Controller部署好然后创建一条Ingress规则apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-ingress spec: ingressClassName: nginx rules: - host: demo.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-svc port: number: 80Ingress里的serviceName指向ClusterIP服务不需要指向NodePort服务。Ingress Controller收到请求后自己会直接转发到Service对应的后端Pod。这一点很多人理解反了以为Ingress还要经过NodePort实际上Ingress Controller位于集群内直接访问Service ClusterIP就能完成转发。测试时先把demo.example.com这个测试域名解析到Ingress Controller所在节点的IP上最简单的办法是改本机的hosts文件然后浏览器访问http://demo.example.com。如果返回Nginx默认页面说明整条链路已经通了。3.4 验证整个访问链路链路通不通不能只测一次。我习惯从内到外分三步验证先在Pod内部用短域名访问一次确认CoreDNS解析正常再在节点上通过NodePort访问一次确认kube-proxy转发正常最后在外部通过Ingress域名访问一次确认入口规则生效。Pod内访问短域名的方法kubectl run -it --rm --imagebusybox:1.28 curl-test --restartNever -- sh进入容器后执行wget -q -O- demo-svc.default.svc.cluster.local或者直接短域名demo-svc。之所以用busybox:1.28是因为新版busybox砍掉了nslookup功能调试DNS会不太顺手。这个镜像版本在很多排障场景下都好使。4. 一次DNS查询的背后数据包是怎么走的4.1 从应用进程到CoreDNS的解析流程当Pod里的应用访问demo-svc.default.svc.cluster.local时流程是这样的应用发起解析请求进程读取本地/etc/resolv.conf把查询发送给nameserver也就是CoreDNS的ClusterIP。CoreDNS收到请求后会看这个域名是否属于cluster.local域如果是就通过kubernetes插件向kube-apiserver查询对应Service的ClusterIP和服务端口然后把结果返回给Pod里的应用。这里有一个关键细节CoreDNS返回的不是Pod IP而是Service的ClusterIP。应用再去连接这个ClusterIP流量才会被kube-proxy转发到后端的某个Pod上。所以DNS解析成功不代表访问成功解析到ClusterIP只是第一步后面还有一层端口转发的逻辑要验证。我见过一个案例应用日志里一直报域名解析失败但手动在容器里nslookup这个域名明明能解析成功。后来发现应用自己的DNS客户端配置了缓存缓存了旧的错误结果。所以排查问题时优先清缓存、重启进程而不是反复修改CoreDNS配置。4.2 集群内部的流量转发链路ClusterIP的流量转发依赖kube-proxy。在Linux内核的iptables或ipvs规则中访问ClusterIP的流量会被改写目标地址转发给某个健康的后端Pod。ipvs模式下这种转发是轮询的性能更好iptables模式下每条规则都要逐条匹配Service数量几百个时会有明显的匹配成本。从这个链路可以看出任意一环出问题都会表现为域名解析正常但请求失败Service的Endpoints为空、Pod健康检查失败、kube-proxy规则异常、网络插件策略拦截每一种都可能。网上经常有人遇到类似问题后就到处问为什么连不上实际上最该做的第一件事是kubectl get endpoints先确认后端Pod列表是否存在。4.3 外部域名和内部域名为什么不能混着配有个危险配置我特别想提醒大家不要在CoreDNS里把互联网域名硬解析成内网地址。比如为了测试某个外部API在Corefile的hosts里把api.real-service.com指向了内网IP当时看起来一切正常但等到这个外部服务上线后全集群的Pod仍然会命中这个hosts记录导致线上事故。Corefile里的hosts配置优先级很高一旦写上域名映射集群内所有Pod都会采用这条记录不会再往上游DNS去查。如果确实要模拟某个外部域名建议用独立的测试域例如fake-api.internal.test不要污染真实域名。这也是一个值得写进规范的好习惯。5. 排障实录七天里踩过的那些坑5.1 常见故障现象速查表现象大概率原因排查动作Pod里nslookup Service名超时CoreDNS副本挂了或网络原因kubectl -n kube-system get pods -l k8s-appkube-dns解析返回NXDOMAINnameserver上CoreDNS配置缺失、搜索域不对检查Corefile、查看resolv.conf域名解析正常但curl超时Service Endpoints为空kubectl get endpoints svcService IP存在但通过NodePort不通节点安全组没放行在节点上curl localhost测试Ingress返回404路径不匹配或Controller未部署查看Ingress Controller日志短域名可访问完整域名失败search域配置被覆盖cat /etc/resolv.conf这张表是我平时排查顺序的浓缩版先确认CoreDNS状态再确认Pods的resolv.conf然后检查Service Endpoints最后才怀疑网络规则。5.2 排查DNS解析问题的标准流程如果遇到Pod之间解析失败我会按下面的顺序执行每一步都有明确产出不会盲目操作。第一步确认CoreDNS自身状态kubectl -n kube-system get pods -l k8s-appkube-dns kubectl -n kube-system logs deployment/coredns --tail30第二步用一个临时Pod做端到端测试避免在生产容器里装额外工具kubectl run -it --rm --imagebusybox:1.28 dnstest --restartNever -- sh nslookup demo-svc.default.svc.cluster.local第三步检查Service和对应的Endpointskubectl get svc -n default kubectl get endpoints -n default第四步检查CoreDNS配置是否被改坏kubectl -n kube-system get configmap coredns -o yaml这套流程走下来90%的DNS问题都能定位到具体环节。如果CoreDNS日志里有大量SERVFAIL通常意味着上游DNS不可达检查Corefile里forward .指向的目标地址是否正常。5.3 我坚持的几个好习惯做服务暴露和域名访问调试久了我总结出三个值得固化的习惯。一是所有Service创建完立即看Endpoints不把隐患留到后面二是所有域名规则改动都走版本化管理CoreDNS的ConfigMap改动必须有记录否则改坏了很难回滚三是在代码和配置里统一用FQDN格式避免依赖search域补全既减少DNS查询次数也让日志里的访问目标更加清晰。另外CoreDNS在高并发场景下容易被流量打穿我自己就遇到过Pod内部频繁调用导致CoreDNS容器CPU报警的情况。缓解办法是调整CoreDNS副本数和资源限制当前生产上常用NodeLocal DNSCache方案把DNS缓存下放到每个Node减少上游请求压力。这个方案配置起来有点工作量但对大规模集群非常值。最后再分享一个小技巧遇到奇怪的域名访问问题先看Pod里/etc/resolv.conf是怎么写的再在Pod里手动解析一遍目标域名配合CoreDNS日志交叉比对。大多数问题不是配置本身错了而是你还没看到真正的现象。服务暴露和域名访问这条链路本质上就是一条从客户端到Pod的路径把每一跳都验证一遍问题自然无处可藏。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询