K8s Operator 开发 Part1:快速上手 Kubebuilder,构建你的第一个 K8s Operator
本文主要分享 K8s Operator 相关的基本概念,以及使用 Kubebuilder 开发 K8s Operator 的大致流程。
Kubebuilder 系列内容比较多,拆分为三篇文章:
- Kubebuilder 开发 Operator 的大致流程
- Operator 开发过程中如何本地调试
- Operator 开发最佳实践
1.基本概念
在开发之前,简单介绍一下 K8s Operator 相关的一些概念。
Operator Pattern
Kubernetes Operator 是一种软件扩展模式,用于基于自定义资源(Custom Resources)来管理应用程序及其组件。它遵循 Kubernetes 的核心原则,特别是控制循环(Control Loop)机制。
在 Kubernetes 中,用户通常希望通过自动化来处理重复性任务,而 Operator 正是为了在 Kubernetes 提供的基础能力之上,进一步扩展自动化管理的范围。
一句话描述:Operator 的工作核心就是通过 自定义资源(CRD) 和 自定义控制器(Controller) 实现对应用的自动化管理。
CRD
CRD(Custom Resource Definition):是 Kubernetes 中扩展 API 的方式,它允许你定义一种新的资源类型,使其看起来就像是 Kubernetes 本身的原生资源(例如 Pods、Services 等)。每个 CRD 都是一个资源类型,它定义了你要管理的应用程序或服务的状态、字段以及控制器的行为。
CRD 对象本身是 Kubernetes 中的一种 API 对象,用于扩展 Kubernetes 的 API,使得你可以创建自定义的资源类型。
Controller
Controller: 控制器是 Kubernetes 中的核心概念之一,它负责不断地检查集群中的资源对象(如 Pod、Service、Deployment 等)是否符合预期状态。Kubernetes 内置的控制器包括 Deployment 控制器、ReplicaSet 控制器等。
k8s 的是一个高度自动化的系统,其中涵盖了常见应用程序所需的大部分功能,比如我们通过 kubectl 命令创建一个 deployment 对象之后,为什么就会有 Pod 创建出来了?
这其实是后台有多个 Controller 在后台工作实现的。
比如
Deployment Controller 根据 Deployment 创建 ReplicasSet 对象
ReplicasSet Controller 则根据 ReplicasSet 创建 Pod
K8s 资源标识 GVK&GVR
在 Kubernetes 中,GVK(Group, Version, Kind)和 GVR(Group, Version, Resource)是用于标识和访问 Kubernetes 资源的两个重要概念。
Group:API 组的名称。例如,
apps
组包含 Deployment、StatefulSet 等资源,batch
组包含 Job、CronJob 等资源。- 注意;早期的
core
组的资源(如 Pod、Service 等)是没有组名的,默认使用空字符串""
。
- 注意;早期的
Version:API 版本。每个资源在 Kubernetes 中可能会有多个版本(如
v1
、v1beta1
、v1alpha1
等),每个版本可能会有不同的功能和行为。Kind:资源的类型。通常是资源的单数形式,如
Pod
、Deployment
、Service
。Resource:资源的名称。通常是复数形式,如
pods
、deployments
、services
GVK 和 GVR 区别在于:
GVR 更侧重于资源的实际操作,特别是动态客户端和工具中使用,用于指定和操作特定类型的资源实例。
GVK 更侧重于资源的定义和描述,特别是在控制器、操作器以及元数据操作中使用,用于唯一标识和处理特定类型的资源。
以下面这个 Deployment 为例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: caddy
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 1
...
Group 为 apps
Version 为 v1
Kind 则是 Deployment
Resource 则是 deployments
声明式 API
所谓声明式就是“告诉 K8s 你要什么,而不是告诉它怎么做的命令”。
举个🌰,我们希望调整到 26 度,声明式和命令式的区别:
声明式:小爱同学,空调调到 26 度
命令式:(看一眼,确认当前空调是 22 度)小爱同学,空调上调 4 度
在 K8s 里面,声明式的体现就是 kubectl apply 命令,在对象创建和后续更新中一直使用相同的 apply 命令,告诉 K8s 对象的终态即可。 声明式 API 让 K8s 的“容器编排”世界看起来温柔美好,而控制器(以及容器运行时,存储,网络模型等)才是这太平盛世的幕后英雄。
哪儿有什么岁月静好~
2.Operator 开发
社区开发了 kubebuilder 项目,让我们可以快速创建脚手架,极大简化了 Operator 开发过程。
从整体来看,通过 Kubebuilder 来实现 Operator 大致可分为以下步骤:
1)初始化项目
2)创建 API,并填充字段
3)实现 Controller,编写核心调谐逻辑(Reconcile)
4)(可选)创建 Webhook,并实现接口
5)本地调试,验证测试
6) 构建镜像并生成 yaml,发布到集群
安装 Kubebuilder
到 Kubebuilder Realease 页 下载即可,这里是直接用得最新版本 v4.3.1。
version="v4.3.1"
curl -L -o kubebuilder "https://github.com/kubernetes-sigs/kubebuilder/releases/download/${version}/kubebuilder_$(go env GOOS)_$(go env GOARCH)"
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
安装成功之后使用 kubebuilder version
查看版本信息
❯ kubebuilder version
Version: main.version{KubeBuilderVersion:"4.3.1", KubernetesVendor:"1.31.0", GitCommit:"a9ee3909f7686902879bd666b92deec4718d92c9", BuildDate:"2024-11-09T12:30:22Z", GoOs:"darwin", GoArch:"arm64"}
项目初始化
源码见 -> lixd/i-operator
先创建一个空文件夹,然后在文件夹内执行下方命令
mkdir i-operator
cd i-operator
kubebuilder init --domain crd.lixueduan.com --repo github.com/lixd/i-operator
--domain crd.lixueduan.com
我们的项目的域名,就是后续 CRD Group 的 Domain- 比如后续创建一个名为 Application 的 CRD,加上 domain,完整名称就是
application.crd.lixueduan.com
。
- 比如后续创建一个名为 Application 的 CRD,加上 domain,完整名称就是
--repo github.com/lixd/i-operator
是仓库地址,也就是go module
名称
完整输出如下:
❯ sudo kubebuilder init --domain crd.lixueduan.com --repo github.com/lixd/i-operator
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.19.1
INFO Update dependencies:
$ go mod tidy
Next: define a resource with:
$ kubebuilder create api
ps:遇到一个问题,mac 下执行命令未添加 sudo 时会初始化失败:
Error: failed to initialize project: unable to scaffold with "base.go.kubebuilder.io/v4": exit status 1
可以看到在最后打印出了一个提示信息
Next: define a resource with:
$ kubebuilder create api
告诉我们下一步就是创建 API,不过在这之前,我们先看下初始化生成的项目结构
❯ sudo tree i-operator
i-operator
├── Dockerfile
├── Makefile
├── PROJECT # Kubebuilder 元数据
├── README.md
├── cmd
│ └── main.go
├── config # 部署相关的配置文件
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_metrics_patch.yaml
│ │ └── metrics_service.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── network-policy
│ │ ├── allow-metrics-traffic.yaml
│ │ └── kustomization.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ └── rbac # rbac 授权相关配置
│ ├── kustomization.yaml
│ ├── leader_election_role.yaml
│ ├── leader_election_role_binding.yaml
│ ├── metrics_auth_role.yaml
│ ├── metrics_auth_role_binding.yaml
│ ├── metrics_reader_role.yaml
│ ├── role.yaml
│ ├── role_binding.yaml
│ └── service_account.yaml
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── test
├── e2e
│ ├── e2e_suite_test.go
│ └── e2e_test.go
└── utils
└── utils.go
11 directories, 29 files
创建 API 对象
(可选)开启 multigroup
默认为关闭,关闭状态下只能创建一个 group,创建第二个 group 时会报错:
unable to inject the resource to "base.go.kubebuilder.io/v3": multiple groups are not allowed by default
如果需要创建多个 Group 时使用以下命令开启 multigroup
kubebuilder edit --multigroup=true
Create API
创建 API 对象时就会用到前面提到的 GVK 了,这里我们创建一个名为 Application 的 CRD 对象,具体如下:
kubebuilder create api --group core --version v1 --kind Application --namespaced=true
--namespaced=true
指定 CRD 的 scope,默认是 namespaced,如果是集群范围的 CRD 在创建时这里需要设置为 false。
注意:创建过程中会询问是否创建资源(Create Resource [y/n]) 和 控制器(Create Controller [y/n]) 都输入 y 同意即可:
INFO Create Resource [y/n]
y
INFO Create Controller [y/n]
y
完整输出如下:
❯ sudo kubebuilder create api --group core --version v1 --kind Application
INFO Create Resource [y/n]
y
INFO Create Controller [y/n]
y
INFO Writing kustomize manifests for you to edit...
INFO Writing scaffold for you to edit...
INFO api/v1/application_types.go
...
INFO internal/controller/application_controller_test.go
INFO Update dependencies:
$ go mod tidy
INFO Running make:
$ make generate
mkdir -p /Users/lixueduan/17x/projects/i-operator/bin
Downloading sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4
go: downloading sigs.k8s.io/controller-tools v0.16.4
...
go: downloading golang.org/x/text v0.19.0
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests
同样在最后给出了提示
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests
先实现 API,然后执行 make manifests
命令去生成相应的 manifest 文件,这样就可以 Apply 到集群里去了。
同样了,在这之前先看下项目目录结构变化
❯ tree i-operator
i-operator
├── Dockerfile
├── Makefile
├── PROJECT
├── README.md
├── api
│ └── v1
│ ├── application_types.go
│ ├── groupversion_info.go
│ └── zz_generated.deepcopy.go
├── bin
│ ├── controller-gen -> /Users/lixueduan/17x/projects/i-operator/bin/controller-gen-v0.16.4
│ └── controller-gen-v0.16.4
├── cmd
│ └── main.go
├── config
│ ├── crd
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_metrics_patch.yaml
│ │ └── metrics_service.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── network-policy
│ │ ├── allow-metrics-traffic.yaml
│ │ └── kustomization.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── application_editor_role.yaml
│ │ ├── application_viewer_role.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── metrics_auth_role.yaml
│ │ ├── metrics_auth_role_binding.yaml
│ │ ├── metrics_reader_role.yaml
│ │ ├── role.yaml
│ │ ├── role_binding.yaml
│ │ └── service_account.yaml
│ └── samples
│ ├── core_v1_application.yaml
│ └── kustomization.yaml
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── internal
│ └── controller
│ ├── application_controller.go
│ ├── application_controller_test.go
│ └── suite_test.go
└── test
├── e2e
│ ├── e2e_suite_test.go
│ └── e2e_test.go
└── utils
└── utils.go
具体如下:
新增了 /api/v1 目录
新增 /bin 目录
config 目录下新增 /config/crd 和 /config/samples
新增 /internal/controllers 目录
API 目录下就是我们的 CRD 对象, /internal/controllers 目录下则是 Controller 了,接下来我们要做的就是完善 CRD 并实现 Controller。
Operator 实现
本次 Demo 我们实现一个 Application 对象,创建对象后 Controller 会根据 App 中指定的 Image 启动对应的 Deployment 运行应用。
完善 CRD
api/v1/application_types.go 内容如下:
这就是我们创建的 Application 对象,默认只填充了一个 Foo 字段,现在我们需要增加其他字段。
// ApplicationSpec defines the desired state of Application.
type ApplicationSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Image string `json:"image,omitempty"`
Enabled bool `json:"enabled,omitempty"`
}
// ApplicationStatus defines the observed state of Application.
type ApplicationStatus struct {
Ready bool `json:"ready,omitempty"`
}
Application 一共增加了 3 个字段:
spec.Image:应用对应的镜像,用于部署服务
spec.Enabled:标记当前应用是否需要部署
status.Ready 则是记录应用是否准备好
Controller 实现
Controller 则是 Watch Application 对象的变化,Demo 希望的逻辑是:
根据 Application 对象创建、删除、更新情况同步维护 Deployment:
Spec 部分:根据 Spec 中的信息做对应的操作
比如 spec.Enabled 为 true 则需要部署应用,根据 spec.Image 创建 Deployment,如果 Deployment 已经存在就判断 Image 是否一致,一致则跳过,不一致则更新。
如果 spec.Enabled 为 false 则不需要部署应用,如果 Deployment 存在则删除
Status 部分:更新 Item 的 Status 信息,便于用户知道当前的状态
- 比如 Application Status 提供了一个 Ready 字段,当前逻辑比较简单,如果 Deployment readyReplicas >= 1,就设置 Ready 为 True,否则为 False。
Controller 逻辑在internal/controller/application_controller.go
中,我们只需要在 Reconcile 方法中实现自定义逻辑即可。
完整代码如下:
func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
log := logger.WithValues("application", req.NamespacedName)
log.Info("start reconcile")
// query app
var app v1.Application
err := r.Get(ctx, req.NamespacedName, &app)
if err != nil {
log.Error(err, "unable to fetch application")
// we'll ignore not-found errors, since they can't be fixed by an immediate
// requeue (we'll need to wait for a new notification), and we can get them
// on deleted requests.
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// examine DeletionTimestamp to determine if object is under deletion
if app.ObjectMeta.DeletionTimestamp.IsZero() {
// The object is not being deleted, so if it does not have our finalizer,
// then lets add the finalizer and update the object. This is equivalent
// to registering our finalizer.
if !controllerutil.ContainsFinalizer(&app, AppFinalizer) {
controllerutil.AddFinalizer(&app, AppFinalizer)
if err = r.Update(ctx, &app); err != nil {
log.Error(err, "unable to add finalizer to application")
return ctrl.Result{}, err
}
}
} else {
// The object is being deleted
if controllerutil.ContainsFinalizer(&app, AppFinalizer) {
// our finalizer is present, so lets handle any external dependency
if err = r.deleteExternalResources(&app); err != nil {
log.Error(err, "unable to cleanup application")
// if fail to delete the external dependency here, return with error
// so that it can be retried.
return ctrl.Result{}, err
}
// remove our finalizer from the list and update it.
controllerutil.RemoveFinalizer(&app, AppFinalizer)
if err = r.Update(ctx, &app); err != nil {
return ctrl.Result{}, err
}
}
// Stop reconciliation as the item is being deleted
return ctrl.Result{}, nil
}
// Your reconcile logic
log.Info("run reconcile logic")
if err = r.syncApp(ctx, app); err != nil {
log.Error(err, "unable to sync application")
return ctrl.Result{}, err
}
// sync status
var deploy appsv1.Deployment
objKey := client.ObjectKey{Namespace: app.Namespace, Name: deploymentName(app.Name)}
err = r.Get(ctx, objKey, &deploy)
if err != nil {
log.Error(err, "unable to fetch deployment", "deployment", objKey.String())
return ctrl.Result{}, client.IgnoreNotFound(err)
}
copyApp := app.DeepCopy()
// now,if ready replicas is gt 1,set status to true
copyApp.Status.Ready = deploy.Status.ReadyReplicas >= 1
if !reflect.DeepEqual(app, copyApp) { // update when changed
log.Info("app changed,update app status")
if err = r.Client.Status().Update(ctx, copyApp); err != nil {
log.Error(err, "unable to update application status")
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
一般的 Reconcile 逻辑:
1)使用 NamespacedName 查询 Item
获取不到如果是 NotFound 则正常返回,说明 Item 可能被删除了
如果是其他错误就返回 Error
2)使用 DeletionTimestamp.IsZero 判断 Item 是否在删除中
如果否则说明 Item 不在删除中,当前为正常状态,接着判断是否有添加指定 Finalizer,有则跳过,没有则加上。
如果是则说明 Item 已经在删除中了,再次判断是否有指定 Finalizer,有则执行清理操作,清理完移除 Finalizer,如果没有则直接返回,说明清理工作也做完了,之后这个 Item 就会被删除
3)接下来就是真正的调谐逻辑了,根据 App.Spec 信息维护 Deployment 对象,并根据 Deployment 状态更新 App.Status
Controller 部分就开发完成了,接下来可以根据需求决定是否创建 Webhook。
(可选)Webhook
K8s 中有两类 Webhook:
Validating Admission Webhook:在资源创建、更新或删除时进行 验证,确保资源满足一定的规则,拒绝不符合要求的请求。
Mutating Admission Webhook:在资源创建或更新时,可以修改资源对象的内容。
开发 Operator 时可以创建一个 ValidatingAdmissionWebhook 来对用户创建的 CRD 对象进行校验,遇到不符合要求的对象直接拒绝。
或者直接创建 MutatingAdmissionWebhook 自动调整或注入所需配置,确保资源始终符合要求,减少人工干预。
比如对于 Application 对象来说,可以使用 ValidatingAdmissionWebhook 校验用户指定的 spec.Image 格式是否正确。
Create Webhook
和创建 API 命令类似:命令如下:
# GVK 需要和创建 API 时保持一致
kubebuilder create webhook --group core --version v1 --kind Application --defaulting --programmatic-validation
输出如下:
❯ kubebuilder create webhook --group core --version v1 --kind Application --defaulting --programmatic-validation
INFO Writing kustomize manifests for you to edit... ilder create webhook --group core --version v1 --kind Application --defaulting --programmatic-validation
INFO Writing scaffold for you to edit...
INFO internal/webhook/v1/application_webhook.go
INFO internal/webhook/v1/application_webhook_test.go
INFO internal/webhook/v1/webhook_suite_test.go
INFO Update dependencies:
$ go mod tidy
INFO Running make:
$ make generate
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Next: implement your new Webhook and generate the manifests with:
$ make manifests
看下有什么变化:
Config 目录下增加了 Webhook 相关配置
internal/webhook 目录下增加了 Webhook 默认实现
Webhook 实现
接下来我们要做的就是实现 Webhook,internal/webhook/v1/application_webhook.go
中增加自定义逻辑,包括以下几个方法:
Default:用于给对象设置一些默认值,如果对象中某些字段没有被明确设置,可以通过这个 webhook 来设置它们的默认值,即对应 mutating webhook
ValidateCreate、ValidateUpdate、ValidateDelete:这些方法是用于验证的。它们分别在对象创建、更新和删除之前被调用,用于确保对象符合特定的验证规则,对应 validating webhook
以 ValidateCreate 为例,判断 spec.Image 格式是否正确:
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Application.
func (v *ApplicationCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
application, ok := obj.(*corev1.Application)
if !ok {
return nil, fmt.Errorf("expected a Application object but got %T", obj)
}
applicationlog.Info("Validation for Application upon creation", "name", application.GetName())
if isValidImageName(application.Spec.Image) {
return nil, fmt.Errorf("invalid image name: %s", application.Spec.Image)
}
return nil, nil
}
3.测试
连接远程集群
然后将 Kubeconfig 复制到本地写入 ~/.kube/config
文件,同时在本地安装 kubectl,验证下,本地可以正常使用 kubectl 命令,就像这样:
❯ kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
calico-apiserver calico-apiserver-6f86f48f4b-cw7nw 1/1 Running 2 (6d5h ago) 7d22h
calico-apiserver calico-apiserver-6f86f48f4b-mww2r 1/1 Running 2 (6d5h ago) 7d22h
calico-system calico-kube-controllers-5f8646f489-8lpms 1/1 Running 0 7d22h
calico-system calico-node-295tr 1/1 Running 0 7d22h
calico-system calico-typha-759985f586-q9dwp 1/1 Running 0 7d22h
calico-system csi-node-driver-bpmd5 2/2 Running 0 7d22h
calico-system tigera-operator-5f4668786-dj2th 1/1 Running 1 (6d5h ago) 7d22h
default app-demo-86b66c84cd-4947h 1/1 Running 0 5d23h
kube-system coredns-5d78c9869d-krwzb 1/1 Running 0 7d22h
kube-system coredns-5d78c9869d-ppx2c 1/1 Running 0 7d22h
kube-system etcd-bench 1/1 Running 0 7d22h
kube-system kc-kubectl-78c9594489-pd6gw 1/1 Running 0 7d22h
kube-system kube-apiserver-bench 1/1 Running 0 7d22h
kube-system kube-controller-manager-bench 1/1 Running 2 (6d5h ago) 7d22h
kube-system kube-proxy-4x99q 1/1 Running 0 7d22h
kube-system kube-scheduler-bench 1/1 Running 2 (6d5h ago) 7d22h
对于没有 Webhook 的 Operator,现在就满足本地调试的条件了,但是如果有 Webhook 则还需要额外配置。
生成 Manifests 并安装到集群
执行 make manifests
命令,会根据我们定义的 CRD 生成对应的 yaml 文件,以及其他部署相关的 yaml 文件:
❯ make manifests
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
生成的 crd 中我们定义的 Spec 和 Status 部分如下:
spec:
description: ApplicationSpec defines the desired state of Application.
properties:
enabled:
type: boolean
image:
type: string
type: object
status:
description: ApplicationStatus defines the observed state of Application.
properties:
ready:
type: boolean
随后执行 make install
命令即可将 CRD 部署到集群,这也就是为什么需要在本地准备好 Kubeconfig 以及 kubectl 工具。
❯ make install
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases09:58:14
Downloading sigs.k8s.io/kustomize/kustomize/v5@v5.5.0
go: downloading sigs.k8s.io/kustomize/kustomize/v5 v5.5.0
go: downloading sigs.k8s.io/kustomize/api v0.18.0
go: downloading sigs.k8s.io/kustomize/cmd/config v0.15.0
go: downloading sigs.k8s.io/kustomize/kyaml v0.18.1
go: downloading k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00
/Users/lixueduan/17x/projects/i-operator/bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/applications.core.crd.lixueduan.com created
本地启动 Controller
执行 make run
命令即可在本地运行 Controller,这也就是为什么需要在本地准备好 kubeconfig 文件。
ps:如果之前创建了 Webhook 这里会无法正常启动,具体见下一篇本地调试~
❯ make run
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases10:00:30
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go run ./cmd/main.go
2024-12-19T10:03:35+08:00 INFO setup starting manager
2024-12-19T10:03:35+08:00 INFO starting server {"name": "health probe", "addr": "[::]:8081"}
2024-12-19T10:03:35+08:00 INFO Starting EventSource {"controller": "application", "controllerGroup": "core.crd.lixueduan.com", "controllerKind": "Application", "source": "kind source: *v1.Application"}
这样本地运行可以比较方便的调试 Controller,当然了也可以直接以 Debug 方式启动,打断点进行调试。
4.部署
之前是本地运行,要部署到集群,则是先将 Controller 构建成镜像。
构建镜像
也很简单,Kubebuilder 在初始化时都准备好了,直接执行 make docker-buildx
命令就好。
会使用 Docker Buildx 构建多架构镜像,因此需要准备好 Buildx 环境。
IMG=lixd96/controller:latest PLATFORMS=linux/arm64,linux/amd64 make docker-buildx
生成部署 yaml
真正将 Controller 部署到集群时,一般使用 Deployment 形式部署。
运行make build-installer
即可生成 CRD 以及 部署 Controller 的 Deployment 对应的 Yaml。
❯ IMG=lixd96/controller:latest make build-installer
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases14:48:30
/Users/lixueduan/17x/projects/i-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
mkdir -p dist
cd config/manager && /Users/lixueduan/17x/projects/i-operator/bin/kustomize edit set image controller=lixd96/controller:latest
/Users/lixueduan/17x/projects/i-operator/bin/kustomize build config/default > dist/install.yaml
最终生成的 dist/install.yaml
就包含了部署 Operator 所需要的多有资源,部署是 apply 该文件即可。
至此,Operator 开发基本完成,逻辑都比较简单,旨在分享一下具体的开发流程~
5.小结
K8s Operator 开发分为以下步骤:
1)使用 Kubebuilder 初始化项目
2)创建 API 对象并填充字段
3)实现 Controller
4)(可选)创建 Webhook
5)本地开发调试
6)构建镜像并生成部署 manifest
本篇主要是分享使用 Kubebuilder 开发 K8s Operator 的大致流程,下一篇单独分享如何在本地进行调试。
源码见 -> lixd/i-operator