ARTICLE DETAIL

资讯详情

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

编写 Tekton Resolver 完全指南:从硬编码 Demo 到自定义远程资源解析器

编写 Tekton Resolver 完全指南:从硬编码 Demo 到自定义远程资源解析器 云原生CI/CDDevOps后端【免费下载链接】pipelineA cloud-native Pipeline resource.项目地址https://gitcode.com/gh_mirrors/pipelin/pipeline点击查看免费下载本文以 docs/how-to-write-a-resolver.md 为主体骨架结合仓库中真实的框架源码、官方resolver-template模板与 Git Resolver 实现从零讲解如何编写一个可部署、可验证的 Tekton Resolver。Tekton Resolution 是 Tekton Pipelines 生态中的一项核心能力它允许用户通过pipelineRef/taskRef直接引用存放在远程位置Git 仓库、OCI Bundle、HTTP、Hub 等的Task与Pipeline而 Resolver 正是负责按需拉取远程 YAML 并交还给 Tekton Pipelines的插件程序。本文将以编写一个返回硬编码 YAML 的最小 Resolver 为例完整走一遍从项目初始化、实现framework.Resolver接口、编写部署清单到端到端验证的每一步并深入到仓库源码中解释每个方法背后的路由、校验与返回契约。读完本文你将掌握编写自有 Resolver 的全部要点并能直接复用仓库中现成的模板快速起步。什么是 Resolver在深入代码之前先明确概念Resolver 是一个运行在 Kubernetes 集群中、与 Tekton Pipelines 协同工作的程序它的职责是把远程资源请求解析为具体的Task/PipelineYAML 内容。举例来说如果用户提交的PipelineRun需要引用一个存放在 Git 仓库中的 Pipeline YAML那么负责从 Git 拉取该文件并返回给 Tekton Pipelines 的就是一个 Resolver。这一模式的价值在于可扩展性支持新的版本控制系统、云存储桶或其他存储后端时开发人员只需要编写一个新的 Resolver完全不需要改动 Tekton Pipelines 本身。Resolver 通过实现框架定义的统一接口接入集群框架负责处理控制器样板代码、请求路由、状态更新等公共逻辑见 pkg/remoteresolution/resolver/framework/controller.go 中NewController的注释This sets up a lot of the boilerplate that individual resolvers shouldnt need to be concerned with。如果你想先看现成的完整可运行示例可以直接查看仓库中的 docs/resolver-template/ 目录——它是本文全部代码的落盘版本内置了demo类型的 Resolver 以及对应的部署与测试 YAML。关于如何在PipelineRun中指定远程 Pipeline可参考 docs/pipelineruns.md。前置条件动手之前需要具备以下基础Go 开发能力并大致理解 Tekton Resolution 的工作方式安装了kubectl和ko的电脑一个Kubernetes 1.28集群本地开发使用kind集群即可使用kind时请确保KO_DOCKER_REPO环境变量设置为kind.local一个可推送镜像的镜像仓库集群中已安装Tekton Pipelines 与远程解析能力安装说明见 docs/install.md。需要特别注意的是仓库中真实部署的远程解析器运行在tekton-pipelines-resolvers命名空间见 config/resolvers/resolvers-deployment.yaml其 Deployment 以ko://前缀的镜像启动并对环境变量、ServiceAccount 有约定要求。我们自己的 Resolver 也要遵循同样的约定详见下文部署配置一节。第一步初始化项目结构与普通 Go 服务一样Resolver 本质上就是运行在集群里的一个程序。首先创建项目目录、初始化 Go module 并建立两个子目录$ mkdir demoresolver $ cd demoresolver $ go mod init example.com/demoresolver $ mkdir -p cmd/demoresolver $ mkdir config其中cmd/demoresolver存放 Resolver 的程序代码config目录最终会存放部署到 Kubernetes 的 YAML 清单。仓库中的 docs/resolver-template/ 正是按此结构组织的cmd/demoresolver/与cmd/resolver/分别对应新旧两套框架的完整实现config/demo-resolver-deployment.yaml为部署清单test-resolver-template.yaml为测试用ResolutionRequest。第二步初始化 Resolver 二进制Resolver 本身不需要任何命令行参数或特殊环境变量因此main.go只需极少量样板代码。创建cmd/demoresolver/main.go最新框架推荐package main import ( context github.com/tektoncd/pipeline/pkg/remoteresolution/resolver/framework knative.dev/pkg/injection/sharedmain ) func main() { sharedmain.Main(controller, framework.NewController(context.Background(), resolver{}), ) } type resolver struct {}旧框架已废弃package main import ( context github.com/tektoncd/pipeline/pkg/resolution/resolver/framework knative.dev/pkg/injection/sharedmain ) func main() { sharedmain.Main(controller, framework.NewController(context.Background(), resolver{}), ) } type resolver struct {}这段代码还不能编译先下载依赖# 视 Go 版本不同可能不需要 -compat 标志 $ go mod tidy -compat1.17仓库模板 docs/resolver-template/cmd/resolver/main.go 在主函数中多做了一步通过filteredinformerfactory.WithSelectors(context.Background(), v1beta1.ManagedByLabelKey)为 informer 注入标签选择器再以sharedmain.MainWithContext启动这样控制器只会监听由 Tekton 管理的资源减少不必要的集群事件。虽然我们的最小示例可以省略但这是真实 Resolver 的推荐写法。第三步实现 framework.Resolver 接口此时执行go build -o /dev/null ./cmd/demoresolver会得到如下编译错误cmd/demoresolver/main.go:11:78: cannot use resolver{} (type *resolver) as type framework.Resolver in argument to framework.NewController: *resolver does not implement framework.Resolver (missing GetName method)这是因为我们已经定义了resolver类型但它尚未实现framework.Resolver接口。新版接口定义在 pkg/remoteresolution/resolver/framework/interface.gotype Resolver interface { Initialize(ctx context.Context) error GetName(ctx context.Context) string GetSelector(ctx context.Context) map[string]string Validate(ctx context.Context, req *v1beta1.ResolutionRequestSpec) error Resolve(ctx context.Context, req *v1beta1.ResolutionRequestSpec) (framework.ResolvedResource, error) }旧版接口定义在 pkg/resolution/resolver/framework/interface.go其ValidateParams方法接收的是[]pipelinev1.Param。两套接口的差异详见后文新老框架对照一节。下面逐个实现这些方法。Initialize初始化依赖Initialize在 Resolver 控制器实例化时被调用适合初始化需要的基础库、资源 lister 等前置条件。本示例不需要任何依赖直接返回nil// Initialize sets up any dependencies needed by the resolver. None atm. func (r *resolver) Initialize(context.Context) error { return nil }作为对比仓库中的 Git Resolverpkg/remoteresolution/resolver/git/resolver.go在Initialize中获取 kube client、日志器并初始化了一个 1024 条、TTL 5 分钟的 LRU 缓存与 SCM 客户端工厂——可见这里是干正事的地方。GetName返回 Resolver 名称GetName返回一个字符串名称用于在日志等场景中标识该 Resolver。本示例返回Demo// GetName returns a string name to refer to this resolver by. func (r *resolver) GetName(context.Context) string { return Demo }Git Resolver 的对应实现返回Git见 pkg/remoteresolution/resolver/git/resolver.go 中的gitResolverName常量。GetSelector请求路由标签GetSelector返回一组标签键值对框架据此把请求路由到当前 Resolver。我们只关心tektoncd/resolution这一标签// GetSelector returns a map of labels to match requests to this resolver. func (r *resolver) GetSelector(context.Context) map[string]string { return map[string]string{ common.LabelKeyResolverType: demo, } }common.LabelKeyResolverType定义于 pkg/resolution/common/labels.go其值为resolution.tekton.dev/type。上面的代码意味着任何带有resolution.tekton.dev/type: demo标签的ResolutionRequest对象都会被路由到我们的示例 Resolver。同时需要在文件顶部补充 import最新框架import ( context // Add this one; it defines LabelKeyResolverType we use in GetSelector github.com/tektoncd/pipeline/pkg/resolution/common github.com/tektoncd/pipeline/pkg/remoteresolution/resolver/framework knative.dev/pkg/injection/sharedmain pipelinev1 github.com/tektoncd/pipeline/pkg/apis/pipeline/v1 )旧框架import ( context // Add this one; it defines LabelKeyResolverType we use in GetSelector github.com/tektoncd/pipeline/pkg/resolution/common github.com/tektoncd/pipeline/pkg/resolution/resolver/framework knative.dev/pkg/injection/sharedmain pipelinev1 github.com/tektoncd/pipeline/pkg/apis/pipeline/v1 )Validate / ValidateParams校验请求参数Validate负责检查ResolutionRequest携带的解析规范resolution spec是否合法。我们的示例 Resolver 不期望任何参数因此首先拒绝所有带params的请求同时它期望url的格式为demoscheme://path因此对 URL 的 scheme 与路径进行校验。旧框架中对应方法是ValidateParams。最新框架// Validate ensures that the resolution spec from a request is as expected. func (r *resolver) Validate(ctx context.Context, req *v1beta1.ResolutionRequestSpec) error { if len(req.Params) 0 { return errors.New(no params allowed) } url : req.URL u, err : neturl.ParseRequestURI(url) if err ! nil { return err } if u.Scheme ! demoscheme { return fmt.Errorf(Invalid Scheme. Want %s, Got %s, demoscheme, u.Scheme) } if u.Path { return errors.New(Empty path.) } return nil }别忘了在 import 中加入net/url别名neturl、fmt与errors。注意这里的req.URL字段属于ResolutionRequestSpec——该结构体定义于 pkg/apis/resolution/v1beta1/resolution_request_types.go包含Params []pipelinev1.Param与URL string两个字段其中URL目前处于 ALPHA 稳定性级别。旧框架已废弃// ValidateParams ensures that the params from a request are as expected. func (r *resolver) ValidateParams(ctx context.Context, params []pipelinev1.Param) error { if len(req.Params) 0 { return errors.New(no params allowed) } return nil }需要补充errors包 import。仓库模板的最新版实现 docs/resolver-template/cmd/resolver/main.go 与上述代码一致先拒绝所有参数再通过neturl.ParseRequestURI校验url并强制demoschemescheme。Resolve核心解析逻辑Resolve负责真正干活——拉取文件内容并返回。它接收解析请求的 spec 作为输入返回一个framework.ResolvedResource接口值。由于 Tekton Pipelines 目前只支持通过远程解析获取 Pipeline 资源我们返回一段硬编码的 Pipeline YAML最新框架// Resolve uses the given resolution spec to resolve the requested file or resource. func (r *resolver) Resolve(ctx context.Context, req *v1beta1.ResolutionRequestSpec) (framework.ResolvedResource, error) { return myResolvedResource{}, nil } // our hard-coded resolved file to return const pipeline apiVersion: tekton.dev/v1beta1 kind: Pipeline metadata: name: my-pipeline spec: tasks: - name: hello-world taskSpec: steps: - image: alpine:3.15.1 script: | echo hello world // myResolvedResource wraps the data we want to return to Pipelines type myResolvedResource struct {} // Data returns the bytes of our hard-coded Pipeline func (*myResolvedResource) Data() []byte { return []byte(pipeline) } // Annotations returns any metadata needed alongside the data. None atm. func (*myResolvedResource) Annotations() map[string]string { return nil } // RefSource is the source reference of the remote data that records where the remote // file came from including the url, digest and the entrypoint. None atm. func (*myResolvedResource) RefSource() *pipelinev1.RefSource { return nil }旧框架已废弃// Resolve uses the given params to resolve the requested file or resource. func (r *resolver) Resolve(ctx context.Context, params []pipelinev1.Param) (framework.ResolvedResource, error) { return myResolvedResource{}, nil } // our hard-coded resolved file to return const pipeline apiVersion: tekton.dev/v1beta1 kind: Pipeline metadata: name: my-pipeline spec: tasks: - name: hello-world taskSpec: steps: - image: alpine:3.15.1 script: | echo hello world // myResolvedResource wraps the data we want to return to Pipelines type myResolvedResource struct {} // Data returns the bytes of our hard-coded Pipeline func (*myResolvedResource) Data() []byte { return []byte(pipeline) } // Annotations returns any metadata needed alongside the data. None atm. func (*myResolvedResource) Annotations() map[string]string { return nil } // RefSource is the source reference of the remote data that records where the remote // file came from including the url, digest and the entrypoint. None atm. func (*myResolvedResource) RefSource() *pipelinev1.RefSource { return nil }ResolvedResource 接口与 RefSource 最佳实践ResolvedResource是返回值中需要实现的第二个接口定义同样位于两个框架的interface.go中只有三个方法实现成本很低type ResolvedResource interface { Data() []byte Annotations() map[string]string RefSource() *pipelinev1.RefSource }其中RefSource结构体定义于 pkg/apis/pipeline/v1/provenance.go包含三个字段URI构建定义来源的标识如https://github.com/tektoncd/catalog、Digest内容的密码学摘要集合如{sha1: f99d13e554ffcb696dee719fa85b695cb5b0f428}、EntryPoint进入构建的入口点通常是构建定义文件的路径如task/git-clone/0.10/git-clone.yaml。最佳实践为了支持 Tekton Chains 在 SLSA provenance 中记录远程数据的来源信息Resolver 应当实现RefSource()并返回正确的值而不是nil// RefSource is the source reference of the remote data that records where the remote // file came from including the url, digest and the entrypoint. func (*myResolvedResource) RefSource() *pipelinev1.RefSource { return v1.RefSource{ URI: https://github.com/user/example, Digest: map[string]string{ sha1: example, }, EntryPoint: foo/bar/task.yaml, } }从源码还可以看到框架对返回数据有额外的安全性约束ValidateResolvedResourcepkg/resolution/resolver/framework/interface.go会反序列化Data()的内容确认其apiVersion属于tekton.dev组、kind属于允许的资源类型列表如 Pipeline、Task防止把 token 等非 Kubernetes 数据或 Secret 等非 Tekton 对象写入无特权的ResolutionRequest对象。新老框架对照当前仓库中同时存在两套 Resolver 框架写作时务必分清维度最新框架推荐旧框架已废弃接口路径pkg/remoteresolution/resolver/framework/interface.gopkg/resolution/resolver/framework/interface.go校验方法Validate(ctx, req *v1beta1.ResolutionRequestSpec) errorValidateParams(ctx, params []pipelinev1.Param) error解析方法Resolve(ctx, req *v1beta1.ResolutionRequestSpec) (...)Resolve(ctx, params []pipelinev1.Param) (...)返回类型pkg/resolution/resolver/framework.ResolvedResource复用旧包类型同左对应模板docs/resolver-template/cmd/resolver/main.godocs/resolver-template/cmd/demoresolver/main.go新框架把参数数组升级为完整的ResolutionRequestSpec含Params与URL语义更清晰也让 Resolver 能基于 URL 做出更丰富的校验与解析决策。旧接口仍在源码中存在pkg/resolution/resolver/framework/interface.go 的Resolver接口已标注Deprecated新代码一律使用最新框架。此外旧框架接口文件中还定义了三个可选增强接口值得在进阶时了解ConfigWatcher实现GetConfigName(ctx) string后Resolver 可以从同名 ConfigMap与 Resolver 同命名空间读取管理员配置例如仓库/镜像仓库白名单、请求超时、API 端点等。Git Resolver 即通过GetConfigName返回其配置 ConfigMap 名称见 pkg/remoteresolution/resolver/git/resolver.go。TimedResolution实现GetResolutionTimeout(...)可覆盖单次请求的默认超时默认 1 分钟。注意核心ResolutionRequest协调器对所有请求还有一个全局超时它会覆盖任何 Resolver 特定超时防止配置错误的僵尸请求无限存活。ResolvedResource校验如前所述ValidateResolvedResource确保返回数据是合法的 Tekton 资源。第四步编写部署配置Resolver 需要一份 Deployment 清单才能在 Kubernetes 中运行。完整的配置说明超出短教程范围但核心要点如下告诉 Kubernetes 运行我们的 Resolver 程序附带底层knative框架期望的环境变量应用部署在tekton-pipelines-resolvers命名空间镜像由ko构建使用的 ServiceAccount 为tekton-pipelines-resolvers——这是该命名空间中所有 Resolver 共享的默认 ServiceAccount可在 config/resolvers/ 下的200-role.yaml、200-serviceaccount.yaml、201-rolebinding.yaml中看到其权限配置。完整清单如下将其保存为config/demo-resolver-deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: demoresolver namespace: tekton-pipelines-resolvers spec: replicas: 1 selector: matchLabels: app: demoresolver template: metadata: labels: app: demoresolver spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - podAffinityTerm: labelSelector: matchLabels: app: demoresolver topologyKey: kubernetes.io/hostname weight: 100 serviceAccountName: tekton-pipelines-resolvers containers: - name: controller image: ko://example.com/demoresolver/cmd/demoresolver resources: requests: cpu: 100m memory: 100Mi limits: cpu: 1000m memory: 1000Mi ports: - name: metrics containerPort: 9090 env: - name: SYSTEM_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: CONFIG_LOGGING_NAME value: config-logging - name: CONFIG_OBSERVABILITY_NAME value: config-observability - name: METRICS_DOMAIN value: tekton.dev/resolution securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true capabilities: drop: - all对照仓库中真实部署的 config/resolvers/resolvers-deployment.yaml可以看到几个值得注意的差异与演进镜像引用image: ko://...前缀表示由ko构建并注入镜像模板中对应为ko://github.com/tektoncd/pipeline/docs/resolver-template/cmd/demoresolver见 docs/resolver-template/config/demo-resolver-deployment.yaml。复制模板后需将镜像路径改为你自己 go module 的导入路径。环境变量SYSTEM_NAMESPACE通过 downward API 取自 Pod 所在命名空间CONFIG_LOGGING_NAME/CONFIG_OBSERVABILITY_NAME指向集群中的日志与可观测性 ConfigMapMETRICS_DOMAIN设为tekton.dev/resolution。真实部署还额外设置了CONFIG_FEATURE_FLAGS_NAME、CONFIG_LEADERELECTION_NAME以及健康检查端口PROBES_PORT8080。安全加固securityContext禁用特权提升、只读根文件系统、以非 root 用户运行并丢弃全部 capabilities。真实部署进一步指定runAsUser: 65532与seccompProfile: RuntimeDefault。第五步部署并验证所有代码就绪后用ko构建并部署$ ko apply -f ./config/demo-resolver-deployment.yaml部署成功后kubectl get deployments -n tekton-pipelines应能看到类似输出多出demoresolverNAME READY UP-TO-DATE AVAILABLE AGE controller 1/1 1 1 2d21h demoresolver 1/1 1 1 91s webhook 1/1 1 1 2d21注意真实环境中远程解析器的 Deployment 名为tekton-pipelines-remote-resolvers位于tekton-pipelines-resolvers命名空间本地验证时可用kubectl get deployments -n tekton-pipelines-resolvers观察。接下来创建一个ResolutionRequest来点名我们的硬编码 Pipeline。新建文件test-request.yamlapiVersion: resolution.tekton.dev/v1beta1 kind: ResolutionRequest metadata: name: test-request labels: resolution.tekton.dev/type: demo提交并持续观察$ kubectl apply -f ./test-request.yaml kubectl get --watch resolutionrequests很快就能看到ResolutionRequest的SUCCEEDED列为Trueresolutionrequest.resolution.tekton.dev/test-request created NAME SUCCEEDED REASON test-request True按 Ctrl-C 返回命令行。此时查看ResolutionRequest的 YAML会发现硬编码的 Pipeline YAML 位于status.data字段中只不过它被base64 编码了Data字段的字符串形式定义见 pkg/apis/resolution/v1beta1/resolution_request_types.go$ kubectl get resolutionrequest test-request -o yaml将其还原为可读 YAML$ kubectl get resolutionrequest test-request -o jsonpath{$.status.data} | base64 -d看到apiVersion: tekton.dev/v1beta1, kind: Pipeline, name: my-pipeline的输出就意味着你的 Resolver 已经端到端跑通进阶从 Demo 走向真实 Resolver到此为止你已经从零写出了第一个 Resolver。接下来的扩展方向有三条1. 替换Resolve()的实现把硬编码字符串换成从你选择的存储后端Git、对象存储、HTTP API 等真实拉取内容。注意保持校验与返回契约不变——返回的数据必须是通过ValidateResolvedResource的合法 Tekton 资源。2. 参考完整的 Git Resolver仓库中的 pkg/remoteresolution/resolver/git/resolver.go 是一个功能完备的参考实现展示了生产级 Resolver 的多个要素GetConfigName接入管理员配置、IsImmutable判断 revision 是否为不可变的 commit SHA40 位 SHA-1 或 64 位 SHA-256、GetResolutionTimeout自定义超时、基于 LRU 的 secrets 缓存、以及通过ResolveWithRetry的重试逻辑。它还实现了ConfigWatcher、TimedResolution、ImmutabilityChecker等多个可选接口编译期用var _ framework.Resolver (*Resolver)(nil)等断言保证接口实现正确。3. 端到端跑通一个PipelineRun写一个引用远程解析的PipelineRun让 Tekton Pipelines 真正执行你 Resolver 返回的硬编码Pipeline。模板 docs/resolver-template/README.md 给出了最简单形态apiVersion: tekton.dev/v1beta1 kind: PipelineRun metadata: name: resolver-demo spec: pipelineRef: resolver: demo4. 复用模板快速起步将 docs/resolver-template/ 整个子目录复制到新项目在项目根执行go mod init与go mod tidy仓库内无需此步因为它依赖仓库根部的go.mod/go.sum然后把 docs/resolver-template/config/demo-resolver-deployment.yaml 中的镜像字段改成你自己 module 的导入路径带ko://前缀即可用ko apply -f ./config/demo-resolver-deployment.yaml部署。总结编写一个 Tekton Resolver 的本质就是实现一个只有五六个方法的 Go 接口并把它部署进集群Initialize做初始化、GetName提供日志标识、GetSelector决定请求路由标签、Validate/ValidateParams把关请求合法性、Resolve负责真正拉取并返回ResolvedResource含Data、Annotations、RefSource三个方法。框架本身承担了控制器样板、路由、状态上报与数据校验的公共职责让开发者只需关注从哪里取、怎么取这一核心问题。本文的完整可运行代码即仓库中的 docs/resolver-template/而 pkg/remoteresolution/resolver/git/resolver.go 则展示了生产级 Resolver 的全部进阶姿势可作为下一步深度学习的范本。赞分享云原生CI/CDDevOps后端【免费下载链接】pipelineA cloud-native Pipeline resource.项目地址https://gitcode.com/gh_mirrors/pipelin/pipeline点击查看免费下载相关推荐Tekton Pipelines远程解析实战从Git、OCI到编写自定义Resolver完整指南Tekton Pipelines远程解析实战从Git、OCI到编写自定义Resolver完整指南 Tekton Pipelines 远程解析Remote R云原生CI/CDDevOps后端Tekton Pipeline Git Resolver 完整指南从 Git 仓库远程解析 Task 与 Pipeline 资源Tekton Pipeline Git Resolver 完整指南从 Git 仓库远程解析 Task 与 Pipeline 资源 导读 Git Resolve云原生CI/CDDevOps后端Tekton Pipeline Resolver 模板实战基于 demo 示例从零搭建自定义 ResolverTekton Pipeline Resolver 模板实战基于 demo 示例从零搭建自定义 Resolver 本文以 Tekton Pipeline 仓库中云原生CI/CDDevOps后端创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表