使用kubebuilder開發簡單的Operator[雲原生]

唯愛薇o朝聖之路 發佈 2024-04-27T16:42:47.166934+00:00

雲原生技術通過方法論、工具集和最佳實踐重塑著整個軟體技術棧和生命周期,雲原生對雲計算服務方式與網際網路架構進行整體性升級, 深刻改變著整個 IT 領域。

雲原生技術通過方法論、工具集和最佳實踐重塑著整個軟體技術棧和生命周期,雲原生對雲計算服務方式與網際網路架構進行整體性升級, 深刻改變著整個 IT 領域。雲原生的核心是 Kubernetes,圍繞 kubernetes 構建滿足自身需求的 PaaS 平台(應用中心)是絕大數企業的訴求, 但是不同企業自身場景往往存在一定的差異,Operator 是最常見 kubernetes 拓展方式。本片博文,我將會給大家理清 operator 的來龍去脈, 同時介紹如何通過 kubebuilder 快速開發一個簡單的 Operator。

一、Operator 誕生的背景

kubernetes 無法做到真正意義的開箱即用的,它與傳統的 PaaS 平台不同,它僅僅只提供核心的基礎設施功能,但是還無法滿足用戶的最終需求,這裡用戶主要指業務開發和業務運維, 比如說業務開發需要 CI/CD 工具實現 DevOps 的功能,原生 kubernetes 是不提供支持的,但是我們可以通過tekton這一個第三方工具實現 DevOps 相關功能, 這也正是 kubernetes 區別傳統PaaS平台的真正強大之處,其提供完善的擴展機制以及基於此而發展出來的海量的第三方工具和豐富的生態。


Operator pattern首先由 CoreOS 提出,通過結合 CRD 和 custom Controller 將特定應用的運維知識轉換為代碼,實現應用運維的自動化和智能化。


Operator 允許 kubernetes 來管理複雜的,有狀態的分布式應用程式,並由 kubernetes 對其進行自動化管理,例如,etcd operator 能夠創建並管理一組 etcd 集群, 定製化的 controller 組件了解這些資源,知道如何維護這些特定的應用。


隨著 kubernetes 的功能越來越複雜,其需要管理的資源在高速增長,對應的 API 和 controller 的數量也愈發無法控制, kubernetes 變得很臃腫,很多不必要的 API 和功能將出現在每次安裝的集群中。


為了解決這個問題,CRD 應運而生,CRD 由 TPR(Third Part Resource v1.2 版本引入)演化而來,v1.7 進入 beta,v1.8 進入穩定, 通過 CRD,kubernetes 可以動態的添加並管理資源。


CRD 解決了結構化數據存儲的問題,Controller 則用來跟蹤這些資源, 保證資源的狀態滿足期望值。


CRD+Controller=Decalartive API,聲明式 API 設計是 kubernetes 重要的設計思想, 該設計保證能夠動態擴展 kubernetes API,這種模式也正是 Operator pattern。


kubernetes 本身也在通過 CRD 添加新功能,我們有什麼理由不使用呢?


二、使用場景總結及舉例

CRD+custom controller 已經被廣泛地使用,按使用場景可劃分為以下兩種:

1、通用型 controller

這種場景和 kubernetes 內置的apps controller類似,主要解決特定通用類型應用的管理

通用型 controller 往往是 kubernetes 平台側用戶,如各大雲廠商和 kubernetes 服務提供商。

通用型Controller樣例

  • cafedeploymentcontroller 阿里解決金融場景下分布式應用特殊需求。
  • oam-kubernetes-runtime 實現了 Application Model (OAM),以系統可持續的方式拓展 kubernetes

2、組件型Operator

組件型Operator則是各種軟體服務提供商, 他們設計時面向單一應用,很多開源的應用的 operator 可以在 operator hub 中獲取。

組件型Operator樣例

  • etcd operator
  • Prometheus operator

通用型Controller與kubernetes自帶的幾個組件型controller類似,旨在解決一些通用的應用模型,而Operator則更加面向單個特定應用, 這兩者沒有本質的區別。


三、如何開發 CRD

作為kubernetes開發者,如何開發 CRD+Custom cntroller 呢?其實官方提供示例項目sample-controller供開發者參考,開發流程大致有以下幾個過程:

  1. 初始化項目結構(可根據 sample controller 修改)
  2. 定義 CRD
  3. 生成代碼
  4. 初始化 controller
  5. 實現 controller 具體邏輯

其中步驟 2,5 是核心業務邏輯,其餘步驟完全可以通過自動生成的方式省略,到目前,社區有兩個成熟的腳手架工具用於簡化開發,一個是有 kube-sig 維護的 kubebuilder, 另一個是由 redhat 維護的 operator-sdk,這兩個工具都是基於 controller-runtime 項目而實現,用戶可自行選擇,筆者用的是 kubebuilder。使用 kubebuilder 能夠幫助我們節省以下工作:

如果你想要快速構建 CRD 和 Custom controller,腳手架工具是個不錯的選擇,如果是學習目的,建議結合 sample-controller 和 kubernetes controller 相關源碼。


四、kubebuilder 詳解

kubebuilder 是一個幫助開發者快速開發 kubernetes API 的腳手架命令行工具,其依賴庫 controller-tools 和 controller-runtime, controller-runtime 簡化 kubernetes controller 的開發,並且對 kubernetes 的幾個常用庫進行了二次封裝, 以簡化開發者使用。controller-tool 主要功能是代碼生成。下圖是使用 kubebuilder 的工作流程圖:

文章後面會結合一個簡單示例來介紹開發流程。

kubebuilder 有非常良好的文檔,包括一個從零開始的示例,您應該以文檔為主。

1、使用 kubebuilder 開發一個 CRD

本次示例創建一個通用的Application資源,Application 包含一個子資源 Deployment 以及一個 Count 資源, 每當 Application 進行被重新協調Reconcil,Count 會進行自增。

全部代碼請參考代碼倉

前提(你需要提前了解的)

  1. Golang 開發者,kubernetes 大量使用Code Generate這一功能來自動生成重複性代碼
  2. 閱讀 kubernetes controller 的代碼
  3. 閱讀 kubebuilder 的文檔
  4. 了解 kustomize


2、開發步驟及主要代碼展示

首先,根據你的開發環境安裝 kubebuilder 工具,mac 下通過 homebrew 安裝命令如下:

➜  ~ brew install kubebuilder
➜  ~ kubebuilder version
Version: version.Version{KubeBuilderVersion:"unknown", KubernetesVendor:"unknown", GitCommit:"$Format:%H$", BuildDate:"1970-01-01T00:00:00Z", GoOs:"unknown", GoArch:"unknown"}

安裝完畢後,首先創建項目目錄custom-controller並使用go mod初始化項目

➜  cd custom-controllers
➜  ls
➜  go mod init controllers.edu-vlovev-cn.ai12306.cn

接著,使用 kubebuilder 初始化項目,生成相關文件和目錄,並創建 CRD 資源

# 使用kubebuilder初始化項目
➜  custom-controllers kubebuilder init --domain controller.daocloud.io --license apache2 --owner "Holder"
# 創建CRD資源
➜  custom-controllers kubebuilder create api --group controller --version v1 --kind Application
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing scaffold for you to edit...
api/v1/application_types.go
controllers/application_controller.go
Running make:
$ make
/Users/donggang/Documents/Code/golang/bin/controller-gen object:headerFile="hack/huilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go


到目前,該項目就已經能夠運行了,不過我們需要添加我們自己業務代碼,主要包括修改 CRD 定義和添加 controller 邏輯兩部分。


首先修改 API 資源定義即 CRD 定義,Application 包含一個 Deployment,我們可以參考 kubernetes Deployment 與 Pod 這兩個類型之間的關係設計 Application 和 Deployment, Deployment 通過欄位spec.template來描述如何創建 Pod,DeploymentTemplateSpec描述了該如何創建 Deployment,

// PodTemplateSpec describes the data a deployment should have when created from a template
type DeploymentTemplateSpec struct {
 // Standard object's metadata.
 // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
 // +optional
 metav1.ObjectMeta json:"metadata,omitempty"

 // Specification of the desired behavior of the pod.
 // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
 Spec v12.DeploymentSpec json:"spec,omitempty"
}

API 描述定義完成了,接下來需要我們來進行具體業務邏輯實現了,編寫具體 controller 實現,首先我們簡單梳理 controller 的主要邏輯。


當一個 application 被創建時,需要創建對應的 deployment,當 application 被刪除或更新時,對應 Deployment 也需要被刪除或更新。


當 application 對應的子資源 deployment 被其他客戶端刪除或更新時,controller 需要重建或恢復它最後一步更新 application 的 status,這裡即 count 加 1


我們在方法func(r *ApplicationReconciler) Reconcile(req ctrl.Request)(ctrl.Result,error)實現相關邏輯, 當然當業務邏輯比較複雜時,可以拆分為多個方法。

func (r *ApplicationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
 ctx := context.Background()
 log := r.Log.WithValues("application", req.NamespacedName)

 var app controllerv1.Application
 if err := r.Get(ctx, req.NamespacedName, &app); err != nil {
  log.Error(err, "unable to fetch app")
  return ctrl.Result{}, client.IgnoreNotFound(err)
 }

 selector, err := metav1.LabelSelectorAsSelector(app.Spec.Selector)
 if err != nil {
  log.Error(err, "unable to convert label selector")
  return ctrl.Result{}, err
 }

 var deploys v12.DeploymentList
 if err := r.List(ctx, &deploys, client.InNamespace(req.Namespace), client.MatchingLabelsSelector{Selector: selector}); err != nil {
  if errors.IsNotFound(err) {
   deploy, err := r.constructDeploymentForApplication(&app)
   if err != nil {
    log.Error(err, "unable to construct deployment")
    return ctrl.Result{
     RequeueAfter: time.Second * 1,
    }, err
   }
   if err = r.Create(ctx, deploy); err != nil {
    return ctrl.Result{RequeueAfter: time.Second * 1}, err
   }
  }
 }
 ...
}


完成Reconcile方法後,我們可以修改config目錄的示例yaml,來進行本地測試了。


五、官方開發自定義 Controller 的指導

kubernetes開箱自帶了多個controller,這些controller在我們開發時具有非常重要的參考價值,同時社區也總結了的 controller 開發所需要遵循十一條原則, 但是請大家結合實際場景靈活運用這些原則:

六、總結及展望

本文簡單介紹了 CRD 以及如何使用腳手架工具 kubebuilder 幫助我們開發自定義 controller,當然這個 controller 示例的邏輯比較簡單, 在實際場景中,我們會遇到很多的挑戰,比如controller 的邏輯會比較複雜、需要通過多個controller等。作為kubernetes開發者, Controller開發是一項必不可少的技能。

關鍵字: