Kubebuilder構(gòu)建operator

1. 為什么需要crd

  • kubernetes默認支持deployment,configmap等資源抛虫,供我們以資源的形式管理云平臺上的服務(wù)松靡。
  • 但當我們需要添加新的資源時,為了不對kubernetes原有代碼進行更改建椰,則需要kubernetes支持動態(tài)擴展新資源類型雕欺。
  • 因此提供了自定義資源crd作為動態(tài)擴展的入口,通過crd可以聲明新資源類型棉姐,聲明后后就能創(chuàng)建該自定義資源的實例屠列,自定義資源對用的具體行為則由用戶創(chuàng)建的operator完成。

2. 整體結(jié)構(gòu)

如圖所示伞矩,其中黃色的部分是kubernetes默認的部分笛洛,而白色的模塊則是動態(tài)擴展的自定義資源及其組件。

  • 在etcd中用戶聲明了自定義crd的結(jié)構(gòu)乃坤,聲明的自定義資源與deploymenet等默認資源使用方式一致
  • 默認資源大部分由kubernetes中的controller-manager等組件管理撞蜂,而自定義資源則由部署的operator服務(wù)通過api-server管理。CRD資源只是一個抽象資源數(shù)據(jù)侥袜,資源對象對應(yīng)的實際服務(wù)是由operator提供。

譬如溉贿,聲明了訪問固定網(wǎng)頁的資源crd枫吧,那么當建立該資源對象時,operator就會獲取到該信息宇色,并根據(jù)資源對象中提供的地址去執(zhí)行訪問網(wǎng)頁的行為九杂。

crdss.png

3. 使用kubebuilder構(gòu)建operator

為什么使用kubebuilder? 因為當我們需要增加新資源時就需要聲明crd資源和構(gòu)建operator宣蠕。不同的opertor都需要連接apiserver同步自定義資源例隆,因此會有高度冗余的邏輯。
kubebuilder不僅會為我們生成opertor服務(wù)框架抢蚀,還能自動同步自定義資源的變化镀层。
用戶只需要定義CRD結(jié)構(gòu),和處理資源變動時的回調(diào)即可皿曲。

3.1 項目結(jié)構(gòu)

以使用kubebuilder創(chuàng)建的operator做結(jié)構(gòu)分析
如下圖所示唱逢,operator主要包含以下組件吴侦, 協(xié)同完成了對自定義資源的監(jiān)控和根據(jù)資源處理對應(yīng)的業(yè)務(wù)。

用戶只需要處理黃色部分的工作

  1. 定義好自定義資源結(jié)構(gòu)crd坞古,由框架注冊資源
  2. 定義回調(diào)函數(shù)controller备韧,資源變動時處理業(yè)務(wù)

以下部分是kubebuilder的框架性組件

  • Cache

Kubebuilder 的核心組件,負責在 Controller 進程里面根據(jù) Scheme 同步 Api Server 中所有該 Controller 關(guān)心 GVKs 的 GVRs痪枫,其核心是 GVK -> Informer 的映射织堂,Informer 會負責監(jiān)聽對應(yīng) GVK 的 GVRs 的創(chuàng)建/刪除/更新操作,以觸發(fā) Controller 的 Reconcile 邏輯奶陈。

  • Controller

Kubebuidler 為我們生成的腳手架文件易阳,我們只需要實現(xiàn) Reconcile 方法即可。

  • Clients

在實現(xiàn) Controller 的時候不可避免地需要對某些資源類型進行創(chuàng)建/刪除/更新尿瞭,就是通過該 Clients 實現(xiàn)的闽烙,其中查詢功能實際查詢是本地的 Cache,寫操作直接訪問 Api Server声搁。

  • Index

由于 Controller 經(jīng)常要對 Cache 進行查詢黑竞,Kubebuilder 提供 Index utility 給 Cache 加索引提升查詢效率。

  • Finalizer

在一般情況下疏旨,如果資源被刪除之后很魂,我們雖然能夠被觸發(fā)刪除事件,但是這個時候從 Cache 里面無法讀取任何被刪除對象的信息檐涝,這樣一來遏匆,導致很多垃圾清理工作因為信息不足無法進行,K8s 的 Finalizer 字段用于處理這種情況谁榜。在 K8s 中幅聘,只要對象 ObjectMeta 里面的 Finalizers 不為空,對該對象的 delete 操作就會轉(zhuǎn)變?yōu)?update 操作窃植,具體說就是 update deletionTimestamp 字段帝蒿,其意義就是告訴 K8s 的 GC“在deletionTimestamp 這個時刻之后,只要 Finalizers 為空巷怜,就立馬刪除掉該對象”葛超。

所以一般的使用姿勢就是在創(chuàng)建對象時把 Finalizers 設(shè)置好(任意 string),然后處理 DeletionTimestamp 不為空的 update 操作(實際是 delete)延塑,根據(jù) Finalizers 的值執(zhí)行完所有的 pre-delete hook(此時可以在 Cache 里面讀取到被刪除對象的任何信息)之后將 Finalizers 置為空即可绣张。

  • OwnerReference

K8s GC 在刪除一個對象時,任何 ownerReference 是該對象的對象都會被清除关带,與此同時侥涵,Kubebuidler 支持所有對象的變更都會觸發(fā) Owner 對象 controller 的 Reconcile 方法。

kube.png

3.2 構(gòu)建operator

我們將使用kubebuilder擴展一個帶有replicas字段的資源 imoocpod。當創(chuàng)建該資源實例后独令,將會維持數(shù)量等于replicas, 名稱等于該實例前綴的一組pod端朵,

  • kubebuilder 搭建與使用

Kubebuilder由Kubernetes特殊興趣組(SIG) API Machinery 擁有和維護,能夠幫助開發(fā)者創(chuàng)建 CRD 并生成 controller 腳手架燃箭。

  • 安裝kubebuilder

kubebuilder 使用起來比較簡單冲呢,首先我們需要安裝 kubebuilder 和它依賴的 kustomize。

os=$(go env GOOS)
arch=$(go env GOARCH)

# download kubebuilder and extract it to tmp
curl -sL https://go.kubebuilder.io/dl/2.3.0/${os}/${arch} | tar -xz -C /tmp/

# move to a long-term location and put it on your path
# (you'll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else)
mv /tmp/kubebuilder_2.3.0_${os}_${arch} /usr/local/kubebuilder
export PATH=$PATH:/usr/local/kubebuilder/bin
  • 初始化operator

首先通過以下命令創(chuàng)建 CR 的GVK(Group招狸、Version敬拓、Kind):

# 定義 crd 所屬的 domain,這個指令會幫助你生成一個工程裙戏。
$ kubebuilder init --domain xiyanxiyan10
# 創(chuàng)建 crd 在 golang 工程中的結(jié)構(gòu)體乘凸,以及其所需的 controller 邏輯
$ kubebuilder create api --group batch --version v1alpha1 --kind ImoocPod

執(zhí)行結(jié)束后,目錄結(jié)構(gòu)如下:

.
├── api         ## 這里定義了 sample 的結(jié)構(gòu)體累榜,以及所需的 deepcopy 實現(xiàn)         
│   └── v1alpha1
│       ├── groupversion_info.go
│       ├── imoocpod_types.go # 本例中需要二次開發(fā) crd定義
│       └── zz_generated.deepcopy.go
├── bin
│   └── manager ## controller 編譯后的 二進制文件
├── config      ## 包含了我們在使用 crd 是可能需要的 yml 文件营勤,包括rbac、controller的deployment 等
│   ├── certmanager
│   ├── crd
│   ├── default
│   ├── manager
│   ├── prometheus
│   ├── rbac
│   ├── samples
│   └── webhook
├── controllers         ## 我們的controller 邏輯就放在這里,
│   ├── imoocpod_controller.go # 本例中需要二次開發(fā) controller
│   └── suite_test.go
├── Dockerfile          
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT

自定義CRD字段

在api/v1/imoocpod_types.go中壹罚,包含了 kubebuilder 為我們生成的 ImoocPodSpec 以及相關(guān)字段, 我們改造生成的ImoocPodSpec, ImoocPodStatus 兩個結(jié)構(gòu)葛作,引入Replicas, PodNames 兩個字段完成demo功能。

package v1alpha1
    
import (    
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)    
    
// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
    
// ImoocPodSpec defines the desired state of ImoocPod
type ImoocPodSpec struct {
        // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
        // Important: Run "make" to regenerate code after modifying this file
    
        // Foo is an example field of ImoocPod. Edit ImoocPod_types.go to remove/update
        Replicas int `json:"replicas"`     //  這里二次開發(fā)猖凛,加入計數(shù)字段
}    
    
// ImoocPodStatus defines the observed state of ImoocPod
type ImoocPodStatus struct {
        // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
        // Important: Run "make" to regenerate code after modifying this file
        Replicas int      `json:"replicas"`
        PodNames []string `json:"podNames"`    //這里二次開發(fā)加入計數(shù)和pod列表
}    
    
// +kubebuilder:object:root=true
    
// ImoocPod is the Schema for the imoocpods API
type ImoocPod struct {
        metav1.TypeMeta   `json:",inline"`
        metav1.ObjectMeta `json:"metadata,omitempty"`
            
        Spec   ImoocPodSpec   `json:"spec,omitempty"`
        Status ImoocPodStatus `json:"status,omitempty"`
}
            
// +kubebuilder:object:root=true
            
// ImoocPodList contains a list of ImoocPod
type ImoocPodList struct {
        metav1.TypeMeta `json:",inline"`
        metav1.ListMeta `json:"metadata,omitempty"`
        Items           []ImoocPod `json:"items"`
}

  • 完善 controller 邏輯

kubebuilder 依賴于 controller-runtime 實現(xiàn) controller 整個處理流程赂蠢,在此工程中,controller 對資源的監(jiān)聽依賴于 Informer 機制辨泳,細節(jié)詳見K8s中 controller & infromer機制虱岂。controller-runtime 在此機制上又封裝了一層,其整體流程入下圖


其中 Informer 已經(jīng)由kubebuilder和contorller-runtime 實現(xiàn)菠红,監(jiān)聽到的資源的事件(創(chuàng)建第岖、刪除、更新试溯、webhock)都會放在 Informer 中绍傲。然后這個事件會經(jīng)過 predict()方法進行過濾,經(jīng)過interface enqueue進行處理耍共,最終放入 workqueue中。我們創(chuàng)建的 controller 則會依次從workqueue中拿取事件猎塞,并調(diào)用我們自己實現(xiàn)的 Recontile() 方法進行業(yè)務(wù)處理试读。
在controllers/imoocpod_controller.go中,有函數(shù)

// +kubebuilder:rbac:groups=sample.sample.io,resources=samples,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=sample.sample.io,resources=samples/status,verbs=get;update;patch
func (r *PlaybookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
        _ = context.Background()
        _ = r.Log.WithValues("imoocpod", req.NamespacedName)

        // your logic here

        return ctrl.Result{}, nil
}

我們改造他荠耽,實現(xiàn)ImoocPod資源對應(yīng)的具體邏輯

// +kubebuilder:rbac:groups=batch.xiyanxiyan10,resources=imoocpods,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=batch.xiyanxiyan10,resources=imoocpods/status,verbs=get;update;patch
        
func (r *ImoocPodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
        ctx := context.Background()
        logger := r.Log.WithValues("imoocpod", req.NamespacedName)
        logger.Info("start reconcile")
        
        // fetch the ImoocPod instance
        instance := &batchv1alpha1.ImoocPod{}
        if err := r.Client.Get(ctx, req.NamespacedName, instance); err != nil {
                if errors.IsNotFound(err) {
                        return ctrl.Result{}, nil
                }
                return ctrl.Result{}, err
        }
        
        // 1. 獲取 name 對應(yīng)的所有的 pod 的列表
        lbls := labels.Set{"app": instance.Name}
        existingPods := &corev1.PodList{}
        if err := r.Client.List(ctx, existingPods, &client.ListOptions{
                Namespace: req.Namespace, LabelSelector: labels.SelectorFromSet(lbls)}); err != nil {
                logger.Error(err, "fetching existing pods failed")
                return ctrl.Result{}, err
        }
        
        // 2. 獲取 pod 列表中的 pod name
        var existingPodNames []string
        for _, pod := range existingPods.Items {
                if pod.GetObjectMeta().GetDeletionTimestamp() != nil {
                        continue
                }
                if pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodPending {
                        existingPodNames = append(existingPodNames, pod.GetObjectMeta().GetName())
                }
        }
        
        
        // 4. pod.Spec.Replicas > 運行中的 len(pod.replicas)钩骇,比期望值小,需要 scale up create
        if instance.Spec.Replicas > len(existingPodNames) {
                logger.Info(fmt.Sprintf("creating pod, current and expected num: %d %d", len(existingPodNames), instance.Spec.Replicas))
                pod := newPodForCR(instance)
                if err := controllerutil.SetControllerReference(instance, pod, r.Scheme); err != nil {
                        logger.Error(err, "scale up failed: SetControllerReference")
                        return ctrl.Result{}, err
                }
       // 5. pod.Spec.Replicas < 運行中的 len(pod.replicas),比期望值大莱坎,需要 scale down delete
        if instance.Spec.Replicas < len(existingPodNames) {
                logger.Info(fmt.Sprintf("deleting pod, current and expected num: %d %d", len(existingPodNames), instance.Spec.Replicas))
                pod := existingPods.Items[0]
                existingPods.Items = existingPods.Items[1:]
                if err := r.Client.Delete(ctx, &pod); err != nil {
                        logger.Error(err, "scale down faled")
                        return ctrl.Result{}, err
                }
        }
       
        return ctrl.Result{Requeue: true}, nil
}      
       
func newPodForCR(cr *batchv1alpha1.ImoocPod) *corev1.Pod {
        labels := map[string]string{"app": cr.Name}
        return &corev1.Pod{
                ObjectMeta: metav1.ObjectMeta{
                        GenerateName: cr.Name + "-pod",
                        Namespace:    cr.Namespace,
                        Labels:       labels,
                },
                Spec: corev1.PodSpec{
                        Containers: []corev1.Container{
                                {
                                        Name:    "busybox",
                                        Image:   "busybox",
                                        Command: []string{"sleep", "3600"},
                                },
                        },
                },
        }
}      

  • 部署 crd 和 controller

在項目主目錄下執(zhí)行make install南片,會自動調(diào)用 kustomize 創(chuàng)建部署 crd 的yml并部署吕粹,我們也可以從 config/crd/bases/下找到對應(yīng)的 crd yaml文件。

然后執(zhí)行 make run 則在本地啟動 operator 主程序务蝠,日志如下,可見已經(jīng)在監(jiān)聽資源

$ make run
...
go vet ./...
/home/xiyanxiyan10/project/app/bin/controller-gen "crd:trivialVersions=true" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
go run ./main.go
2021-07-16T19:51:29.959+0800    INFO    controller-runtime.metrics  metrics server is starting to listen    {"addr": ":8080"}
2021-07-16T19:51:29.959+0800    INFO    setup   starting manager
2021-07-16T19:51:29.959+0800    INFO    controller-runtime.manager  starting metrics server {"path": "/metrics"}
2021-07-16T19:51:29.959+0800    INFO    controller-runtime.controller   Starting EventSource    {"controller": "imoocpod", "source": "kind source: /, Kind="}
2021-07-16T19:51:30.060+0800    INFO    controller-runtime.controller   Starting Controller {"controller": "imoocpod"}
2021-07-16T19:51:30.060+0800    INFO    controller-runtime.controller   Starting workers    {"controller": "imoocpod", "worker count": 1}
2021-07-16T19:51:30.060+0800    INFO    controllers.ImoocPod    start reconcile {"imoocpod": "default/demo"}

當我們想以deployment 方式部署controller時烛缔,可以使用 Dockerfile 構(gòu)建鏡像馏段,使用config/manager/manager.yml 部署。

  • 資源創(chuàng)建實例
    新建文件demo.yaml, 并使用命令部署 kubectl create -f ./demo.yaml
apiVersion: batch.xiyanxiyan10/v1alpha1
kind: ImoocPod
metadata:       
  name: demo 
spec:        
  replicas: 2 
  • 確認結(jié)果

可以看到践瓷,在kubernetes已經(jīng)根據(jù)自定義資源實例創(chuàng)建了對應(yīng)的pod對象


demo.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末院喜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子晕翠,更是在濱河造成了極大的恐慌喷舀,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淋肾,死亡現(xiàn)場離奇詭異硫麻,居然都是意外死亡,警方通過查閱死者的電腦和手機巫员,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門庶香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人简识,你說我怎么就攤上這事赶掖。” “怎么了七扰?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵奢赂,是天一觀的道長。 經(jīng)常有香客問我颈走,道長膳灶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任立由,我火速辦了婚禮轧钓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘锐膜。我一直安慰自己毕箍,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布道盏。 她就那樣靜靜地躺著而柑,像睡著了一般文捶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上媒咳,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天粹排,我揣著相機與錄音,去河邊找鬼涩澡。 笑死顽耳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的筏养。 我是一名探鬼主播斧抱,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼渐溶!你這毒婦竟也來了辉浦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茎辐,失蹤者是張志新(化名)和其女友劉穎宪郊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拖陆,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡弛槐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了依啰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乎串。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖速警,靈堂內(nèi)的尸體忽然破棺而出叹誉,到底是詐尸還是另有隱情,我是刑警寧澤闷旧,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布长豁,位于F島的核電站,受9級特大地震影響忙灼,放射性物質(zhì)發(fā)生泄漏匠襟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一该园、第九天 我趴在偏房一處隱蔽的房頂上張望酸舍。 院中可真熱鬧,春花似錦里初、人聲如沸父腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽璧亮。三九已至,卻和暖如春斥难,著一層夾襖步出監(jiān)牢的瞬間枝嘶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工哑诊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留群扶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓镀裤,卻偏偏與公主長得像竞阐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子暑劝,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容