Kubernetes 核心网络枢纽:Service 与 Ingress 完全指南
Kubernetes 核心网络枢纽:Service 与 Ingress 完全指南
一、开头:Pod 会"消失",那怎么访问它?
写 [[有关docker Namespace原理]] 的时候,我觉得容器隔离已经够抽象了。结果这周云原生课从 Docker 讲到 Kubernetes,老师一上来就把我问住了:
“现在集群里跑着 5 个副本,你告诉浏览器,它该访问哪个 IP?”
我第一反应:“每个 Pod 配个 IP,直接访问呗。”
老师反问:“那这个 Pod 挂了,控制器立刻拉起一个新的,新 Pod 的 IP 和原来不一样,你怎么知道现在该访问谁?”
我愣了一下。对啊——Pod 是会死的。kubectl delete 一个 Pod,Deployment 马上补一个新的,新 Pod 的 IP 跟旧的完全不同。要是前端把 Pod IP 写死,那 Pod 一重启就要改一次配置,这不就回到原始社会了吗?
当时卡住我的具体问题是这三条:
- Pod IP 动态变化,谁能提供一个"不变的入口"?
- 好几个副本,流量怎么分发到它们头上?
- 外部用户想访问集群里的服务,路径到底怎么走?
这篇文章就把 K8s 里管这些事的两个核心概念讲清楚:Service(集群内部的"稳定入口")和 Ingress(集群对外的"统一网关")。它俩一个管"集群内部怎么找到 Pod",一个管"外部流量怎么进集群",配合起来才是一条完整的访问链路。
先交代环境:K8s 我是用 minikube 在虚拟机上搭的单机集群,driver 用的 Docker,kubectl 装在本机。minikube 对资源有要求,我这台虚拟机刚开始内存只分了 1G,后面 minikube start 卡住折腾了半天,坑都在下面慢慢说。
二、基石篇:Service —— 让 Pod “可以被找到”
2.1 什么是 Service?一句话:Pod 的"固定电话号码"
Service 的逻辑很简单:它是一组 Pod 的抽象入口,用一个稳定的虚拟 IP(ClusterIP)和 DNS 名字,把背后不断变化的 Pod 藏起来。
怎么关联到具体 Pod?靠 Label Selector(标签选择器)。Service 用标签匹配 Pod,只要 Pod 身上带着对应的 label(比如 app: my-app),就自动被收进这个 Service 的"通讯录"里,不需要手动登记 IP。
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector: # ← 关键:靠标签找到背后的 Pod
app: my-app
ports:
- port: 80 # Service 自己的端口(虚拟 IP 上的端口)
targetPort: 80 # 转发到 Pod 里容器的端口
我的理解:Pod 就像一群随时可能换号的员工,Service 是前台总机。 外面只需要拨总机号码,总机负责转接给任意一个空闲员工。员工换了一拨又一拨,总机号码始终不变。
2.2 Service 的四种类型(Type)
spec.type 决定这个 Service 对外"暴露到什么程度":
| Type | 访问范围 | 一句话理解 |
|---|---|---|
| ClusterIP(默认) | 集群内部 | 服务间调用的主力,只有集群内能访问 |
| NodePort | 集群外部(节点 IP:端口) | 每个节点开一个静态端口(30000-32767),简单粗暴 |
| LoadBalancer | 集群外部(云厂商 LB) | 对接云负载均衡器,自动分配公网/私网 IP,NodePort 的"豪华版" |
| ExternalName | 集群内 → 外部域名 | 只是个 CNAME 映射,把 Service 指到集群外的域名,不做负载均衡 |
记忆:ClusterIP 是"内部电话",NodePort 是"给每栋楼配个门卫",LoadBalancer 是"统一前台接待",ExternalName 是"帮你把电话转到别的公司"。
2.3 底层实现:kube-proxy 的三种代理模式
Service 的 ClusterIP 是"虚拟"的,集群里没有一张网卡真叫这个名字。那流量是怎么被转发的?靠每个节点上常驻的 kube-proxy 组件。
| 模式 | 原理 | 特点 |
|---|---|---|
| userspace | 用户态代理,内核收包后交给用户态转发 | 历史遗留,性能差,基本淘汰 |
| iptables | 用 Netfilter 规则做 DNAT 转发 | 主流默认,规则是线性匹配,Service 多了会变慢 |
| IPVS | 内核态虚拟服务器,基于哈希表 | 高性能,支持多种调度算法(rr/wrr/lc),适合大规模集群 |
minikube 默认走 iptables。怎么确认?不用看日志,直接问 kube-proxy 自己:
minikube ssh -- curl -s 127.0.0.1:10249/proxyMode
# iptables
iptables 模式的短板我记在笔记里:Service 越多,iptables 规则越长,转发效率越差。所以大集群一般会切成 IPVS。这块我现在只停在概念层面,知道有这回事,还没真刀真枪调过。
2.4 无头服务(Headless Service)
普通 Service 会分配一个 ClusterIP 当"总机"。但如果你把 clusterIP 显式写成 None,Service 就不再提供 VIP,而是直接通过 DNS 返回所有后端 Pod 的 IP——这就是 Headless Service(无头服务)。
spec:
clusterIP: None # ← 无头服务
selector:
app: my-db
什么时候用?StatefulSet 里的数据库,比如 Cassandra、ZooKeeper。每个 Pod 需要有稳定的身份,客户端想自己挑 Pod 连,不想要中间那层负载均衡。这时候配合无头服务 + 每个 Pod 的稳定 DNS 名字(pod-name.svc-name.namespace.svc.cluster.local),就能做到"指名道姓"地连某个具体实例。
一句话:普通 Service 是"总机转接",无头 Service 是"把通讯录直接甩给你,你自己打电话"。
三、进阶篇:Ingress —— 七层流量的"总指挥"
3.1 为什么有了 Service 还需要 Ingress?
到这里集群内部访问已经通了:Service 提供 ClusterIP 和 DNS,Pod 之间互相调用没问题。问题出在对外暴露。
如果直接用 NodePort:每个服务占一个节点端口(30000-32767),服务一多就是端口管理灾难——你得记住"8080 是 A 服务,8081 是 B 服务"。
如果直接用 LoadBalancer:每个服务配一个云负载均衡器,LB 是按小时计费的,暴露 10 个服务就是 10 个 LB,钱包先扛不住。
更麻烦的是:NodePort 和 LoadBalancer 都是四层(TCP/UDP)转发,只认"IP:端口",不认"域名和路径"。 但我想要的是:
api.example.com→ API 服务web.example.com→ 前端服务example.com/static/→ 静态文件服务
四层转发做不到这种"按域名、按路径分流"。这就是 Ingress 存在的意义——它是七层(HTTP/HTTPS)的统一入口。
记忆:NodePort/LoadBalancer 是"每个服务开一个门",Ingress 是"一个大门,里面按门牌号分流"。注意,Ingress 不是取代 Service,而是站在 Service 前面,给流量做第一层分流。
3.2 核心概念:Ingress 资源和 Ingress Controller 是两回事
这是最容易被混淆的点,我第一次就栽在这。
- Ingress 资源:一段 YAML 规则,定义"域名/路径 → 后端 Service"的路由。它只是一份说明书,本身不处理任何流量。
- Ingress Controller:真正干活的那个组件(Nginx Ingress、Traefik、Istio Gateway 等),它读 Ingress 规则,然后按规则转发流量。需要单独部署。
类比:Ingress 是"交通规则",Ingress Controller 是"交警"。 光有规则书、没有交警上岗,等于没有规则。
关键结论:集群里如果没装任何 Ingress Controller,你 apply 再多 Ingress 资源也白搭——流量根本没地方进。这是新手最容易踩的坑,我在实战篇就栽了一次。
3.3 Ingress 的典型使用场景
| 场景 | 怎么配 |
|---|---|
| 基于域名路由 | 一个 Ingress 里写多个 host,不同域名走不同 Service |
| 基于路径路由 | 同一个域名下,/api/* 走 API 服务,/static/* 走静态服务 |
| TLS/HTTPS 终止 | 在 Ingress 层统一挂证书,后端 Pod 只跑 HTTP,减轻应用负担 |
| 灰度/金丝雀发布 | 通过注解(annotation)按权重或 Header 把部分流量切到新版本 |
| 限流/超时/重试/CORS | 依赖具体 Controller 提供的注解,不是 Ingress 标准能力 |
四、协作篇:Service 与 Ingress 是怎么配合的
4.1 完整流量链路
把整条链路画出来,画过一遍就不会混了:
外部用户
│ ① 访问 http://app.example.com
▼
Ingress Controller(Pod)
│ ② 按 Ingress 规则匹配域名/路径,找到后端 Service
▼
Service(ClusterIP)
│ ③ 通过 Label Selector 找到一组后端 Pod
▼
后端 Pod(3 个副本,kube-proxy 做负载均衡)
外部流量不直接打 Pod,而是:Ingress → Service → Pod。
4.2 为什么 Ingress 不直接连 Pod?
这个问题我纠结了很久。Ingress Controller 明明也在集群里,直接解析出 Pod IP 转发过去不就行了?绕一圈走 Service 图啥?
答案有两个:
- Pod IP 是动态的。如果 Ingress 直接记 Pod IP,Pod 一重启就得重新感知。而 Service 的 ClusterIP 是稳定的,Ingress 只需要把流量交给 Service,剩下的(Pod 变动、负载均衡)Service 自己搞定。
- Service 是"多功能中转站"。同一个 Service,既能被 Ingress 用,也能被集群内部的其他服务通过 DNS 直接调用。让 Ingress 统一走 Service,就只有一个入口,规则不散。
一句话:Ingress 负责"怎么进来",Service 负责"分给谁"。 各管一段,互不越界。
4.3 常见坑点
- Ingress Controller 自己也是一个 Deployment,它要对外暴露,通常用 NodePort 或 LoadBalancer 类型的 Service(或 hostNetwork),而不是 ClusterIP——不然流量还是进不来。
- Ingress 规则里的后端 Service 名和端口必须跟 Service 定义完全一致,拼错一个就 404。
- 不同 Ingress Controller 对注解(annotation)的支持差异很大。Nginx Ingress 的注解换到 Traefik 上不一定管用。
五、实战篇:minikube 里从零搭一个
理论说得再多,不如亲手跑一遍。下面是完整的操作记录。
5.1 启动 minikube + 开 Ingress 插件
# 启动单机集群(driver 用 docker)
minikube start --driver=docker
# 开启 ingress 插件(这一步千万别忘!)
minikube addons enable ingress
# 确认插件状态
minikube addons list | grep ingress
踩坑记录一:minikube 启动卡住。 我第一次
minikube start半天不动,日志里全是 “Exiting due to … cpus”。查了才发现是资源不够——minikube 默认要 2 核 2G,我虚拟机只分了 1G 内存。最后去 VMware 设置里把内存调到 4G 才起来。所以先确认虚拟机资源,再谈别的。踩坑记录二:忘了开 ingress 插件。 我直接
kubectl apply -f ingress.yaml,Ingress 也创建成功了,但 curl 就是不通。后来kubectl get pods -n ingress-nginx一看,根本没有 ingress controller 的 Pod——规则有了,交警没上岗,白搭。网上很多教程默认你开好了,这个环节很容易被跳过。
5.2 部署一个 Web 应用 Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: nginx:1.25
ports:
- containerPort: 80
kubectl apply -f deployment.yaml
kubectl get pods # 等 3 个 Pod 都 Running
5.3 创建 ClusterIP Service
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 80
kubectl apply -f service.yaml
kubectl get svc
# NAME TYPE CLUSTER-IP PORT(S)
# my-app-svc ClusterIP 10.109.214.31 80/TCP
5.4 编写 Ingress 规则
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-svc
port:
number: 80
kubectl apply -f ingress.yaml
kubectl get ingress
踩坑记录三:YAML 缩进错。 我在这份文件上报过
kubectl apply直接抛错:error: error validating "ingress.yaml": error validating data: yaml: line 8: did not find expected key一查是
paths下面的层级缩进多退了一格,backend挂错了父级。YAML 对缩进敏感得令人发指。后来养成习惯:kubectl apply --dry-run=client -f xxx.yaml先做语法校验再真正 apply。另外注意:网上老教程写的是
serviceName/servicePort,那是旧 API 版本(extensions/v1beta1)的写法;networking.k8s.io/v1里已经改成backend.service.name/backend.service.port.number。照着老教程抄容易翻车。
5.5 验证访问链路
minikube 的 Ingress Controller 是直接监听在节点 IP 的 80/443 上的,先拿节点 IP:
minikube ip
# 比如输出 192.168.49.2
然后要么配 /etc/hosts,要么直接用 Host 头绕过 hosts:
# 方式一:临时带 Host 头(不用改 hosts,调试最方便)
curl -H "Host: app.example.com" http://$(minikube ip)/
# 方式二:写进 hosts
echo "$(minikube ip) app.example.com" | sudo tee -a /etc/hosts
curl http://app.example.com/
能看到 nginx 欢迎页,链路就通了。
踩坑记录四:域名解析不到。 我第一次直接
curl http://app.example.com/,报Could not resolve host,一脸懵。后来才意识到:Ingress 规则里的host只是流量分流的标签,不等于公网真的解析了这个域名。 你得自己把域名映射到 minikube IP(改 hosts 或带 Host 头),否则浏览器根本不知道把请求发给谁。这跟之前理解的"买域名 + DNS 解析"完全是两码事。
5.6 配置 TLS,启用 HTTPS(可选)
先自签一张证书,导入成 Secret:
# 生成自签名证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt -subj "/CN=app.example.com"
# 导入为 K8s Secret
kubectl create secret tls app-tls-secret \
--key tls.key --cert tls.crt
然后在 Ingress 里挂上:
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls-secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-svc
port:
number: 80
curl -k https://app.example.com/ # -k 表示忽略自签名证书的警告
六、对比篇:Service vs Ingress 决策矩阵
到底什么时候用啥?把这阵子的判断整理成一张表:
| 场景 | 推荐方案 |
|---|---|
| 集群内部服务间调用 | ClusterIP Service |
| 快速调试/测试,临时对外暴露 | NodePort Service |
| 生产环境单服务暴露(云环境) | LoadBalancer Service |
| 多个服务共用一个公网入口 | Ingress + ClusterIP Service |
| 需要 TLS/HTTPS 统一管理 | Ingress |
| 需要细粒度流量治理(灰度、限流) | Ingress(高级 Controller) |
| 非 HTTP 协议(如 MySQL、Redis) | LoadBalancer 或 NodePort(Ingress 主要支持 HTTP/HTTPS) |
最后一行的坑很隐蔽:Ingress 本质是七层,HTTP/HTTPS 是它的主场。想暴露 MySQL(TCP 3306)就别指望 Ingress 做域名路由——老老实实用 LoadBalancer 或 NodePort。
七、未来展望:Gateway API —— Ingress 的下一代替代者
老师课上提了一嘴 Gateway API,课后查了一下,理清了一个大方向:
Ingress 的局限:
- 只支持 HTTP/HTTPS,TCP/UDP 无能为力
- 字段少,很多能力要靠 Controller 的注解,注解还不统一
- 不同 Controller 行为差异大,迁移成本高
Gateway API 的设计思路:
- 角色分离:基础设施团队定义
Gateway,应用团队只写HTTPRoute,互不越权 - 多协议:原生支持 gRPC、TCP、UDP
- 更丰富的路由能力:按 Header、按权重灰度更标准
现状:Ingress 仍是绝对主流,新项目可以关注 Gateway API。这个我还没实操,先记个概念。
八、总结 + 速记清单
把整个逻辑串一遍:
Service:Pod 的稳定入口
├─ ClusterIP(内部)/ NodePort(外部简单)/ LoadBalancer(云 LB)/ ExternalName(外部域名)
├─ 靠 Label Selector 关联 Pod
└─ kube-proxy 转发:iptables(主流)/ IPVS(高性能)
Ingress:七层统一网关
├─ Ingress 资源 = 规则说明书(host + path → Service)
├─ Ingress Controller = 执行者(Nginx / Traefik / ...)
└─ 只管 HTTP/HTTPS
链路:外部用户 → Ingress Controller → Service(ClusterIP) → Pod
速记清单:
Service 解决 Pod 动态变化 → 稳定入口
Ingress 解决多服务对外暴露 → 统一网关
Ingress 是规则,Controller 是执行者
Ingress → Service → Pod,各管一段
ClusterIP 内部用,NodePort 调试用,LoadBalancer 上生产
Ingress 只管 HTTP/HTTPS,数据库别用它
Headless Service:clusterIP: None,自己挑 Pod 连
我踩过的坑合集
| 坑 | 我的翻车经历 | 正确做法 |
|---|---|---|
| minikube 启动卡住 | 虚拟机内存只给了 1G,minikube start 起不来 | 先把虚拟机内存调到 2G+(我用的 4G) |
| 忘了开 ingress 插件 | kubectl apply 了 Ingress 但流量不通 | minikube addons enable ingress,确认 ingress-nginx 里有 Pod |
| curl 域名解析不到 | 直接 curl app.example.com 报 Could not resolve host | 域名要自己映射:改 /etc/hosts 或用 curl -H "Host: ..." |
| Ingress 的 service 名拼错 | kubectl describe ingress 的 Events 报错,后端 404 | backend.service.name 必须和 Service 定义一致,用 kubectl get svc 核对 |
| YAML 缩进错 | kubectl apply 报 yaml: line 8: did not find expected key | 先 kubectl apply --dry-run=client -f xx.yaml 做语法校验 |
| 照着老教程写 serviceName/servicePort | apply 报字段不存在 | networking.k8s.io/v1 里是 backend.service.name / port.number |
| 以为 Ingress 能暴露数据库 | 想给 MySQL 配 Ingress 域名路由 | Ingress 是七层,非 HTTP 协议用 LoadBalancer/NodePort |
最后说两句
写这篇的过程,我反复在 minikube 里 kubectl apply、kubectl delete、翻 kubectl describe ingress 的 Events,中间 Ingress Controller 的镜像还拉取卡了一阵子(网络问题,多试了几次才好)。
还没完全搞懂的地方:IPVS 模式的调度算法我只知道有 rr/wrr/lc,真让我在大集群里调优肯定不行;Gateway API 目前只是概念,没实操;TLS 那块我是拿自签名证书糊弄的,真实环境里跟云厂商证书、cert-manager 自动续期怎么配合,还没碰过。
下一步打算:minikube ssh 进去翻一翻 kube-proxy 到底生成了哪些 iptables 规则(iptables -t nat -L 看看 Service 的 DNAT 长啥样),然后抽时间把 Gateway API 的 HTTPRoute 动手玩一玩。
对了,回看 [[Docker 容器核心原理:Cgroup 资源限制与 rootfs 隔离机制详解]] 里"容器 = Namespace + Cgroup + rootfs",到 K8s 这层,Pod 就是那套东西再包一层;而 Service / Ingress 解决的是 Pod 之上的访问问题。学过的东西真的会串起来。
2026年8月 · 写于云原生课 K8s 网络整理 · minikube 里那个 nginx 欢迎页,被我 curl 了不知道多少遍
更多推荐
所有评论(0)