Cilium Cilium

使用 Cilium 的 Gateway API 支持暴露服务

GatewayClass、Gateway 和 HTTPRoute —— 基于主机名的路由、路径匹配、URL 重写和 TLS,无需单独的 gateway Pod。

14 min 中级

Kubernetes Gateway API 是 Ingress 的标准化继任者,Cilium 直接使用自己的 eBPF 数据路径,加上用于七层处理的内嵌 Envoy 来实现它。本指南介绍核心资源 GatewayClass、Gateway、HTTPRoute,以及由它们构建的路由模式,并假设 Cilium 已安装并启用了 Gateway API(参见上一篇指南)。

GatewayClass:声明 Cilium 为可用的控制器

GatewayClass 告诉集群”这个控制器可供 Gateway 使用”:

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cilium
spec:
  controllerName: io.cilium/gateway-controller

如果你在安装时设置了 gatewayAPI.gatewayClass.create: false(如上一篇指南所建议),那么这就是你需要自己创建来填补该空缺的资源。

使用 CiliumGatewayClassConfig 自定义该 class

Cilium 提供了自己的 CRD CiliumGatewayClassConfig,用于应用到使用某个 class 的所有 Gateway 的设置,最常见的是 Gateway 所通过的 Kubernetes Service 类型:

apiVersion: cilium.io/v2alpha1
kind: CiliumGatewayClassConfig
metadata:
  name: gateway-api-config
  namespace: default
spec:
  service:
    type: NodePort
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: cilium
spec:
  controllerName: io.cilium/gateway-controller
  parametersRef:
    group: cilium.io
    kind: CiliumGatewayClassConfig
    name: gateway-api-config
    namespace: default

这里的 NodePort 专门是为迁就 kind 集群而设,下文会介绍;在有真正云负载均衡器可用的集群上,LoadBalancer 是更典型的选择,而本系列后面关于 BGP 路由通告的指南则介绍了如何在完全没有云服务商负载均衡器的情况下获得可路由地址。

创建 Gateway

Gateway 是真正的监听器配置,它引用上面的 class:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: demo-gateway
  namespace: default
spec:
  gatewayClassName: cilium
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      allowedRoutes:
        namespaces:
          from: All
      tls:
        mode: Terminate
        certificateRefs:
          - name: demo-tls-secret

没有 gateway Pod:这正是 Cilium 的不同之处

在大多数 Gateway API 实现中,创建一个 Gateway 会为该 gateway 实例生成专用的 Deployment 和 Pod。而在 Cilium 中不会:流量在节点层面通过 eBPF 被拦截,并按 Gateway 的配置路由,只会出现一个 Kubernetes Service,没有专属于 Gateway 本身的 Pod。

kubectl get gateway demo-gateway
kubectl get service -l gateway.networking.k8s.io/gateway-name=demo-gateway

特别是在 kind 集群上,NodePort Service 默认会得到一个随机端口,而不是你真正想要的端口(前面 kind 集群配置中的 30080/30443)。需要显式打补丁:

kubectl patch service <gateway-service-name> --type='json' -p='[
  {"op": "replace", "path": "/spec/ports/0/nodePort", "value": 30080},
  {"op": "replace", "path": "/spec/ports/1/nodePort", "value": 30443}
]'

这一打补丁步骤纯粹是针对 kind 缺少真正负载均衡器的变通办法,并不是 Cilium 的通用限制。在拥有真正云 LoadBalancer 实现的集群上,或者有 Cilium 自己的 L2 announcements 或 BGP 路由通告(本系列后面的指南)的环境中,Gateway 的 Service 无需手动打补丁就能获得可路由地址。

按主机名路由

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route
  namespace: default
spec:
  parentRefs:
    - name: demo-gateway
  hostnames:
    - "python.example.com"
  rules:
    - backendRefs:
        - name: python-service
          port: 5000

parentRefs 将此路由挂接到 Gateway(它们必须位于同一命名空间,否则路由需要显式的跨命名空间引用策略)。任何带有 Host 头 python.example.com 的请求都会被发送到 python-service。

按路径路由:精确匹配与前缀匹配

  rules:
    - matches:
        - path:
            type: Exact
            value: /
      backendRefs:
        - name: python-service
          port: 5000

Exact 只匹配那个字面路径;对 /status 的请求不会匹配范围限定为 / 的规则。对于单一固定路径之外的任何情况,PathPrefix 匹配给定的前缀,并原样转发路径的其余部分:

  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /

这个区别在实践中很重要:一个路由背后有多个端点的 API 需要使用 PathPrefix,而不是 Exact,否则除了字面匹配的那个路径之外,所有端点都会返回 route-not-found,根本到不了后端。

URL 重写:对外是干净的路径,对内是另一套

一种常见的模式是在一个统一的主机名下,用整洁的路径方案暴露多个后端服务,而每个后端保留自己的内部路由,并不知道这个外部前缀:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: python-route
  namespace: default
spec:
  parentRefs:
    - name: demo-gateway
  hostnames:
    - "example.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /api/python
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /
      backendRefs:
        - name: python-service
          port: 5000

对 example.com/api/python/status 的请求会被 /api/python 前缀匹配,并在到达 python-service 之前被重写为 /status,因此后端本身完全不需要知道外部世界看到的 /api/python 前缀。为每个后端服务定义一条类似的路由(/api/go 指向 Go 服务,依此类推),就可以用若干独立开发的后端构建出一个统一的 API 网关,每个后端都不需要了解网关对外呈现的路径结构。

TLS:两件事必须一致

提供 HTTPS 流量需要两部分配置保持一致:

  1. Gateway 的 HTTPS 监听器(如上所示)引用一个 TLS Secret,并设置 mode: Terminate。
  2. 每个应当接受 HTTPS 流量的 HTTPRoute,其 parentRefs 条目都需要按名称实际指向那个 HTTPS 监听器,而不仅仅是整个 Gateway:
  parentRefs:
    - name: demo-gateway
      sectionName: https

如果没有用 sectionName 特别指向 HTTPS 监听器,路由会默认挂接到其匹配条件所适合的任意监听器,而那可能并不包括你想要的那个。请在应用引用它的 Gateway 之前创建好 TLS Secret:

kubectl create secret tls demo-tls-secret \
  --cert=path/to/cert.pem \
  --key=path/to/key.pem \
  -n default

如果某个监听器没有按预期接收流量,首先应检查 kubectl describe gateway demo-gateway;TLS Secret 缺失或格式错误,是监听器显示为未被接受的常见原因。

热门指南