K8S Ingress 详解

Ingress 架构、原理与实践

Posted by Jason Lee on 2026-11-01

一、为什么需要 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 # 记下 EXTERNAL-IP
kubectl get ingressclass # 应看到 nginx(及是否 default)

注: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 # 指定由哪个 Controller 接管
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 # 看到 ADDRESS 和 HOSTS 即为已生效
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" # 通配只匹配一级:x.example.com 命中,x.y.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" # HTTP 自动跳 HTTPS
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 # cert-manager 自动创建并续期

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:
# 路径重写:/api/foo → /foo(后端不认识 /api 前缀时用)
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"

# 灰度:10% 流量到 canary
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"

# 后端是 HTTPS / gRPC
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                    # 查看 ADDRESS / HOSTS / PORTS
kubectl describe ingress web-ingress # 看 Events、规则解析、默认后端
kubectl get ingressclass # 有哪些 Controller、谁是默认类
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 是否在活跃维护。

七、小结

  1. Ingress = 声明,Controller = 实现。只 apply Ingress 资源而不装 Controller,什么都不会发生。
  2. Ingress 是七层网关,Service 是四层负载,两者是上下游关系:Ingress → Service → Pod,Controller 通常还会绕过 ClusterIP 直连 PodIP。
  3. 核心价值:一个入口承载多个服务(省 LB 与公网 IP)、host/path 七层路由、集中 TLS 终止、灰度/限流/鉴权等流量治理。
  4. 落地时先定三件事:用哪个 Controller、怎么把流量送进集群(LoadBalancer / hostNetwork / NodePort)、证书怎么签发与续期。
  5. 长期看,Gateway API 是官方演进方向,新项目值得优先评估。


赏 支付宝打赏 微信打赏

赞赏一下