news 2026/9/12 2:12:17

Kubernetes域名访问实践:从Service到Ingress的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kubernetes域名访问实践:从Service到Ingress的完整指南

做过线上服务的人,多半都经历过这么一件事:服务已经跑在Kubernetes里了,Deployment也正常,Pod也Ready,可别人要访问它,总不能每次都用IP加端口。尤其是对外提供服务,大家习惯的是输入一个域名就直接打开页面。这篇文章就围绕一个很实际的场景展开:在k8s集群中,通过域名访问Deployment管理的Pod。我会把背后涉及的原理、几种实现方案、完整实操步骤和踩坑记录都梳理出来,适合刚接触k8s的运维和开发,也适合集群已经跑起来但对外访问还没打通的团队。

先说一个很容易混淆的点:k8s里的“域名访问”其实分两条路。一条是集群内部的Service DNS,也就是Pod之间通过服务名互相访问;另一条是集群外部用真实域名访问集群内的Pod。这两条路的流量路径完全不一样,很多人一开始被绕进去,就是因为没分清这套逻辑。下面我会从原理讲到实操,把这条路完整走一遍。

1. 先搞清楚:为什么“域名访问Pod”这件事值得单独聊

1.1 Pod的IP天生不稳定,域名才是长期可依赖的入口

在Kubernetes里,Pod是最小的调度单位,但它有个让人头疼的特性——IP不稳定。Deployment管理的Pod,只要发生滚动更新、节点故障、资源紧张导致驱逐、手动扩容缩容,Pod都会被重建,而重建之后的IP几乎必然变化。假设你辛辛苦苦配好一个负载均衡,把流量转发到某个Pod IP上,结果第二天Pod重启了,IP换了,所有转发全部失效。

这就是为什么Deployment这个控制器很重要,它保证了Pod始终维持在期望的副本数,而且每个Pod都是“临时工”,随时可以被替换。面对这种流动性极强的环境,我们不能拿IP当长期入口,必须找一个稳定的抽象层。这个稳定入口就是Service。Service通过标签选择器(selector)动态关联一组Pod,对外提供固定的虚拟IP和端口,无论背后的Pod怎么换,Service入口都不变。

很多刚接触k8s的人,会把k8s和Docker混为一谈。其实Docker解决的是“在一台机器上怎么跑容器”,而k8s解决的是“在多台机器上怎么统一调度和管理这些容器”。在这个场景里,Docker负责单机容器运行,k8s负责跨节点调度、服务发现和流量接入,两者是不同层面的东西。所以当你说“通过域名访问Deployment的Pod”时,本质上是在问:如何在k8s这个分布式平台上,为动态变化的Pod提供一个稳定的域名入口。

1.2 域名访问实际拆成两件事:集群内DNS解析和集群外流量接入

k8s集群内部天生带一套DNS服务,通常由CoreDNS实现。这套DNS负责解析Service域名,格式一般是服务名.命名空间.svc.cluster.local。比如你在default命名空间下建了一个名为my-service的Service,那么集群内任何一个Pod都可以通过my-service.default.svc.cluster.local访问到它,也可以直接用简写my-service访问同命名空间下的服务。

这套内部DNS解决了“服务之间的互相调用”,但它管不到集群外部。外部用户输入app.example.com要访问你的Pod,这个域名需要在公网DNS(或公司内部DNS)里能解析到集群入口,流量先打到集群入口,再经过路由规则转发到Service,最后Service分发到Pod。这个链路里,内部域名解析和外部域名解析用的是两套不同的DNS系统,两套配置各自独立,经常有人内部DNS调通了,外部还是访问不了,问题就出在外面那一段。

1.3 一句话说清本篇文章要解决的问题

我用一个流程图般的思路把目标场景描述清楚,大家先有个整体概念:

用户浏览器输入 app.example.com ↓ 公网DNS解析到集群入口(云负载均衡/节点IP) ↓ Ingress Controller 收到请求,根据域名和路径匹配规则 ↓ 转发到对应名称空间的 Service ↓ Service 转发到后端 Pod(容器实例)

最终达到的效果是:用户只记一个域名,剩下的路由、负载均衡、Pod自动伸缩都不用关心。这篇文章完整讲解的就是这个链路的搭建、配置和排障过程。

2. 方案选型:到底用哪种方式把域名接到Pod上

2.1 ClusterIP、NodePort、LoadBalancer 的定位与局限

Kubernetes里的Service有几种类型,各有各的用途。先说ClusterIP,这是默认类型,也是内部服务发现的主力。它只给Service分配一个集群内部可达的虚拟IP,外部流量进不来,只能在集群内部通过DNS或ClusterIP访问。如果你只想让A服务调用B服务,那ClusterIP完全够用,这也是集群内最推荐的方式,因为它不暴露端口,安全性高。

然后是NodePort。这种类型的Service会在每个节点的IP上开放一个指定端口(默认范围是30000-32767),外部可以通过任意节点IP:NodePort访问到Service。NodePort的好处是不依赖云厂商,裸机集群也能直接使用,但缺点也很明显:用户需要记住一堆随机端口,非常不雅观,而且节点IP一旦变化,端口入口也会跟着变,端口还容易冲突。项目多起来之后,端口和IP的对应关系就是一场灾难。

最后是LoadBalancer。这是云环境下的方案,Service创建后,云厂商会自动分配一个负载均衡器,并绑定一个公网IP或域名。外部流量经过云负载均衡器分发到各个节点上的NodePort。LoadBalancer体验比NodePort好,但意味着每个Service都要买一个负载均衡器,费用不低,而且同域名下的多个路径也做不到统一管理。如果你只有几套服务,LoadBalancer还能接受,一旦服务数量上来了,成本和管理复杂度都会飙升。

2.2 Ingress 才是域名访问的标准答案

如果说Service解决了Pod的不稳定IP问题,那Ingress解决的就是“如何用一个统一入口管理所有外部流量”。Ingress不是一种Service类型,而是一个独立的API对象,它的职责是定义“域名+路径”到Service的路由规则。真正处理流量转发的是Ingress Controller,比如NGINX Ingress Controller、Traefik、HAProxy Ingress等,它们会读取Ingress规则,将外部HTTP/HTTPS请求转发到对应的Service。

有了Ingress之后,多个服务可以共享同一个入口IP。比如app.example.com这个域名下,/api路径转发到A服务的Service,/web路径转发到B服务的Service,而另一个域名admin.example.com则整体转发到C服务的Service。这种模式最大的优势就是收敛:所有外部流量都进一个入口,证书、路由、分流策略都集中管理,不用每加一个服务就申请一个新负载均衡器。

而且Ingress支持TLS证书卸载。你在Ingress上配置好证书,外部请求通过HTTPS到达Ingress后,Ingress内部再用HTTP转发给后端Service。这样证书只需管理一份,不用每个Pod都塞证书,大幅降低了证书维护成本。

2.3 方案对比:一张表看懂四种方式

我把四种对外访问方式的核心差异整理成一张表,方便大家做技术选型时直接参考:

访问方式流量入口是否支持域名成本适用场景
ClusterIP集群内部虚拟IP仅集群内DNS服务间内部调用
NodePort所有节点的高位端口需要自己绑定节点IP+端口临时测试、小规模服务
LoadBalancer云厂商负载均衡器云厂商分配公网IP/域名云环境下的独立服务
Ingress统一入口IP/域名支持多域名+路径路由生产环境对外服务的标准方案

结论很明显:如果你要在生产环境里通过域名稳定访问Deployment管理的Pod,优先考虑Ingress。它把域名、证书、路由规则都集中到了入口层,运维成本最低,扩展性最好。

3. 完整实操:从Deployment到域名访问的落地过程

3.1 前置条件:集群、域名、Ingress Controller

在开始之前,需要确认三个前提条件。第一,你有一个可用的k8s集群,不管是裸机二进制部署、kubeadm搭建,还是云厂商托管集群都行;本文的操作不区分集群类型,命令都是一样的。第二,你有一个可以配置解析的域名,生产环境建议使用正规注册的域名;如果只是本地测试,可以用nip.io这类通配域名服务,它会自动把127.0.0.1.nip.io解析到127.0.0.1,省去购买域名的步骤。第三,集群里已经安装了Ingress Controller。

这里以NGINX Ingress Controller为例,安装方式很简单,官方提供了部署yaml。这里需要注意一个热词里很多人踩过的坑:国内网络环境下,从GitHub拉取镜像可能比较慢,需要提前把镜像换成可访问的镜像源地址。安装核心命令如下:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml

如果你的集群环境无法直接访问GitHub,可以把yaml下载到本地,修改其中的镜像地址部分,再apply。安装完成后,确认Controller是否正常运行:

kubectl get pods -n ingress-nginx

正常情况下,你会看到名为ingress-nginx-controller-xxxx的Pod处于Running状态。这里要特别提醒:这个Controller Pod默认通过hostNetwork: true运行,也就是说它会直接占用节点上的80和443端口。如果你的节点上已经有其他服务占用了这两个端口,Controller会启动失败,这是最常见的一个前置问题,后面排障章节会细说。

3.2 创建Deployment和Service

集群和入口就绪后,接下来就是搭建真正的业务服务。我这里用一个简单的nginx示例来模拟业务Pod,实际项目中替换成你自己的镜像即可。先创建Deployment:

apiVersion: apps/v1 kind: Deployment metadata: name: demo-web namespace: default spec: replicas: 2 selector: matchLabels: app: demo-web template: metadata: labels: app: demo-web spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80

这个Deployment会创建两个nginx Pod,负责跑实际业务。注意Pod的标签是app: demo-web,这个标签非常关键,因为后面Service要靠它找到这些Pod。执行创建命令:

kubectl apply -f deployment.yaml

确认Pod创建成功并处于Running状态:

kubectl get pods -l app=demo-web

接下来创建Service。为了让外部流量能够到达这组Pod,需要定义一个Service,并将它的selector指向app: demo-web。这里我用ClusterIP类型,因为外部的流量会先走Ingress,Ingress再转发到Service,所以Service本身不需要暴露到集群外:

apiVersion: v1 kind: Service metadata: name: demo-web-svc namespace: default spec: type: ClusterIP selector: app: demo-web ports: - port: 80 targetPort: 80

这里的port是Service对外监听的端口,targetPort是Pod容器实际监听的端口,示例中两个都是80。执行创建并验证:

kubectl apply -f service.yaml kubectl get svc demo-web-svc

你会看到Service被分配了一个ClusterIP地址。此时在集群内的任意Pod里,都可以通过demo-web-svc.default.svc.cluster.local或简写demo-web-svc访问到这个Service。这里的逻辑可以理解为:Service是一个固定的门牌号,Pod是不断更换的新住户,快递员(流量)只需要认识门牌号就行,不用关心住户是谁。

3.3 创建Ingress规则,把域名指向Service

Service已经就绪,现在到了最关键的一步——创建Ingress规则。Ingress要完成两件事:告诉Ingress Controller“当域名是什么的时候,请把流量转给哪个Service”。我以app.example.com为例:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-web-ingress namespace: default spec: ingressClassName: nginx rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-web-svc port: number: 80

这里面有几个关键点:ingressClassName: nginx告诉Ingress Controller使用哪个Ingress Class,对应你安装的NGINX Ingress Controller;host指定匹配的域名;pathType: Prefix表示前缀匹配,/会匹配该域名下的所有路径。执行创建:

kubectl apply -f ingress.yaml

创建之后,一定要检查Ingress配置是否正确:

kubectl get ingress kubectl describe ingress demo-web-ingress

describe的输出里,你会看到类似下面的信息:

Rules: Host Path Backends ---- ---- -------- app.example.com / demo-web-svc:80 (10.244.0.5:80, 10.244.0.6:80)

括号里的IP列表就是后端的Pod地址,如果这里没有显示Pod地址,说明Service的selector没有匹配到任何Pod,流量就会回到404或者503。这一步是排查问题的核心入口,后面还会展开。

3.4 域名解析与本地验证

Ingress规则创建完成后,Ingress Controller已经知道该把哪个域名的流量转到哪个Service,但外部用户要访问app.example.com,还需要把这个域名解析到Ingress Controller所在的节点IP,或者解析到Ingress Controller前面挂的负载均衡器IP。

首先找到Ingress Controller的对外访问地址。在裸机或自建集群中,Controller通常直接监听节点IP的80和443端口,所以域名应该解析到Controller所在节点的IP。在云环境里,你通常会给Controller Service配置LoadBalancer类型,此时域名要解析到云负载均衡器分配的公网IP或域名。查看方式:

kubectl get svc -n ingress-nginx kubectl get pods -n ingress-nginx -o wide

拿到IP之后,去你的DNS管理后台配置一条A记录,将app.example.com指向这个IP。配置完成后,可以用pingnslookup确认解析是否生效。

在正式切换线上流量之前,我建议先在本地做一次Host文件级别的验证,避免DNS还没生效时误判配置有误。修改本地/etc/hosts文件,添加一行:

123.123.123.123 app.example.com

然后执行:

curl http://app.example.com

如果你看到nginx的默认欢迎页面,说明整条链路已经打通:域名 → DNS解析 → Ingress Controller → Service → Pod。此时再把测试用的Host记录删掉,让DNS正式接管即可。

3.5 补充HTTPS证书配置

生产环境光有HTTP是不够的,浏览器会直接拦截不安全的访问。给Ingress配置HTTPS有两种常见做法:一是手动创建TLS Secret,二是在集群里装cert-manager自动签发证书。这里推荐第二种,因为证书到期自动续期,不用人肉记着什么时候去更新。

先安装cert-manager,安装方式官方文档写得很清楚,这里直接给部署命令:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

然后创建一个ClusterIssuer,告诉cert-manager去哪申请证书,以Let's Encrypt为例:

apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: you@example.com privateKeySecretRef: name: letsencrypt-prod-key solvers: - http01: ingress: class: nginx

接着修改Ingress配置,把证书签发注解和tls段落加进去:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: demo-web-ingress namespace: default annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: nginx tls: - hosts: - app.example.com secretName: demo-web-tls rules: - host: app.example.com http: paths: - path: / pathType: Prefix backend: service: name: demo-web-svc port: number: 80

重新apply之后,等待一两分钟,用下面的命令查看证书是否申请成功:

kubectl get certificate kubectl get secret demo-web-tls

一切正常的话,curl https://app.example.com就能直接访问,且证书可信。这个过程里cert-manager会通过HTTP-01方式校验域名归属,它会在Ingress里临时插入一条验证路径,所以前提是你的域名已经正确解析到Ingress Controller了,否则证书申请会一直报错。

4. 实战中常见的问题与排查技巧

4.1 页面一直404/503,先查这条链路

在实际操作中,域名通了但页面打不开是最常见的问题,而且404和503还不一样。404通常说明Ingress Controller收到了请求,但找不到对应的后端Service;503通常说明Controller找到了Service,但转发不到Pod,可能是Pod还没就绪,或者Service没有匹配到任何Pod。

拿到404,先执行kubectl describe ingress demo-web-ingress,看Backends那一行有没有Pod地址。如果没有Pod地址,问题基本出在Service的selector写错了,仔细检查Service里的spec.selector是否和Deployment的Pod模板标签一致。这里特别容易踩的坑是:Deployment里标签写的是run: demo,但Service里selector写的是app: demo,完全对不上,后端自然为空。

拿到503,进容器里验证一下Service是否能转发到Pod:

kubectl get endpoints demo-web-svc

如果Endpoints里有Pod IP,说明Service到Pod这一段没问题;如果没有,检查Pod是否处于Running状态,是否因为资源不足一直Pending,或者容器启动失败了。记住一条原则:Ingress只是路由,真正的流量断点往往在Service的selector或者Pod本身,所以排查时要沿着“Ingress → Service → Endpoints → Pod”这条链路一层层往下看,哪里断了修哪里。

4.2 集群内DNS解析异常的排查

内部DNS也是容易出问题的地方。有时候外部域名访问都通了,但Pod之间互相调用时却解析不了Service名,比如A Pod里执行curl http://demo-web-svc报错“could not resolve host”。这种情况基本可以锁死是CoreDNS的问题。

先看CoreDNS Pod是否正常运行:

kubectl get pods -n kube-system -l k8s-app=kube-dns

如果Pod正常,进入一个业务Pod里测试DNS解析:

kubectl run -it --rm test-pod --image=busybox -- sh nslookup demo-web-svc.default.svc.cluster.local

如果解析不了,看看Pod里的/etc/resolv.conf配置,正常情况下它应该指向kube-dns.kube-system.svc.cluster.local。还有一个很隐蔽的问题:如果集群的clusterDomain不是默认的cluster.local,那么Service的完整域名也会跟着变,需要检查集群初始化时的配置。二进制搭建的集群尤其常见这种定制化配置,查完就清楚了。

4.3 证书和Ingress配置的常见坑

证书这块踩的坑也不少。最典型的问题是cert-manager一直不生成证书,kubectl describe certificate里报错“Failed to verify domain”。这种情况先确认域名解析是否已经生效,因为HTTP-01验证必须让CA服务器能访问到你的域名,如果你还在用本地hosts测试,CA服务器根本访问不到,自然验证失败。

还有一个坑是Ingress的secretName和已有Secret重名。如果集群里已经存在一个同名的Secret,cert-manager可能不会覆盖它,导致证书一直是旧的。遇到这种情况,把旧的Secret删掉再重新申请即可。另外,如果你在一个Ingress里配置了多个域名,需要确保每个域名都写进tls的hosts列表,否则某个域名会一直报证书不匹配。

4.4 上线后性能与稳定性调整建议

链路通了之后,我会建议做几件锦上添花的事。第一,给Ingress Controller的Service配置外部负载均衡时,不要只挂一个节点,最好在云厂商负载均衡层做健康检查,后面挂多个Controller副本,避免单点故障。裸机集群可以通过kubectl scale deployment ingress-nginx-controller -n ingress-nginx --replicas=3扩容。

第二,静态资源类请求可以在Ingress层直接开启gzip压缩,减少带宽消耗。NGINX Ingress Controller默认支持,可以通过ConfigMap配置gzip-enabled: "true"。第三,如果你有大量读请求,可以考虑在Service后面加HPA自动扩容,Deployment配合HPA能实现在入口流量增加时自动拉起更多Pod,这对保持访问体验很有帮助。曾经遇到一个场景就是热词里提到的“2c4g的Pod能支撑多少并发”,这个没有标准答案,不同业务的资源消耗差异极大,但我建议先压测再上生产,别靠猜。

5. 踩坑几次之后的个人体会

文章写到最后,分享一下我自己的实际操作体会。早期我图省事,服务直接用NodePort暴露,端口从30001排到32000,后来服务一多,端口和IP的对应关系乱得像密码本,同事问我要某个服务的访问地址,我得翻半天表格。切到Ingress之后,所有服务都收敛到一个入口IP,用户只记域名就行,不管是新增服务还是调整路径,改一份Ingress配置就能搞定,运维体验提升了好几个档次。

还有一个很深的感触是:在k8s里排查问题,不要凭感觉跳步骤。很多人在浏览器打开域名发现404,第一反应是去改Ingress规则,改来改去还是404,最后发现是Service的selector少写了一个标签。我的习惯是严格按照“DNS解析 → Ingress → Service Endpoints → Pod运行状态”的顺序逐层验证,每一步都有对应的命令、有明确的输出,看到哪个环节不对再动手修,这样效率最高。最后再分享一个小技巧:每次改完Deployment、Service或Ingress的yaml,先用kubectl describe看一眼Events,比什么都管用,往往几秒钟就能定位问题。希望这篇文章能帮你在k8s域名访问这条路上少走一些弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 2:12:12

MODBUS RTU调试实战:从RS485物理层到CRC校验的完整链路排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:10:26

ABB ACS系列变频器GSDML文件:FENA模块接入TIA Portal的完整配置指南

简介:ABB ACS系列变频器GSDML文件,面向工业自动化现场运维与PLC系统集成工程师,用于解决ACS系列变频器在Profibus/Profinet等总线组态时的设备描述与参数配置问题。压缩包内共2个文件,包含1个XML格式的GSDML设备描述文件及1个txt格…

作者头像 李华
网站建设 2026/9/12 2:07:46

轻量IDE不是新工具,而是Java开发者的环境裁剪术

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:07:25

SpringBoot+Vue书评系统实战:从后端设计到部署上线

简介:这是一份基于SpringBoot与Vue开发的书评系统毕业设计源码包,面向计算机相关专业学生,可满足毕业设计、课程设计、项目演示及初期立项需求,也适合作为SpringBoot与Vue前后端分离项目的入门范例。项目代码已按前后端分离结构组…

作者头像 李华