Envoy 数据面 API 风格指南全解:从 proto 编写规范到扩展接入流程 Envoy 数据面 API 风格指南全解从 proto 编写规范到扩展接入流程【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy本文以 Envoy 仓库 api/STYLE.md 为核心骨架系统讲解 Envoy 数据面 API 的 proto 编写规范、包组织方式、扩展配置接入流程、注解体系与 xDS API 底层设计原则。读完本文你将掌握在 Envoy 生态中编写、审查与提交 API 定义所必需的全部约定并能独立完成一个 v3 扩展配置 proto 从创建到格式化的完整流程。一、为什么 Envoy 需要一套 API 风格指南Envoy 的 api 目录 承载的是驱动 Envoy 运行的数据面 APIData Plane API这些 API 同时被其他代理实现、管理系统与配置生成器所使用因此被视为一套通用数据面 APIuniversal data plane API。既然面向整个生态API 的稳定性和可读性就直接决定了生态的健康发展。API 风格指南的首要目标有两个维持 API 稳定性所有开发者都必须熟悉 API_VERSIONING.md 中定义的版本化准则任何对 API 造成破坏性变更breaking change的 PR 都不会被合并保证可读性一致性让不同开发者、不同扩展提交的 proto 定义在命名、注释、字段组织上保持统一便于机器生成与人工审查。整体风格遵循 Google API 设计指南Google Cloud API Design Guide尤其是其中关于 proto3、命名约定的部分以及 Google 官方的 Protocol Buffers Style Guide。理解这套风格指南本质上就是理解如何为长期演进且不破坏兼容性的分布式系统编写 proto。从源码结构看Envoy API 的每个细节约定几乎都能在仓库中找到对应的强制校验工具tools/proto_format负责格式化与 BUILD 生成api_proto_breaking_change_detector负责破坏性变更检测这保证了风格指南不是纸面文章而是可执行规范。二、proto 定义的核心编写规范2.1 每个 proto 目录都应有 README.md每个 proto 目录下都应提供一个README.md说明该目录内容的用途。例如 api/envoy/service/README.md 用一句话说明了envoy.service包的内容gRPC 与 REST 服务的 Protocol Buffer 定义并提示默认情况下可见性应保持为 nonenone 为默认值。这样的 README 让新贡献者可以快速判断我要找的东西是否在这个目录。2.2 谨慎使用 wrapped scalar typesproto3 中未设置字段会得到默认值0/false/如果某个字段未来可能需要一个与 proto3 默认值不一致的默认值则应使用包装标量类型wrapped scalar types即google.protobuf.wrappers.proto中的UInt32Value、BoolValue、StringValue等。典型场景包括新特性的默认值未来可能发生变化安全缓解措施希望未来默认开启、当前暂不开启。使用包装类型后字段的显式赋值与未赋值可以被区分默认值的演进就不会破坏已部署的配置。2.3 未实现实体用[#not-implemented-hide:]隐藏对尚无 Envoy 实现的字段应在注释中使用[#not-implemented-hide:]的 protodoc 注解。该注解表明该实体未在 Envoy 中实现应当从 Envoy 文档中隐藏避免用户误以为该配置已经生效。2.4 与 work-in-progress 相关的状态标记对于正在开发中work-in-progress的扩展或配置 proto 文档已被[#not-implemented-hide:]隐藏的扩展需要同时在extensions_metadata.yaml中将其status字段设为wip。对于用于核心 API 一部分的 proto 文件、消息或字段如果属于进行中工作且不受威胁模型与破坏性变更策略约束则应分别使用以下注解(xds.annotations.v3.file_status).work_in_progress文件级(xds.annotations.v3.message_status).work_in_progress消息级(xds.annotations.v3.field_status).work_in_progress字段级这相当于对扩展的 wip/alpha 标记但允许对核心 API 中的 proto 就地标记为进行中而无需将其拆到独立文件中。注意当移除文件级work_in_progress注解时还必须同步更新 release notes见 changelogs 目录下的版本化记录结构。2.5repeated字段必须使用复数命名所有repeated字段必须使用复数形式例如filters而不是filter。这条约定与 JSON/YAML 语义的一致性有关——数组在 JSON 中天然表达多个复数命名让配置文件读起来更自然。2.6 单数字段未来可能转 repeated提前用约束限制由于 Envoy 将 JSON/YAML 视为一等公民输入不能轻易把单数字段改成 repeated 字段JSON/YAML 数组结构差异、单复数命名差异都会破坏兼容性这也在 api/API_VERSIONING.md 中被列为破坏性变更。因此如果某字段有合理的未来需要重复的预期但现在不需要可以现在就声明为 repeated并用校验约束强制最大长度为 1。示例来自 api/STYLE.mdrepeated OutputSink sinks 1 [(validate.rules).repeated {min_items: 1, max_items: 1}];这样既保留了未来演进的自由度又不会让当前配置出现歧义。2.7 消息类型与枚举类型使用 UpperCamelCase消息类型与枚举类型必须使用不含嵌入缩写的大驼峰命名例如HttpRequest而不是HTTPRequest或http_request。字段名则保持 snake_case这符合 proto3 的通用约定。2.8 优先用多字段 定义优先级而非布尔重载或 oneof当需要表达多种互斥的取值方式时优先使用多个字段并定义明确的优先级而不是布尔字段重载或oneof。文档给出的正反例推荐写法// Simple path matcher. If regex_path is set, this field is not used. string simple_path 1; // Regex path matcher. If set, takes precedence over simple_path. string regex_path 2;不推荐写法一布尔重载string path 1; bool path_is_regex 2;不推荐写法二oneofoneof path_specifier { string simple_path 1; string regex_path 2; }推荐多字段方案的原因有两点线上更高效单字段直接取值无需解析 oneof 包装演进更平滑未来新增匹配方式时控制平面可以在保持向后兼容的前提下增加新字段。2.9 百分比类型Percent 与 FractionalPercentAPI 提供两种表示百分比的类型定义于 api/envoy/type/percent.protoPercent本质是一个取值在[0.0, 100.0]的double值通过(validate.rules).double {lte: 100.0 gte: 0.0}校验边界FractionalPercent一个整数分数由numerator分子与denominator分母构成同样表示[0.0, 100.0]的百分比。从 percent.proto 源码可以看到DenominatorType枚举支持三个固定分母枚举值数值含义示例HUNDRED0分母 1001/100 1%TEN_THOUSAND1分母 100001/10000 0.01%MILLION2分母 10000001/1000000 0.0001%在性能关键路径上优先使用FractionalPercent因为随机性计算可以只用整数取模与比较操作完成完全不需要浮点转换。大多数用户在这些路径上并不需要无限精度。此外需要注意当分母小于分子时最终百分比会被截断封顶为 1即 100%。2.10 枚举值的设计模式对于枚举类型如果某个枚举值覆盖了绝大多数使用场景应将其作为第一个枚举值且数值为 0。否则第一个枚举值应定义为TYPE_NAME_UNSPECIFIED 0并视为错误。这一设计模式强制开发者显式选择正确的枚举值避免因 proto3 默认值0而误解默认行为。例如 percent.proto 中的HUNDRED 0就属于常用值作首个值的情形。2.11 时间字段使用 well-known types时间相关字段应优先使用google.protobuf.Duration或google.protobuf.Timestamp而不是用原始整数表示秒数。这既保证了单位明确也方便与各语言的时间库互操作。2.12 二进制数据用bytes而非string如果字段要承载原始字节而非人类可读字符串必须声明为bytes类型避免编码转换造成的二义性。2.13 字段按逻辑排序而非按编号排序proto 文件中的字段应按逻辑顺序排列例如先基本配置、再高级配置而不是按字段编号排序。字段编号是线协议层面的稳定标识而文件排版是为了人的阅读体验两者不应混淆。三、包组织Package OrganizationAPI 定义按层级组织在包中自顶向下分层如下包前缀内容示例envoy.extensions所有扩展定义包结构应匹配source目录结构envoy.extensions.filter.networkenvoy.service支撑服务的 gRPC 定义及服务的顶层消息envoy.service.route.v3RDS、envoy.service.listener.v3LDSenvoy.config服务配置、bootstrap 及部分遗留核心类型envoy.config.bootstrap.v3envoy.dataEnvoy 产生的数据类型的数据格式声明envoy.data.accesslog.v3envoy.type通用 protobuf 类型如 percent、range、matcherenvoy.type、envoy.type.matcher.v3扩展应使用常规层级例如网络过滤器network filters的配置属于envoy.extensions.filter.network下的包。从 api/BUILD 的v3_protos与 api/versioning/BUILD 的active_protos依赖列表中可以看到实际的包命名空间严格遵循这一组织方式例如//envoy/extensions/filters/http/router/v3:pkg、//envoy/config/route/v3:pkg。四、向 API 添加扩展配置的完整流程扩展当前必须以 v3 API 形式加入遵循上述 包组织。向 API 添加扩展配置的完整步骤如下对应 api/STYLE.md 的 Adding an extension configuration to the API 一节步骤 1放置 v3 扩展配置 proto 与初始 BUILD 文件将 v3 扩展配置.proto放在api/envoy/extensions或api/contrib/envoy/extensions下例如api/envoy/extensions/filters/http/foobar/v3/foobar.proto并附带初始 BUILD 文件load(envoy_api//bazel:api_build_system.bzl, api_proto_package) licenses([notice]) # Apache 2 api_proto_package( deps [xds//udpa/annotations:pkg], )这里的api_proto_package宏定义在 api/bazel/api_build_system.bzl负责生成对应的 Bazel proto 规则。步骤 2标记 WiP 状态如适用如果该扩展仍是 WiP 且可能发生破坏性变更请标记option (xds.annotations.v3.file_status).work_in_progress true;并可选择用[#not-implemented-hide:]从文档中隐藏。步骤 3导入状态 proto确保 proto 导入 v3 扩展配置状态 protoimport udpa/annotations/status.proto;步骤 4声明为 ACTIVE确保 proto 被跟踪为可投入使用option (udpa.annotations.file_status).package_version_status ACTIVE;这一步是必需的只有声明为 ACTIVE配置 proto 才会被自动纳入 api/versioning/BUILD 的active_protos该文件头部标注 DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py。从 active_protos 可以看到所有活跃开发版本 proto 的完整清单。步骤 5加入 api/BUILD 的 v3_protos在 api/BUILD 的v3_protos下添加对该 v3 扩展配置的引用。步骤 6更新 extensions_metadata.yaml更新 source/extensions/extensions_metadata.yaml 或 contrib/extensions_metadata.yaml补充类别category、安全姿态与状态status依据 EXTENSION_POLICY.md 中的扩展稳定性与安全姿态定义任何加入extensions_metadata.yaml的扩展类别都应在且仅在一个 proto 文件中与某 proto 消息的字段关联例如message SomeMessage { // An ordered list of http filters // [#extension-category: envoy.http.filters] repeated core.v3.TypedExtensionConfig http_filter_extensions 1; }每个加入extensions_metadata.yaml的扩展都应在且仅在一个 proto 文件中标注扩展名例如// [#protodoc-title: Your New Filter] // [#extension: envoy.http.filters.your_new_filter] // YourFilterConfig is the configuration for a YourFilter (write real documentation here). message YourFilterConfig { }步骤 7新增扩展类别时更新 extensions_schema.yaml如果引入了全新的扩展类别还需要将其名称加入 tools/extensions/extensions_schema.yaml 的categories下。步骤 8加入构建配置更新 source/extensions/extensions_build_config.bzl 或 contrib/contrib_build_config.bzl将新扩展纳入构建。步骤 9接入 API 文档导航如未隐藏如果扩展未被隐藏需要查找或创建带 toctree 的文档文件并引用你的 proto确保用户能从 API 文档导航到它同时保证文档构建不失败例如 docs/root/api-v3/admin/admin.rst。步骤 10运行 proto_format.sh 修复格式./tools/proto_format/proto_format.sh fix重要前提运行脚本前必须先commit 本地变更。通过提交工具能识别本次改动并按需重新生成BUILD文件、重新格式化foobar.proto。如果上述步骤有遗漏proto_format.sh可能会删除你新增的某些文件此时可回退到已提交状态待问题解决后重试。步骤 11参考真实 PR 案例仓库维护者建议参考历史 PR 了解向 common 添加新扩展点的完整示例如 key-value-store 扩展点的引入。五、API 注解体系Envoy API 中大量使用注解来携带额外 API 元数据下面按类别逐一说明注解的 proto 定义位于 api/envoy/annotations 下的deprecation.proto与resource.protoxDS 相关注解来自udpa/xds外部依赖。5.1 字段级注解Field Level注解含义[deprecated true]标记某字段在某大版本中已废弃计划在下一个大版本周期移除遵循 CONTRIBUTING.md 中的破坏性变更策略[envoy.annotations.disallowed_by_default true]依据破坏性变更策略默认禁用该字段[(udpa.annotations.field_migrate).rename new field name]标记该字段将在下一个 API 大版本中被重命名为给定名称[(udpa.annotations.sensitive) true]标记敏感字段在日志输出或配置 dump 等场景中应被脱敏PGV 注解protoc-gen-validate提供的字段值约束其中废弃相关注解的定义可以在 api/envoy/annotations/deprecation.proto 中找到它扩展了google.protobuf.FieldOptions与google.protobuf.EnumValueOptions定义了disallowed_by_default与deprecated_at_minor_version等字段字段编号是从注解名的 SHA256 摘要前 28 位推导出的魔术数字。文件中还注明字段被废弃后的一个 Envoy 发布周期内废弃字段会默认被禁用该状态可通过运行时覆盖恢复。5.2 枚举值级注解Enum Value Level[(udpa.annotations.enum_value_migrate).rename new enum value name]表示该枚举值将在下一个 API 大版本中被重命名。5.3 消息级注解Message Leveloption (udpa.annotations.versioning).previous_message_type message type name;用于标记升级后消息的先前类型名。开发者不应手动编写该注解它由protoxform工具自动生成升级路径中的类型重命名与迁移映射由工具链维护。5.4 服务级注解Service Leveloption (envoy.annotations.resource).type resource type name;用于标记 xDS 服务定义的资源类型。定义位于 api/envoy/annotations/resource.proto它扩展了google.protobuf.ServiceOptions提供ResourceAnnotation消息其中的type字段表示资源类型的全限定 Protobuf 类型名。5.5 文件级注解File Level注解含义option (udpa.annotations.file_migrate).move_to_package package name;标记在下一个 API 大版本中该文件将被移动到给定包由protoxform消费option (xds.annotations.v3.file_status).work_in_progress true;标记文件仍处于进行中状态允许破坏性变更六、xDS API 设计原则Principles扩展或修改 xDS API 时必须遵循以下底层原则这些原则定义了 Envoy API 演进的根本约束6.1 传输与数据模型的逻辑区分xDS API 在逻辑上区分两个层面xDS 传输协议xDS-TP描述 xDS 配置资源通过何种网络传输交付给客户端。xDS 提供带 ACK/NACK 反馈的版本化 gRPC 流式协议xDS 配置资源也可以通过其他传输方式如 HTTP、文件系统交付但存在一些限制例如无版本反馈xDS 数据模型描述 xDS 配置资源本身如 listener、route configuration、cluster、endpoint、secret。6.2 客户端与服务端中立性xDS API 在方向上是客户端与服务端中立的。尽管 API 的许多方面反映了其源自 Envoy 控制面 API 的历史但后续 API 决策应体现客户端中立原则。6.3 proto3 为规范形态JSON/YAML 同样受支持xDS API 以 proto3 为规范表达。JSON 与 YAML 也是受支持的格式客户端配置摄入时使用标准的 JSON-proto3 转换。6.4 最终一致性优先xDS API 以最终一致性为先。例如如果 RDS 引用了一个 CDS 尚未提供的 cluster则该引用应被静默忽略在 CDS 更新到达前不转发流量。如果管理服务器能够小心地对 xDS API 排序例如使用 ADS API则可以提供更强的一致性保证——对所有相关资源按[CDS, EDS, LDS, RDS]顺序下发可以避免配置更新期间的流量中断。6.5 面向机器生成与消费同时保持人类可读API 主要用于机器生成与消费管理服务器负责将高层配置概念映射为 API 响应静态配置片段可由模板化工具生成。与此同时API 工件也应保持人类可读以便调试和理解实际应用配置。这意味着 API 不必以人体工学为首要驱动但仍应合理可读。用于生成 xDS 配置的 API 与工具超出了本仓库定义的范围。6.6 传输能力分层所有受支持的传输xDS-TP、HTTP、文件系统都支持基本的单例 xDS 订阅服务 CDS/EDS/LDS/RDS/SDS。高级 API 如 HDS、ADS 与 EDS 多维负载均衡仅限 xDS-TP 使用这避免将复杂的双向流语义映射到 REST 等协议上。6.7 版本化策略版本化遵循 api/API_VERSIONING.md 描述的方案。核心目标是在没有实质性功能、性能、安全或实现简化收益的情况下API 消费者不应暴露于破坏性变更。为此项目愿意在 API 内部保留技术债——例如残留的废弃字段或降低的人体工学——以满足这一原则。6.8 反向 DNS 命名空间扩展、元数据等使用反向 DNS 命名方案例如com.google.widget、com.lyft.widget。客户端内置组件可以用客户端名做前缀例如envoy.foo、grpc.bar。七、与版本化策略的联动风格指南与 API_VERSIONING.md 紧密咬合理解风格指南必须理解其背后的版本化约束包级语义化版本每个包如envoy.config.bootstrap.v3独立版本化大版本体现在包名与目录结构中所有 proto 必须直接位于版本化包命名空间内不允许子包破坏性变更被严格禁止字段不可重新编号或改类型字段与包命名空间的重命名被禁止——这比标准 proto 开发更严格因为 YAML/JSON 与 text proto 的加载依赖字段名gRPC 端点 URL 依赖包名Any中的 type URL 依赖包名此外单数字段升级为 repeated、用 oneof 包装既有字段、收紧 PGV 注解严格度也被视为破坏性变更例外情况新字段/消息引入后 14 天内且未随版本发布允许调整vNalpha版本内允许任意破坏性变更带有work_in_progress注解的 proto 不受破坏性变更策略约束生命周期现实由于 xDS 生态的广泛使用v3 在事实上是 API 的最终大版本并将被永久支持废弃仍会发生作为推荐新配置方式的提示但字段永远不会被移除。正是这些约束反过来解释了风格指南中大量反直觉的约定为什么不能轻易把单数字段改成 repeated2.6 节、为什么重命名被禁止、为什么要用work_in_progress注解来为未稳定 API 留出破坏性变更的空间。八、结语api/STYLE.md 篇幅不长却是 Envoy 数据面 API 生态的宪法它把 proto 编写规范、包组织、扩展接入流程、注解体系与 xDS 设计原则编织成一个自洽的整体其每一条约定都能在 api/BUILD、api/versioning/BUILD、api/envoy/type/percent.proto、api/envoy/annotations 等源码中找到落点并通过tools/proto_format与破坏性变更检测器在 CI 中强制执行。无论你是 Envoy 扩展作者、xDS 管理服务器开发者还是其他数据面实现的维护者遵循这套风格都是参与通用数据面 API 生态的入场券。【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考