首页 / 资讯中心 / 文章详情

Kubernetes服务暴露与CoreDNS域名解析全链路排障实战

Kubernetes服务暴露与CoreDNS域名解析全链路排障实战 ★ FEATURED ARTICLE
我们在日常维护 Kubernetes 集群时最绕不开的两件事就是服务暴露和域名解析。前者决定用户从哪儿能访问到你的应用后者决定应用内部怎么互相找到对方。这两件事看似基础但真正到了线上各种千奇百怪的问题总能把人折腾到怀疑人生。本文会从服务暴露的几种方式开始梳理再深入 CoreDNS 的解析机制最终落到一套完整的端到端实操最后附上我实际排查过的坑和常见问题的速查表。希望能帮你把这条链路彻底打通少走几趟弯路。1. 服务暴露体系整体拆解从 ClusterIP 到 Ingress 的选型逻辑Kubernetes 里 Service 的本质是一组 Pod 的访问入口它解决了 Pod 实例动态变化带来的 IP 不固定问题。理解 Service 的几种类型分别适用什么场景比背概念重要得多。我见过不少团队一上来就无脑上 Ingress结果内部调试时反而被七层网关的各种配置折腾得够呛其实很多场景用 ClusterIP 或 NodePort 就能干净利落地解决。1.1 四种 Service 类型的适用边界先说ClusterIP它是默认类型只能在集群内部访问提供一个稳定的虚拟 IP。ClusterIP 的价值在于给服务提供了一个固定的“内部电话分机号”不管后端 Pod 怎么扩容缩容调用方只需要记住这个 ClusterIP 或者它对应的 DNS 域名就行。很多新手误以为 ClusterIP 没用实际上微服务之间的互相调用绝大多数都应该用 ClusterIP 加域名的方式根本不需要暴露到集群外部。然后是NodePort它在每个节点上开一个高位端口默认 30000-32767把 Service 流量转发到后端 Pod。NodePort 适合什么场景临时调试、离线环境、或者是云厂商不提供负载均衡器的时候。它的缺点也很明显端口范围有限、每多一个 Service 就多占一个端口、而且需要自己管理节点列表。生产环境如果直接对外用 NodePort 暴露业务早晚会被端口管理和安全策略搞得头大。LoadBalancer是云厂商提供的能力它会自动创建一个云负载均衡器把流量导入到集群的 NodePort 上。这个类型依赖云平台如果你在裸金属集群上用就得自己实现对应的 Controller。用了 LoadBalancer 之后外部流量入口的 IP 变得可控但它还只是四层转发看不到 HTTP 的路径信息所以复杂的路由规则它管不了。最后是Ingress它不是一个 Service 类型而是一组 API 对象加上一个控制器比如社区常用的 NGINX Ingress Controller、云厂商自研的网关工作在七层。Ingress 的优势是可以用一条规则完成基于域名和路径的转发还能顺便做 TLS 终止、灰度分流等操作。只要你有多个 HTTP 服务需要对外Ingress 基本是标准答案。不过要注意Ingress 只是一个配置声明真正干活的还是那个控制器 Pod排查问题的时候别对着 Ingress 对象干瞪眼得去查控制器本身的日志和状态。1.2 服务暴露方案选择的三个判断标准选哪种暴露方式我总结了三个判断标准流量类型、访问方是谁、以及是否需要精细化路由。如果流量是内部服务间的 RPC 或数据库访问直接 ClusterIP 加域名不要画蛇添足如果只是临时开个端口给同事联调NodePort 最省事如果是有公网访问需求的 HTTP 服务且需要按域名或路径分流那 Ingress 是唯一合理的选择。LoadBalancer 一般在云环境且对四层转发有强需求时才用比如数据库、Redis 这类不想走七层网关的中间件。还有一个容易忽略的点流量链路越长排障越难。能走 ClusterIP 的就别绕 Ingress能让负载均衡器直接打到 Service 的就别再加一层 NodePort。我在一个生产集群里见过客户端 - 云负载均衡 - NodePort - ClusterIP - Pod 这么一条五跳链路每次延迟抖动都要花半天时间定位后来简化成客户端 - Ingress - Service - Pod 才消停。2. CoreDNS 域名解析机制详解服务发现到底是怎么转起来的Kubernetes 集群内部的服务发现默认就是靠 CoreDNS 这个 Pod 跑的。它监听在 kube-system 命名空间下通过 ConfigMap 管理配置。ClusterIP 类型的 Service 创建之后CoreDNS 会自动记录一条 A 记录格式是服务名.命名空间.svc.cluster.local这样同一个命名空间里的服务可以直接通过服务名互相访问跨命名空间就要带上命名空间名。2.1 完整域名规范与搜索域兜底逻辑我们先记住最完整的域名格式service-name.namespace.svc.cluster-domain默认的集群域名是cluster.local。假设你有个服务叫order-api跑在prod命名空间下那么完整域名就是order-api.prod.svc.cluster.local。实际使用中你会发现在 Pod 里面直接curl order-api也能通这靠的是 Pod 的/etc/resolv.conf里配置的 search 域。默认情况下Kubernetes 会给每个 Pod 注入类似这样的配置search default.svc.cluster.local svc.cluster.local cluster.local nameserver 10.96.0.10 options ndots:5ndots:5的意思是如果查询的域名里点号数量小于 5就先走 search 域拼接否则先走绝对域名查询。这就是为什么你curl order-api时实际解析顺序是先查order-api.default.svc.cluster.local再查order-api.svc.cluster.local最后才查order-api.cluster.local。了解了这个机制以后遇到解析慢或者解析到错误地址的情况第一反应就该去查 search 域配置有没有被某些自定义的 resolv.conf 覆盖掉。2.2 无头服务的特殊解析行为返回全部 Pod IP无头服务Headless Service是指clusterIP: None的 Service。它不会有一个固定的虚拟 IPCoreDNS 会直接返回后端所有 Pod 的 IP 列表。这个特性非常有用比如状态ful应用如数据库集群需要知道所有对端实例或者你希望客户端自己做负载均衡时就可以把 Service 设置为无头模式。我实际用过的一个场景是自建的一个分布式存储组件。它需要每个节点都知道集群里其它节点的真实 IP如果用普通 ClusterIP所有客户端都打到同一个虚拟 IP 上节点间直连就失效了。换成无头服务后curl一下服务域名直接返回三个 Pod IP组件启动时拿这个列表去建集群问题瞬间解决。这里也要注意无头服务返回的是端点列表不是稳定单 IP如果你只是想在 Kubernetes 里做个简单的“数据库主从”这种单写多读架构无头服务配合外部指定端点的用法要小心因为客户端不一定具备自己选主的能力容易把流量打到一个不可用的副本上。2.3 ClusterIP 与 Endpoints 的联动关系Service 和 Pod 之间并不是直接绑定的中间隔着一个 Endpoints 对象或者更现代的 EndpointSlice。当你创建一个 Service并且它通过 selector 选中了某些 Pod那么控制面会自动维护 Endpoints 列表把所有匹配 Pod 的 IP:Port 填进去。CoreDNS 的解析只是把域名叫到 Service 的 ClusterIP真正把流量分发到后端 Pod靠的是 kube-proxy 维护的转发规则。所以排查链路时可以这样分层域名解析只负责告诉你“这个服务叫啥IP 是啥”剩下的路由和负载均衡是另一层的事。如果 Curl 域名通但业务访问失败大概率不是 DNS 的问题而是 Endpoints 里压根没有可用 Pod或者 kube-proxy 的规则没刷新。3. 端到端实操从 Deployment 到 Ingress 域名访问全流程落地理论讲再多不如真刀真枪跑一遍。这一节我会用一个模拟项目假设一个内部订单查询服务带你从头部署一套完整链路。先说明环境我用的是某云厂商托管的 Kubernetes 集群版本在 1.28 左右Ingress Controller 用的社区版 NGINX Ingress。3.1 基础环境与镜像准备我建议你准备一个简单的 Web 服务镜像只要能响应 HTTP 请求就行。这里我随便用一个现有的测试镜像比如hashicorp/http-echo它可以通过-text参数返回自定义内容非常适合验证链路。先创建一个命名空间保持环境隔离kubectl create namespace demo然后把我们的服务部署起来Deployment 定义如下apiVersion: apps/v1 kind: Deployment metadata: name: order-api namespace: demo labels: app: order-api spec: replicas: 2 selector: matchLabels: app: order-api template: metadata: labels: app: order-api spec: containers: - name: http-echo image: hashicorp/http-echo:latest args: - -listen:8080 - -textHello from order-api ports: - containerPort: 8080写入文件后执行kubectl apply -f deployment.yaml这里要提醒一点生产环境千万别用latest镜像我这里只是图方便做验证。实际部署一定要锁定具体版本号否则哪天镜像被重新打 tag你的集群就莫名其妙变了行为。3.2 创建 ClusterIP 服务并验证内部域名解析接下来创建对应的 Service我们选择 ClusterIP 类型apiVersion: v1 kind: Service metadata: name: order-api namespace: demo spec: selector: app: order-api ports: - port: 8080 targetPort: 8080应用之后先检查 Service 和 Endpoints 状态kubectl -n demo get svc order-api kubectl -n demo get endpoints order-api正常情况下可以看到 Endpoints 里有两个 Pod IP。现在进入集群里任意一个 Pod 验证域名解析kubectl -n demo run curl-test --imagecurlimages/curl --rm -it --command -- sh进去之后执行curl http://order-api.demo.svc.cluster.local:8080 curl http://order-api.demo.svc.cluster.local:8080第一次 curl 可以故意写成完整域名第二次可以简写成order-api。如果都在同一命名空间内你会发现curl order-api:8080也能顺利拿到响应。这就说明 CoreDNS 的 search 域兜底逻辑生效了也是我们日常最常用的访问方式。3.3 暴露到集群外部NodePort 方式快速调试如果别人想从集群外临时访问这个服务最省事的方式是改成 NodePort。只需要在 Service 里加一条spec: type: NodePort ports: - port: 8080 targetPort: 8080 nodePort: 30080节点端口固定写死的好处是方便配防火墙白名单但要注意别和其他 Service 冲突。然后就可以通过http://任意节点IP:30080访问服务了。这种方式验证完记得马上删掉或者改回 ClusterIP避免线上环境裸奔。关于 NodePort 有一个坑节点 IP 变化时访问入口也就变了而且 kube-proxy 的转发依赖节点的网络规则如果节点之间有防火墙策略可能导致部分节点通、部分节点不通。所以我在调试阶段会用但正式暴露业务时绝不依赖它。3.4 用 Ingress 实现域名和路径维度分流搭建 Ingress 之前先确认集群里已经安装 NGINX Ingress Controller。你可以用kubectl -n ingress-nginx get pods查看。没有安装的话直接用 Helm 安装也行这里不再赘述安装步骤。然后创建一个 Ingress 资源apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: order-api-ingress namespace: demo annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx rules: - host: order.demo.local http: paths: - path: /order pathType: Prefix backend: service: name: order-api servicePort: 8080这里我特意设置了host: order.demo.local和路径/order。生产环境中这个域名通常会替换成你的真实业务域名同时还要在 DNS 服务商那边把域名解析到 Ingress Controller 的负载均衡 IP 上。应用到集群后查看 Ingress 状态kubectl -n demo get ingress order-api-ingress如果有 ADDRESS 字段说明 Ingress Controller 已经为它分配了入口。现在在本机/etc/hosts里临时加上一行把order.demo.local解析到 Ingress Controller 的外部 IP然后浏览器访问http://order.demo.local/order就能看到之前部署的http-echo返回“Hello from order-api”。如果访问不通过别急着怀疑 Ingress 配置先分三步排查# 1. 查看 Ingress Controller 的 Pod 日志 kubectl -n ingress-nginx logs -f deployment/ingress-nginx-controller # 2. 确认 Service 的 Endpoints 正常 kubectl -n demo get endpoints order-api # 3. 直接进入 Ingress Controller Podcurl 一下后端 Service 的 ClusterIP kubectl -n ingress-nginx exec -it controller-pod-name -- curl http://order-api.demo.svc.cluster.local:8080很多时候问题都出在后端 Service 上Ingress 规则本身是好的。3.5 定制 CoreDNS 配置的补丁技巧CoreDNS 默认配置对大多数场景已经够用但偶尔也需要调整。最常见的需求是自定义域名解析到集群外地址。比如你想让集群内的服务访问internal.company.local时直接解析到某个内网负载均衡 IP不用每次改代码。这时就可以修改 kube-system 命名空间下的corednsConfigMap。操作方法是编辑 ConfigMapkubectl -n kube-system edit configmap coredns添加 hosts 配置段.:53 { hosts { 10.10.0.5 internal.company.local fallthrough } ... }注意fallthrough的意思是如果 hosts 里没命中就把请求继续往下面别的插件丢避免干扰 Kubernetes 自身的服务发现。改完之后 CoreDNS 会自动热加载配置不需要重启但建议稍等几秒让配置生效。这种改写方式解决了我不少实际痛点。某次数据中台要对接一个老旧的内部系统它只在内网 DNS 里有记录但 Kubernetes 集群的 Pod 用的是 CoreDNS解析不到外部域名我们就在 CoreDNS 里加了一条 hosts 映射马上整条链路就通了改代码和重建镜像都省了。4. 服务暴露与域名访问的实战问题排查实录这部分是本文的重头戏我把这些年踩过的坑和总结出的排查方法整理成一套速查表和几个典型案例。遇到问题别慌先按链路分层排查通常都能快速缩小范围。4.1 经典故障链路定位思路我习惯把链路拆成四层客户端到入口、入口到 Service、Service 到 Pod、Pod 内应用是否正常。任何一层的故障都会表现为“访问不通”但具体现象有差别。现象可能原因排查命令域名解析超时CoreDNS 异常或网络策略阻断 UDP 53kubectl -n kube-system get pods -l k8s-appkube-dns解析正常但连接拒绝Pod 没起来或端口没监听kubectl -n demo get endpoints order-api访问时通时不通kube-proxy 规则过期或后端 Pod 部分异常kubectl -n demo get pods -o wideIngress 返回 404路径配置错误或者后端服务名不对kubectl -n ingress-nginx logs -f ...跨命名空间访问失败search 域没覆盖到或服务名写错kubectl exec -it pod -- cat /etc/resolv.conf平时遇到问题我第一步永远是看 Pod 状态和日志第二步看 Endpoints第三步才跳到 DNS。别一上来就dig很多时候 DNS 是被冤枉的。4.2 案例一Pod 内无法解析 Service 域名某次同事反馈说他的 Pod 里curl http://order-api:8080一直卡住超时。我先看了/etc/resolv.conf发现 nameserver 指向的是10.96.0.10这是 CoreDNS 的 ClusterIP看起来正常。接着检查 CoreDNS 本身的运行状态发现有两个副本但有一个一直 CrashLoopBackOff查看日志后确定是配置文件中某一行格式报错导致整个进程起不来。解决办法很简单修正 CoreDNS 的 ConfigMap把可能残留的多余配置块清理掉。这里要提醒大家改 CoreDNS 配置前最好先备份原 ConfigMap因为一个语法错误就可能导致集群所有 Pod 都无法解析域名影响面非常大。我在测试环境都翻过车更别提生产了。4.3 案例二Ingress 配置正确但一直返回 503这是个很有意思的问题。Ingress 配置看起来毫无问题域名也能解析到 Controller但请求进去后就返回 503。查了后端 Pod状态都是 RunningEndpoints 也正常甚至直接在 Controller Pod 里 curl 后端 Service 都通。那问题出在哪最后发现是后端服务监听的是localhost:8080而不是0.0.0.0:8080导致 kube-proxy 把流量转到 Pod IP 的 8080 端口时被拒绝。这个问题非常经典本地调试时服务跑得好好的部署到 Kubernetes 就出问题基本都是监听地址写死的坑。写代码时一定要用0.0.0.0监听而不是 127.0.0.1连带容器健康检查的探测地址也要注意。4.4 案例三解析到了已删除的 Pod IP集群中某个应用间歇性访问失败排查日志发现请求偶尔会打到一台已经不存在的节点 IP 上。后来发现是旧 Pod 被删除后由于某种原因对应的 Endpoints 没有及时更新导致 kube-proxy 还在往旧地址转发流量。解决办法是手动删除一下异常 EndpointSlice或者重启 kube-proxy 让它重新同步。另外也建议检查一下集群中的 Pod 是否是优雅删除的如果大量 Pod 被强杀控制面处理 EndpointSlice 更新的速度可能跟不上。这种问题在节点异常宕机时特别容易暴露最好给关键服务加上 PodDisruptionBudget减少非优雅删除的概率。4.5 案例四跨命名空间访问总是解析到错误记录某个服务在 A 命名空间里叫user-api在 B 命名空间里也有一个同名服务代码里没有写全命名空间直接curl user-api结果请求总是跑到默认命名空间的那个服务上。这个不算 bug而是 search 域机制的预期行为同命名空间的服务名有更高优先级。解决方案有两个一是调用方和服务方保持同一命名空间二是所有跨命名空间访问一律写完整域名例如user-api.b.svc.cluster.local。别嫌全域名长它至少不会让你在微服务多了之后彻底迷失。4.6 附五条新人最容易踩的配置坑第一Service 的port和targetPort搞混。port是 Service 对外暴露的端口targetPort是容器实际监听的端口两个可以不相等但如果你在 Service 里只写了port那默认targetPort和它一样此时如果容器实际监听 8081 而你写成 8080流量必挂。第二Ingress 和 Ingress Controller 的版本兼容。新的networking.k8s.io/v1要求 Ingress 里必须写ingressClassName而老版本集群可能用的是 annotation 标识如果配错了 Controller 根本不会加载规则。第三忘记配置 requests 和 limits。虽然不影响连通性但 Pod 被 OOMKilled 后频繁重启会让 Endpoints 忽有忽无间接引发访问异常。第四健康检查路径返回非 200。kube-proxy 之后 Pod 是否 Ready 决定了流量能不能过去如果 ReadinessProbe 一直失败Pod 虽然 Running 但不会出现在 Endpoints 里服务自然就“间歇性”不可用。第五在 ConfigMap 里乱加rewrite或者其他插件导致 CoreDNS 启动失败。改 CoreDNS 一定要谨慎它是全局组件一挂全集群解析瘫痪。任何改动建议先在测试集群验证并且改完马上观察 CoreDNS Pod 是否正常运行。5. 给新手的落地建议与个人经验补充我见过很多团队刚开始容器化时每天就在“服务访问不通”这件事上反复摩擦。其实核心原因无非就那几类监听地址没写对、命名空间不统一、Ingress 规则写错、CoreDNS 配置被改坏。只要把这几个点都提前排查一遍基本能解决 90% 的问题。如果让我给一个最实用的建议那就是从第一天就把完整域名写进代码和配置。别看service.namespace.svc.cluster.local很长但它可以让你在后期引入多集群、多命名空间时省下大量时间。简写域名虽然方便但它隐式依赖 Pod 的 search 域一旦你把应用迁移到别的环境或者自定义 DNS 配置这些隐式依赖就会变成一颗颗雷。另外养成一个习惯准备一套验证链路最小化的脚本。比如创建两个测试 Namespace各部署一个 http-echo互相用完整域名和简写域名各curl一次再把测试结果固定到团队的文档里。这样每次网络配置有改动了跑一次脚本就能知道基础链路是否正常省掉大量重复排障时间。我个人实际维护集群时的体会是域名访问这件事最怕的不是不会配而是配置太多层、太多隐式依赖。能少一层就少一层能写全域名就写全域名能锁定版本号就不要用 latest。这套原则帮我躲过了不知道多少个大半夜的告警电话。你在自己的环境里按这套思路把基础打牢后续无论做多复杂的微服务治理都不会因为最底层的服务暴露问题绊住脚。
阅读完成 · 觉得有帮助?
咨询建站