南北流量:Ingress、Gateway API 与出口治理
流量从集群外进来的几种正规入口,以及出方向该不该管、怎么管。
学完这节你能做到
- 说出 Ingress 与 Gateway API 的能力差异和迁移路径
- 规划入口层:LB VIP、Ingress 控制器副本、证书从哪来
- 给出限制 Pod 出网的两种做法及其代价
建议先学
跳过这几节会看不懂本节的部分推导
入口的三层结构
集群外的一个 HTTPS 请求要走到 Pod,中间是三层,各管一件事:
客户端
↓ DNS 解析到 VIP
LoadBalancer(MetalLB / 云 LB) ← 四层:把 VIP 引到某台节点
↓
Ingress 控制器 / Gateway ← 七层:按域名和路径挑后端,终止 TLS
↓
Pod ← 直接用 Pod IP,通常跳过 Service 的 DNAT
排障时先分清是哪一层:VIP 不通是上一节的事;能连上但 404/证书错是这一层。
新人常绕不过来的一环:Ingress 控制器本身是跑在集群里的 Pod,
它也要被集群外访问,所以它自己就是一个 type: LoadBalancer 的 Service。
kubectl get svc -n ingress-nginx
# NAME TYPE EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 172.18.15.200 80:31234/TCP,443:31235/TCP整个集群通常只需要一到两个这样的 VIP,之后所有 HTTP 服务共享它,靠域名区分。 这就是 Ingress 相对"每个服务一个 LoadBalancer"的最大价值——省地址、省成本。
Ingress API:够用但表达力有限
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m" # 能力靠注解扩展
spec:
ingressClassName: nginx
tls:
- hosts: [web.example.com]
secretName: web-tls
rules:
- host: web.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port: { number: 8080 }
Ingress 的核心表达能力只有三样:Host、Path、TLS。超出这三样的需求 (改写、超时、限流、灰度、gRPC、mTLS)全靠控制器自定义注解—— 于是配置就绑死在某个控制器上,换实现要重写。
kubectl get ingress -A
kubectl describe ingress web # Events 里能看到证书与后端解析结果
kubectl logs -n ingress-nginx <pod> | tail -50
Gateway API:把角色拆开
Gateway API 是 Ingress 的接班人。它最重要的改变不是功能更多,而是把一份配置拆成三种资源, 对应三种人:
| 资源 | 谁维护 | 管什么 |
|---|---|---|
GatewayClass | 平台/基础设施团队 | 用哪个实现(Istio、kgateway、nginx…) |
Gateway | 平台团队 | 监听哪些端口、绑哪些证书、哪些命名空间可以接入 |
HTTPRoute | 业务团队 | 我的域名、路径、后端与权重 |
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shared
namespace: infra
spec:
gatewayClassName: istio
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
certificateRefs: [{ name: wildcard-tls }]
allowedRoutes:
namespaces: { from: Selector, selector: { matchLabels: { gateway-access: "true" } } }
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: web
namespace: team-a
spec:
parentRefs: [{ name: shared, namespace: infra }]
hostnames: [web.example.com]
rules:
- matches: [{ path: { type: PathPrefix, value: /api } }]
backendRefs:
- { name: api-v1, port: 8080, weight: 90 }
- { name: api-v2, port: 8080, weight: 10 } # 灰度是原生能力
比 Ingress 强的地方:权重灰度、按 Header 匹配、跨命名空间引用、 超时与重试都是标准字段,不再依赖注解。
Gateway API 和 Ingress 可以并存,控制器大多同时支持两者。实际路径:
- 新服务用 HTTPRoute,老的 Ingress 先不动
- 平台团队先把
Gateway建好(证书、端口、准入范围) - 遇到需要灰度或按 Header 路由的服务,顺手迁过来
- 依赖大量 nginx 注解的服务放到最后——它们迁移成本最高
不要为了"用新 API"做一次性大迁移,收益不匹配风险。
证书:交给 cert-manager
TLS 在这一层终止,所以证书管理是入口层的固定组成部分。手工签发和续期是不可持续的—— 证书过期是最常见的"网络故障"之一,而它跟网络毫无关系。
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef: { name: letsencrypt-account }
solvers:
- dns01: # 泛域名证书必须用 DNS-01
cloudflare:
apiTokenSecretRef: { name: cf-token, key: token }
之后 Ingress 上加一个注解就自动签发续期:
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt
# 签发状态:READY=True 才算好
kubectl get certificate -A
kubectl describe certificate web-tls | tail -20 # 卡住时看 Events
kubectl get certificaterequest,order,challenge -A # 逐层往下查
kubectl get certificate 显示 READY: False 时,按 Certificate → CertificateRequest →
Order → Challenge 一层层 describe 下去,失败原因写在最里面那层的 Events 里。
- HTTP-01 校验失败:Let's Encrypt 要从公网访问
/.well-known/acme-challenge/..., 这要求域名已经解析到你的 VIP 且 80 端口可达。内网服务用 HTTP-01 永远签不下来。 - 泛域名用了 HTTP-01:
*.example.com只能用 DNS-01,这是 ACME 协议的规定。
内网场景的正解是 DNS-01(只需要 DNS API 权限,不需要公网可达), 或者干脆自建 CA 签发内部证书。
Egress:出方向该不该管
默认情况下 Pod 可以访问任何外部地址。这在很多环境里是不可接受的: 被入侵的容器可以往外传数据、可以当跳板扫内网。
三种做法,代价递增:
| 做法 | 怎么实现 | 代价 |
|---|---|---|
| NetworkPolicy 限制出方向 | 白名单允许的目标 | 只能按 IP/端口,域名支持有限 |
| 强制走出口代理 | 只放通代理地址,其它 deny | 要维护代理与白名单 |
| Egress Gateway | 指定几台节点作为统一出口 | 依赖 CNI 支持,多一跳 |
Egress Gateway 还有一个非安全的用途,而且经常是引入它的真实原因: 固定出口 IP。对方要求"把你的 IP 加白名单"时,你不能给出全部节点 IP (节点会扩缩容),需要一个稳定的出口。
# 大意如此:让匹配的 Pod 统一从某个节点、以某个源 IP 出网
apiVersion: cilium.io/v2
kind: CiliumEgressGatewayPolicy
metadata:
name: to-partner
spec:
selectors:
- podSelector: { matchLabels: { app: payment } }
destinationCIDRs: ["203.0.113.0/24"]
egressGateway:
nodeSelector: { matchLabels: { egress-node: "true" } }
egressIP: 10.0.12.200
所有匹配流量都从那几台节点出去,它们就成了故障域和带宽瓶颈。三件事要做:
- 至少两台出口节点,别配单台
- 监控它们的带宽与 conntrack 用量(所有出网连接都记在这里)
- 想清楚它挂掉时的行为:是流量中断,还是回退到直接出网(后者可能是安全问题)
检查点
集群里有 50 个需要对外提供 HTTPS 的服务。最合理的入口方案是?
给内网服务申请 *.internal.example.com 的证书,cert-manager 一直卡在 READY: False。最可能的原因?
合作方要求把你的出口 IP 加白名单,但集群节点会扩缩容。怎么办?
这节课的落点
- 入口三层:LoadBalancer(四层引流)→ Ingress/Gateway(七层路由 + TLS)→ Pod
- Ingress 控制器自己就是一个
type: LoadBalancer;一到两个 VIP 承载所有 HTTP 服务 - Ingress 的表达力只有 Host / Path / TLS,其余靠注解,配置绑死在某个控制器上
- Gateway API 的关键改变是角色分离:平台管
Gateway,业务管HTTPRoute; 灰度、Header 匹配、超时都是标准字段 - 迁移不要二选一:新服务用 HTTPRoute,重注解的老服务放最后
- 证书交给 cert-manager;泛域名只能 DNS-01,HTTP-01 要求公网可达
- 证书卡住按 Certificate → CertificateRequest → Order → Challenge 逐层查
- Egress 默认全放开;限制有三档,而固定出口 IP 常是引入 Egress Gateway 的真实原因
- Egress Gateway 是新单点:至少两台、监控 conntrack、明确故障时的行为
延伸资料
- The Kubernetes Networking Guide ↗
- k8s-in-action
network/ingress-nginx/SKILL.md - k8s-in-action
network/istio/README.md - k8s-in-action
network/cert-manager/SKILL.md