本文介紹如何使用 kubebuilder 實(shí)現(xiàn)一個(gè)存儲(chǔ)用戶信息的 CRD,同時(shí)開發(fā) controller 綁定同名的 ServiceAccount。
不過多介紹 kubebuilder 的理論知識(shí),直接開干。
開發(fā)環(huán)境準(zhǔn)備
初始化 kubebuilder
mkdir lanyulei
cd lanyulei
kubebuilder init --domain lanyulei.com --repo lanyulei
- init:初始化命令參數(shù)。
- --domain:可以理解為接口組的分組。根據(jù)實(shí)際情況來定,我一般定義為
項(xiàng)目名.com。 - --repo:項(xiàng)目名稱,若是當(dāng)前項(xiàng)目下已經(jīng)存在 go.mod,則無需此參數(shù)。
初始化成功后的代碼結(jié)構(gòu)。
.
├── 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
開啟支持多接口組
如果你本次項(xiàng)目開發(fā)的 operator 是多接口組的話,則需要執(zhí)行一下命令。
例如:同時(shí)需要用戶相關(guān)接口和角色相關(guān)接口,則需要兩組 api group,因此需要執(zhí)行以下操作。
kubebuilder edit --multigroup=true
需要的工具
這三個(gè)工具是需要提前下載好的,放在項(xiàng)目的根目錄下的 bin 目錄下面的。
controller-gen (Version: v0.8.0)
kustomize (Version: v4.5.4)
setup-envtest (Version: latest)
Github 下載對(duì)應(yīng)系統(tǒng)的二進(jìn)制文件即可,版本的話,我測試的版本已標(biāo)注,根據(jù)實(shí)際情況自行調(diào)整版本即可。
注意:工具下載完后,放到 bin 目錄后,后面操作才可正常執(zhí)行。
創(chuàng)建 API
執(zhí)行以下命令創(chuàng)建我們需要 api group。
$ kubebuilder create api --group user --version v1 --kind User
Create Resource [y/n] # 是否創(chuàng)建資源對(duì)象
y
Create Controller [y/n] # 是否創(chuàng)建 controller
y
- --group:接口分組
- --version:接口版本
- --kind:對(duì)應(yīng) k8s 資源對(duì)象中的 kind
創(chuàng)建接口組后的代碼結(jié)構(gòu)
.
├── 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
代碼實(shí)現(xiàn)
定義用戶字段
通過完善 Spec 后綴的結(jié)構(gòu)體,來完善 k8s 中資源對(duì)象對(duì)應(yīng)的 spec 字段。
我們?cè)谶@里加上用戶相關(guān)的字段描述。
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 手機(jī)號(hào)
Tel string `json:"tel,omitempty"`
// IsAdmin 是否超級(jí)管理員
IsAdmin string `json:"is_admin,omitempty"`
// IsActive 是否可用
IsActive string `json:"is_active,omitempty"`
}
controller 開發(fā)
此部分開發(fā),主要是綁定 ServiceAccount。
在創(chuàng)建 User 的時(shí)候,則創(chuàng)建對(duì)應(yīng)同名的 ServiceAccount,刪除亦同理。
為方便統(tǒng)一管理,將 ServiceAccount 統(tǒng)一存放在 lanyulei_users 的命名空間中。
kubebuilder 幫我們實(shí)現(xiàn)了大部分功能,因此我們只需要實(shí)現(xiàn) Reconcile 函數(shù)即可,req 會(huì)返回當(dāng)前變更的對(duì)象的 Namespace 和 Name 信息,有這兩個(gè)信息,我們就可以獲取到這個(gè)對(duì)象了,進(jìn)行處理了。
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 # 添加此項(xiàng)注釋,表示為當(dāng)前 controller 可對(duì) ServiceAccount 進(jìn)行 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
}
// 創(chuàng)建 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,
},
}
// 綁定對(duì)應(yīng)的sa,刪除的時(shí)候連帶著刪除
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 這種格式的注釋,此類注釋是為了實(shí)現(xiàn)代碼生成而添加的注釋,此類內(nèi)容較多,可通過搜索引擎,進(jìn)行了解即可。
部署
首先我們需要本地需要有 kubectl 命令,并且可以連接到 k8s 集群。如果滿足這個(gè)條件,那么我們就可以部署測試我們的 operator 了。
將 crd 部署到 k8s 集群上。
kubebuilder 幫我們寫好了 Makefile 如果沒有定制化的需求,例如指定 k8s 集群配置文件,則直接執(zhí)行下面的命令即可,若是有此類需求,還請(qǐng)自行調(diào)整 Makefile。
部署 crd 到 k8s 集群
make install
本地啟動(dòng) controller
make run
controller 部署到 k8s 集群運(yùn)行
前面我們?cè)陂_發(fā)環(huán)境將 controller 運(yùn)行起來嘗試了所有功能,在實(shí)際生產(chǎn)環(huán)境中,controller 并非這樣獨(dú)立于 k8s 之外,而是以 pod 的狀態(tài)運(yùn)行在 k8s 之中,接下來我們嘗試將 controller 代碼編譯構(gòu)建成 docker 鏡像,再在k8s上運(yùn)行起來。
首先你需要有一個(gè) docker hub 的賬號(hào),然后使用 docker login 命令進(jìn)行登陸。
執(zhí)行下面的命令構(gòu)建鏡像并推送到 docker hub。
make docker-build docker-push IMG=lanyulei/lanyulei:v1.0.0
若推送網(wǎng)速過慢,可自行配置阿里云容器鏡像加速器。
鏡像準(zhǔn)備好之后,執(zhí)行以下命令即可在 k8s 集群中部署 controller。
make deploy IMG=lanyulei/lanyulei:v1.0.0
驗(yàn)證部署結(jié)果。
[root@karmada-work-1 ~]# 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
# 這個(gè)就是我們部署的 controller, 2/2 表示容器運(yùn)行中了。
lanyulei-system lanyulei-controller-manager-7cb9cd6565-8wst8 2/2 Running 0 96m
[root@karmada-work-1 ~]#
查看日志,確認(rèn)程序是否正常啟動(dòng)了。
kubectl logs -f \
lanyulei-controller-manager-7cb9cd6565-8wst8 \
-c manager \
-n lanyulei-system
沒有 Error 日志,則表示 controller 正常啟動(dòng)了,可以處理請(qǐng)求了。
自此我們開發(fā),存儲(chǔ)管理用戶信息的 operator 就開發(fā)完成,可以通過 postman, 測試接口的增刪改查。
本文為原創(chuàng)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載本站文章。
原文出處:蘭玉磊的個(gè)人博客
原文鏈接:https://www.fdevops.com/2022/04/10/kubebuilder-crd-31074
版權(quán):本文采用「署名-非商業(yè)性使用-相同方式共享 4.0 國際」知識(shí)共享許可協(xié)議進(jìn)行許可。