Ingress 是 Kubernetes 的七层入口声明:按域名与路径把 HTTP 或 HTTPS 流量路由到不同 Service,并可终结 TLS。声明本身只是规则,真正干活的是Ingress 控制器——一个需要单独部署的常驻组件(如 ingress-nginx)。没有控制器,Ingress 声明只是账本里的一段安静文字。
Service 解决了"一个入口对应一组副本",但真实业务的入口需求是七层的:不同域名进不同服务、同域名不同路径进不同服务、HTTPS 证书统一管理、灰度按比例分流。LoadBalancer 类型每个服务都要独占一个云 LB,成本与管理的双重浪费。Ingress 把这些七层逻辑收拢成一份声明,一个控制器服务全集群。
先把三层的分工钉死,初学者最容易在这里混:
声明一个双域名双服务的外露入口:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: shop-ingress namespace: production spec: ingressClassName: nginx # 指定由哪个控制器执行 tls: - hosts: [api.shop.cn, admin.shop.cn] secretName: shop-tls # 证书存于 Secret,第6章详述 rules: - host: api.shop.cn # 第一条:订单域名 http: paths: - path: / pathType: Prefix backend: service: name: orders-api port: number: 80 - host: admin.shop.cn # 第二条:后台域名 http: paths: - path: / pathType: Prefix backend: service: name: admin-web port: number: 80
pathType 的两种语义值得记:Prefix 按前缀匹配(/orders 匹配 /orders 与 /orders/123),Exact 要求全等。同域名下"长路径优先"是控制器的通用规则——把 /admin 精确路径写在 / 之前,避免被兜底规则截胡。
提交后验证的固定三板斧:声明状态、控制器地址、真实请求:
kubectl get ingress shop-ingress -n production # NAME CLASS HOSTS ADDRESS PORTS # shop-ingress nginx api.shop.cn,admin.shop.cn 203.0.113.10 80, 443 # ADDRESS 是控制器对外的入口地址 # 把域名解析指向该地址后做真实请求 kubectl run tmp --rm -it --image=curlimages/curl:8.7 -- curl -s https://api.shop.cn/healthz # ok
Ingress 控制器自己就是一个 Deployment 加一个特殊 Service(通常是 LoadBalancer 或 NodePort 类型)。它的运行逻辑是 2.3 节 Watch 机制的又一次应用:监听全集群的 Ingress 对象,把任何变更翻译成自己的运行时配置——对 ingress-nginx 而言就是生成一份 nginx 配置并热加载。你新增一条规则,几秒内全集群生效,不需要碰任何控制器配置。
不同控制器的能力差异主要体现在扩展注解上。常见的几类操作都通过 annotations 声明:
| 注解(ingress-nginx 为例) | 作用 |
|---|---|
| 重写跳转类 | 把外部路径改写为后端实际路径 |
| 限流类 | 按客户端 IP 或连接数限制速率 |
| 超时与重试类 | 定制代理超时、失败重试策略 |
| 灰度类(金丝雀注解) | 按权重或头部把部分流量分给新版本 Service |
灰度注解值得多说一句:它把 4.2 节结尾提到的金丝雀发布变成了纯声明操作——同一个域名配两条 Ingress,注解声明权重 90 比 10,新版本 Service 只接一成流量,观察指标后再调权重或下线旧规则。发布策略从运维脚本挪进了可见、可回滚的声明结构。
⚠️ 常见坑:装了 Ingress 声明却没部署控制器,或者 ingressClassName 写错。声明的账本状态一切正常(get 不报错),但 ADDRESS 列长期为空——规则没有任何人执行。排查顺序:先看 ADDRESS 是否有值,再查控制器 Pod 是否运行。
背景:新版订单查询接口(路径前缀 /v2)要先放 10% 流量验证,其余路径继续走老版本,两版本并存两周。
操作:
# 第一步:两个版本各自有 Service(同标签不同版本由各自 selector 区分) kubectl get svc -n production | grep orders # orders-api ClusterIP 10.96.38.104 80/TCP # orders-api-v2 ClusterIP 10.96.39.211 80/TCP # 第二步:为 v2 配一条带金丝雀注解的 Ingress(同域名同路径) kubectl apply -f orders-canary.yaml # ingress.networking.k8s.io/orders-api-canary created # 注解声明:canary weight 10,其余 90 由主 Ingress 承接 # 第三步:核对分流生效(响应头带版本标记,抽样统计约 1 比 9) kubectl run tmp --rm -it --image=curlimages/curl:8.7 -- \ sh -c 'for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code} " https://api.shop.cn/v2/orders; done' # 200 200 200 200 ... (20 次全部 200,新版无报错)
结果:两周观察期内 v2 错误率与延迟达标,权重逐步调到 100,最后删除金丝雀规则与旧版本 Service,入口声明回到单条。
解读:整个灰度过程没有修改过任何 Pod 或控制器,只有 Ingress 声明的增删调参——流量策略也是一种声明,同样可版本化、可回滚。代价是控制器必须支持对应注解(换控制器要核对能力清单),且两条规则并存期间要小心路径重叠的优先级。
变式:按请求头灰度(只让带特定标记的内部账号进新版)适合功能验收阶段;TCP 或 UDP 的非 HTTP 服务则超出 Ingress 能力范围,要用控制器自定义资源或直接 Service 暴露。
入口与分流已经就绪,最后一个问题是边界:默认情况下集群里任何 Pod 可以访问任何 Pod,这在多团队共用的集群里是裸奔状态。下一节用 NetworkPolicy 给这套流量体系立规矩——谁可以访问 orders-api,orders-api 又只被允许去哪里。