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 图啥?

答案有两个:

  1. Pod IP 是动态的。如果 Ingress 直接记 Pod IP,Pod 一重启就得重新感知。而 Service 的 ClusterIP 是稳定的,Ingress 只需要把流量交给 Service,剩下的(Pod 变动、负载均衡)Service 自己搞定。
  2. 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.comCould not resolve host域名要自己映射:改 /etc/hosts 或用 curl -H "Host: ..."
Ingress 的 service 名拼错kubectl describe ingress 的 Events 报错,后端 404backend.service.name 必须和 Service 定义一致,用 kubectl get svc 核对
YAML 缩进错kubectl applyyaml: line 8: did not find expected keykubectl apply --dry-run=client -f xx.yaml 做语法校验
照着老教程写 serviceName/servicePortapply 报字段不存在networking.k8s.io/v1 里是 backend.service.name / port.number
以为 Ingress 能暴露数据库想给 MySQL 配 Ingress 域名路由Ingress 是七层,非 HTTP 协议用 LoadBalancer/NodePort

最后说两句

写这篇的过程,我反复在 minikube 里 kubectl applykubectl 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 了不知道多少遍

更多推荐