3.9. Ingress 进阶:Contour 与 HTTPProxy
Ingress 只描述“域名和路径指向哪个 Service”,表达不了权重、镜像、重试和超时。Contour 用 Envoy 做数据平面,用 HTTPProxy 把七层流量策略变成类型化资源。本章从 Ingress v1 讲到 HTTPProxy,再完成流量分割与镜像实验。
- 层级:
L3 · 平台
- 难度:
进阶
- 预计:
90 分钟
- 前置:
Service 与 Ingress、TLS 基础
3.9.1. 你将学到
Ingress v1 的
ingressClassName、pathType与 TLS 写法,以及 IngressClass 的作用。Contour 的控制平面/数据平面分工,Envoy 如何通过 xDS 获得配置。
HTTPProxy 的 virtualhost、routes、conditions、services 与流量策略字段。
用权重做流量分割、用 mirror 做影子流量,用 header 条件做灰度路由。
3.9.2. Ingress v1 与 IngressClass
networking.k8s.io/v1 在 v1.19 成为稳定版本,v1beta1 在 v1.22 被移除。v1 的关键变化是:ingressClassName 取代 kubernetes.io/ingress.class 注解,pathType 成为必填,后端改成嵌套的 service.name 与 service.port.number。pathType: Prefix 按路径段匹配,Exact 要求完全相等,ImplementationSpecific 则交给控制器解释。
IngressClass 描述控制器的名字与参数。集群里可以有多个 Ingress Controller,Ingress 通过 ingressClassName 选择其中一个;给 IngressClass 加 ingressclass.kubernetes.io/is-default-class: "true" 可以让未写 class 的 Ingress 落到它身上。Contour 的默认类名是 contour,对应 controller projectcontour.io/ingress-controller。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: demoapp, namespace: dev}
spec:
ingressClassName: contour
tls:
- {hosts: ["www.ilinux.io"], secretName: tls-ingress-www-ilinux}
rules:
- host: www.ilinux.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demoapp
port: {number: 80}
原书的 ingress-v1-demo.yaml 还留着 kubernetes.io/ingress.class 注解,同时没有写 ingressClassName。v1.31 上这样创建通常不会报错,但控制器可能根本不接管;迁移时把注解删掉,补上 ingressClassName。
3.9.3. Contour 架构
Contour 由两部分组成。控制平面是 projectcontour/contour Deployment,它 watch Ingress、HTTPProxy、Service、EndpointSlice 和 Secret,把期望的路由、集群、监听器计算成 Envoy 配置,通过 gRPC xDS 在 8001 端口下发给数据平面。数据平面是 envoy DaemonSet,直接监听节点的 80/443 或通过 NodePort/LoadBalancer Service 暴露,负责真正的 TLS 终止、路由和转发。
两者之间还有一份证书:contour-certgen Job 生成 contourcert 与 envoycert,contour 在 xDS 服务端启用 mTLS,Envoy 作为客户端出示证书,避免任何人伪造配置。ConfigMap contour 里的 contour.yaml 控制访问日志格式、最低 TLS 版本、fallback 证书等全局行为。
仓库里的 contour.yaml 是 v1.6.1 的整包清单,CRD 仍是 apiextensions.k8s.io/v1beta1,在 v1.31 上 apply 会失败。它适合阅读架构,不要直接用于新集群;安装时用官方 quickstart 或对应版本的 release manifest。
3.9.4. HTTPProxy 资源模型
HTTPProxy 是 Contour 自己的 CRD,比 Ingress 多出流量治理能力。一个 HTTPProxy 对应一个 virtualhost,fqdn 同时用于 Host 匹配和 TLS SNI;routes 按条件匹配请求,services 指定后端和权重。
字段 |
作用 |
|---|---|
|
域名,也是证书 SNI |
|
同命名空间的 TLS Secret,Envoy 在此终止 TLS |
|
不终止 TLS,按 SNI 转发 TCP |
|
|
|
后端 name/port,可带 |
permitInsecure: true 允许同一 fqdn 同时提供 HTTP 与 HTTPS;默认只接受 HTTPS。TLS Secret 必须与 HTTPProxy 同命名空间,跨命名空间引用要靠 TLSCertificateDelegation 显式授权。
3.9.5. 流量治理
路径与头路由:conditions 里写 prefix: /api 按路径匹配,写 header 按请求头匹配。多个 header 条件写在同一列表里是 AND 关系;没有条件的 route 作为兜底。把更具体的路由放在前面,兜底路由放最后,可读性最好。
流量分割与镜像:多个 service 的 weight 是相对权重,不是百分比;90/10 表示大约 9:1。mirror: true 的服务会收到请求的副本,但它的响应被丢弃,客户端只看到主服务的响应,适合在上线前用真实流量验证新版本。
重试、超时与健康检查:retryPolicy.count 是最大重试次数,perTryTimeout 限制单次尝试;timeoutPolicy.response 限制整个响应时间,idle 限制空闲连接。healthCheckPolicy 是 Envoy 主动探测,和 Kubernetes 的 readinessProbe 相互独立,两者都通过流量才会转发。
TLS 与 passthrough:常规模式由 Envoy 用 Secret 终止 TLS,后面走明文 HTTP;passthrough 模式只按 SNI 转发 TCP,Envoy 看不到 HTTP 内容,因此权重、头路由、重试全部失效。后端必须自己处理 TLS,适合 Kubernetes Dashboard、Redis 这类端到端加密服务。
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata: {name: dashboard, namespace: kubernetes-dashboard}
spec:
virtualhost:
fqdn: dashboard.ilinux.io
tls: {passthrough: true}
tcpproxy:
services:
- {name: kubernetes-dashboard, port: 443}
3.9.6. 与 ingress-nginx 的对比
维度 |
ingress-nginx |
Contour |
|---|---|---|
配置模型 |
大量 annotations |
HTTPProxy CRD + Ingress |
数据平面 |
NGINX |
Envoy |
流量分割 |
canary 注解 |
service |
流量镜像 |
mirror 注解 |
service |
TLS passthrough |
|
|
ingress-nginx 的强项是生态成熟、注解覆盖广、调试资料多;Contour 的强项是类型化配置、动态更新和 Envoy 的 L7 能力。已有大量 nginx 注解的团队不必为了分流迁移,新建平台且需要精细流量治理时 Contour 更合适。
3.9.7. 动手实验
实验一:用 HTTPProxy 做 90/10 流量分割
目标:部署 Contour 与两个版本的 demoapp,把 90% 请求给 v1.0、10% 给 v1.1。
步骤 1 · 安装 Contour
kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
kubectl -n projectcontour rollout status deploy/contour
步骤 2 · 部署两个版本
kubectl create namespace dev
kubectl -n dev create deployment demoapp --image=ikubernetes/demoapp:v1.0
kubectl -n dev create deployment demoappv11 --image=ikubernetes/demoapp:v1.1
kubectl -n dev expose deployment demoapp --port=80 --name=demoapp
kubectl -n dev expose deployment demoappv11 --port=80 --name=demoappv11
步骤 3 · 创建 HTTPProxy
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata: {name: demoapp-split, namespace: dev}
spec:
virtualhost: {fqdn: www.ilinux.io}
routes:
- conditions: [{prefix: /}]
services:
- {name: demoapp, port: 80, weight: 90}
- {name: demoappv11, port: 80, weight: 10}
kubectl -n dev apply -f httpproxy-traffic-splitting.yaml
kubectl -n dev get httpproxy
kubectl -n projectcontour port-forward svc/envoy 8080:80 &
步骤 4 · 验证比例
for i in $(seq 1 20); do
curl -s -H 'Host: www.ilinux.io' http://127.0.0.1:8080/; echo
done | sort | uniq -c
预期大约 18 条来自 v1.0、2 条来自 v1.1。权重是概率,样本越小偏差越大;把权重改成 50/50 再跑 100 次,比例会更接近。kubectl -n dev describe httpproxy demoapp-split 可以看到 Valid 状态和每条 route 的后端。
实验二:用 mirror 复制线上流量
目标:把线上流量复制一份给 v1.1,观察镜像请求不影响主响应。
步骤 · 用 mirror 复制流量
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata: {name: demoapp-mirror, namespace: dev}
spec:
virtualhost: {fqdn: www.ilinux.io}
routes:
- conditions: [{prefix: /}]
services:
- {name: demoapp, port: 80}
- {name: demoappv11, port: 80, mirror: true}
先删掉实验一的 HTTPProxy,避免同一个 fqdn 冲突:
kubectl -n dev delete httpproxy demoapp-split
kubectl -n dev apply -f httpproxy-traffic-mirror.yaml
kubectl -n dev logs -l app=demoappv11 --tail=10
客户端仍然只收到 v1.0 的响应,但 v1.1 的日志里会出现相同请求。镜像流量不参与响应,也不会因为镜像后端失败而影响主请求。
3.9.7.1. 验证清单
kubectl -n projectcontour get pods中 contour 与 envoy 均 Running,Envoy Service 有地址。kubectl -n dev get httpproxy的 STATUS 为Valid,FQDN 与预期一致。20 次请求中 v1.0 与 v1.1 的比例接近 9:1,且调整权重后比例随之变化。
镜像模式下客户端只收到 v1.0,v1.1 日志能看到复制请求。
3.9.8. 常见问题与排错
现象 |
可能原因 |
处理方式 |
|---|---|---|
Ingress 创建成功但 404 |
没写 |
|
HTTPProxy STATUS 为 |
service 名/端口错误、fqdn 冲突、TLS Secret 缺失 |
|
Envoy 返回 503 |
后端 Endpoints 为空,或主动健康检查全部失败 |
查 Service selector 与 Endpoints;看 |
TLS 握手失败 |
Secret 不在同一命名空间、证书与 fqdn 不匹配 |
查 |
passthrough 无法按路径/头路由 |
TCP 模式看不到 HTTP,按 SNI 整条转发 |
这是预期行为;需要 L7 路由就必须在 Envoy 终止 TLS |
3.9.9. 进阶挑战
给 HTTPProxy 加
timeoutPolicy.response: 2s、retryPolicy.count: 3和健康检查,用一个故意慢 3 秒的后端观察重试与超时。用
TLSCertificateDelegation让dev命名空间引用cert-manager命名空间的证书,再配permitInsecure: false强制 HTTPS。
3.9.10. 小结
Ingress v1 用
ingressClassName、pathType和嵌套 backend;IngressClass 决定哪个控制器接管。Contour 控制平面把对象翻译成 Envoy 配置,通过 xDS 下发;数据平面只负责转发。
HTTPProxy 用 weight 做分割、mirror 做影子流量、header/prefix 做路由,并补齐重试、超时与健康检查。
3.9.11. 延伸阅读
Ingress 与 MetalLB 的基础见负载均衡与 Ingress 流量入口。