草庐IT

kubebuilder 实战之开发一个存储用户信息的 operator

LanYuLei 2023-03-28 原文
本文介绍如何使用 kubebuilder 实现一个存储用户信息的 CRD,同时开发 controller 绑定同名的 ServiceAccount。

不过多介绍 kubebuilder 的理论知识,直接开干。

开发环境准备

初始化 kubebuilder

mkdir lanyulei
cd lanyulei
kubebuilder init --domain lanyulei.com --repo lanyulei
  • init:初始化命令参数。
  • --domain:可以理解为接口组的分组。根据实际情况来定,我一般定义为​​项目名.com​​。
  • --repo:项目名称,若是当前项目下已经存在 go.mod,则无需此参数。
初始化成功后的代码结构。

.
├── Dockerfile
├── Makefile
├── PROJECT
├── config
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ └── manager_config_patch.yaml
│ ├── manager
│ │ ├── controller_manager_config.yaml
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ └── rbac
│ ├── auth_proxy_client_clusterrole.yaml
│ ├── auth_proxy_role.yaml
│ ├── auth_proxy_role_binding.yaml
│ ├── auth_proxy_service.yaml
│ ├── kustomization.yaml
│ ├── leader_election_role.yaml
│ ├── leader_election_role_binding.yaml
│ ├── role_binding.yaml
│ └── service_account.yaml
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── main.go

开启支持多接口组

如果你本次项目开发的 ​​operator​​​ 是​​多接口组​​的话,则需要执行一下命令。

例如:同时需要​​用户相关接口​​​和​​角色相关接口​​​,则需要​​两组 api group​​,因此需要执行以下操作。


kubebuilder edit --multigroup=true

需要的工具

这三个工具是需要提前下载好的,放在项目的根目录下的 ​​bin​​ 目录下面的。


controller-gen (Version: v0.8.0)
kustomize (Version: v4.5.4)
setup-envtest (Version: latest)
Github 下载对应系统的二进制文件即可,版本的话,我测试的版本已标注,根据实际情况自行调整版本即可。

注意:工具下载完后,放到 ​​bin​​ 目录后,后面操作才可正常执行。


创建 API

执行以下命令创建我们需要 ​​api group​​。


$ kubebuilder create api --group user --version v1 --kind User

Create Resource [y/n] # 是否创建资源对象
y
Create Controller [y/n] # 是否创建 controller
y
  • --group:接口分组
  • --version:接口版本
  • --kind:对应 k8s 资源对象中的 kind
创建接口组后的代码结构

.
├── Dockerfile
├── Makefile
├── PROJECT
├── apis
│ └── user # 用户接口组
│ └── v1
│ ├── groupversion_info.go
│ ├── user_types.go
│ └── zz_generated.deepcopy.go
├── bin # 常用工具目录
│ ├── controller-gen
│ ├── kustomize
│ └── setup-envtest
├── config
│ ├── crd
│ │ ├── kustomization.yaml
│ │ ├── kustomizeconfig.yaml
│ │ └── patches
│ │ ├── cainjection_in_users.yaml
│ │ └── webhook_in_users.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ └── manager_config_patch.yaml
│ ├── manager
│ │ ├── controller_manager_config.yaml
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── role_binding.yaml
│ │ ├── service_account.yaml
│ │ ├── user_editor_role.yaml
│ │ └── user_viewer_role.yaml
│ └── samples
│ └── user_v1_user.yaml
├── controllers # controller 管理
│ └── user
│ ├── suite_test.go
│ └── user_controller.go
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── main.go

代码实现

定义用户字段

通过完善 ​​Spec​​​ 后缀的结构体,来完善 k8s 中资源对象对应的 ​​spec​​ 字段。

我们在这里加上用户相关的字段描述。

apis/user/v1/user_types.go

// UserSpec defines the desired state of User
type UserSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Nickname 用户名
Nickname string `json:"nickname,omitempty"`

// Password 密码
Password string `json:"password,omitempty"`

// Email 邮箱地址
Email string `json:"email,omitempty"`

// Tel 手机号
Tel string `json:"tel,omitempty"`

// IsAdmin 是否超级管理员
IsAdmin string `json:"is_admin,omitempty"`

// IsActive 是否可用
IsActive string `json:"is_active,omitempty"`
}

controller 开发

此部分开发,主要是绑定 ​​ServiceAccount​​。

在创建 ​​User​​​ 的时候,则创建对应同名的 ​​ServiceAccount​​,删除亦同理。

为方便统一管理,将 ​​ServiceAccount​​​ 统一存放在 ​​lanyulei_users​​ 的命名空间中。

kubebuilder 帮我们实现了大部分功能,因此我们只需要实现 ​​Reconcile​​​ 函数即可,​​req​​​ 会返回当前变更的对象的 ​​Namespace​​​ 和 ​​Name​​ 信息,有这两个信息,我们就可以获取到这个对象了,进行处理了。

controllers/user/user_controller.go

//+kubebuilder:rbac:groups=user.lanyulei.com,resources=users,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=user.lanyulei.com,resources=users/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=user.lanyulei.com,resources=users/finalizers,verbs=update
//+kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;create;delete # 添加此项注释,表示为当前 controller 可对 ServiceAccount 进行 get、list、create、delete 操作。

// Reconcile is part of the main k8s reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the User object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile
func (r *UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 判断同名 ServiceAccount 是否存在
sa := &corev1.ServiceAccount{}
saReq := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: UserNamespace,
Name: req.Name,
},
}
err := r.Get(ctx, saReq.NamespacedName, sa)
if errors.IsNotFound(err) {
err := r.createServiceAccountIfNotExists(ctx, req)
if err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

// 创建 ServiceAccount
func (r *UserReconciler) createServiceAccountIfNotExists(ctx context.Context, req ctrl.Request) (err error) {
logger := log.Log.WithValues("func", "createServiceAccountIfNotExists")

logger.Info("start create service account.")

user := &userv1.User{}
err = r.Get(ctx, req.NamespacedName, user)
if err != nil {
logger.Error(err, "Failed to get user.")
return
}

sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: req.Name,
Namespace: UserNamespace,
},
}

// 绑定对应的sa,删除的时候连带着删除
err = controllerutil.SetControllerReference(user, sa, r.Scheme)
if err != nil {
logger.Error(err, "SetControllerReference error")
return
}

err = r.Create(ctx, sa)
if err != nil {
logger.Error(err, "create service account error")
return
}

return
}
上面的代码中,我们看到了好多 ​​//+kubebuilder​​​ 这种格式的注释,此类注释是为了实现​​代码生成​​而添加的注释,此类内容较多,可通过搜索引擎,进行了解即可。


部署

首先我们需要本地需要有 ​​kubectl​​​ 命令,并且可以连接到 ​​k8s​​​ 集群。如果满足这个条件,那么我们就可以部署测试我们的 ​​operator​​ 了。

将 crd 部署到 k8s 集群上。

kubebuilder 帮我们写好了 ​​Makefile​​​ 如果没有定制化的需求,例如指定 k8s 集群配置文件,则直接执行下面的命令即可,若是有此类需求,还请自行调整 ​​Makefile​​。


部署 crd 到 k8s 集群

make install

本地启动 controller

make run

controller 部署到 k8s 集群运行

前面我们在开发环境将 controller 运行起来尝试了所有功能,在实际生产环境中,controller 并非这样独立于 k8s 之外,而是以 pod 的状态运行在 k8s 之中,接下来我们尝试将 controller 代码编译构建成 docker 镜像,再在k8s上运行起来。

首先你需要有一个 docker hub 的账号,然后使用 ​​docker login​​ 命令进行登陆。

执行下面的命令构建镜像并推送到 docker hub。

make docker-build docker-push IMG=lanyulei/lanyulei:v1.0.0
若推送网速过慢,可自行配置​​阿里云容器镜像加速器​​。

镜像准备好之后,执行以下命令即可在 k8s 集群中部署 controller。

make deploy IMG=lanyulei/lanyulei:v1.0.0
验证部署结果。

[root@karmada-work-1 <sub>]# kubectl get po -A
NAMESPACE NAME READY STATUS RESTARTS AGE
cert-manager cert-manager-64d9bc8b74-ptl4n 1/1 Running 0 149m
cert-manager cert-manager-cainjector-6db6b64d5f-xcw2d 1/1 Running 0 149m
cert-manager cert-manager-webhook-6c9dd55dc8-wk8lw 1/1 Running 0 149m
kube-system coredns-64897985d-wtcqq 1/1 Running 0 15h
kube-system coredns-64897985d-x8g7s 1/1 Running 0 15h
kube-system etcd-karmada-work-2-control-plane 1/1 Running 0 15h
kube-system kindnet-8wcr6 1/1 Running 0 15h
kube-system kube-apiserver-karmada-work-2-control-plane 1/1 Running 0 15h
kube-system kube-controller-manager-karmada-work-2-control-plane 1/1 Running 0 15h
kube-system kube-proxy-5w2ln 1/1 Running 0 15h
kube-system kube-scheduler-karmada-work-2-control-plane 1/1 Running 0 15h
local-path-storage local-path-provisioner-5ddd94ff66-fkw28 1/1 Running 0 15h

# 这个就是我们部署的 controller, 2/2 表示容器运行中了。
lanyulei-system lanyulei-controller-manager-7cb9cd6565-8wst8 2/2 Running 0 96m
[root@karmada-work-1 </sub>]#
查看日志,确认程序是否正常启动了。

kubectl logs -f \
lanyulei-controller-manager-7cb9cd6565-8wst8 \
-c manager \
-n lanyulei-system
没有 Error 日志,则表示 controller 正常启动了,可以处理请求了。

自此我们开发,存储管理用户信息的 ​​​operator​​​ 就开发完成,可以通过 ​​postman​​, 测试接口的增删改查。

 本文为原创文章,未经授权禁止转载本站文章。

 原文出处:兰玉磊的个人博客

 原文链接:https://www.fdevops.com/2022/04/10/kubebuilder-crd-31074

 版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。

有关kubebuilder 实战之开发一个存储用户信息的 operator的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  4. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  5. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  6. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  7. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  8. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  9. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  10. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

随机推荐