一、为什么需要 Ingress
我们先从"没有 Ingress 的世界"说起。在 Kubernetes 里,Pod 的 IP 是不稳定的(重建就变),所以 Kubernetes 提供了 Service 作为一组 Pod 的稳定访问入口。但要把服务暴露到集群外部,Service 只有三种类型:
| 类型 |
工作方式 |
问题 |
ClusterIP |
只分配集群内虚拟 IP |
集群外访问不到 |
NodePort |
在每个节点的 30000+ 端口上开洞 |
端口难管理、节点暴露面大、无七层能力、无 TLS 终止 |
LoadBalancer |
每个 Service 申请一个云厂商四层 LB |
一个 Service 一个 LB,成本高、公网 IP 爆炸,仍然只有四层能力 |
假设你有 50 个微服务,用 LoadBalancer 就意味着 50 个公网 IP + 50 个 LB,而且你依然无法做到:
- 按域名(
a.example.com / b.example.com)路由;
- 按路径(
/api → 服务 A,/web → 服务 B)路由;
- 统一的 HTTPS 证书管理与 TLS 终止;
- 灰度发布、限流、鉴权、重写等流量治理能力。
Ingress 要解决的核心问题就是:用一个(或少数几个)集群入口,把七层 HTTP/HTTPS 流量按需分发到集群内众多的 Service。 它是 Kubernetes 里的"反向代理声明规范",等价于集群内的 Nginx/Envoy 网关。
一句话总结 Ingress 的目的:
统一入口(省成本与公网 IP) + 七层路由(host/path) + 集中式 TLS 与流量治理 + 声明式、可移植的配置。
二、整体架构
2.1 分层架构图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| ┌────────────────────────────────┐ │ 客户端 (浏览器 / curl / App) │ └───────────────┬────────────────┘ │ https://shop.example.com/api ▼ ┌────────────────────────────────┐ │ DNS 解析 → 外部四层负载均衡 │ │ (云厂商 LB / MetalLB / VIP) │ │ 只做 L4 转发,不理解 HTTP │ └───────────────┬────────────────┘ ══════════════════════════════════════════ │ ════════════════════════════ Kubernetes 集群 ▼ ┌──────────────────────────────────────────┐ │ Ingress Controller (数据面) │ │ nginx / envoy / traefik ... 以 Pod 运行 │ │ ─ 监听 80/443,做 TLS 终止 │ │ ─ 按 host + path 做七层路由 │ │ ─ 重写 / 限流 / 鉴权 / 灰度 │ └───┬─────────────┬─────────────┬──────────┘ │ │ │ ┌─────────┘ ┌─────┘ └─────────┐ ▼ ▼ ▼ Service: svc-web Service: svc-api Service: svc-admin (ClusterIP) (ClusterIP) (ClusterIP) │ │ │ ┌─────┴────┐ ┌────┴────┐ ┌────┴────┐ ▼ ▼ ▼ ▼ ▼ ▼ Pod Pod Pod Pod Pod Pod
|
要点:外部 LB 只负责把流量"扔进"集群,真正的七层决策发生在 Ingress Controller 这个 Pod 里。
2.2 控制面与数据面分离
这是理解 Ingress 最关键的一点:
Ingress 资源本身不做任何转发。它只是一份"声明"(存在 etcd 里的 YAML);真正干活的是 Ingress Controller。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| kubectl apply -f ingress.yaml │ ▼ ┌──────────────────┐ ┌──────────────────────────────────────┐ │ kube-apiserver │◄──────►│ etcd │ │ │ 存储 │ Ingress / IngressClass / Secret │ └────────┬─────────┘ └──────────────────────────────────────┘ │ List-Watch(Informer 机制) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Ingress Controller 进程(控制面循环) │ │ │ │ 1. Watch: Ingress / IngressClass / Service / EndpointSlice / │ │ Secret / ConfigMap │ │ 2. 归并、排序、校验 → 构建一份内部路由模型 │ │ 3. 渲染成数据面配置: │ │ - ingress-nginx → nginx.conf + Lua balancer 动态 upstream │ │ - Envoy Gateway → xDS 配置,热更新,无 reload │ │ 4. 生效:nginx -s reload(或增量热推送) │ │ 5. 回写状态:kubectl get ingress 看到的 ADDRESS │ └────────────────────────────┬────────────────────────────────────┘ │ ▼ 数据面按最新配置转发流量
|
所以如果你 kubectl apply 了 Ingress 却访问不通,第一件事是检查集群里到底有没有装 Ingress Controller —— 光有 Ingress 资源,K8s 默认什么都不会发生。
2.3 三个核心对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ┌─────────────────────────────────────────────────────────────────┐ │ IngressClass │ │ 「哪个 Controller 来负责」 │ │ spec.controller = k8s.io/ingress-nginx │ │ 可设默认类:metadata.annotations: │ │ ingressclass.kubernetes.io/is-default-class: "true" │ └─────────────────────────────────────────────────────────────────┘ ▲ 引用(spec.ingressClassName) │ ┌─────────────────────────────────────────────────────────────────┐ │ Ingress(路由规则声明) │ │ rules: host + path → backend(Service:Port) │ │ tls: hosts + secretName │ │ defaultBackend: 兜底(404 页 / 默认服务) │ │ annotations: 各家 Controller 的私货(重写、限流、灰度...) │ └─────────────────────────────────────────────────────────────────┘ ▲ 由...实现 │ ┌─────────────────────────────────────────────────────────────────┐ │ Ingress Controller(真正的实现,集群外安装) │ │ ingress-nginx / Traefik / APISIX / HAProxy / Kong / │ │ Envoy Gateway / Istio Gateway ... │ └─────────────────────────────────────────────────────────────────┘
|
IngressClass 的意义:一个集群里可以同时装多个 Controller(比如内网用 nginx、公网用 APISIX)。每个 Ingress 通过 spec.ingressClassName 明确归属,避免多个 Controller 抢同一条规则互相覆盖。
三、技术原理
3.1 Service(四层)与 Ingress(七层)的关系
1 2 3 4 5 6 7 8 9 10 11 12
| L7 HTTP 语义:Host、URI、Header、Cookie、TLS SNI ┌──────────────────────────────────────────────────────────┐ │ Ingress │ │ host: shop.example.com path: /api → svc-api │ │ host: shop.example.com path: / → svc-web │ │ host: admin.example.com path: / → svc-admin │ └──────────────────────────────────────────────────────────┘ L4 TCP/UDP 语义:IP + Port ┌──────────────────────────────────────────────────────────┐ │ Service (ClusterIP) → kube-proxy (iptables/IPVS) │ │ svc-api:8080 → PodIP:8080, PodIP:8080 ... │ └──────────────────────────────────────────────────────────┘
|
Ingress 不是 Service 的替代品,而是 Service 上游的一层。 规则里的 backend 指向的仍然是 Service:Port,而不是直接写 Pod。这样 Pod 的漂移、扩缩容仍然由 Service/EndpointSlice 负责发现,Ingress 只负责"选哪个 Service"。
3.2 一次请求的完整链路
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 1. 客户端 DNS 查询 shop.example.com → 解析到外部 LB 的公网 VIP(或节点 IP)
2. 客户端与 LB 建立 TCP 连接,发起 TLS 握手(SNI = shop.example.com) → LB 是四层,只透传字节流(也可配置 LB 自己做 TLS,但通常交给 Ingress)
3. 流量到达 Ingress Controller Pod → 若是 HTTPS:Controller 用 Secret 里的证书完成 TLS 终止, 解密后拿到明文 HTTP 请求 → 若配置了 ssl-redirect / HSTS,先做跳转和响应头处理
4. 路由匹配(按优先级) a) 先匹配 Host(精确 / 通配 *.example.com) b) 再匹配 Path(Exact > Prefix > ImplementationSpecific,最长前缀优先) c) 命中 → 取该 path 对应的 backend.service d) 未命中 → defaultBackend(默认 404 页面)
5. 上游选择 Controller 通过 EndpointSlice 实时拿到该 Service 后端所有 PodIP:Port, **直接转发到 Pod IP,不经过 ClusterIP / kube-proxy** → 少了 iptables/IPVS 一跳,且能自己做负载均衡算法、会话保持、重试
6. 请求头处理 X-Forwarded-For / X-Real-IP / X-Forwarded-Proto → 后端应用据此拿到真实客户端 IP(注意配置 externalTrafficPolicy: Local 或 PROXY protocol,否则拿到的是节点/LB 的地址)
7. 转发到 Pod,返回响应,原路回传
|
关于第 5 步补充一句:ingress-nginx 在 nginx 里用 balancer_by_lua + 共享内存动态维护 upstream 列表,因此 Pod 扩缩容不会触发 nginx reload,避免长连接抖动;只有路由规则本身变化才需要 reload。
3.3 路径匹配规则(pathType)
| pathType |
含义 |
示例 |
Prefix |
前缀匹配,按 / 分段,不做字符串包含 |
/api 匹配 /api、/api/v1;不匹配 /apix |
Exact |
完全相等(大小写敏感) |
/foo 只匹配 /foo,不匹配 /foo/ |
ImplementationSpecific |
交给 Controller 自己解释 |
nginx 下配合 use-regex 注解可写正则 |
注意 Prefix 的分段语义是很多人踩坑的地方:/foo 不会匹配 /foobar,但会匹配 /foo/bar。
3.4 TLS 终止示意
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Client ──HTTPS(TLS)──► Ingress Controller ──HTTP(明文)──► Pod ▲ │ │ └── 证书来自 Secret(tls.crt / tls.key) │ kubectl create secret tls shop-tls \ --cert=tls.crt --key=tls.key
可选模式: ┌────────────────────────────────────────────────────────────┐ │ 1) TLS 终止(默认) :Controller 解密,后端走 HTTP │ │ 2) TLS 透传 Passthrough:Controller 按 SNI 转发,后端自己解密│ │ nginx: nginx.ingress.kubernetes.io/ssl-passthrough │ │ 3) 后端 mTLS / HTTPS :Controller 解密后再用 HTTPS 转发给 Pod │ │ nginx: nginx.ingress.kubernetes.io/backend-protocol: HTTPS│ └────────────────────────────────────────────────────────────┘
|
证书可以手工创建 Secret,也可以用 cert-manager 配合 Let’s Encrypt 自动签发与续期(生产推荐):Ingress 上打 cert-manager.io/cluster-issuer 注解,cert-manager 自动申请证书并写入 Secret。
3.5 灰度发布(Canary)原理
ingress-nginx 的灰度实现:同一 host+path 下,如果存在带 canary: "true" 注解的 Ingress,Controller 会把它作为主 Ingress 的"影子"规则,按权重/Header/Cookie 分流。
1 2 3 4 5
| ┌────────────────────────────┐ 请求 ───────►│ Ingress (主) 100% 权重 │──► svc-stable │ Ingress (canary) 10% 权重 │──► svc-canary └────────────────────────────┘ 分流依据:canary-weight / canary-by-header / canary-by-cookie
|
四、使用方式
4.1 安装 Ingress Controller
以最常用的 ingress-nginx 为例(Helm 安装,云环境):
1 2 3 4 5 6 7 8
| helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx --create-namespace \ --set controller.service.type=LoadBalancer \ --set controller.ingressClassResource.name=nginx \ --set controller.ingressClassResource.default=true
|
裸金属(无云 LB)常见三种暴露方式:
1 2 3 4 5 6 7 8 9 10
| 1) controller.service.type=NodePort 客户端 → 节点 IP:3xxxx → Service → Controller Pod 简单,但端口不标准,前面通常还要挂一层 Nginx/HAProxy
2) controller.hostNetwork=true + kind=DaemonSet(推荐) Controller 直接占用节点 80/443,每个节点都能接流量 客户端 → 任意节点 IP:443 → Controller Pod
3) MetalLB / PureLB 给 LoadBalancer 类型的 Service 分配一个局域网 VIP
|
验证安装:
1 2 3
| kubectl get pods -n ingress-nginx kubectl get svc -n ingress-nginx kubectl get ingressclass
|
注:ingress-nginx 项目官方已宣布退役(2026 年 3 月起不再发布新版本与安全修复)。新集群建议评估 Gateway API 实现(Envoy Gateway、Nginx Gateway Fabric、Traefik 等)或其他活跃维护的 Controller。
4.2 最小可用示例:Deployment + Service + Ingress
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: nginx:1.25 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: svc-web spec: selector: app: web ports: - port: 80 targetPort: 80 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-ingress spec: ingressClassName: nginx rules: - host: shop.example.com http: paths: - path: / pathType: Prefix backend: service: name: svc-web port: number: 80
|
1 2 3
| kubectl apply -f web.yaml kubectl get ingress web-ingress curl -H "Host: shop.example.com" http://<EXTERNAL-IP>/
|
4.3 同一域名按路径分发(Fan-out)
1 2 3 4
| shop.example.com/ ├── /api → svc-api:8080 ├── /web → svc-web:80 └── /admin → svc-admin:80(一般再配合 IP 白名单注解)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: fanout spec: ingressClassName: nginx rules: - host: shop.example.com http: paths: - path: /api pathType: Prefix backend: service: name: svc-api port: number: 8080 - path: /web pathType: Prefix backend: service: name: svc-web port: number: 80 - path: /admin pathType: Prefix backend: service: name: svc-admin port: number: 80
|
4.4 多域名 + 通配域名
1 2 3 4 5 6 7 8 9
| spec: ingressClassName: nginx rules: - host: a.example.com http: paths: [{ path: /, pathType: Prefix, backend: { service: { name: svc-a, port: { number: 80 } } } }] - host: "*.example.com" http: paths: [{ path: /, pathType: Prefix, backend: { service: { name: svc-wildcard, port: { number: 80 } } } }]
|
4.5 HTTPS / TLS
1 2 3 4
| kubectl create secret tls shop-tls \ --namespace default \ --cert=shop.example.com.crt \ --key=shop.example.com.key
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: tls-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: nginx tls: - hosts: - shop.example.com secretName: shop-tls rules: - host: shop.example.com http: paths: - path: / pathType: Prefix backend: service: name: svc-web port: number: 80
|
cert-manager 自动签发(推荐生产用法):
1 2 3 4 5 6 7
| metadata: annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: [shop.example.com] secretName: shop-tls-auto
|
4.6 常用注解(以 ingress-nginx 为例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| metadata: annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "10"
nginx.ingress.kubernetes.io/limit-rps: "100" nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
nginx.ingress.kubernetes.io/enable-cors: "true" nginx.ingress.kubernetes.io/cors-allow-origin: "https://shop.example.com"
nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
|
对应 rewrite 的 path 写法:
1 2 3 4 5 6 7
| - path: /api(/|$)(.*) pathType: ImplementationSpecific backend: service: name: svc-api port: number: 8080
|
4.7 日常运维命令
1 2 3 4 5
| kubectl get ingress kubectl describe ingress web-ingress kubectl get ingressclass kubectl logs -n ingress-nginx deploy/ingress-nginx-controller kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf
|
五、排障速查表
| 现象 |
常见原因 |
排查方向 |
kubectl get ingress 的 ADDRESS 为空 |
没装 Controller,或 Controller 没起来 |
kubectl get pods -n ingress-nginx、看 Controller 日志 |
| 创建了 Ingress 但规则不生效 |
ingressClassName 与 Controller 不匹配(且无默认类) |
kubectl get ingressclass -o yaml 对比 spec.controller |
| 404(nginx 默认页 / default backend) |
host 拼错、path 不匹配、Prefix 分段语义踩坑 |
describe ingress、curl -H "Host: ..."、检查 pathType |
| 503 |
Service 没有可用 Endpoint(selector 不匹配 / Pod 未 Ready) |
kubectl get endpoints svc-web |
| 502 |
后端 Pod 端口/协议不对,或 Pod 启动慢 |
看 Controller 日志中 upstream 地址,直连 Pod 验证 |
| HTTPS 证书不对 / 报默认证书 |
Secret 名字或命名空间不对、证书 SAN 不含该域名 |
kubectl get secret shop-tls -o yaml、检查证书 SAN |
| 后端拿不到真实客户端 IP |
LB 又做了一次 NAT,或未开 PROXY protocol |
externalTrafficPolicy: Local、use-proxy-protocol |
| 配置改了偶尔抖动 |
nginx reload 导致长连接断开 |
避免频繁改规则;考虑 Envoy 系实现(热更新无 reload) |
| 多个 Controller 互相打架 |
都认领了同一条 Ingress |
明确 spec.ingressClassName,只保留一个默认类 |
六、Ingress 的定位与演进
1 2 3 4 5 6 7 8 9 10
| NodePort / LoadBalancer Service → 四层,一个服务一个入口 │ ▼ Ingress(本文) → 七层,统一入口,但能力靠 annotations 私有化 │ ▼ Gateway API(下一代) → 七层,标准角色分离: GatewayClass / Gateway / HTTPRoute 支持按团队划分命名空间、跨命名空间路由、 标准的流量拆分与权重、无 annotations 黑话
|
Ingress 的最大遗留问题是:路由/流量治理能力(重写、灰度、限流)没有标准化,全靠各家 Controller 的 annotations,不可移植。这也是 Gateway API 出现的直接原因。Ingress 目前处于"冻结演进"状态(不会再有新功能),Kubernetes 官方主推 Gateway API。
实践建议:
- 存量集群:继续使用 Ingress 完全没问题,它是稳定且广泛支持的 GA 特性;
- 新集群或有复杂流量治理需求:直接上 Gateway API(Envoy Gateway / Nginx Gateway Fabric 等);
- 无论如何,先确认你的 Controller 是否在活跃维护。
七、小结
- Ingress = 声明,Controller = 实现。只 apply Ingress 资源而不装 Controller,什么都不会发生。
- Ingress 是七层网关,Service 是四层负载,两者是上下游关系:
Ingress → Service → Pod,Controller 通常还会绕过 ClusterIP 直连 PodIP。
- 核心价值:一个入口承载多个服务(省 LB 与公网 IP)、host/path 七层路由、集中 TLS 终止、灰度/限流/鉴权等流量治理。
- 落地时先定三件事:用哪个 Controller、怎么把流量送进集群(LoadBalancer / hostNetwork / NodePort)、证书怎么签发与续期。
- 长期看,Gateway API 是官方演进方向,新项目值得优先评估。