我是 LEE,老李,一个在 IT 行业摸爬滚打 17 年的技术老兵。
在本文中,我将使用最少的字和内容展现一个完整使用 Kubebuilder
从零开始构建 Kubernetes Operator
的整个过程。这是一次对知识的极度压缩和挑战,希望能帮助更多的小伙伴对构建 Kubernetes Operator
有一个全新的了解,同时对自己也是一个知识的深层梳理。
Kubernetes Operator
是一种强大的模式,允许开发者扩展 Kubernetes
的功能,以管理复杂的状态和自动化操作。我们将按照以下步骤逐步指导您创建您自己的 Operator
:
- 环境准备:介绍如何准备开发环境,包括安装 Go、Docker 和 Kubebuilder。
- 项目初始化 :使用
Kubebuilder
初始化新项目,创建基础项目结构和配置。 - 创建 API 和控制器:引导您创建自定义资源定义(CRD)的 API 和相应的控制器。
- 实现控制器逻辑:详细介绍如何在控制器中实现业务逻辑,包括资源的观察、分析和调节。
- 构建和部署 Operator :讨论如何构建
Operator
容器镜像,推送到容器镜像仓库,并部署到Kubernetes
集群中。
此外,还将讨论编写 CRD 时需要注意的要点,包括定义规格、状态和注释等方面的最佳实践。
1. 环境准备和初始化项目
初始化项目是使用 Kubebuilder
开发 Kubernetes Operator
的第一步。这一步骤创建了一个新的项目目录,其中包含了项目的基础目录结构和配置文件,为开发自定义资源定义(CRD)和相应的控制器提供了基础框架。
步骤 1: 创建项目目录
首先,决定你的项目将位于哪个目录。通常,这会是你的工作空间中的一个新目录。使用命令行进入到你希望创建项目的父目录中,然后创建项目目录:
bash
mkdir my-operator
cd my-operator
步骤 2: 初始化项目
在项目目录中,使用 kubebuilder init
命令来初始化你的 Kubebuilder
项目。这个命令会创建项目的基础结构,包括Go
模块支持、Makefile(用于构建和部署项目)、以及项目的基本目录结构。
命令的基本格式如下:
bash
kubebuilder init --domain mydomain.com --repo github.com/myusername/my-operator
在上述命令中,使用 --domain
参数指定你的资源类型的域,使用 --repo
参数指定代码库的路径。这些参数将影响你的 CRDs 的 API 组和路径。
项目结构
完成初始化后,你的项目目录将包括以下基本结构:
config/
: 存储 kustomize 文件和其他配置文件,用于部署你的Operator
到Kubernetes
集群。api/
: 存放自定义资源的 API 定义文件。使用Kubebuilder create api
命令创建新的 API 时,相关文件将添加到此目录。controllers/
: 存放控制器的代码。控制器观察Kubernetes
集群的状态,并确保自定义资源的状态与其规格相匹配。Makefile
: 包含用于构建和部署项目的命令。go.mod
和go.sum
:Go
语言的模块依赖文件。
注意事项
- 在运行初始化命令之前,请确保正确安装了
Kubebuilder
和 Go。 - 初始化命令会在当前目录下创建项目,请确保在正确的目录下运行它。
- 使用的域名应该是你控制下的域,以避免与其他人的 API 发生冲突。
- 项目名称(repo 参数)应该是一个有效的
Go
模块名称,因为Kubebuilder
项目基于Go
模块。
2. 创建 API 和控制器
在使用 Kubebuilder
初始化项目之后,下一步是创建自定义资源定义(CRD)的 API 和相应的控制器。这是开发 Kubernetes
Operator
的核心部分,因为它定义了自定义资源的数据模型以及如何管理这些资源的逻辑。以下是创建 API 和控制器的详细步骤:
步骤 1: 创建 API
运行 kubebuilder create api
命令来创建一个新的 API 资源和控制器。你需要指定资源的组(group)、版本(version)和种类(kind)。这三个属性共同定义了你的 CRD 的全名。
bash
kubebuilder create api --group <group> --version <version> --kind <Kind>
例如,要创建一个名为 WebApp
的资源,位于 webapp.mydomain.com
组下,版本为 v1
,可以运行以下命令:
bash
kubebuilder create api --group webapp --version v1 --kind WebApp
在执行此命令时,Kubebuilder
会询问你两个问题:
- 是否为资源创建 CRD:输入
Yes
。 - 是否为资源创建控制器:输入
Yes
。
步骤 2: 定义 API 结构
创建 API 后,Kubebuilder
会在 api/<version>/
目录下生成一个名为 <kind>_types.go
的文件,其中包含了自定义资源的 Go
类型定义。你需要编辑这个文件来定义资源的规格(Spec)和状态(Status)。
例如,对于 WebApp 资源,你可能需要定义如下:
go
// WebAppSpec 定义了 WebApp 的期望状态。
type WebAppSpec struct {
// +kubebuilder:validation:MinLength=1
// Size 是一个描述 webapp 大小的字符串。
Size string `json:"size,omitempty"`
}
// WebAppStatus 定义了 WebApp 的观察到的状态。
type WebAppStatus struct {
// Nodes 存储运行 WebApp 实例的节点名称。
Nodes []string `json:"nodes"`
}
步骤 3: 实现控制器逻辑
控制器的骨架代码会生成在 controllers/
目录下的 <kind>_controller.go
文件中。在这个文件中,你需要实现资源的业务逻辑,根据资源的规格来调整集群的状态,使其与期望的状态匹配。
go
func (r *WebAppReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
// 逻辑实现
return ctrl.Result{}, nil
}
步骤 4: 注册 CRD 和启动控制器
完成 API 结构和控制器逻辑的定义后,需要执行以下操作:
- 重新生成 CRD 定义并更新到
Kubernetes
集群中。 - 确保控制器在项目启动时被注册。
为了执行这些任务,你可以使用 Kubebuilder
自动生成的 Makefile 中提供的命令:
bash
make install
make run
注意事项
- 在定义 API 结构时,使用
Kubebuilder
提供的字段验证标记(例如// +kubebuilder:validation:MinLength=1
)来进行验证。 - 在实现控制器逻辑时,处理资源不存在(被删除)的情况,并处理可能的错误。
- 定期运行
make
命令,以确保代码、CRD 定义和 RBAC 规则与Kubernetes
集群同步。 - 在开发过程中,密切关注
Kubernetes
事件和控制器的日志输出,这有助于调试和理解控制器的行为。
小结
通过以下步骤,可以为你的 Kubernetes
Operator
创建自定义资源的 API 和控制器,从而向 Kubernetes
集群添加新功能:
- 运行
kubebuilder create api
命令创建 API 资源和控制器。 - 在生成的文件中定义 API 结构和控制器逻辑。
- 注册 CRD 并启动控制器。
这些步骤是向 Kubernetes
集群添加新功能的关键。
3. CRD 资源编写
CRD(Custom Resource Definition)资源编写是在 Kubernetes
中扩展 API 的关键步骤。CRD 允许你定义新的自定义资源类型,这些资源类型可以像内置资源(如 Pods、Deployments 等)一样被 Kubernetes
API 服务器处理。通过定义 CRD 和相应的控制器,你可以实现自定义的逻辑来管理这些资源,从而扩展 Kubernetes
的功能。以下是编写 CRD 资源的详细步骤和注意事项。
步骤 1: 定义 CRD 结构
CRD 的定义包括两个主要部分:Spec(规格)和 Status(状态)。Spec 定义了用户期望的资源状态,而 Status 反映了资源的当前状态。
以下是编写 CRD 的步骤:
- 选择 API 组和版本:为你的 CRD 选择一个合适的 API 组和版本。API 组反映了功能相关性,版本表示 API 的稳定性和成熟度。
- 定义 Kind :Kind 是你的 CRD 的名称,在
Kubernetes
中表示资源的类型。 - 编写 CRD 规格:定义你的 CRD 的属性。可以使用特殊的注释为字段添加元数据,例如字段描述、必需字段、字段默认值等。
步骤 2: 使用 Kubebuilder
创建 CRD
如果你使用 Kubebuilder
或类似的工具,创建 CRD 变得更加简单。Kubebuilder
可以根据你的 Go
代码自动生成 CRD 定义。
以下是创建 CRD 的步骤:
-
创建 API :使用
Kubebuilder
创建新的 API 和控制器骨架:bashkubebuilder create api --group <your-group> --version <version> --kind <Kind>
-
编辑
Go
类型定义 :在api/<version>/<kind>_types.go
文件中定义你的 CRD 的 Spec 和 Status。 -
添加注释 :使用
Kubebuilder
特有的注释来添加字段验证规则和其他元数据。
步骤 3: 生成 CRD 定义
Kubebuilder
可以根据你的 Go
类型定义自动生成 YAML 格式的 CRD 定义。
bash
make manifests
这个命令会生成一个或多个 CRD 定义文件,通常位于 config/crd/bases
目录下。
步骤 4: 部署 CRD 到 Kubernetes
将生成的 CRD 定义文件应用到你的 Kubernetes
集群中:
bash
kubectl apply -f config/crd/bases
注意事项
- 遵循 API 约定 :按照
Kubernetes
API 约定,如命名规范和字段命名,使你的 CRD 易于理解和使用。 - 考虑版本升级:在设计 CRD 时,考虑未来可能的版本升级,并提供清晰的升级路径以维护兼容性。
- 验证和默认值 :使用
Kubebuilder
注释为 CRD 字段添加验证规则和默认值,确保资源的正确性和稳定性。 - 支持多版本:如果需要支持多个版本的 CRD,请正确配置版本转换逻辑,保持不同版本间的兼容性。
通过遵循这些步骤和注意事项,你可以高效地编写和部署 Kubernetes
CRD,为你的应用或服务在 Kubernetes
平台上提供强大的扩展和自定义能力。
4. 实现控制器逻辑
实现控制器逻辑是创建 Kubernetes
Operator
的核心步骤。控制器负责观察 Kubernetes
API 中的资源状态,处理自定义资源(CR)的创建、更新和删除事件,并确保资源状态与其规格定义相匹配。下面将详细介绍如何在使用 Kubebuilder
创建的项目中实现控制器逻辑。
控制器的职责
在 Kubernetes
中,控制器遵循控制循环(control loop)模式,不断地比较资源的期望状态(由资源的 Spec 字段定义)与实际状态(由资源的 Status 字段和实际的集群状态反映)。控制器的职责包括:
- 观察 :监听
Kubernetes
API 中特定资源的变化。 - 分析:比较观察到的资源状态与其期望状态。
- 调节 :通过创建、更新或删除
Kubernetes
资源来调整状态,以使观察到的状态与期望状态一致。
实现控制器逻辑的步骤
以下是在 Kubebuilder
项目中实现控制器逻辑的基本步骤:
1. 定义资源逻辑
首先,你需要确定你的自定义资源(CR)所需的 Kubernetes
原生资源(如 Deployments、Services 等),以支持其运行。
2. 更新控制器代码
Kubebuilder
会在创建 API 时自动生成控制器的框架代码,在 controllers
目录下的 <your-resource>_controller.go
文件中。你需要在这个文件中填充业务逻辑。
控制器的主要逻辑集中在 Reconcile
方法中。这个方法包含了控制循环的核心逻辑,即观察、分析和调节的过程。Reconcile
方法的签名如下:
go
func (r *<YourResource>Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 业务逻辑实现
}
在 Reconcile
方法中,根据自定义资源的规格检查和创建所需的 Kubernetes
资源,并更新自定义资源的状态。
示例代码
以下是一个简化的示例,展示了如何在 Reconcile
方法中实现控制器逻辑:
go
import (
"context"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (r *<YourResource>Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 定义一个变量来存储你的自定义资源
var myResource <yourdomain>/<YourResourceVersion>.<YourResource>
if err := r.Get(ctx, req.NamespacedName, &myResource); err != nil {
log.Error(err, "unable to fetch <YourResource>")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// 根据 myResource.Spec 创建或更新 `Kubernetes` 资源
// 例如,创建一个 Deployment
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: myResource.Name,
Namespace: myResource.Namespace,
},
// ...
}
// 调用 Create 或 Update 方法来部署或更新资源
// 记得处理可能的错误并返回相应的结果
return ctrl.Result{}, nil
}
注意事项
- 错误处理 :确保对
Reconcile
方法中可能发生的所有错误进行适当处理,包括处理Kubernetes
API 调用的错误。 - 幂等性 :确保
Reconcile
方法是幂等的,即无论调用多少次,最终的状态都应该是一致的,不会因多次调用而产生不一致的结果。
5. 构建和部署你的 Operator
构建和部署 Operator
是将你的代码转换成在 Kubernetes
集群中运行的实际应用的过程。使用 Kubebuilder,这一过程包括构建 Operator
容器镜像、推送镜像到镜像仓库,并将 Operator
部署到 Kubernetes
集群。以下是详细的步骤和注意事项:
步骤 1:构建 Operator 容器镜像
-
编译 Operator : 使用
Go
工具链编译你的Operator
代码,生成一个可执行文件。通常,Kubebuilder
项目包含一个Makefile
,可以自动化这一步骤:bashmake build
-
构建容器镜像 : 将编译后的可执行文件打包到一个容器镜像中。编写一个
Dockerfile
来定义构建镜像的步骤,然后使用Docker
或其他容器工具来构建镜像:bashmake docker-build IMG=<your-image-name>:<tag>
在这里,
<your-image-name>
是你的容器镜像的名称,<tag>
是镜像的标签,通常用于版本控制。
步骤 2:推送镜像到仓库
完成构建后,需要将容器镜像推送到容器镜像仓库,以供 Kubernetes
访问和拉取。如果使用 Docker Hub、Google Container Registry (GCR) 或其他容器镜像服务,推送镜像的命令如下:
bash
docker push <your-image-name>:<tag>
确保你的 Kubernetes
集群可以访问该镜像仓库。对于私有仓库,可能需要在 Kubernetes
集群中配置相应的访问凭证。
步骤 3:部署 Operator 到 Kubernetes 集群
-
生成部署配置 : 使用 Kustomize 来管理
Kubernetes
资源的部署。你可以根据需要修改config/
目录下的配置文件。 -
部署 CRDs: 部署自定义资源定义(CRD)到集群中:
bashmake install
-
部署 Operator : 使用生成的部署配置将
Operator
部署到集群:bashmake deploy IMG=<your-image-name>:<tag>
这一步会应用
config/
目录下的Kubernetes
资源配置,将Operator
和必要的权限配置部署到集群中。
注意事项
- 安全考虑 : 如果你的
Operator
需要访问Kubernetes
API 或其他敏感资源,请确保正确配置 RBAC 权限,并遵循最小权限原则。 - 镜像版本控制 : 使用有意义的镜像标签(如版本号),而不是
latest
,这有助于避免生产环境中的意外更新。 - 测试部署 : 在部署到生产环境之前,先在测试集群中部署并验证你的
Operator
。确保它按预期工作,并妥善处理错误情况。
通过以上步骤,你可以构建、推送和部署你的 Kubebuilder Operator
到 Kubernetes
集群,从而实现对集群资源的管理和自动化。
最后
匆匆忙忙写完这篇文章,因为我最近就在准备使用 Kubebuilder
开发一个 Operator
。在学习一段时间后,想好好梳理一下自己的知识,同时发现 Kubebuilder
的文档跟老太太的裹脚布一样,又臭又长。随之就想挑战下自己,看看能不能用最少的字和内容来讲清楚一个完整的 Kubebuilder
开发 Operator
的过程。 编写过程中也是修修补补,所以可能有些地方表述不够准确,还请大家多多包涵。
当然,这篇文章只是一个开始,后续我会继续深入学习 Kubebuilder
,并将自己的学习过程和心得分享给大家。希望这篇文章能帮助到你,一起学习和进步。