《 Kubebuilder v2 使用指南 》-P6-CRD Admission Webhook

CRD Admission Webhook

前言

前面的文章中,實現了Unit資源對象實例持久化之後的controller管理的過程。除此之外,Kubernetes額外支持了一些很有趣且實用的功能,例如經常被用在資源准入控制上的Adminssion Webhook,它是對APIServer接收准入請求的擴展。詳情請參考官方文檔:

extensible-admission-controllers

認識Adminssion Webhook

什麼是Adminssion Webhook?

在官方文檔裏對的話來說就是:

Admission webhooks are HTTP callbacks that receive admission requests and do something with them. You can define two types of admission webhooks, validating admission webhook and mutating admission webhook. Mutating admission webhooks are invoked first, and can modify objects sent to the API server to enforce custom defaults. After all object modifications are complete, and after the incoming object is validated by the API server, validating admission webhooks are invoked and can reject requests to enforce custom policies.

一句話概括:Adminssion Webhook是APIServer接收到資源准入請求後執行的回調鉤子

在K8s中,追求一切皆資源,Webhook也是一種GVK,可以動態地向APIServer注入/修改/更新鉤子。

Adminssion Webhook的作用

Adminssion Webhook可以用作:在資源對象實例持久化之前,將 Kubernetes APIServer 的請求進行攔截,加入自定義的邏輯再處理之後,再返回給APIServer,更形象一點說,即是對APIServer接收的請求進行攔截和“篡改”

Adminssion Webhook的分類

mutating webhook

可用作一些"篡改"的邏輯,例如修改pod的結構,爲pod注入sidecar容器,istio就是這麼幹的。

validating webhook

用作校驗的邏輯,雖然go是嚴格的強類型語言,結構體內的字段已經做了type校驗,但字段的檢驗總是相對生硬死板的,validating webhook可以做很好的補充校驗。

注意:按照執行順序,validating webhook在mutating webhook之後執行。

Adminssion Webhook流程圖

Kubebuilder使用webhook

回到項目根目錄,執行:

# group version kind替換成自己的
mbp-16in:Unit ywq$ kubebuilder create webhook --group custom --version v1 --kind Unit --defaulting --programmatic-validation

Writing scaffold for you to edit...
api/v1/unit_webhook.go

可以看到,api/v1/下多了一個unit_webhook.go文件,裏面有4個方法以及列出來了

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Unit) Default() {

	unitlog.Info("default", "name", r.Name)
	// TODO(user): fill in your defaulting logic.

}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-custom-my-crd-com-v1-unit,mutating=false,failurePolicy=fail,groups=custom.my.crd.com,resources=units,versions=v1,name=vunit.kb.io

var _ webhook.Validator = &Unit{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Unit) ValidateCreate() error {
	unitlog.Info("validate create", "name", r.Name)

	// TODO(user): fill in your validation logic upon object creation.

}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *Unit) ValidateUpdate(old runtime.Object) error {
	unitlog.Info("validate update", "name", r.Name)

	// TODO(user): fill in your validation logic upon object update.
	return nil
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *Unit) ValidateDelete() error {
	unitlog.Info("validate delete", "name", r.Name)

	// TODO(user): fill in your validation logic upon object deletion.
	return nil
}

從命名和註釋就可以看出來,4個方法分別用於:

  • Default() 用於修改,即對應mutating webhook
  • ValidateCreate()用於校驗,對應validating webhook,僅在create操作使用
  • ValidateUpdate()用於校驗,對應validating webhook,僅在create操作使用
  • ValidateDelete()用於校驗,對應validating webhook,僅在create操作使用

Default()ValidateCreate()爲例,來實現簡單的Unit實例持久化之前的修改Unit默認字段、內容校驗的邏輯

Default()

func (r *Unit) Default() {
	unitlog.Info("default", "name", r.Name)

	// TODO(user): fill in your defaulting logic.
	// 這裏可以加入一些Unit 結構體對象初始化之前的一些默認的邏輯,比如給一些字段填充默認值

	// default replicas set to 1
	unitlog.Info("default", "name", r.Name)

	if r.Spec.Replicas == nil {
    defaultReplicas := int32(1)
		r.Spec.Replicas = &defaultReplicas
	}

	// add default selector label
	labelMap := make(map[string]string, 1)
	labelMap["app"] = r.Name
	r.Spec.Selector = &metav1.LabelSelector{
		MatchLabels: labelMap,
	}

	// add default template label
	r.Spec.Template.Labels = labelMap

	r.Status.LastUpdateTime = metav1.Now()
	
	// 當然,還可以根據需求加一些適合在初始化時做的邏輯,例如爲pod注入sidecar
	// ...

}

給Unit指定默認的副本數、添加默認的自定義標籤,還可以添加一些其他的東西,例如sidecar、initContainer等等

###ValidateCreate()

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-custom-my-crd-com-v1-unit,mutating=false,failurePolicy=fail,groups=custom.my.crd.com,resources=units,versions=v1,name=vunit.kb.io

// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Unit) ValidateCreate() error {
	unitlog.Info("validate create", "name", r.Name)

	// TODO(user): fill in your validation logic upon object creation.

	// 檢查Unit.Spec.Category
	switch r.Spec.Category {
	case CategoryDeployment:
		return nil
	case CategoryStatefulSet:
		return nil
	default:
		err := errors.New("spec.category only support Deployment or StatefulSet")
		unitlog.Error(err, "creating validate failed", "name", r.Name)
		return err
	}
}

因Unit動態支持Sts和Deploy,但只能二取其一,默認Unit結構體內的字段校驗顯然不適合做這種類型的校驗,所以把校驗的操作放到這裏來。

Kubebuilder marker

在validateCreate()方法上方,有兩行特殊的註釋不知有沒有吸引到你的注意力:

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-custom-my-crd-com-v1-unit,mutating=false,failurePolicy=fail,groups=custom.my.crd.com,resources=units,versions=v1,name=vunit.kb.io

第一行註釋說明,修改第二行中的verbs字段可以改變validateCreate()的工作插入時機。

看看第二行註釋的擡頭:+kubebuilder:,有沒有突然想起項目中有多處這種擡頭的註釋。這種格式的註釋對kubebuilder有着特殊的意義,被稱爲marker。在執行make指令的時候,kubebuilder會根據特定位置的marker,進行相應的轉換邏輯。

主要在如下3個地方使用到了marker:

webhook

api/v1/unit_webhook.go:39


// +kubebuilder:webhook:path=/mutate-custom-my-crd-com-v1-unit,mutating=true,failurePolicy=fail,groups=custom.my.crd.com,resources=units,verbs=create;update,versions=v1,name=munit.kb.io

var _ webhook.Defaulter = &Unit{}

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Unit) Default() {
	unitlog.Info("default", "name", r.Name)

	...

}

// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-custom-my-crd-com-v1-unit,mutating=false,failurePolicy=fail,groups=custom.my.crd.com,resources=units,versions=v1,name=vunit.kb.io


// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Unit) ValidateCreate() error {
	unitlog.Info("validate create", "name", r.Name)
  ...
}

這兩個鉤子函數的上方,都有marker的存在,這裏的marker的內容,會影響最終生成的Webhook資源的配置。

CRD type

api/v1/unit_types.go:79

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector
// Unit is the Schema for the units API
type Unit struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`

	Spec   UnitSpec   `json:"spec,omitempty"`
	Status UnitStatus `json:"status,omitempty"`
}

這裏有兩個後加入的marker,分別講一下:

+kubebuilder:subresource:status

上一篇文章中有提過,更新Unit.Status()時,儘量局部更新避免併發衝突。實現Status局部更新,需要指定此marker

+kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.selector

添加這個marker並正確指定replicas字段的specpath以及statuspath後,可以使用kubectl scale命令的形式來方便地修改CRD的replicas值,就像Deploy/Sts一樣。

Reconcile

controllers/unit_controller.go:64

// +kubebuilder:rbac:groups=custom.my.crd.com,resources=units,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=custom.my.crd.com,resources=units/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=statefulSet,verbs=get;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=deployment,verbs=get;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=service,verbs=get;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=endpoint,verbs=get
// +kubebuilder:rbac:groups=core,resources=persistentVolumeClaimStatus,verbs=get;update;patch;delete
// +kubebuilder:rbac:groups=extensions,resources=ingress,verbs=get;update;patch;delete

func (r *UnitReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
...
}

很容易理解,Reconcile控制邏輯運行過程中,難免要對多種資源進行增刪改查操作,rbac授權必不可少,因此,每種需要關注的GVK資源的授權都在這裏的以marker形式指定,在make之後,會生成相應的rbac yaml文件,體現在config/rbac目錄下。

總結

準備工作已經完成了,但代碼編寫很難一蹴而就,總會遇到error需要反覆調試,下一篇開始講述如何使本地環境與K8s集羣連接,在本地環境中進行代碼調試。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章