基于Envoy的Kubernetes API网关与Ingress Controller
Emissary-ingress 的前身是 Ambassador,是 CNCF 孵化的 API 网关项目,数据平面同样基于 Envoy。
它和 Contour 都属于「Envoy 系」的入口组件,但定位不同:
Contour 侧重「Ingress Controller」,把入口路由做好做全
Emissary 侧重「API 网关」,在入口之上叠加认证、限流、流量切分等能力
具体到能力清单上,Emissary 更强调这些:
Mapping 资源直接描述「把哪个 URL 前缀映射到哪个服务」,概念上更接近 API 网关而不是 Ingress。Emissary 由两个部分组成:
emissary 数据平面 + 控制平面,基于 Envoy,部署在 emissary 命名空间
emissary-apiext CRD 的转换 Webhook,部署在 emissary-system 命名空间
emissary-apiext 是必须保持运行的组件。它负责在 getambassador.io/v2 与 getambassador.io/v3alpha1 之间做 CRD 版本转换,一旦它挂掉,所有 v3alpha1 的自定义资源都会失效。
Emissary 的 CRD 与主程序是分开发布的,必须分两步走:先装 CRD,再装主程序。
# 方式一:经典 Helm 仓库
helm repo add datawire https://app.getambassador.io
helm repo update
# 1. 先创建命名空间并安装 CRD
kubectl create namespace emissary
kubectl apply -f https://app.getambassador.io/yaml/emissary/3.9.1/emissary-crds.yaml
# 2. 等待 CRD 转换 Webhook 就绪
kubectl wait --timeout=90s --for=condition=available \
deployment emissary-apiext -n emissary-system
# 3. 安装主程序
helm install emissary-ingress --namespace emissary datawire/emissary-ingress
# 4. 等待数据平面就绪
kubectl -n emissary wait --for condition=available --timeout=90s \
deploy -lapp.kubernetes.io/instance=emissary-ingress
4.x 起官方改用 OCI 仓库,CRD 也做成了独立的 chart:
helm install emissary-crds \
oci://ghcr.io/emissary-ingress/emissary-crds-chart --version=4.0.1 --wait
helm install emissary --namespace emissary --create-namespace \
oci://ghcr.io/emissary-ingress/emissary-ingress --version=4.0.1
注意 CRD chart 与主程序会落在不同的命名空间,helm list -A 才能看全。
验证并获取入口地址:
kubectl get all -n emissary
kubectl get all -n emissary-system
kubectl get crd | grep getambassador.io
# 如果 EXTERNAL-IP 一直是 pending,用端口转发代替
kubectl port-forward deployment/emissary-ingress 8080:8080 -n emissary
Mapping 把 URL 前缀映射到 Kubernetes Service,最核心的资源
Listener 声明监听端口、协议与安全模型
Host 虚拟主机,绑定域名与 TLS 证书
TLSContext TLS 配置,可被 Host 或 Listener 引用
Filter 请求/响应过滤器,做认证、限流、改写
FilterPolicy 把 Filter 绑定到具体的 Mapping 上
最小的 Mapping,把 /backend/ 映射到一个 Service:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: backend-mapping
namespace: default
spec:
hostname: "*"
prefix: /backend/
service: backend-svc.default:8080
带重写、超时与重试的 Mapping:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: api-mapping
namespace: default
spec:
hostname: "api.example.com"
prefix: /v1/
service: api-svc.default:8080
rewrite: /
timeout_ms: 30000
idle_timeout_ms: 60000
connect_timeout_ms: 5000
retry_policy:
retry_on: "5xx"
num_retries: 3
per_try_timeout: 5s
按权重做灰度。Emissary 用两个 Mapping 加 weight 实现,权重是相对值:
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: app-v1
namespace: default
spec:
hostname: "app.example.com"
prefix: /
service: app-v1.default:80
weight: 90
---
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: app-v2
namespace: default
spec:
hostname: "app.example.com"
prefix: /
service: app-v2.default:80
weight: 10
按 Header 精确切流给测试用户时,用 precedence 控制优先级(数字小的先匹配):
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
name: app-canary
namespace: default
spec:
hostname: "app.example.com"
prefix: /
service: app-v2.default:80
precedence: 1
headers:
X-Canary: "true"
Host 资源用来声明域名与证书:
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
name: example-host
namespace: default
spec:
hostname: app.example.com
acmeProvider:
authority: none # 用自备证书时设为 none
tlsSecret:
name: example-tls # 必须是 kubernetes.io/tls 类型
要让 Emissary 自己通过 ACME 申请证书,把 acmeProvider.authority 改成 Let's Encrypt 的目录地址并配置 acmeProvider.email。
kubectl create secret tls example-tls --cert=tls.crt --key=tls.key -n default
Emissary 也支持标准的 Ingress,但能力受限于标准 API 的字段:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
kubernetes.io/ingress.class: emissary
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend-svc
port:
number: 80
# 数据平面日志
kubectl -n emissary logs deploy/emissary-ingress --tail=100 -f
# CRD 转换 Webhook 日志,CRD 相关问题的第一站
kubectl -n emissary-system logs deploy/emissary-apiext --tail=100
# 查看所有 Mapping 及其状态
kubectl get mappings -A
kubectl describe mapping backend-mapping
# Envoy 管理接口
kubectl -n emissary port-forward deploy/emissary-ingress 8001:8001
curl -s localhost:8001/clusters
升级必须严格两步走,顺序不能反:
# 1. 先更新 CRD
kubectl apply -f https://app.getambassador.io/yaml/emissary/3.10.0/emissary-crds.yaml
# 2. 再升级主程序
helm upgrade emissary-ingress --namespace emissary datawire/emissary-ingress
# 3. 观察是否全部就绪
kubectl -n emissary rollout status deploy/emissary-ingress
helm install 不会替你创建它们。少了这一步,主程序起来后所有 Mapping、Host 都会创建失败。emissary-apiext 转换 Webhook 必须保持运行。它负责 CRD 的版本转换,Pod 挂掉期间所有 v3alpha1 资源都无法读取或写入。这个组件在 emissary-system 命名空间,不在 emissary,排查时容易漏掉。emissary-apiext 的证书一年后过期且不会自动续期。这是 2.x 与 3.x 上的已知问题,到期后 CRD 操作会开始报 x509 错误。处理办法是删除旧 Secret 并重启:先 kubectl delete --all secrets --namespace=emissary-system,再 kubectl rollout restart deploy/emissary-apiext -n emissary-system。weight 实现的。权重是相对值而不是百分比,两个 Mapping 的权重比就是流量比。写 weight: 90 与 weight: 10 表示九比一,单独一个 Mapping 写 weight: 5 没有意义。precedence 数字越小优先级越高。不指定时 Emissary 会按映射的匹配长度自动排序,一旦手工设置了 precedence 就必须自己保证整组 Mapping 的优先级顺序合理,否则会出现难以理解的匹配结果。tlsSecret 必须与 Host 在同一命名空间,且 Secret 类型必须是 kubernetes.io/tls。用 kubectl create secret generic 建的 Opaque 类型不会被识别,证书静默失效。emissary-ingress,不是 ambassador。Emissary 2.x/3.x 要使用 emissary-ingress 这个 chart,用错 chart 会装出另一套东西。app.getambassador.io 仓库里没有独立的 emissary-crds chart。经典仓库路径下 CRD 只能通过 kubectl apply 那份 emissary-crds.yaml 安装,要做 GitOps 时应当把它作为普通清单纳入,而不是当成 HelmRelease。kubectl — Kubernetes集群管理工具envoy — Emissary的数据平面ingress — Emissary同时支持的标准路由对象contour — 同样基于Envoy的Ingress Controllergateway-api — Emissary逐步支持的标准APIhelm — Kubernetes包管理器