Argo CD ApplicationSet Plugin Generator 完全指南:用自定义插件动态生成 Application

Argo CD ApplicationSet Plugin Generator 完全指南:用自定义插件动态生成 Application Argo CD ApplicationSet Plugin Generator 完全指南用自定义插件动态生成 Application【免费下载链接】argo-cdDeclarative Continuous Deployment for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/ar/argo-cd导读Plugin Generator插件生成器是 Argo CD ApplicationSet 提供的七种内置生成器中最特殊的一种它不内置任何固定的数据源逻辑而是通过一个标准的 HTTP RPC 接口把从何处取数、如何取数完全交给用户自己实现从而让 ApplicationSet 可以基于任意数据源数据库、CI 系统、镜像仓库、内部 API……动态创建 Argo CD Application。本文以 docs/operator-manual/applicationset/Generators-Plugin.md 为骨架结合本仓库中 Plugin 生成器实现、HTTP 客户端 与 测试用例 等源码系统讲解其工作原理、完整配置、密钥管理、插件协议实现以及如何与 Matrix / Pull Request 生成器组合出只有构建成功才会生成标签的实战方案。为什么需要 Plugin GeneratorArgo CD ApplicationSet 内置的其他生成器都遵循既定逻辑Cluster generator 通过argocd.argoproj.io/secret-type: cluster标签选择集群 Secret将其展开为name、server、project、metadata.labels.key等参数Git generator 基于 Git 仓库的目录、文件与 GitLab/GitHub 分组展开参数List、Pull Request、SCM Provider、Cluster Decision Resource 等生成器同样各有固定取数逻辑。Plugin Generator 则完全不同它把取数逻辑作为一个黑盒交给插件服务只定义一个最小的 HTTP 契约。这使得它具有以下能力边界任意语言实现插件只需是一个响应 HTTP 请求的服务Go、Python、Node.js 等皆可协议极简插件只响应 RPC 式 HTTP 请求无需引入任何 SDK灵活部署既可以作为 Sidecar 与应用集控制器同 Pod 部署也推荐作为独立 Deployment 部署交付周期短不需要等待上游 3-5 个月的 review、merge 和 Argo CD 发版周期今天写完今天就能用可组合性可与 Matrix generator 或 Merge generator 组合把其他生成器产出的参数作为插件的输入。[!NOTE] GitOps 精神边界 官方文档特别提示插件的存在不应削弱 GitOps数据外置到 Git 之外的精神其定位是在特定场景下做补充。典型例子是 Pull Request 生成器只能拿到 commit hash无法得知 CI 构建是否成功、镜像 digest 是什么此时用插件从独立数据源如镜像仓库 API取回这些参数就能补足生成器的能力。Plugin Generator 的完整工作流程从源码与官方文档可以完整还原其执行链路。整体流程如下ApplicationSet 控制器每隔requeueAfterSeconds默认 30 分钟向插件的baseUrl发送一次 HTTP POST 请求请求体包含 ApplicationSet 中定义的input.parameters插件服务接收请求读取输入参数执行自定义逻辑查询数据库、调用 CI API、计算 digest 等构造一个输出参数对象列表插件将参数列表封装在响应中返回给 ApplicationSet 控制器控制器遍历每个参数对象用它填充 ApplicationSet 的template逐个创建/更新 Argo CD Application由此实现基于用户自定义模板 参数 逻辑的 Application 动态创建。在源码层面这条链路由 PluginGenerator.GenerateParams 驱动它先调用getPluginFromGenerator从 ConfigMap 读取baseUrl/token/requestTimeout并构造插件客户端再调用pluginClient.List(ctx, providerConfig.Input.Parameters)发起 RPC最后把返回的参数列表经过generateParams与模板渲染逻辑组合成最终参数集。其中两个关键的源码细节值得注意请求路径是写死的HTTP 客户端在 plugin_service.go 中固定使用POST api/v1/getparams.execute这就是插件唯一需要实现的路由请求体结构ServiceRequest携带applicationSetName方便插件侧日志定位与input即input.parameters映射ServiceResponse的output.parameters是一个对象列表如 client.go 所示HTTP 客户端默认超时 30 秒、User-Agent 固定为argocd-applicationset并以Authorization: Bearer token头携带预共享令牌。快速上手一个最简单的 Plugin Generator 示例定义 ApplicationSet使用插件生成器但不与 Matrix/Merge 组合时一个最小示例继承自官方文档如下apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: myplugin spec: goTemplate: true goTemplateOptions: [missingkeyerror] generators: - plugin: # 指定存放插件配置的 ConfigMap configMapRef: name: my-plugin # 可以向插件传递任意参数。input.parameters 是 map值可以是任意类型。 # 这些参数在生成器的输出中可以通过 generator.input.parameters 键访问。 input: parameters: key1: value1 key2: value2 list: [list, of, values] boolean: true map: key1: value1 key2: value2 key3: value3 # 也可以在生成器输出上附加任意值模板中通过 values 键访问。 values: value1: something # 使用 Plugin 生成器时ApplicationSet 控制器按 requeueAfterSeconds # 间隔轮询默认每 30 分钟以检测变化。 requeueAfterSeconds: 30 template: metadata: name: myplugin annotations: example.from.input.parameters: {{ index .generator.input.parameters.map key1 }} example.from.values: {{ .values.value1 }} # 其余输出由插件决定 example.from.plugin.output: {{ .something.from.the.plugin }}字段说明configMapRef.name包含插件 RPC 调用配置的ConfigMap名称input.parameters随 RPC 调用传给插件的输入参数可选值是任意类型的 mapvalues直接附加到生成器输出、不随 RPC 发送给插件的键值对requeueAfterSeconds轮询间隔单位秒。从类型定义看PluginGenerator 结构体 恰好对应这四个字段ConfigMapRef、Input、RequeueAfterSeconds、Template、Values其中注释明确说明Values不会作为参数发给插件。而 plugin.go 中的GetRequeueAfter表明未设置时使用DefaultPluginRequeueAfter 30 * time.Minute源码中的默认常量设置后按秒转换。添加 ConfigMap 配置插件访问apiVersion: v1 kind: ConfigMap metadata: name: my-plugin namespace: argocd data: token: $plugin.myplugin.token # 也可以写成 $某个_K8S_secret:plugin.myplugin.token baseUrl: http://myplugin.plugin-ns.svc.cluster.local. requestTimeout: 60token用于 HTTP 请求认证的预共享令牌指向你在argocd-secretSecret 中创建的键baseUrl集群内暴露插件的 Kubernetes Service 地址requestTimeout请求插件服务的超时时间秒默认 30。源码 getConfigMap 会对 ConfigMap 做强校验baseUrl与token缺失或为空会直接报错getToken 要求token必须以$开头指向 Secret 键否则同样报错。requestTimeout通过strconv.Atoi解析为整数后传入 HTTP 客户端构造见 plugin_service.go。存储凭据令牌存放在argocd-secretSecret 中值必须只做一次base64 编码以下值对应printf strong-password | base64apiVersion: v1 kind: Secret metadata: name: argocd-secret namespace: argocd labels: app.kubernetes.io/name: argocd-secret app.kubernetes.io/part-of: argocd type: Opaque data: # ... plugin.myplugin.token: c3Ryb25nLXBhc3N3b3Jk # ...备选方案把敏感数据放在另一个 Secret 中如果不想把令牌放进argocd-secretArgo CD 也支持把敏感数据存放在另一个KubernetesSecret中只要 ConfigMap 中某个值以$开头控制器就会去对应的 Secret 中查找键。语法$k8s_secret_name:该_secret_中的_键名[!NOTE] 该 Secret 必须带有标签app.kubernetes.io/part-of: argocd。示例another-secretapiVersion: v1 kind: Secret metadata: name: another-secret namespace: argocd labels: app.kubernetes.io/part-of: argocd type: Opaque data: # ... # 像下面这样存放客户端密钥 # 值必须只做一次 base64 编码 # 此值对应 printf strong-password | base64 plugin.myplugin.token: c3Ryb25nLXBhc3N3b3Jk对应 ConfigMap 中的写法即token: $another-secret:plugin.myplugin.token。源码层面ParseSecretKey 负责解析若键包含:则拆出 Secret 名称与键名否则默认落在argocd-secret上随后控制器在自身命名空间内client.Get读取该 Secret 并取出对应键值测试用例 中的$plugin.token与argocd-secret组合正是对这一解析逻辑的验证。实现插件HTTP 服务协议与 Python 示例插件既可以作为 Sidecar 部署也可以作为独立 Deployment 部署官方推荐后者。以下 Python 示例假设令牌存放在/var/run/argo/token文件中strong-passwordimport json from http.server import BaseHTTPRequestHandler, HTTPServer with open(/var/run/argo/token) as f: plugin_token f.read().strip() class Plugin(BaseHTTPRequestHandler): def args(self): return json.loads(self.rfile.read(int(self.headers.get(Content-Length)))) def reply(self, reply): self.send_response(200) self.end_headers() self.wfile.write(json.dumps(reply).encode(UTF-8)) def forbidden(self): self.send_response(403) self.end_headers() def unsupported(self): self.send_response(404) self.end_headers() def do_POST(self): if self.headers.get(Authorization) ! Bearer plugin_token: self.forbidden() if self.path /api/v1/getparams.execute: args self.args() self.reply({ output: { parameters: [ { key1: val1, key2: val2 }, { key1: val2, key2: val2 } ] } }) else: self.unsupported() if __name__ __main__: httpd HTTPServer((, 4355), Plugin) httpd.serve_forever()用 curl 模拟控制器的请求curl http://localhost:4355/api/v1/getparams.execute -H Authorization: Bearer strong-password -d \ { applicationSetName: fake-appset, input: { parameters: { param1: value1 } } }实现协议时的几个要点与源码契约一一对应只需实现/api/v1/getparams.execute这一个端点——这正是 plugin_service.go 中写死的请求路径必须校验Authorization头是否与/var/run/argo/token中的 bearer 值一致不一致返回 403——对应客户端 client.go 设置Authorization: Bearer token的行为输入参数在请求体中通过input.parameters变量读取输出必须是一个 map参数列表嵌套在output.parameters键下即{output: {parameters: [...]}}——与 ServiceResponse 的结构一致保留键保护generator.input.parameters与values是保留键如果插件的输出中出现了这两个键会被 ApplicationSet 的 Plugin generator spec 中input.parameters和values的内容覆盖参见 generateParams 的实现。非 goTemplate 模式下的输出扁平化值得补充的一个底层细节当 ApplicationSet 未开启goTemplate即使用 fasttemplate 模式时插件返回的嵌套对象会被扁平化为点号分隔的键。这在 plugin_test.go 的测试数据中体现得很直观——插件返回{key2: {key2_1: ..., key2_2: {key2_2_1: ...}}}最终展开为key2.key2_1、key2.key2_2.key2_2_1这样的扁平行键且所有值被fmt.Sprintf(%v, v)字符串化数字123变成123而goTemplate: true时则保留原始嵌套结构maps.Copy直拷。因此建议在新项目中统一开启goTemplate模板内通过index访问嵌套键官方示例中的{{ index .generator.input.parameters.map key1 }}正是此用法。进阶实战与 Matrix Pull Request 生成器组合官方文档给出了一个极具代表性的组合示例插件为给定分支返回一组镜像 digest且每次只返回该分支最新构建镜像的一个条目。整体思路是Pull Request 生成器提供分支列表Plugin 生成器按分支名查询镜像仓库只有当构建产物digest真实存在时才会渲染出带完整镜像地址的 Application——仅凭 commit hash 无法证明构建成功这正是单独用 Pull Request 生成器做不到的。apiVersion: argoproj.io/v1alpha1 kind: ApplicationSet metadata: name: fb-matrix spec: goTemplate: true goTemplateOptions: [missingkeyerror] generators: - matrix: generators: - pullRequest: github: ... requeueAfterSeconds: 30 - plugin: configMapRef: name: cm-plugin input: parameters: branch: {{.branch}} # 由 pullRequest 生成器提供 values: branchLink: https://git.example.com/org/repo/tree/{{.branch}} template: metadata: name: fb-matrix-{{.branch}} spec: source: repoURL: https://github.com/myorg/myrepo.git targetRevision: HEAD path: charts/my-chart helm: releaseName: fb-matrix-{{.branch}} valueFiles: - values.yaml values: | front: image: myregistry:{{.branch}}{{ .digestFront }} # digestFront 由插件生成 back: image: myregistry:{{.branch}}{{ .digestBack }} # digestBack 由插件生成 project: default syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespacetrue destination: server: https://kubernetes.default.svc namespace: {{.branch}} info: - name: Link to the Applications branch value: {{values.branchLink}}执行过程分解假设 pullRequest 生成器返回两个分支feature-branch-1和feature-branch-2Matrix 组合器会把每个分支作为输入分别驱动 Plugin 生成器因此插件会被调用两次curl http://localhost:4355/api/v1/getparams.execute -H Authorization: Bearer strong-password -d \ { applicationSetName: fb-matrix, input: { parameters: { branch: feature-branch-1 } } }curl http://localhost:4355/api/v1/getparams.execute -H Authorization: Bearer strong-password -d \ { applicationSetName: fb-matrix, input: { parameters: { branch: feature-branch-2 } } }每次调用插件返回唯一的输出例如{ output: { parameters: [ { digestFront: sha256:a3f18c17771cc1051b790b453a0217b585723b37f14b413ad7c5b12d4534d411, digestBack: sha256:4411417d614d5b1b479933b7420079671facd434fd42db196dc1f4cc55ba13ce } ] } }{ output: { parameters: [ { digestFront: sha256:7c20b927946805124f67a0cb8848a8fb1344d16b4d0425d63aaa3f2427c20497, digestBack: sha256:e55e7e40700bbab9e542aba56c593cb87d680cefdfba3dd2ab9cfcb27ec384c2 } ] } }两个生成器组合后模板中的{{.digestFront}}、{{.digestBack}}会被替换为对应分支的真实 digest{{values.branchLink}}则用于 Application 的 info 链接。这种矩阵 插件的模式传达了一个关键设计思想把参数存在性当作一种认证。插件可以在查询不到任何构建产物时返回空列表而非报错Matrix 组合器自然就不会为该分支生成 Application从而保证只有镜像真正构建成功应用才会被创建这正是仅靠 commit hash 无法达成的效果。进一步探索生成器总览docs/operator-manual/applicationset/Generators.mdMatrix / Merge 组合语法Generators-Matrix.md、Generators-Merge.md生成器源码与测试plugin.go、plugin_test.go插件 HTTP 客户端实现plugin_service.go、client.go、utils.go类型定义applicationset_types.go想快速开始编写自己的插件可以基于官方示例仓库applicationset-hello-pluginargoproj-labs 组织生成一个新仓库在其骨架上替换为你的取数逻辑即可。【免费下载链接】argo-cdDeclarative Continuous Deployment for Kubernetes项目地址: https://gitcode.com/GitHub_Trending/ar/argo-cd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考