Skip to content

Gateway API 实施者指南(Implementer's Guide) ​

原文:https://gateway-api.sigs.k8s.io/guides/implementers-guide/

关于构建一个 Gateway API 实现,你想知道的所有事情——只是一直没敢问。

本文档是收集各种"如何编写一个 Gateway API 实现"小贴士和技巧的地方——这些内容在底层类型的 godoc 字段里没有合适的位置可放。

它也意在记录一些指导原则,帮助本 API 的实施者避免常见的错误。

如果你打算作为最终用户使用本 API(而不是基于它构建东西),本文档可能对你帮助不大。

这是一份活文档——如果你发现漏掉了什么,欢迎提 PR!

关于 Gateway API,需要记住的重要事项 ​

希望这些内容对大多人来说都不奇怪,但它们有一些并不明显的含义,我们会在这里一一列出。

Gateway API 是一个 kubernetes.io API ​

Gateway API 使用 gateway.networking.k8s.io API group。这意味着——与核心 Kubernetes 二进制中提供的 API 一样——每次发布时,这些 API 都已经过上游 Kubernetes 评审者的 review,和核心二进制中提供**的 API 一样。

Gateway API 通过 CRD 交付 ​

Gateway API 以一组 CRD 的形式提供,版本受我们的版本策略控制。

该版本策略中最重要的部分是:看起来是同一个对象(即 group、version、kind 都相同)的资源,其模式可能略有不同。我们以兼容的方式进行变更,因此事情通常"就是会工作"。但实现需要做一些动作才能让"就是会工作"更可靠——这些见下。

这种"基于 CRD 交付"的模型也意味着:如果实现尝试在 CRD 尚未安装时使用(即 get、list、watch 等)Gateway API 对象,那么你的 Kubernetes 客户端代码很可能会返回严重的错误。处理这种情况的技巧也见下。

Gateway API 对象的 CRD 定义都包含两个特定的注解:

  • gateway.networking.k8s.io/bundle-version: <semver-release-version>
  • gateway.networking.k8s.io/channel: <channel-name>

"bundle version"和"channel"("发布通道"的简写)的概念在版本化文档中有解释。

实现可以用它们来判断集群中是否安装以及安装了哪个 schema 版本。

Standard 通道 CRD 的变更保持向后兼容 ​

Standard 通道 CRD 的合约的一部分是:同一个 API 版本内的变更必须是兼容的。注意:属于 Experimental 通道的 CRD 不提供任何向后兼容性保证。

虽然 Gateway API 版本策略总体上与上游 Kubernetes API 保持一致,但它也允许"对校验的修正"。例如,如果 API 规范声明某个值无效、但相应的校验未覆盖到该情况,那么未来的发布可能会新增校验来阻止这种无效输入。

这条合约也意味着:实现在 API 版本比它编写时所基于的版本更新时不会失败,因为由 Kubernetes 存储的较新 schema 一定能够被序列化为实现代码中使用的旧**版本。

类似地,如果实现基于一个更高的版本编写,那么它能够理解的新字段值将永远不会被使用——因为它们并不存在于较旧的版本中。

实现规则与指导 ​

CRD 管理 ​

关于如何管理 Gateway API CRD 的信息——包括何时把 CRD 安装与你的实现打包在一起是可接受的——请参考我们的 CRD 管理指南。

一致性与版本兼容性 ​

一个合规的 Gateway API 实现是指通过了每个 Gateway API bundle 版本发布中随附的一致性测试的实现。

一个实现必须****没有任何跳过测试地通过一致性套件才算合规。开发过程中可以跳过测试,但你想要声明为合规的版本必须不跳过任何测试。

扩展特性可以根据 Extended 状态的合约被关闭。

Gateway API 一致性与版本强相关。一个对 N 版本通过一致性测试的实现未必对 N+1 版本通过一致性测试,除非你做相应变更。

实现应当把一致性测试套件生成的报告提交回 Gateway API 的 GitHub 仓库,详细记录他们的测试情况。

一致性套件的输出中包含所支持的 Gateway API 版本。

Union feature 一致性 ​

部分特性只有在与其他特性组合实现时才有意义。我们把这种行为称为 union feature —— 一种辅助地作用于任何与之表面兼容的其他特性的特性。

例如,BackendTLSPolicy 只有在与会把流量转发到后端的 route 或 filter 组合时才有用——TLS 已终止的 route、带后端引用的 route、或像 RequestMirror 这样针对后端 Service 的 filter。类似地,ReferenceGrant 只有在与会进行跨命名空间引用的资源组合时才有意义(跨命名空间引用后端的 route、跨命名空间引用** Secret 的 Gateway 等)。

决定支持一个 union feature 的实现,只要该组合适用,就必须支持它与所报告的每个其他特性的组合——无论那些特性处于 Core 还是 Extended 支持级别。

下面这些例子可以参考:

  • 一个报告支持 BackendTLSPolicy 和 GRPCRoute 的实现必须支持:在一条 GRPCRoute 内使用 BackendTLSPolicy 来针对一个 Service。
  • 一个报告支持 BackendTLSPolicy 和 TLSRouteTerminate 的实现必须支持:在使用 mode=Terminate listener 的 TLSRoute 中,使用 BackendTLSPolicy 来针对一个 Service。
  • 一个报告支持 BackendTLSPolicy 和 HTTPRouteRequestMirror 的实现必须支持:当一条 route 定义了 httproute.spec.rules[].filters.requestMirror.backendRef 为 Service 类型时,使用 BackendTLSPolicy。

依赖 Union feature 行为的特性必须在它们的 Enhancement Proposal 以及 API Reference 中明确说明。

发现 union feature 支持情况 ​

实现通过 GatewayClass.status.supportedFeatures 字段报告它们所支持的特性。这是实施者和用户****了解实现支持哪些特性(从而支持哪些特性组合)的主要机制。

一致性测试套件通过其多特性测试门控自动校验 union feature 组合。每个一致性测试会声明它所要求的特性集。只有当实现声明支持该测试所列的全部特性时,该测试才会运行。这意味着:

  • 如果实现声明同时支持 BackendTLSPolicy 和 GRPCRoute,那么那些要求这两个特性的测试会自动运行——如果这些测试失败,就说明该实现实际上并不支持该组合。
  • 如果实现只声明支持 BackendTLSPolicy 而没有 GRPCRoute,那么这些组合测试会被跳过。
  • 一致性报告的输出会按 profile 展示 supportedFeatures 和 unsupportedFeatures,让用户可以验证哪些组合被校验过。

简而言之,一致性套件让"声称支持某个 union feature 组合"但实际上"未通过其校验测试"变得不可能。

Union features 与一致性 profile ​

Union features 必须被列入****每个该组合适用的一致性 profile 的 ExtendedFeatures 集合中。这确保了:当一个实现对某个 profile 运行一致性测试并声明一个 union feature 时,该 profile 中的组合测试会被执行。

例如,BackendTLSPolicy 适用于任何涉及把流量通过 TLS 转发到后端的 profile。它因此****必须出现在 GATEWAY-HTTP、GATEWAY-GRPC 以及 GATEWAY-TLS profile 的 extended features 中——而不仅仅是某一个 profile。

Union features 可以处于不同的支持级别:

  • Core union features:ReferenceGrant 在所有 Gateway profile 中都是 Core 特性。只要该 profile 被声明,对跨命名空间引用的支持就是强制的。因为它是 Core,所以实现在该 profile 中不能选择不支持它。
  • Extended union features:BackendTLSPolicy 是一个 Extended 特性。实现可以选择不支持它,但如果它们支持了,就必须在它们报告的所有适用的特性组合中都**支持它。

版本兼容性 ​

一旦 v1.0 发布,对于支持 Gateway 和 GatewayClass 的实现,它们必须设置一个新的 Condition:SupportedVersion。status: true 表示所安装的 CRD 版本受支持,status: false 表示不受支持。

标准 Status 字段与 Conditions ​

Gateway API 有很多资源,但在设计时,我们一直努力让不同对象的 status 体验尽可能保持一致——方法是使用 Condition 类型和 status.conditions 字段。

大多数资源都有 status.conditions 字段,但部分资源也有一个命名空间字段,其中包含一个 conditions 字段。

对于后者,Gateway 的 status.listeners 和 Route 的 status.parents 字段是典型的例子——slice 中每个元素都与某部分配置相关联的 Conditions。

对于 Gateway 来说,这样做是为了支持每个 Listener 一组 Conditions;对于 Route 来说,这样做是为了支持每个实现一组 Conditions(因为 Route 对象可能被多个 Gateway 使用,而这些 Gateway 又可能被不同的实现协调)。

所有这些情况中,都有一些相对通用的 Condition 类型,它们的含义也比较相似:

  • Accepted:资源或其部分包含可接受的配置,将在实现控制的底层数据平面中产生一些配置。这并不意味着整个配置都是有效的,只说明有足够的部分是有效的,能产生某种**效果。
  • Programmed:表示一个晚于 Accepted 的阶段——当资源或其部分****被 Accepted 并被配置到底层数据平面之后。用户应当预期配置很快就会准备好接流量。该 Condition 被设置时并不意味着数据平面已就绪,只说明一切都是有效的,它很快就会就绪**。"很快"在不同实现中可能含义不同。
  • ResolvedRefs:该 Condition 表示资源或其部分****中的所有引用都****有效,且****指向的对象既存在也****允许该引用。如果该 Condition 被设置为 status: false,那么资源或其部分中至少有一个引用因某种原因无效**,且 message 字段****应当指明哪一个是无效的。

实施者应当检查每种类型的 godoc,以了解这些 Condition 在每个资源(或其部分)上的具体细节。

此外,上游的 Conditions 结构体包含一个可选的 observedGeneration 字段——实现必须使用该字段,并在生成 status 时把它设置为对象的 metadata.generation 字段。这让 API 的使用者可以判断该 status 是否对当前版本的对象仍然相关**。

TLS ​

TLS 在 Gateway API 中是一个大主题,相关能力还在持续扩展。这里有一份 TLS 指南——它从用户视角更深入地覆盖了这个话题;本节则试图从实施者视角补齐一些空白**。

Listener 隔离(Listener Isolation) ​

在 Gateway 内部,TLS 配置目前仅与 Listener 绑定。为了让这种做法可控,我们鼓励****所有实现朝"完整的 Listener 隔离"这一目标迈进(定义见下):

请求应当至多匹配一个 Listener。例如,如果为 foo.example.com 和 *.example.com 分别定义了 Listener,那么对 foo.example.com 的请求应当****只用**附加到 foo.example.com Listener 上的 Route 来路由(而****不是 *.example.com Listener 上的 Route)。

不支持 Listener 隔离的实现必须****清楚地在文档中说明这一点。未来,我们计划新增 HTTPS Listener 隔离一致性测试,以保证这一行为在声明支持该特性的实现之间保持一致**。最新进展见 #2803。

间接配置 ​

有多种情况下,TLS 证书可能不直接由 Gateway 拥有者管理。虽然这里不打算做穷举性的罗列,但它记录了一些我们期望看到的、用于通过 Gateway API 管理 TLS 证书的做法:

1. 来自其他来源的证书 ​

部分提供方支持把 TLS 证书配置并托管在 Kubernetes 之外。能够连接这些外部提供方的实现,可以在 Gateway Listener 的 TLS option 中暴露**该能力,例如:

yaml
  listeners:
  - name: https
    protocol: HTTPS
    port: 443
    tls:
      mode: Terminate
      options:
        vendor.example.com/certificate-name: store-example-com

在本例中,store-example-com 这个名字指代了由外部 vendor.example.com TLS 证书提供方存储的一张证书的名称。

2. 自动生成的 TLS 证书(稍后填充) ​

许多用户更愿意让 TLS 证书自动为他们生成。一种可能的实现是:有一个控制器监听 Gateway 和 HTTPRoute,自动生成 TLS 证书并把它们挂到 Gateway 上。取决于实现细节,Gateway 拥有者****可能需要在 Gateway 或 Listener 层级做某些配置,以显式开启该特性。例如,假设有人创建了 acme-cert-generator 来按此模式生成证书。该生成器可以选择只在那些 tls.options 中设置了 acme.io/cert-generator(或为整个 Gateway 设置了类似的 annotation)的 Gateway Listener 上生成和填充证书。

注意:这与 [Cert Manager 目前的做法] (https://cert-manager.io/docs/usage/gateway/) 非常类似——但那需要 Gateway 拥有者引用一张 Kubernetes Secret,再由它填充。之所以必须这样做,是因为到 Gateway API v1.1 之前,TLS CertificateRefs 被强制要求指定。

伴随 v1.1 对 Gateway API 校验的放宽,TLS 证书在创建时可以留空,从而让使用自动生成 TLS 证书时配置更少。

3. 由其他角色指定的证书 ​

在部分组织中,应用开发者负责管理 TLS 证书(见角色与身份了解更多关于这一点以及其他角色)。

为了支持这种用例,可以创建一个新的控制器和 CRD。该 CRD 将主机名与用户提供的证书关联起来,然后该控制器将该 CRD 中指定的证书填充到那些主机名匹配的 Gateway listener 上。这也可能得益于** Listener 或 Gateway 级别的显式开启机制。

TLS 扩展的总体指导 ​

在 Gateway API 之上构建 TLS 扩展时,务必遵循以下指导:

  1. 对任何 TLS Option 或 Annotation,都使用带有域名前缀且对你的实现唯一的名称。(例如,使用 example.com/certificate-name,而不只是 certificate-name)。
  2. 不要把证书等敏感信息编码进 option 或 annotation 的值中。改为通过简洁易懂的引用名称**。虽然这些值技术上可以长达 253 字符,但我们强烈建议把值控制在 50 字符以内**,以保持整体可读性和 UX。
  3. 为了支持这些扩展,Gateway API v1.1+ 将不再要求在 Gateway Listener 上指定** TLS 配置。当一个 Gateway Listener 未指定足够的 TLS 配置时,实现****必须把该 Listener 的 Programmed Condition 设置为 False,理由为 InvalidTLSConfig。
  4. 无论你选择支持哪些扩展,都很重要的一点是**:支持****核心的 TLS 配置——这些核心配置旨在所有实现间保持可移植性。扩展在 API 中有其位置,但****所有实现仍****必须支持** API 的****核心能力。

资源细节 ​

对每个当前可用的一致性 profile,都有一组资源是实现应当去协调的。

下面一节会逐个过每种 Gateway API 对象,并说明期望的行为。

GatewayClass ​

GatewayClass 只有一个主要的 spec 字段——controllerName。每个实现被期望申领一个带域名前缀的字符串值(如 example.com/example-ingress)作为它的 controllerName。

实现必须监听所有 GatewayClass,并协调那些 controllerName 匹配的 GatewayClass。该实现必须从这些 controllerName 匹配的 GatewayClass 集合中至少选择一个兼容的 GatewayClass,并通过在该 GatewayClass 上设置 Accepted Condition 为 status: true 来表示它接受**该 GatewayClass 的处理。任何 controllerName 匹配但没有被 Accepted 的 GatewayClass 必须把 Accepted Condition 设置为 status: false。

实现可以只从那一组本可接受的 GatewayClass 中挑出一个(如果它只能协调一个),或者——如果它有能力协调多个 GatewayClass——也可以按需挑出任意数量。

如果某个 GatewayClass 有使其不兼容的内容(在本文撰写时,唯一的可能原因是:存在一个指向实现不支持的 paramsRef 对象的指针),那么该实现应当把不兼容的 GatewayClass 标记为未 Accepted。

Gateway ​

Gateway 对象必须****在 spec.gatewayClassName 字段中引用一个存在、且被某个实现 Accepted 的 GatewayClass,该实现才会去协调它。

已超出协调范围的 Gateway 对象(例如,因为它们引用的 GatewayClass 被删除),实现可以作为删除流程的一部分清除它们的 status,但这不是****必须**的。

Routes ​

所有 Route 对象都有一些共性**:

  • 它们必须****被附加到一个在范围内的父级,该实现才会认为它们可协调。
  • 实现必须为每个在范围内的 Route 更新 status(使用带命名空间的 parents 字段)。详见各 Route 类型的具体说明,但通常包括 Accepted、Programmed 以及 ResolvedRefs Conditions。
  • 已超出范围的 Route 应当****不被更新** status,因为这种更新可能会覆盖任何新的拥有者。observedGeneration 字段会表明剩余的 status 是过时的。

HTTPRoute ​

HTTPRoute 路由的是未加密的、可被检视的 HTTP 流量。这包括在 Gateway 处终止的 HTTPS 流量(因为已被解密),从而让 HTTPRoute 能够使用 path、method 或 headers 等 HTTP 属性来做路由决策。

TLSRoute ​

TLSRoute 使用 SNI 头,对加密的 TLS 流量做路由——不解密该流量流——到对应**的后端。

TCPRoute ​

TCPRoute 把到达 Listener 的 TCP 流路由到给定的后端之一。

UDPRoute ​

UDPRoute 把到达 Listener 的 UDP 包路由到给定的后端之一。

ReferenceGrant ​

ReferenceGrant 是一种特殊资源,它由目标资源所在命名空间的拥有者使用,用以****有选择地允许来自其他命名空间的 Gateway API 对象的**引用。

ReferenceGrant 创建在它所授权被引用资源所在的那个命名空间内,从而允许来自其他命名空间、其他 Kind 或两者的**访问。

支持跨命名空间引用的实现必须****监听 ReferenceGrant,并协调任何指向某个对象的 ReferenceGrant——该对象被某个在范围内的 Gateway API 对象所引用**。

基于 MIT 协议发布