Kubernetes 客户端库现状与演进:2017 贡献者峰会讨论实录
Kubernetes 客户端库现状与演进2017 贡献者峰会讨论实录【免费下载链接】communityKubernetes Community Documentation项目地址: https://gitcode.com/GitHub_Trending/com/community2017 年 12 月 Kubernetes 贡献者峰会上来自 Client 方向的核心维护者围绕客户端库当前状态展开了一场技术讨论议题覆盖 OpenAPISwagger描述能力的局限、JSON/Protobuf 双线协议、SPDY 与 WebSockets 的取舍、kubeconfig 跨语言语义不一致以及 Go/Rust/C 等语言的客户端诉求。本文以本次峰会的讨论实录为主体结合本仓库中 客户端生成指南、API 约定文档 及同峰会姊妹场次笔记系统还原这场讨论的技术脉络帮助读者理解 Kubernetes 客户端生态在 2017 年底的真实痛点以及它们如何塑造了后续 client-go、OpenAPI 3 与各语言客户端的发展方向。讨论背景客户端库结构提案与文档定位本场讨论的故事线承接两份设计文档一份是 api-machinery 方向提出的csi-client-structure-proposal客户端结构提案讨论中明确引用另一份是 kubernetes-client 社区中的clients-library-structure客户端库结构设计文档。二者共同回答了一个基本问题多语言客户端库应当如何组织代码结构、如何与 API 描述体系协同。需要说明的是这份会议记录采用要点速记风格记录的是现场对话的原始结论而非完整教程因此下文按主题整理并补充了仓库中可查证的实现细节。与其配套的还有同峰会的 客户端库开放空间讨论Open Space由 brendandburns 主持聚焦自动生成、OpenAPI 描述状态与流畅化客户端三个高层主题两篇笔记互为补充。流畅化愿景客户端库应拥抱原生语言特性讨论的第一个核心诉求是希望客户端库更加fluent流畅/地道即客户端库的 API 设计应当与目标语言的原生习惯对齐而非简单照搬 Go 客户端的风格。具体表现包括每个平台都应提供符合该语言生态的文档体系例如 Java 客户端的 JavaDoc各语言客户端应提供与语言特性结合的封装而非千篇一律的泛型请求壳。姊妹场次的开放空间笔记进一步点明了这一愿景的可扩展性质疑流畅化必然意味着大量手写代码这与多语言自动生成的路线存在张力——如果每个语言都需要大量手工打磨自动生成的收益就会被稀释。会议现场为此抛出了一个方向性问题能否在保持生成能力的同时为各语言定制出地道的 API 表面开放空间笔记还指出Go 客户端是一个特殊案例它早于 Swagger 工具链存在绕过了自动生成流程完全手写因此成为奇怪的一等公民——它最成熟却最不符合生成管线。当时已有计划将 Go 客户端拆分为两份一份手写、一份自动生成该计划即后来 client-go 与生成型 clientset 分化的雏形可参见 clientset 生成与发布周期文档 中迁移到 client-go的说明。OpenAPISwagger作为描述基石生成方式与固有局限OpenAPI 文档是怎么来的讨论明确了一个关键事实Kubernetes 的 OpenAPI当时即 Swagger描述不是手工维护的而是通过运行一个真实的 api-server 生成的。api-server 自身知道哪些动词可以分派给哪些对象让它把这份分派知识输出为 OpenAPI 文档就能保证描述与实现始终一致。这与 API 约定文档 中Kubernetes API 风格为 RESTful客户端通过标准 HTTP 动词POST、PUT、DELETE、GET创建、更新、删除或获取对象描述的约定是同一套机制的两面动词体系由服务器定义客户端库只是这套动词的外语翻译。OpenAPI 2 的局限与 OpenAPI 3 的期待现场对 OpenAPISwagger的批评集中在OpenAPI 2 存在硬性表达缺陷迫使客户端库写大量丑陋的 workaround。最典型的例子是一次调用的响应类型可能不止一种——例如同一接口可能返回正常结果或错误对象而 OpenAPI 2 难以精确表达这种多返回类型语义客户端必须自行判断响应形状。讨论给出的判断是OpenAPI 3 修复了其中相当一部分局限。开放空间笔记也印证了这一点——客户端库中有大量代码专门用来处理 OpenAPI 2 的局限其中一些在 OpenAPI 3 中已修复问题是要不要全面迁移到 OpenAPI 3以及迁移需要什么条件。此外还涉及生成管线的另一层选择描述源头到底应该是 Go 类型还是 OpenAPI 描述。当时存在两条路线go-restful从 Go 类型出发Kubernetes 当时在用go-swagger从 OpenAPI 描述出发。现场还提出一个折中设想直接给 Go 类型加注解让生成器少做一些猜谜工作。这个思路最终在 client-gen 上落地为// genclient系列标签详见下文动词与对象的语义鸿沟。动词与对象的语义鸿沟Go 语言没有的动词分派概念讨论点出了一个颇具语言哲学意味的难点客户端库要回答哪些动词适用于哪些对象但 Go 语言原生没有这个概念。在 Kubernetes 的 REST 约定里资源对象如 Pod、PersistentVolume天然关联一组动词get、list、create、update、patch、delete、watch 等这是服务器端分派语义但 Go 的类型系统不会自动告诉你Pod 可以 watch、PersistentVolume 没有 namespace这份知识必须由生成器注入。仓库中的 clientset 生成指南 正是这套语义的工程化答案——通过 Go 源码注释标签显式声明动词能力生成器标签含义// genclient生成默认客户端动词函数create、update、delete、get、list、patch、watch且当类型含.Status字段时还会生成 updateStatus// genclient:nonNamespaced所有动词函数不携带 namespace如 PersistentVolume// genclient:onlyVerbscreate,get仅生成列出的动词// genclient:skipVerbswatch生成全部默认动词但跳过 watch// genclient:noStatus即使存在.Status字段也跳过 updateStatus// genclient:methodScale,verbupdate,subresourcescale,input...,result...为子资源生成非标准动词方法并可用 input/result 覆盖默认类型在pkg/apis/${GROUP}/${VERSION}/types.go中给类型打上标签后在 k8s.io/kubernetes 仓库内运行hack/update-codegen.sh即可仓库外使用则需显式指定输入例如$ client-gen --inputapi/v1,extensions/v1beta1 --clientset-namemy_release此外还有// groupName、// groupGoName两个可选标签用于处理 group 名称冲突如policy.authorization.k8s.io与policy.k8s.io会产生两个Policy()方法。手工扩展则通过${TYPE}_expansion.go文件中的 expansion 接口追加生成器不会覆盖这些手写文件——这也是讨论中用注解避免生成器问题设想的直接实践。线协议双轨JSON 之外的 Protobuf 与内容协商客户端如何用 Protobuf 与 api-server 通信讨论给出的答案是直截了当的设置 Content-Type 即可。Kubernetes API 同时接受 JSON 与 Protobuf 两种序列化格式客户端只要在请求头声明对应的媒体类型api-server 就会以 Protobuf 线格式响应。仓库中的 API 约定文档 对此有权威表述输入输出的默认序列化必须是 JSON但内置资源也接受一种 Protobuf 编码。envelope信封封装与 Protobuf 的固有缺陷Protobuf 的引入并非没有代价现场指出了两个关键点Kubernetes 的 Protobuf 定义使用了一个envelope信封类型用来承载类似 storage version存储版本这样的元信息。这与 API 约定文档 的记载完全一致由于 proto 不是自描述的存在一个信封封装器envelope wrapper用于描述内容的类型。Protobuf 不是自描述的因此对 api-server 的 CRUD 操作并不友好——JSON 自带字段名客户端可以自解释而 proto 消息离开 schema 后就无法解读字段含义。现场还提出一个低成本高收益的改进建议在 Protobuf 定义中补充少量额外信息会非常有价值例如复数化pluralisation——即告知生成器资源名的单复数形式否则生成器需要自行推断如 Pod→pods 这类不规则变化。二进制流传输协议SPDY 与 WebSockets 的取舍针对交互式场景exec、attach、portforward、log现场提出了一个明确的倾向性问题是否应该弃用 SPDY、全面转向 WebSockets答案很可能是的。仓库中的 API 约定文档 完整记载了当时的双协议格局可作为这段讨论的技术注脚exec、attach、portforward、log 通过可升级的 HTTP 连接RFC 2817暴露以子资源 动词exec/log/attach/portforward形式提供GET 与 POST 均可触发GET 为了支持浏览器中的 JavaScript流式通道Streamed channels多路二进制流如 shell 的 STDIN/STDOUT/STDERR、多端口转发通过两种帧协议复用到一个 TCP 连接上——一种是基于 SPDY channels 的帧协议另一种是 WebSocket 帧协议每个二进制块前加一个通道号字节WebSocket 还提供可选子协议对字节做 base64 编码、并以字符0/1/2作为通道前缀方便浏览器 JavaScript 使用流式响应Streaming response默认日志输出走 HTTP Chunked Transfer-Encoding可返回任意二进制流但浏览器 JavaScript 对大体积 chunked 响应的访问能力受限因此流式端点支持可选的 WebSocket 升级提供服务器到客户端的单向通道并以二进制 WebSocket 帧分块当时的官方建议是客户端若原生支持 SPDY 应优先使用 SPDY否则回退到 WebSockets但需注意 WebSockets 存在队头阻塞Head-of-Line blocking问题客户端必须顺序读取并逐条处理每个消息。开放空间笔记补充了另一面WebSockets 协议当时文档严重不足而 Kubernetes Java 客户端保存了该协议的最佳描述是研究细节的最佳参考同时明确SPDY 仍在被使用与本次讨论要不要弃用的问题形成呼应。用户对客户端库的诉求清单现场直接征集了你想要什么样的客户端库得到的答案被如实记录诉求说明Go 客户端体积小于 40MB当时 Go 客户端体积较大编译产物对嵌入/分发场景不友好支持动态类型的 Go 客户端现场备注这个是否已存在说明当时对动态类型支持情况存疑Rust 客户端语言社区呼声C 客户端语言社区呼声关于 Service Catalog 等扩展组件的客户端归属问题那些库应该放在哪里开放空间笔记给出了可操作的答案扩展 API 的客户端不要等官方直接用生成器工具生成自己的客户端当时指向 kubernetes-client/gen 工具链同时建议informer 这类机制不应塞进每个语言的客户端库更合理的方向是构建一个通用的 multiwatch 控制器。次年 client-go 讨论clientgo-notes.md也印证了这一判断——其他语言客户端最缺的不是 HTTP 层而是 work queue、informer 这类控制器构件。kubeconfig 语义未标准化跨语言实现的隐性分叉现场点名了一个极易被忽视却后果严重的兼容性议题kubeconfig 的语义从未被标准化不同语言重新实现时会得出不一致的行为。记录中给出的实例是URL 不带 scheme 的写法Go 客户端支持而 Java 客户端不支持——同样的配置文件在两个语言里产生不同结果。开放空间笔记把这个问题挖得更深当时kubeconfig 没有正式规范曾有提案转向kubeconfig.d每个集群一个配置文件或引入新的v2 格式与既有格式并存除 Go 外没有客户端支持合并多个 kubeconfig改动最大的阻力在于存量配置数量巨大迁移成本高更进一步kubeconfig 事实上把几类本不该强耦合的东西打包在了一起身份信息identities、集群列表、命名空间、当前上下文current context。对客户端库开发者而言这段讨论的启示是实现 kubeconfig 解析时不能想当然地参考 Go 实现即可而应以兼容性为第一原则并关注官方是否发布了统一语义。滚动授权证书客户端如何优雅续期讨论提出了另一个现实运维问题客户端应当如何处理滚动更新的授权证书rolling authorisation certificates这关系到长时间运行的控制器与 CI 任务的稳定性——证书会过期、会被轮换客户端若在握手时一次性读取证书并缓存轮换后就会静默失联。该议题在实录中仅被提出而未给出定论说明当时各语言客户端对证书轮换的处理方式尚未收敛属于待标准化的开放问题。类型怪癖IntOrString 与 Quantity讨论最后点出了两个跨语言客户端必须特殊处理的类型褶皱type wrinklesint-or-stringKubernetes 中部分字段典型如 Service 的 port、探针的 port允许传入整数或字符串二选一JSON 层面没有固定类型客户端在强类型语言中需要构造专门的联合类型来建模Quantity资源配额类字段使用带单位后缀的量值如128Mi、1.5Gi无法直接用普通数值类型表达。仓库中的 API 约定文档 为 Quantity 提供了设计依据单位要么显式写在字段名中如timeoutSeconds要么作为值的一部分如resource.Quantity时长字段必须以整数字段 单位后缀的形式表示如leaseDurationSecondsAPI 刻意不用 Go 的 Duration 类型因为那会迫使所有客户端实现 Go 兼容的解析逻辑——这正是客户端库需要各自实现量值解析器的根源。IntOrString与Quantity也因此成为各语言客户端Java、Python、.NET、JavaScript中反复出现的专用类型属于少数类型吃掉大部分兼容性工作量的典型。参与社区Client SIG 的开放姿态实录以一句社区倡议收尾Client SIG 的代表性相对不足但参与门槛很低欢迎大家加入。开放空间笔记也强调了类似观点kubernetes-client 社区是进入 Kubernetes 贡献者体系相对容易的入口值得更公开地宣传。对于想参与客户端生态的开发者本仓库提供了可直接上手的资料clientset 生成指南理解生成管线、API 约定文档理解语义约定以及历年峰会关于客户端库与控制器开发的讨论记录clientgo-notes.md。结语一份讨论实录的技术价值这份 2017 年末的实录之所以至今仍有参考价值在于它准确刻画了客户端库生态的几组长期张力流畅化与自动生成之间的成本权衡、OpenAPI 描述能力与 API 现实之间的差距、JSON 自描述与 Protobuf 高性能之间的取舍、以及 kubeconfig 这类隐性契约缺乏规范的兼容性风险。其中不少讨论OpenAPI 3 迁移、Go 客户端拆分、informers 不进多语言库、WebSockets 逐步取代 SPDY都在后续版本中得到了不同程度的兑现而客户端库应拥抱原生语言特性为生成器补充注解信息警惕 kubeconfig 跨语言不一致等结论对今天编写或维护任何 Kubernetes 客户端库的开发者依然是值得对照检视的经验清单。【免费下载链接】communityKubernetes Community Documentation项目地址: https://gitcode.com/GitHub_Trending/com/community创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考