ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Crossplane 如何用 terraform-provider-runtime 生成 Provider:从 Terraform Schema 到托管资源控制器的设计解析

Crossplane 如何用 terraform-provider-runtime 生成 Provider:从 Terraform Schema 到托管资源控制器的设计解析 Crossplane 如何用 terraform-provider-runtime 生成 Provider从 Terraform Schema 到托管资源控制器的设计解析【免费下载链接】crossplaneThe Cloud Native Control Plane项目地址: https://gitcode.com/gh_mirrors/cr/crossplane导读本文基于 Crossplane 仓库中的设计文档 design-doc-terraform-provider-runtime.md深入解析 Crossplane 团队提出的“用代码生成方式从 Terraform schema 元数据自动创建 Provider、CRD 与resource.Managed实现”的技术方案。文章将以该设计文档为核心骨架完整展开其原型架构连接池、插件注入、CRUD API 封装、代码生成器设计Overlays 覆盖机制、元数据配置、Conditions 处理、以及 Secrets/References 等后续工作的三种候选方案并结合当前仓库源码如 apis/core/v2/resource.go 中的引用与策略类型进行印证。读完本文你将理解 Crossplane 如何在运行时借助 Terraform provider 的 gRPC 协议执行资源 CRUD以及这套方案如何为海量小规模云服务快速铺开 Provider 覆盖提供可复制的路径。背景与目标为什么要生成 ProviderCrossplane 的 Provider 覆盖范围扩张速度受制于人工编写成本。为加速这一进程并让那些已有 Terraform provider 实现的小规模云服务能自动获得 Crossplane Provider该设计提出使用**代码生成code generation**技术从 Terraform 的 schema 元数据自动创建Crossplane Reconciler协调器CRDCustomResourceDefinition及与之配套的resource.Managed实现Provider 入口与资源注册代码。一个关键设计决策是最终的实现并不重新发明 CRUD 逻辑而是直接复用 Terraform provider 二进制通过 Terraform 的 gRPC APIplugin protocol执行云端 provider 侧的增删改查。这意味着生成的 Provider 本质上是一个“代理层”把 Crossplane 的协调语义翻译成对 Terraform provider 插件的调用。在构建代码生成软件之前先构建一个“最终生成产物的原型”是工程上的常见做法。本文档正是用来评审这个原型及其支撑库terraform-provider-runtime的设计并勾勒从引导阶段走向可用 alpha 的后续阶段。原型设计概览三个仓库的分工设计文档将原型工作拆分为三个仓库均在研发阶段使用github.com/crossplane作为导入路径仓库职责provider-terraform-gcp生成型 Provider 的示例以 GCP IAM 资源类型为样板展示“生成的 provider 长什么样”terraform-provider-runtime生成型 Provider 的公共库与运行时支撑承载连接池、插件注入与 CRUD 封装terraform-provider-gen代码生成工具链的所在地当时只保留早期原型重构后残留的、对未来生成器有用的片段其中terraform-provider-runtime是整个方案的核心它不针对每种资源生成一个独立的控制器类型而是提供一个通用的ExternalClient运行时将调用分派dispatch到资源特定的插件代码上插件代码通过**依赖注入dependency injection**接入。runtime 库的四个包连接、插件、API 与控制器terraform-provider-runtime由四个包组成各司其职pkg/clientTerraform provider 插件子进程连接池该包封装了管理一批 Terraform provider 插件子进程的细节。这些进程在多种资源类型之间共享并通过 Terraform 插件 gRPC 协议访问。连接的生命周期与协调循环reconcile loop绑定连接在协调循环的Connect方法中从连接池借用borrowConnect同时启动一个 goroutine一旦Reconcile()传入的 context 被取消即本次协调结束就归还连接并清理因此一条连接上的“租约lease”跨越整个Reconcile过程。代码假设 Terraform provider 二进制存放在一个没有其他 provider 实例的路径下这一前提可由容器构建流程保证插件在构建管线生成的镜像中位于固定的规范位置同时提供一个 flag 允许在开发场景下指定不同路径。阻塞行为与超时Terraform API 调用是阻塞式的一次长阻塞操作会把连接从池中取走直到云端返回响应。由于连接会被多个 Kind 的协调循环共享一个慢请求可能耗尽整个池并延迟队列处理。因此设计明确指出有必要为连接池状态提供可观测性visibility以便在队列积压时观察内部状态、排除其他问题。pkg/plugin资源特定代码的注册与访问该包定义了注册与访问依赖注入资源代码所需的类型与接口其核心目标是实现“通用协调逻辑”与“资源特定的序列化/初始化逻辑”的关注点分离并支持多层实现覆盖用户自定义实现可以在不同层级覆盖生成代码——要么覆盖整个资源要么覆盖plugin.Implementation中的单个字段。组件清单如下ProviderInit文档自嘲命名可能不佳它的存在主要是因为Crossplane 的 provider 配置与凭据需要以资源特定的方式翻译给 Terraform 使用。ProviderInit.Initializer被期望内部自行完成查找 CR、执行 Terraform 特定配置翻译、建立 Terraform 插件子进程/连接。它由连接池在资源Connect方法被调用时以**懒初始化lazy init**方式触发。ProviderInit是一个把运行时元数据GVK 与 Scheme同初始化函数映射起来的结构用于注册 provider CRD并传递连接池在既有ExternalConnector接口周边工作所需的一切值。一组单方法接口覆盖资源/provider 特定能力provider 接口 / Initializer / implementation如上所述负责 provider 初始化。compare 接口ResourceMerger封装“合并托管资源的集群侧表示与 provider 侧表示”的逻辑返回值是MergeDescription——一种位掩码风格的类型描述发生了何种变更供控制器决定是否需要更新Spec、Annotations或执行其他如可观测性操作。configure 接口ReconcilerConfigurer负责Reconciler的初始化与注册对生成代码而言这通常是样板代码但这正是替代实现用来覆盖生成实现的字段。representations 接口描述在resource.Managed与cty.ValueTerraform 的原生序列化格式之间进行翻译的方法。plugin.Implementation结构体持有上述一组接口。plugin.ImplementationMerger通过Overlay方法收集一串 ImplementationMerge()生成一个合并结果——从最高可能层级为每个字段挑选非 nil 值。plugin.IndexerImplementationMerger 与单个资源绑定Indexer 负责为每个 GVK 创建对应的 ImplementationMerger并在 Implementation 被覆盖时委托给其Overlay方法。plugin.Index由Indexer.BuildIndex()生成是截至调用时刻所有plugin.Implementation的扁平化表示用于创建 Invoker。plugin.Invoker围绕扁平化 Implementation 的薄封装提供语法糖通过Index()上的InvokerForGVK()获取。pkg/api面向resource.Managed的 CRUD APIAPI 方法直接操作resource.Managed类型让控制器代码把资源视为泛型接口类型而把资源特定方法推入依赖注入的回调。函数签名大致统一为func Create(p *client.Provider, inv *plugin.Invoker, res resource.Managed) (resource.Managed, error)其中*client.Provider是 Terraform provider 子进程连接包装*plugin.Invoker提供调用依赖注入通常为生成函数的能力。CRUD 方法的通用流程从 Terraform provider经 gRPC获取 Schema用CtyEncoder得到资源的 Terraform 原生表示用 cty 编码值与 Terraform 资源名构造 gRPC 请求发起 gRPC 调用到 Terraform provider处理错误用CtyDecoder把 Terraform 响应值翻译回resource.ManagedExternalClient实现拿到结果resource.Managed有时借助ResourceMerger对比/更新本地资源。文档还记录了作者正在考虑的重构方向把 api CRUD 方法做成一个实例化类型以取代 invoker 位置使Invoker类型对ExternalClient隐藏同时MergeDescription可能并入返回值或与resource.Managed一起包装进新返回类型。pkg/controller兑现ExternalClient契约控制器利用 API 包暴露的 CRUD 功能与给定资源的ResourceMerger来履行ExternalClient契约。它采用与既有 Crossplane provider 不同的初始化方案每个生成资源包或用户开发的 overlay必须有一个Index()方法返回该资源的plugin.Implementation映射同一 GVK 的多层 Implementation 通过*plugin.ImplementationMerger合并。main.go短小精悍的入口生成型 provider 的 cmd如 Google 的main.go同样计划由代码生成。由于大部分初始化逻辑已下沉到 controller 包main 非常简洁providerInit : generated.ProviderInit() idxr : plugin.NewIndexer() generated.Index(idxr) idx, err : idxr.BuildIndex() kingpin.FatalIfError(err, Failed to index provider plugin) opts : ctrl.Options{SyncPeriod: syncPeriod} ropts : client.NewRuntimeOptions(). WithPluginDirectory(*pluginDirectory). WithPoolSize(5) log.Debug(Starting, sync-period, syncPeriod.String()) err controller.StartTerraformManager(idx, providerInit, opts, ropts, log)这段示例展示了几个关键运行时配置点插件目录WithPluginDirectory与连接池大小WithPoolSize示例为 5以及同步周期SyncPeriod——这正是前文“固定规范路径 可配置 flag”与“连接池”设计的落地形态。代码生成器Provider 输出与资源输出代码生成器的输出目标被划分为provider 输出与resource 输出两类对provider生成main.go入口、使用提供的plugin.Indexer注册所有注入插件实现层的Index()方法以及PluginInit初始化函数对resource则生成该资源的 CRD Go 类型、resource.Managed实现与插件层代码。Overlays用自定义代码覆盖生成代码为了用自定义用户代码例如既有的手写托管资源控制器覆盖生成的资源代码需要一种元数据格式描述可替换的导入路径——指定与某 CRD 相关的类型可从哪个备选包获取。plugin.ImplementationMerger提供了开放式的设计空间用于实验“覆盖粒度”的合适层级。在下列两种情况下你的代码将替换处理链中的哪些部分取决于指定包的Index()方法返回的 Implementation 中哪些字段非 nil注册级覆盖在配置中指定一个完整限定的包导入路径该包Index()返回的plugin.Implementation通过提供ReconcilerConfigurer来整体取代该资源的生成代码——这允许把生成代码与手写托管资源混搭字段级覆盖修改plugin.Implementation的其他成员例如替换ResourceMerger或为 Implementation 类型增加新字段以承载新能力如 Secrets 处理。文档坦诚这一灵活性仍在实验中需要验证其合理性。两种场景的配置示例resource_overlays: - terraform_name: google_service_account full_package_name: github.com/crossplane/provider-gcp/apis/iam/v1alpha1/MetadataTerraform schema 无法推导的信息有些元数据无法从 Terraform schema 推导必须由配置指定api groupkubernetes 名称可以自动生成一个不错的默认值但可能需要覆盖kubectl 列表展示字段printcolumns。配置示例resource_metadata: - terraform_name: google_service_account api_group: iam.gcp.terraform-plugin.crossplane.io crd_name: ServiceAccount kubectl_printcolumns: - type: string json_path: .spec.forProvider.displayName name: DISPLAYNAME这里api_group: iam.gcp.terraform-plugin.crossplane.io体现了该方案与当前仓库中“provider 独立 API group”惯例的一致性——在 cluster/crds 中可以看到各 provider/扩展的 CRD 均以*.crossplane.io分组如apiextensions.crossplane.io、ops.crossplane.io、pkg.crossplane.io、protection.crossplane.io生成型 Terraform provider 同样遵循terraform-plugin.crossplane.io后缀约定。Conditions两类状态条件的区分Crossplane 资源代码中的 Status conditions 存在两个概念层级存在性existence与就绪性readiness。有些资源在云端确认创建完成即视为就绪——例如 IAM 用户创建请求完成后立即可用而 RDS 数据库、Kubernetes 集群这类资源有比布尔存在更细粒度的状态模型RDS 除Available外还有Creating、Deleting、Unavailable等状态。文档明确生成类型只会设置前一种通用的Readycondition表示资源是否存在于 provider 中更深的就绪语义留待后续工作见下文。后续工作三个待设计的领域以下领域需要额外的设计工作。文档特别指出 Secrets 与 References 的解决方案可能部分重叠而 Conditions 需要更通用的“在协调循环中执行任意用户代码”的方案。Conditions复刻云厂商的细粒度状态模型为复刻 RDS 这类状态模型需要允许自定义代码指定响应字段值与状态模型之间的映射。作者也开放接受以更声明式的方式表达这些 conditions 的提议。Secrets密码的三种处理选项Crossplane 在部分场景会为接受用户指定密码的云 API 生成密码而 Terraform对密码或机密没有任何特殊处理——密码就是字符串且以明文存储在 Terraform state 文件中。文档给出三个选项与 Terraform 一致把密码当作 CRD 中与其他字符串字段无异的字段处理允许引用 Kubernetes Secret把密码字段连接到 k8s Secret 引用扩展 CRD 语义在 CRD 语义中加入“某字段应生成密码”的标记类似于现有部分场景的密码生成行为。作者倾向多数用户会希望自己指定密码这也能把密码生成的职责从系统移除因此建议先采用选项 1再撰写规范讨论选项 2 或 3 如何运作。文档还指出Reconciler/External 的连接发布契约通过从Observe、Create或Update方法返回ConnectionDetails值实现因此该方案可以限定在 Terraform reconciler 范围内。References引用支持的三种路径对资源的某个字段有的用户希望运行时按引用获取值有的希望直接设置还有的依赖composition 填充。既有的资源引用设计要求每个需要引用支持的字段附带两个辅助字段FieldName必须同时有类型为*xpv1.Reference的FieldNameRef。这一点在当前仓库源码中可以直接印证在 apis/core/v2/resource.go 中定义了Reference、NamespacedReference、TypedReference、Selector以及带Resolve/Resolution策略的Policy类型——Resolve支持Always与IfNotPresent默认Resolution支持Required默认与Optional。这种“每字段两个附加字段”的模式对代码生成构成特殊挑战因为Terraform 并不在生成时提供任何元数据表明哪些字段需要引用支持。三个候选方案向代码生成器提供描述引用的结构化元数据需要大量人工或接入其他元数据描述并翻译成公共格式还要为此编写生成器把引用处理回调加入plugin.Implementation方案可通过ReferenceResolver接口接入Reconciler。这与现状实现最接近起步成本最低但会累积“为每个需要引用支持的资源编写引用解析器”的长期债务构建不需要知道资源间如何互相引用的引用实现作者偏好这与 Terraform 的处理方式类似——把“引用”问题推入用户空间语法用于引用嵌套值。一种可能的设计是在 Spec 中增加一个 Reference 块语义与 composition 类似但用于运行时解析。这可能是对 Crossplane 影响最大的方案但每个选项都需要额外的设计与讨论。工作阶段从原型到可用的 alpha设计文档给出了明确的推进路线图阶段时间目标Prototype8 月让原型 100% 跑通 IAM 资源确定需要生成哪些代码Code Generation8–9 月构建代码生成工具为全部 Google 与 AWS 资源复现原型功能适配与已有样例不同的资源在无引用、无 Secrets、无深度就绪检查的前提下覆盖全部资源构建扩展点机制依据核心团队反馈重构References, Secrets9–10 月9 月撰写设计文档并做原型代码生成稳定后不再因测试更复杂资源而产生重大变更于 10 月初开始实现Operability10 月具备基础资源支持后与 alpha 用户协作完成 dogfood MVP重点收集可观测性与调试方面的开发者体验反馈Conditions9 月开始设计更深的 condition 检查设计在 References 与 Secrets 状态满意后开始实现Documentation8–10 月调研如何从 Terraform 文档生成文档可能作为独立项目尤其在研发阶段考察 Terraform 文档仓库并评估集成方式可能受制于代码生成器需先达到能生成 CRD Go 类型源、供 doc.crds.dev 工具消费的程度Integration Testing10 月调研为所有资源生成集成测试确保资源在发布前已验证可 observe/create/update/delete并随测试/QC 进度按块发布每个 provider与当前仓库的关联印证虽然本设计文档属于方案性文档其核心概念与当前 Crossplane 仓库仍有多处可互相印证引用与策略模型文档讨论的*xpv1.Reference与ReferenceResolver在 apis/core/v2/resource.go 中有完整的类型实现含Resolve/Resolution策略说明该设计构建在成熟的资源引用基础之上CRD 生成链路文档目标是从 schema 生成 CRD 及resource.Managed实现当前仓库中apis/apiextensions/v1alpha1/mrd_types.go 的ManagedResourceDefinitionMRD代表了 Crossplane 动态定义托管资源 API 的最新方向其ConnectionDetails字段与文档中ConnectionDetails的返回契约遥相呼应生成的 CRD 也落位于 cluster/crds 目录运行时依赖仓库 go.mod 中声明了对github.com/crossplane/crossplane-runtime/v2的依赖这正是文档中resource.Managed、ExternalClient、Reconciler等概念的来源库Terraform 生态联动设计文档 design-doc-observe-only-resources.md 中也提到了利用 provider-terraform 借助 Terraform 观察资源的思路说明“复用 Terraform 生态”是 Crossplane 持续探索的方向。结语terraform-provider-runtime设计展示了一条“以 Terraform schema 为单一事实来源通过代码生成 依赖注入 连接池复用自动铺开 Crossplane Provider”的务实路径pkg/client管理共享的 Terraform 插件子进程连接池pkg/plugin以多层 Implementation 合并机制支持生成代码与手写代码的灵活混搭pkg/api把 CRUD 语义统一翻译为 Terraform gRPC 调用pkg/controller最终兑现 Crossplane 的ExternalClient契约。对于 Secrets、References、Conditions 等更复杂的语义文档则给出分阶段、可取舍的开放设计。对于希望理解“Crossplane 如何借力 Terraform 生态加速覆盖”的读者这份设计文档及其原型是极具参考价值的起点。【免费下载链接】crossplaneThe Cloud Native Control Plane项目地址: https://gitcode.com/gh_mirrors/cr/crossplane创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表