基于Envoy的Kubernetes Ingress Controller,提供HTTPProxy与Gateway API能力
Contour 是 VMware 主导、CNCF 孵化的 Ingress Controller,数据平面用的是 Envoy。它在众多 Ingress Controller 里最鲜明的特点是不用注解。
Ingress 标准只覆盖了最基础的 host/path 路由,想让 nginx 做一次重写、加一个超时,就得靠 nginx.ingress.kubernetes.io/* 这类注解。Contour 的做法是另起一套 CRD —— HTTPProxy —— 把这些能力变成有 schema、有校验、有状态反馈的字段:
services[].weight 这样的正式字段,不是注解。timeoutPolicy、retryPolicy 里。kubectl apply 当场报错,而不是静默忽略。status,kubectl describe 就能看到。代价是绑定。HTTPProxy 是 Contour 独有的资源,用它就意味着放弃可移植性。所以 Contour 的定位是:你认同这套 CRD 的设计,并且愿意接受被它锁定。
Contour 在集群里跑两个组件:
contour 控制平面,监听 Ingress / HTTPProxy / Gateway API 资源,翻译成 xDS
envoy 数据平面,实际监听 80/443 并转发流量
两者通过 xDS 通信,也就是说 Envoy 的配置是 Contour 动态下发的,不要手改。
它同时支持三种配置入口:
Ingress 标准 API,兼容但不推荐(能力受限)
HTTPProxy Contour 自有 CRD,能力最全
Gateway API 通过 Gateway Provisioner 提供,是官方的推荐演进方向
三种安装方式,按场景选择:
# 方式一:快速开始清单,自带 Ingress 支持(仅适合测试)
kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
# 方式二:Helm,便于做配置管理
helm repo add contour https://projectcontour.github.io/helm-charts/
helm repo update
helm install my-release contour/contour \
--namespace projectcontour --create-namespace
# 方式三:Gateway Provisioner,用 Gateway API 驱动
kubectl apply -f https://projectcontour.io/quickstart/contour-gateway-provisioner.yaml
验证部署:
kubectl get pods -n projectcontour -o wide
kubectl get ingressclass
# 部署官方示例应用
kubectl apply -f https://projectcontour.io/examples/httpbin.yaml
# 用 Helm 安装时,示例 Ingress 的 ingressClassName 需要改一下
kubectl patch ingress httpbin -p '{"spec":{"ingressClassName": "contour"}}'
# 端口转发到 Envoy 验证(Service 名随安装方式不同)
kubectl -n projectcontour port-forward service/envoy 8888:80
# Helm 安装的 Service 名带 release 前缀,Gateway Provisioner 方式则是 envoy-contour
curl -H "Host: local.projectcontour.io" http://localhost:8888/get
最小的 HTTPProxy,一个虚拟主机加一条路由:
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: basic
namespace: default
spec:
virtualhost:
fqdn: www.example.com
routes:
- conditions:
- prefix: /
services:
- name: frontend-svc
port: 80
带 TLS 的虚拟主机。证书放 Secret,再由 tls.secretName 引用:
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: tls-proxy
namespace: default
spec:
virtualhost:
fqdn: secure.example.com
tls:
secretName: example-tls # 必须与 HTTPProxy 在同一命名空间
routes:
- conditions:
- prefix: /
services:
- name: frontend-svc
port: 80
灰度、超时、重试与负载均衡策略都是正式字段:
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: weighted
namespace: default
spec:
virtualhost:
fqdn: app.example.com
routes:
- conditions:
- prefix: /
timeoutPolicy:
response: 30s
idle: 60s
retryPolicy:
count: 3
perTryTimeout: 5s
retryOn: "5xx,gateway-error,connect-failure"
services:
- name: app-v1
port: 80
weight: 90
- name: app-v2
port: 80
weight: 10
requestHeadersPolicy:
set:
- name: X-Canary
value: "true"
strategy: RoundRobin # 也可用 WeightedLeastRequest、Random、Cookie
healthCheckPolicy:
path: /healthz
intervalSeconds: 5
unhealthyThresholdCount: 3
HTTPProxy 支持把一个虚拟主机的一部分路径委托给别的 HTTPProxy,甚至跨命名空间,这是它相比 Ingress 的重要优势:
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: root
namespace: default
spec:
virtualhost:
fqdn: www.example.com
includes:
- name: app-team-proxy
namespace: team-a # 跨命名空间委托
conditions:
- prefix: /team-a
---
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
name: app-team-proxy
namespace: team-a
spec:
routes:
- conditions:
- prefix: /team-a
services:
- name: team-a-svc
port: 80
委托有三条硬约束:子代理的 conditions 前缀必须落在父代理 include 的前缀范围内;不能形成环(A 委托 B、B 又委托 A 会被判为非法);一个 route 要么委托给别的 HTTPProxy,要么直接列 services,不能两者兼有。
HTTPProxy 会把校验结果写进 status,这是它比 Ingress 好用的地方:
kubectl get httpproxy -A
kubectl describe httpproxy basic
# 直接取校验状态,valid 表示合法,invalid 会附带 description
kubectl get httpproxy basic -o jsonpath='{.status.currentStatus}'
用 Gateway Provisioner 方式安装时,需要再创建 GatewayClass 与 Gateway:
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: contour
spec:
controllerName: projectcontour.io/gateway-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: contour
namespace: projectcontour
spec:
gatewayClassName: contour
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
之后就可以用标准的 HTTPRoute 声明路由,Provisioner 会自动拉起对应的 Envoy 实例。
# 控制平面日志:配置为什么没下发
kubectl -n projectcontour logs deploy/contour --tail=100 -f
# Envoy 日志:请求为什么不通
kubectl -n projectcontour logs deploy/envoy --tail=100 -f
# Envoy 管理接口
kubectl -n projectcontour port-forward deploy/envoy 9001:9001
curl -s localhost:9001/config_dump | jq '.configs | length'
curl -s localhost:9001/clusters
fqdn 在同一个 Contour 实例内必须唯一。两个不同命名空间的 root HTTPProxy 声明了同一个域名,后创建的那个会被判为 invalid,status 里会写明「fqdn already in use」。多团队共用集群时要提前约定域名归属。kubectl apply 会成功,错误信息只写在 status.currentStatus 与 description 里,必须主动去看。养成 kubectl describe httpproxy 的习惯,否则配置错了也毫无察觉。502 Bad Gateway,引用了不存在的 Service 会返回 503 Service Unavailable。Contour 会尽量处理部分合法的配置,所以「一部分能通、一部分不通」是常见现象,要逐条路由排查。envoy 会变成 my-release-contour-envoy,照抄快速开始的 port-forward service/envoy 会报找不到 Service。kubectl — Kubernetes集群管理工具ingress — Contour同时支持的标准路由对象envoy — Contour的数据平面gateway-api — Contour通过Gateway Provisioner支持的标准APIhelm — Kubernetes包管理器