minikube Addon 开发完全指南:从零创建、注册到提交一个全新的 Addon
【免费下载链接】minikubeRun Kubernetes locally项目地址: https://gitcode.com/gh_mirrors/mi/minikube
导读
本文基于 minikube 官方贡献文档,系统讲解如何为 minikube 开发一个全新的 Addon(插件):从 fork 仓库、编写 Kubernetes 清单文件,到通过//go:embed嵌入资源、注册到pkg/addons/config.go、接入minikube addons list与minikube addons open,再到本地测试与提交 PR 的完整流程。文中所有步骤均与当前仓库源码一一对应,读者学完后可以独立完成一个 Addon 的端到端开发与合入,并理解 addon 在启动、启用、禁用时的底层运行机制。
一、Addon 机制概述:一个 Addon 在 minikube 中是如何存在的
minikube 的 Addon 本质上是一组 Kubernetes 清单(manifest),它们被打包进 minikube 二进制文件,在用户执行minikube addons enable <name>时被拷贝进虚拟机(或容器)并应用(apply)到集群中。
一个 Addon 从源码到运行要经过三个层次的登记,缺一不可:
| 层次 | 文件 | 作用 |
|---|---|---|
| 清单文件 | deploy/addons/ / | 存放该 Addon 的所有 YAML /.tmpl模板 |
| 资源嵌入 | deploy/addons/assets.go | 通过//go:embed把清单编译进二进制 |
| 注册表 | pkg/minikube/assets/addons.go | 声明每个 Addon 要拷贝哪些文件、目标路径、权限、默认开关与维护者 |
此外,命令行层的注册在 pkg/addons/config.go,它决定了该 Addon 是否出现在minikube addons list中,以及启用/禁用时要触发哪些回调。后文会逐步展开这四层的具体写法。
从源码结构看,整个 addon 系统还支持两种"特殊形态":一类是带有自定义逻辑的 Addon(如
gcp-auth、auto-pause、gvisor,它们注册了额外的callbacks/validations),另一类是基于 Helm Chart 的 Addon(如traefik,见 pkg/minikube/assets/addons.go)。本文先聚焦最通用的"纯清单型" Addon。
二、第一步:创建 Addon 目录与清单文件
2.1 Fork 并检出仓库
首先 fork minikube 仓库并检出你的 fork:
git clone git@github.com:<username>/minikube.git cd minikube2.2 创建子目录并放入 YAML
在deploy/addons/下为你的 Addon 创建子目录:
mkdir deploy/addons/<addon name>把你的清单文件拷贝进去:
cp *.yaml deploy/addons/<addon name>以当前仓库中的registryAddon 为例,它的清单目录是 deploy/addons/registry,包含三个文件:registry-rc.yaml.tmpl(ReplicationController/Deployment)、registry-svc.yaml(Service)和registry-proxy.yaml.tmpl(代理组件)。
一个值得注意的细节:minikube 的 Addon 清单是 Go 模板(.tmpl后缀),运行时会被注入模板数据。例如 deploy/addons/registry/registry-rc.yaml.tmpl 中的镜像字段:
image: {{.CustomRegistries.Registry | default .ImageRepository | default .Registries.Registry }}{{.Images.Registry}}这行模板依次回退到:用户自定义 registry → 集群级--image-repository→ Addon 默认 registry,再拼接上镜像名。模板数据由 pkg/minikube/assets/addons.go 的GenerateTemplateData生成,其中包含KubernetesVersion、Arch、ImageRepository、LoadBalancerStartIP、LoadBalancerEndIP、ContainerRuntime、Images、Registries、CustomRegistries、NetworkInfo等字段。另一个典型例子是 deploy/addons/metallb/metallb-config.yaml.tmpl,它把minikube start --load-balancer-start-ip/--load-balancer-end-ip的配置渲染进 MetalLB 的地址池:
addresses: - {{ .LoadBalancerStartIP }}-{{ .LoadBalancerEndIP }}2.3 需要 GCP 认证时的可选 Label
如果这个 Addon永远不需要GCP 认证(即不希望 gcp-auth 把 GCP 凭据挂载进它的 Pod),建议在 Pod 的 YAML 上加上以下标签:
gcp-auth-skip-secret: "true"该标签的实际语义可以在源码中验证:pkg/addons/addons_gcpauth.go 在刷新 Pod 挂载凭据时,会跳过带gcp-auth-skip-secret标签的 Pod:
// Skip pods we're explicitly told to skip if _, ok := p.Labels["gcp-auth-skip-secret"]; ok { continue }同时,启用 gcp-auth 时终端会输出提示:如果不想让某个 Pod 挂载凭据,就给它的配置加上gcp-auth-skip-secret标签(见 pkg/addons/addons_gcpauth.go)。
三、注册 Addon:让minikube addons list认识它
3.1 在 pkg/addons/config.go 中添加条目
为了让新 Addon 出现在minikube addons list中,需要在 pkg/addons/config.go 的Addons切片里添加一个条目。官方文档给出的registry示例适用于任何不需要自定义代码的 Addon:
{ name: "registry", set: SetBool, callbacks: []setFn{EnableOrDisableAddon}, },在 pkg/addons/config.go 的完整注册表中可以看到,Addon结构体包含四个字段:
| 字段 | 含义 |
|---|---|
name | Addon 名称,必须与目录名一致 |
set | 把值写入配置的函数,常规 Addon 统一用SetBool |
validations | 启用前的前置校验(可为空),例如gvisor要求运行时是 containerd(isRuntimeContainerd)、nvidia-driver-installer要求 KVM 驱动(isKVMDriverForNVIDIA)、csi-hostpath-driver要求先启用 volumesnapshots(isVolumesnapshotsEnabled) |
callbacks | 启用/禁用时执行的回调链。最基础的是EnableOrDisableAddon;需要校验 Pod 是否就绪的 Addon(如ingress、registry、metrics-server、traefik、csi-hostpath-driver)还会追加verifyAddonStatus |
以auto-pause为例,它比普通 Addon 多了一个自定义回调:
{ name: "auto-pause", set: SetBool, callbacks: []setFn{EnableOrDisableAddon, enableOrDisableAutoPause}, },而gcp-auth的回调链最长,因为它还要负责把 GCP 凭据挂载进集群内所有 Pod:
{ name: "gcp-auth", set: SetBool, callbacks: []setFn{enableOrDisableGCPAuth, EnableOrDisableAddon, verifyGCPAuthAddon}, },3.2 回调链的执行顺序
从源码看,回调的执行有严格的先后顺序(pkg/addons/addons.go):RunCallbacks先运行validations,再运行callbacks,任一步返回错误都会中断后续流程。EnableOrDisableAddon内部(pkg/addons/addons.go)的核心流程是:
- 解析 bool 值,检查 Addon 是否已处于目标状态(
isAddonAlreadySet); - 通过 libmachine API 加载节点主机;
- 若集群未运行,则只写配置、跳过实际部署;
- 调用
SelectAndPersistImages处理镜像选择与持久化(支持--addon-images、--addon-registries覆盖); - 生成模板数据
GenerateTemplateData; - 调用
enableOrDisableAddonInternal:启用时把资源拷贝进 VM 并kubectl apply,禁用时删除资源文件并kubectl delete,apply 失败会按指数退避重试(最多约 2 分钟)。
四、嵌入资源:deploy/addons/assets.go 中的 //go:embed
清单文件写好后,需要通过//go:embed指令把它们嵌入二进制。编辑 deploy/addons/assets.go,新增一个embed.FS变量。官方文档给出的是csi-hostpath-driver的示例:
// CsiHostpathDriverAssets assets for csi-hostpath-driver addon //go:embed csi-hostpath-driver/deploy/*.tmpl csi-hostpath-driver/rbac/*.tmpl CsiHostpathDriverAssets embed.FS当前仓库中该变量实际嵌入的范围更广(deploy/addons/assets.go),把 deploy 与 rbac 目录下的.tmpl和.yaml全部纳入:
//go:embed csi-hostpath-driver/deploy/*.tmpl csi-hostpath-driver/deploy/*.yaml csi-hostpath-driver/rbac/*.yaml CsiHostpathDriverAssets embed.FS//go:embed的匹配规则是从deploy/addons/目录(即 assets.go 所在目录)出发的 glob 模式,可以同时写多个模式。整个 deploy/addons/assets.go 文件里定义了 40 余个嵌入变量,几乎每个 Addon 一个,命名惯例是<AddonName>Assets。注意:embed.FS是只读的虚拟文件系统,运行时通过MustBinAsset(addons.XXXAssets, "相对路径", ...)来按路径取出文件内容。
五、声明文件清单:pkg/minikube/assets/addons.go
这是最关键的登记步骤:在 pkg/minikube/assets/addons.go 的Addonsmap 中,为该 Addon 添加NewAddon条目,声明要拷贝进集群的所有文件。官方文档的registry示例:
"registry": NewAddon([]*BinAsset{ MustBinAsset(addons.RegistryAssets, "registry/registry-rc.yaml.tmpl", vmpath.GuestAddonsDir, "registry-rc.yaml", "0640", false), MustBinAsset(addons.RegistryAssets, "registry/registry-svc.yaml.tmpl", vmpath.GuestAddonsDir, "registry-svc.yaml", "0640", false), MustBinAsset(addons.RegistryAssets, "registry/registry-proxy.yaml.tmpl", vmpath.GuestAddonsDir, "registry-proxy.yaml", "0640", false), }, false, "registry", "google"),5.1 MustBinAsset 参数详解
MustBinAsset(定义于 pkg/minikube/assets/vm_assets.go)的签名是:
func MustBinAsset(fs embed.FS, name, targetDir, targetName, permissions string) *BinAsset各参数含义如下:
| 参数 | 含义 | 典型值 |
|---|---|---|
fs | 资源所在的 embed.FS 变量 | addons.RegistryAssets(定义于 deploy/addons/assets.go) |
name | 源文件名(相对 assets.go 的路径) | "registry/registry-rc.yaml.tmpl" |
targetDir | 虚拟机内的目标目录 | 通常为vmpath.GuestAddonsDir |
targetName | 拷贝后在虚拟机内的文件名 | "registry-rc.yaml"(会去掉.tmpl后缀,因为模板已在本地渲染) |
permissions | 目标文件权限 | 通常为"0640" |
5.2 关于模板替换与默认启用的布尔值
注意:官方文档中示例代码的最后多了一个布尔参数(控制是否做模板替换),而当前仓库中
MustBinAsset的签名是 5 个参数——模板替换不再由该参数控制,而是由源文件名是否以.tmpl结尾自动判定(见 pkg/minikube/assets/addons.go:addon.IsTemplate()为真时调用addon.Evaluate(data)渲染模板)。因此现在写代码时应使用 5 参数形式。
5.3 NewAddon 的其余参数
NewAddon的完整签名(pkg/minikube/assets/addons.go):
func NewAddon(assets []*BinAsset, enabled bool, addonName, maintainer, verifiedMaintainer, docs string, images, registries map[string]string, helmChart *HelmChart) *Addon- 第二个参数(enabled):Addon 是否默认启用。新 Addon 必须写
false。当前仓库中仅default-storageclass和storage-provisioner为true。 - maintainer 字段:告知用户该 Addon 镜像的控制方。例如
registry的维护者是 minikube 团队(当前仓库里写的是"minikube"),freshpod是"Google",metallb是"3rd party (MetalLB)"。创建新 Addon 时,应当联系镜像来源方,询问其是否愿意作为该 Addon 的联系人;若对方不接受,留空也是可以的(例如kubeflow就写的是"3rd party")。 - verifiedMaintainer:可选的维护者 GitHub 用户名,如
traefik的"traefik"、headlamp的"yolossn"。 - images / registries:Addon 使用的镜像名与默认 registry 的映射。例如 registry Addon:
map[string]string{ "KubeRegistryProxy": "minikube/kube-registry-proxy:v0.0.11@sha256:e321acf067df0a78fba3ff97748c10029ca2c413c5b7207e4ca000c62fcdac93", "Registry": "registry:3@sha256:1be55279f18a2fe1a74edf2664cac61c1bea305b7b4642dab412e7affdcb3e33", }, map[string]string{ "KubeRegistryProxy": "registry.k8s.io", "Registry": "docker.io", },这些镜像引用会在模板渲染时作为{{.Images.XXX}}/{{.Registries.XXX}}使用,并支持通过minikube addons enable <name> --addon-images key=value --addon-registries key=value覆盖(见 pkg/minikube/assets/addons.go 的SelectAndPersistImages)。
- helmChart:如果该 Addon 基于 Helm Chart 安装,则传
&HelmChart{...},此时Assets可为空。traefik是当前仓库唯一的 Helm 型 Addon(pkg/minikube/assets/addons.go),它指定了 Chart 仓库、命名空间与Values覆盖项,并通过service.labels.kubernetes\.io/minikube-addons-endpoint=traefik支持minikube addons open traefik。
更多新增 Addon 的历史范例,可以查看 deploy/addons 目录的提交历史;基于 Helm 的 Addon 则参见仓库中的 Helm Based Addons(若该页面存在于你的仓库版本中)。
六、支持minikube addons open:NodePort Service 标签
如果你的 Addon 包含 NodePort 类型的 Service,请为它添加kubernetes.io/minikube-addons-endpoint: <addon name>标签,minikube addons open命令依赖它来发现可打开的端点:
apiVersion: v1 kind: Service metadata: labels: kubernetes.io/minikube-addons-endpoint: <addon name>在源码中,traefik正是通过给 Service 打上kubernetes.io/minikube-addons-endpoint=traefik标签来让minikube addons open traefik可用的(pkg/minikube/assets/addons.go)。
已知限制:minikube addons open目前只对kube-system命名空间生效(对应上游 issue #8089)。因此如果 Service 部署在其他命名空间,需要确认open行为是否符合预期。
七、测试 Addon 改动
修改清单或代码后,重新构建 minikube 二进制,并以开启详细日志的方式启用 Addon:
make && make test && ./out/minikube addons enable <addon name> --alsologtostderr注意:每次修改 YAML 文件后都必须重新执行make,因为清单是通过//go:embed编译进二进制的,不重新构建就不会生效。同样,make test会运行 pkg/addons/addons_test.go、pkg/addons/validations_test.go 等单元测试,覆盖SetBool、EnableOrDisableAddon及各类校验逻辑。
当需要应用新的改动时,先禁用再重新启用:
./out/minikube addons disable <addon name> --alsologtostderr--alsologtostderr会把 klog 日志同时输出到标准错误,便于观察模板渲染、文件拷贝、kubectl apply/delete的详细过程。调试时还可以关注这些关键日志点(均在 pkg/addons/addons.go):
"Setting addon %s=%s in %q":开始处理某个 Addon;"installing %s"/"Removing %+v":逐个拷贝/删除目标文件;"apply failed, will retry: %v":apply 失败后进入指数退避重试。
八、提交流程:发送 PR
完成本地测试后,点击 new pull request 向 minikube 主仓库发起 PR。合入前的最终自查清单:
deploy/addons/<addon name>/目录包含全部清单文件;- 不需要 GCP 认证的 Pod 已加
gcp-auth-skip-secret: "true"标签; - pkg/addons/config.go 中已注册该 Addon(
minikube addons list可见); - deploy/addons/assets.go 中已添加
//go:embed变量; - pkg/minikube/assets/addons.go 中已添加
NewAddon条目,第二个参数为false(不默认启用),并填好 maintainer; - 含 NodePort Service 时已加
kubernetes.io/minikube-addons-endpoint标签; make && make test通过,addons enable/disable验证成功。
九、结语
从上述流程可以看到,minikube 的 Addon 体系是一条"声明式"的流水线:清单文件 →//go:embed嵌入 →assets.Addons声明 →config.go注册,四步即可让一个新 Addon 完整可用,且天然支持list、enable、disable、open全部命令。若你的 Addon 需要更复杂的生命周期逻辑(如运行时校验、自定义部署行为),可以仿照gvisor、auto-pause、gcp-auth注册额外的validations与callbacks,并在 pkg/addons 目录下实现对应的回调函数。这正是 minikube 生态能够不断扩展内置能力(从 Ingress、Metrics Server 到 Istio、KubeVirt、gVisor)的底层机制所在。
【免费下载链接】minikubeRun Kubernetes locally项目地址: https://gitcode.com/gh_mirrors/mi/minikube
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考