CRD 開發(fā)詳解

前言

最近一直在從事云原生相關(guān)的開發(fā)蚂四,開發(fā)的時候難免要進行 CRD 的控制器和 webhook 的開發(fā)歼疮。從最開始的磕磕絆絆,到現(xiàn)在還算順暢近顷,所以打算將這個過程總結(jié)了一下生音,寫成文檔,讓后來者少走些彎路窒升。

CRD 是什么缀遍,大家可以自行查閱,以后有時間精力饱须,我也會寫一篇文檔總結(jié)一下域醇。

這篇文檔,主要分為以下幾個部分:

  • kubebuilder 安裝
  • 項目創(chuàng)建
  • 項目實現(xiàn)
  • 項目部署

kubebuilder 安裝部分中蓉媳,會和大家如何進行安裝 kubebuilder譬挚。

項目創(chuàng)建部分會和大家一起使用 kubebuilder 創(chuàng)建我們的項目。

項目實現(xiàn)部分會和大家完成我們的項目代碼酪呻。

項目部署會部分和大家一起將我們的代碼部署到k8s集群减宣。

kubebuilder 安裝

介紹

Kubebuilder 是一個使用自定義資源定義 (CRD) 構(gòu)建 Kubernetes API 的框架。它可以提升我們開發(fā)CRD的效率玩荠,降低我們的開發(fā)成本漆腌,使我們低成本的進行 k8s Operator 開發(fā)。

和大多數(shù)開源項目一樣阶冈,kubebuilder 的安裝非常簡單闷尿。 只需要下載好我們需要的對應(yīng)的版本的安裝包就可以安裝了。

需要注意的是女坑,版本兼容問題:不同 kubebuilder 版本創(chuàng)建出來 controller 可能與 k8s 集群存在一定的兼容性問題填具,開發(fā)前,需要先明確自己的集群版本堂飞,然后再去選擇 kubebuilder 的版本灌旧。

安裝

在這里我選擇的是 v3.3.0 的 kubebuilder 進行安裝。

kubebuilder 開源倉庫

kubebuilder 發(fā)版地址

# 下載kubebuilder 二進制文件 注意 需要根據(jù)自己的系統(tǒng)內(nèi)核和CPU架構(gòu)進行下載  
sudo curl -L -o kubebuilder https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.3.0/kubebuilder_linux_amd64  
# 修改權(quán)限  
chmod +x kubebuilder  
# 移動到bin目錄  
mv kubebuilder /usr/local/bin/  
# 查看版本  
kubebuilder version  
Version: main.version{KubeBuilderVersion:"3.3.0", KubernetesVendor:"1.23.1", GitCommit:"47859bf2ebf96a64db69a2f7074ffdec7f15c1ec", BuildDate:"2022-01-18T17:03:29Z", GoOs:"linux", GoArch:"amd64"}

這樣绰筛,我們就完成了 kubebuilder 的安裝枢泰,是不是非常簡便。

接下來铝噩,我們就可以使用它進行項目創(chuàng)建了衡蚂。

項目創(chuàng)建

項目創(chuàng)建中,我們會使用到一些 kubebuilder 常用的命令來幫助我們快速創(chuàng)建項目。

命令介紹

我將在實戰(zhàn)中使用下面3個命令來創(chuàng)建我們的項目:

# 初始化項目
# 使用 --domain 可以指定<域>, 我們在這個項目中所創(chuàng)建的所有的API組都將是<group>.<domain>
# 使用 --projiect-name 可以指定我們項目的名稱毛甲。
kubebuilder init --domain test.crd --project-name test.crd

# 創(chuàng)建API 這個命令可以幫助我們快速創(chuàng)建CRD資源以及CRD控制器
# 使用 --group 可以指定資源組年叮。
# 使用 --version 可以指定資源的版本。
# 使用  --kind 可以指定資源的類型玻募,這個類型就是你自定義的CRD的名字只损。
kubebuilder create api --group test --version v1 --kind Test

# 創(chuàng)建WebHook
# --group  --kind  --version 和上述一致
# 使用 --defaulting 會為我們生成一個 webhook 的 Deafult 的接口實現(xiàn),需要我們自己完成七咧,用來補全CRD的默認值
# 使用--programmatic-validation 會為我們生成一個 webhook 的 Validation 的接口實現(xiàn)跃惫,要我們自己完成,用來校驗CRD資源在創(chuàng)建艾栋,更新爆存,刪除時數(shù)據(jù)是否正確。
kubebuilder create webhook --group test --version v1 --kind Test --defaulting --programmatic-validation

實戰(zhàn)

在這里蝗砾,我們創(chuàng)建一個CRD先较,這個CRD是用來送外賣訂單:

  • kind: Order
  • group: demo
  • domain: sumeng.com
  • version: v1

初始化項目

創(chuàng)建項目

# 創(chuàng)建項目文件夾
mkdir crd-demo
cd crd-demo
# 初始化項目
kubebuilder init --domain sumeng.com --project-name crd-demo
# 查看項目結(jié)構(gòu)
tree
.
├── 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_binding.yaml
│       ├── auth_proxy_role.yaml
│       ├── auth_proxy_service.yaml
│       ├── kustomization.yaml
│       ├── leader_election_role_binding.yaml
│       ├── leader_election_role.yaml
│       ├── role_binding.yaml
│       └── service_account.yaml
├── Dockerfile # 構(gòu)建鏡像的 Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt # 代碼頭文件
├── LICENSE
├── main.go 
├── Makefile
├── PROJECT
└── README.md

修改頭文件

# 修改頭文件
vim hack/boilerplate.go.txt

boilerplate.go.txt

/*
Copyright 2022 The CRD-Demo Authors. # 修改了這里

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

修改 Makefile

使用golang版本為1.18時 直接使用 kubebuilder create api命令會出現(xiàn) controller-gen:no such file or directory 的錯誤。我們需要修改一下 Makefile 來避免錯誤:

修改 go-get-tool 使用go install 來替代 go get

# go-get-tool will 'go get' any package $2 and install it to $1.
PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
define go-get-tool
@[ -f $(1) ] || { \
set -e ;\
TMP_DIR=$$(mktemp -d) ;\
cd $$TMP_DIR ;\
go mod init tmp ;\
echo "Downloading $(2)" ;\
GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ # 修改這里 go get --> go install
rm -rf $$TMP_DIR ;\
}
endef

創(chuàng)建API

# 創(chuàng)建API
# kubebuilder 會詢問你是否創(chuàng)建資源和控制器悼粮,我們都選擇是就可以了闲勺。
kubebuilder create api --group demo --version v1 --kind Order

# 查看結(jié)構(gòu) 我們會發(fā)現(xiàn)多出來了以下內(nèi)容。
tree
.
├── api # 我們資源的資源結(jié)構(gòu)存放在這里矮锈。
│   └── v1 # 我們定義的版本在結(jié)構(gòu)中以目錄的形式呈現(xiàn)出來霉翔。
│       ├── groupversion_info.go # 我們定義的CRD的 Group 和 version 信息
│       ├── order_types.go # CRD 結(jié)構(gòu)體定義,我們需要根據(jù)自己的需求修改。
│       └── zz_generated.deepcopy.go
├── bin
│   └── controller-gen # 用來生成控制器的工具
├── config # 該目錄下的大多數(shù)配置文件都不需要手動編寫,可以使用工具生成
│   ├── crd # crd 相關(guān)配置
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches
│   │       ├── cainjection_in_orders.yaml
│   │       └── webhook_in_orders.yam
│   ├── rbac # 權(quán)限相關(guān)
│   │   ├── order_editor_role.yaml
│   │   ├── order_viewer_role.yaml
│   └── samples # 使用demo
│       └── demo_v1_order.yaml
├── controllers # 控制器代碼目錄
│   ├── order_controller.go
│   └── suite_test.go

創(chuàng)建WebHook

# 創(chuàng)建WebHook
kubebuilder create webhook --group demo --version v1 --kind Order --defaulting --programmatic-validation
# 查看結(jié)構(gòu)
tree
.
├── api
│   └── v1
│       ├── order_webhook.go # webhook 代碼文件
│       └── webhook_suite_test.go
├── config
│   ├── certmanager #認證密鑰相關(guān)
│   │   ├── certificate.yaml
│   │   ├── kustomization.yaml
│   │   └── kustomizeconfig.yaml
│   ├── default
│   │   ├── manager_webhook_patch.yaml
│   │   └── webhookcainjection_patch.yaml
│   └── webhook
│       ├── kustomization.yaml
│       ├── kustomizeconfig.yaml
│       └── service.yaml

項目實現(xiàn)

經(jīng)過上述步驟畸陡,我們已經(jīng)完成了項目的創(chuàng)建坦刀,接下來我們就開始項目實現(xiàn)的過程,主要分為以下幾步:

  • 項目介紹
  • CRD 編寫
  • 控制器實現(xiàn)
  • WebHook實現(xiàn)

項目介紹

外賣訂單臭杰,訂單里有客戶的點商品信息粤咪,商家信息,以及配送信息渴杆。

訂單狀態(tài)變更為:

  • "" ---> 未接單
  • 未接單 ---> 已接單
  • 已接單 ---> 制作中
  • 制作中 ---> 制作完成
  • 制作完成 ---> 待配送
  • 待配送 ---> 配送中
  • 配送中 ---> 訂單完成

因為我們沒有實際的商家和騎手來更新狀態(tài)寥枝,所以我們隨機一個時間,來推動訂單進程磁奖。

接下來囊拜,我們就來實現(xiàn)這樣一個簡單的demo吧!

CRD 編寫

./api/v1/order_types.go

Spec

Spec 部分主要用于描述我們的自定義資源信息比搭。在這里冠跷,我們用來編寫訂單的信息,如商品信息,商家信息蜜托。

// OrderSpec defines the desired state of Order  
type OrderSpec struct {  
   // the information for the Shop  
   // +kubebuilder:validation:Required  
   Shop *ShopInfo `json:"shop"`  
  
   // Commodities is a list of CommodityInfo  
   // +kubebuilder:validation:Required  
   Commodities []CommodityInfo `json:"commodity"`  
  
   // TotalPrice is the total price of the Order  
   // +kubebuilder:validation:Required  
   TotalPrice int64 `json:"totalPrice"`  
  
   // Remark of Order  
   // +optional  
   Remark string `json:"remark,omitempty"`  
}  
  
type ShopInfo struct {  
   // Name of the shop   
   Name string `json:"name"`  
}  
  
type CommodityInfo struct {  
   // Name of the commodity   
   Name string `json:"name"`  
  
   // Price of the commodity   
   Price int64 `json:"price"`  
  
   // Quantity of commodity   
   Quantity int64 `json:"quantity"`  
}

上述代碼中抄囚,我們可以看到一些帶 ‘+’ 的注釋,這些是我們在生成 crd yaml 文件時的一些標(biāo)識橄务,他可以幫我做一些簡單的功能幔托,節(jié)約我們的開發(fā)時間。

  • +kubebuilder:validation:Required 用于標(biāo)記該字段是必填項蜂挪。
  • +optional 用于標(biāo)記該字段是可選項

更多的標(biāo)記我們可以查閱:Kubebuilder Book

Status

status 用來記錄資源的狀態(tài)柑司,這樣我們用來記錄訂單的狀態(tài)。

// OrderStatus defines the observed state of Order  
type OrderStatus struct {  
   // Conditions a list of conditions an order can have.   
   // +optional   
   Conditions []OrderCondition `json:"conditions,omitempty"`  
   // +optional  
   Phase OrderPhase `json:"phase,omitempty"`  
   // +optional  
   Message string `json:"message,omitempty"`  
}

type OrderCondition struct {  
   // Type of order condition.   Type OrderConditionType `json:"type"`  
   // Phase of the condition, one of True, False, Unknown.  
   Status corev1.ConditionStatus `json:"status"`  
   // The last time this condition was updated.  
   LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`  
   // Last time the condition transitioned from one status to another.  
   LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`  
   // The reason for the condition's last transition.  
   Reason string `json:"reason,omitempty"`  
   // A human-readable message indicating details about the transition.  
   Message string `json:"message,omitempty"`  
}  
  
type OrderConditionType string  
  
const (  
   ConditionShop     OrderConditionType = "Shop"  
   ConditionDelivery OrderConditionType = "Delivery"  
)

type OrderPhase string  
  
const (  
   OrderNotAccepted  OrderPhase = "未接單"  
   OrderAccepted     OrderPhase = "已接單"  
   OrderInMaking     OrderPhase = "制作中"  
   OrderMakeComplete OrderPhase = "制作完成"  
   OrderWaiting      OrderPhase = "待配送"  
   OrderDelivery     OrderPhase = "配送中"  
   OrderFinish       OrderPhase = "訂單完成"  
)

Order

主體部分我們不需要修改锅劝,在此處攒驰,僅僅只是加上打印標(biāo)識,方便我們后續(xù)觀察故爵。

//+genclient  
//+kubebuilder:object:root=true  
//+kubebuilder:subresource:status  
//+kubebuilder:printcolumn:name="STATUS",type="string",JSONPath=".status.phase",description="The order status phase"  
//+kubebuilder:printcolumn:name="MESSAGE",type="string",JSONPath=".status.message",description="The order status message"  
  
// Order is the Schema for the orders APItype Order struct {  
   metav1.TypeMeta   `json:",inline"`  
   metav1.ObjectMeta `json:"metadata,omitempty"`  
  
   Spec   OrderSpec   `json:"spec,omitempty"`  
   Status OrderStatus `json:"status,omitempty"`  
}
  • kubebuilder:printcolumn 打印列

上述我們就完成了我們代碼CRD的編寫玻粪,接下來,我們就開始我們代碼的控制器的實現(xiàn)诬垂。

控制器實現(xiàn)

./controllers/order_controller.go

const MaxSpeedTime int = 60
func (r *OrderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {  
   // Fetch the order instance  
   order := &demov1.Order{}  
   err := r.Get(context.TODO(), req.NamespacedName, order)  
   if err != nil {  
      if errors.IsNotFound(err) {  
         return ctrl.Result{}, nil  
      }  
      // Error reading the object - requeue the request.  
      return ctrl.Result{}, err  
   }  
     
   status := order.Status.DeepCopy()  
   defer func() {  
      err := r.updateScaleStatusInternal(order, *status)  
      if err != nil {  
         klog.Errorf("update order(%s/%s) status failed: %s",  
            order.Namespace, order.Name, err.Error())  
         return  
      }  
   }()  
  
   //Simulate the time spent in each phase  
   speedTime := rand.Int() % MaxSpeedTime  
   switch status.Phase {  
   case "":  
      status.Phase = demov1.OrderNotAccepted  
      status.Message = "Order not accepted"  
   case demov1.OrderNotAccepted:  
      status.Phase = demov1.OrderAccepted  
      status.Message = "Order accepted"  
      cond := NewOrderCondition(demov1.ConditionShop, corev1.ConditionFalse, status.Message, status.Message)  
      SetOrderCondition(status, *cond)  
   case demov1.OrderAccepted:  
      status.Phase = demov1.OrderInMaking  
      status.Message = "Order in making"  
   case demov1.OrderInMaking:  
      status.Phase = demov1.OrderMakeComplete  
      status.Message = "Order make complete"  
      cond := NewOrderCondition(demov1.ConditionShop, corev1.ConditionTrue, status.Message, status.Message)  
      SetOrderCondition(status, *cond)  
   case demov1.OrderMakeComplete:  
      status.Phase = demov1.OrderWaiting  
      status.Message = "Order wait delivery"  
      cond := NewOrderCondition(demov1.ConditionDelivery, corev1.ConditionFalse, status.Message, status.Message)  
      SetOrderCondition(status, *cond)  
   case demov1.OrderWaiting:  
      status.Phase = demov1.OrderDelivery  
      status.Message = "Order delivery"  
   case demov1.OrderDelivery:  
      status.Phase = demov1.OrderFinish  
      status.Message = "Order finished,customer has signed"  
   case demov1.OrderFinish:  
      cond := NewOrderCondition(demov1.ConditionDelivery, corev1.ConditionTrue, "Success", status.Message)  
      SetOrderCondition(status, *cond)  
      return ctrl.Result{}, nil  
   }  
  
   return ctrl.Result{RequeueAfter: time.Duration(speedTime) * time.Second}, nil  
}  
  
func (r *OrderReconciler) updateScaleStatusInternal(scale *demov1.Order, newStatus demov1.OrderStatus) error {  
   if reflect.DeepEqual(scale.Status, newStatus) {  
      return nil  
   }  
   clone := scale.DeepCopy()  
   if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {  
      if err := r.Client.Get(context.TODO(),  
         types.NamespacedName{Name: scale.Name, Namespace: scale.Namespace},  
         clone); err != nil {  
         klog.Errorf("error getting updated scale(%s/%s) from client",  
            scale.Namespace, scale.Name)  
         return err  
      }  
      clone.Status = newStatus  
      if err := r.Client.Status().Update(context.TODO(), clone); err != nil {  
         return err  
      }  
      return nil  
   }); err != nil {  
      return err  
   }  
   oldBy, _ := json.Marshal(scale.Status)  
   newBy, _ := json.Marshal(newStatus)  
   klog.V(5).Infof("order(%s/%s) status from(%s) -> to(%s)", scale.Namespace, scale.Name, string(oldBy), string(newBy))  
   return nil  
}  
  
// SetupWithManager sets up the controller with the Manager.
func (r *OrderReconciler) SetupWithManager(mgr ctrl.Manager) error {  
   //Order status changes do not trigger the reconcile process  
   predicates := builder.WithPredicates(predicate.Funcs{  
      UpdateFunc: func(e event.UpdateEvent) bool {  
         oldObject := e.ObjectOld.(*demov1.Order)  
         newObject := e.ObjectNew.(*demov1.Order)  
         if oldObject.Generation != newObject.Generation || newObject.DeletionTimestamp != nil {  
            klog.V(3).Infof("Observed updated for order: %s/%s", newObject.Namespace, newObject.Name)  
            return true  
         }  
         return false  
      },  
   })  
   return ctrl.NewControllerManagedBy(mgr).  
      For(&demov1.Order{}, predicates).  
      Complete(r)  
}

上面便是控制器的邏輯劲室,其實很多時候也和crud沒太多區(qū)別,我們就在 Reconcile 里實現(xiàn)我們的業(yè)務(wù)邏輯就可以了结窘。

WebHook實現(xiàn)

./api/v1/order_webhook.go

// Default implements webhook.Defaulter so a webhook will be registered for the type
func (in *Order) Default() {  
   // Set the default value.  
   // However, we have noting to do in this crd resources.
}  
  

var _ webhook.Validator = &Order{}  
  
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (in *Order) ValidateCreate() error {  
   orderlog.Info("validate create", "name", in.Name)  
   return in.Spec.validate()  
}  
  
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (in *Order) ValidateUpdate(old runtime.Object) error {  
   orderlog.Info("validate update", "name", in.Name)  
   return in.Spec.validate()  
}  
  
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (in *Order) ValidateDelete() error {  
   return nil  
}  
  
func (in *OrderSpec) validate() error {  
   if in.TotalPrice <= 0 {  
      return fmt.Errorf("total price must be greater than 0")  
   }  
  
   var totalPrice int64 = 0  
   for i := range in.Commodities {  
      err := in.Commodities[i].validate()  
      if err != nil {  
         return err  
      }  
      totalPrice += in.Commodities[i].Price * in.Commodities[i].Quantity  
   }  
  
   if totalPrice != in.TotalPrice {  
      return fmt.Errorf("the total price of the item is incorrect")  
   }  
   return nil  
}  
  
func (in *CommodityInfo) validate() error {  
   if in.Quantity <= 0 {  
      return fmt.Errorf("commodity %s quantity must be greater than 0", in.Name)  
   }  
   if in.Price <= 0 {  
      return fmt.Errorf("commodity %s price must be greater than 0", in.Name)  
   }  
   return nil  
}

在上面的代碼中很洋,我們只是簡單的校驗的訂單數(shù)據(jù),具體的開發(fā)中隧枫,我們可以根據(jù)我們的需求去實現(xiàn)喉磁。

項目部署

經(jīng)過上面的步驟,我們項目算是編碼完成了官脓,接下來协怒,我們就開始部署我們的服務(wù)。

主要分為以下幾步:

  • 修改 Makefile
  • 修改 Dockerfile
  • 生成部署文件
  • 部署服務(wù)

修改 Makefile

大多數(shù)情況下卑笨,kubebuilder 生產(chǎn)的 makefile 文件已經(jīng)夠我們使用了孕暇,但為了更加方便我們的開發(fā)與部署,我們可以進行以下改造:

# ===remove===  
# Image URL to use all building/pushing image targets  
# IMG ?= controller:latest  

# ===add=== 
# your docker repositories  
REPO ?=  
  
# your project name  
PROJ ?=order  
  
# your project tag or version  
TAG ?=latest

.PHONY: deploy-yaml  
# Generate deploy yaml.  
deploy-yaml: kustomize ## Generate deploy yaml.  
   $(call gen-yamls)

define gen-yamls  
{\  
set -e ;\  
[ -f ${PROJECT_DIR}/_output/yamls/build ] || mkdir -p ${PROJECT_DIR}/_output/yamls/build; \  
rm -rf ${PROJECT_DIR}/_output/yamls/build/${PROJ}; \  
cp -rf ${PROJECT_DIR}/config/* ${PROJECT_DIR}/_output/yamls/build/; \  
cd ${PROJECT_DIR}/_output/yamls/build/manager; \  
${KUSTOMIZE} edit set image controller=${REPO}/${PROJ}:${TAG}; \  
set +x ;\  
echo "==== create deploy.yaml in ${PROJECT_DIR}/_output/yamls/ ====";\  
${KUSTOMIZE} build ${PROJECT_DIR}/_output/yamls/build/default > ${PROJECT_DIR}/_output/yamls/deploy.yaml;\  
}  
endef

# ===modify===
.PHONY: docker-build  
docker-build: ## Build docker image with the manager.  
   docker build --no-cache . -t ${REPO}/${PROJ}:${TAG}  
   docker rmi `docker images | grep none | awk '{print $$3}'` # clean the assets of docker images when build endings  
  
.PHONY: docker-push  
docker-push: ## Push docker image with the manager.  
   docker push ${REPO}/${PROJ}:${TAG}

## Location to install dependencies to  
LOCALBIN ?= $(shell pwd)/bin  
$(LOCALBIN):  
   mkdir -p $(LOCALBIN)  
  
KUSTOMIZE = $(LOCALBIN)/kustomize  
KUSTOMIZE_VERSION ?= v3.8.7  
KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  
.PHONY: kustomize  
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.  
$(KUSTOMIZE): $(LOCALBIN)  
ifeq ($(wildcard $(KUSTOMIZE)),)  
   curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN)  
endif

上述文件中:

  • 將 docker 鏡像名稱拆分成了3部分:REPO/PROJ/TAG,方便我們構(gòu)建鏡像赤兴。
  • 新增了開始生成 deploy.yaml 的代碼妖滔。
  • 修改了獲取 kustomize 的方式

修改 Dockerfile

原來的 Dockerfile 中,每次構(gòu)建都需要重新拉去依賴桶良,我們可以使用 go vendor 的形式減少省下拉取依賴的時間座舍。

但是這就要求我們在構(gòu)建代碼前,現(xiàn)將依賴拉取到本地艺普。

# ===remove===
RUN go mod download
# ===add===
COPY vendor/ vendor/

生成部署文件

使用 makefile 可以幫我們快速生成相關(guān)配置文件

生成CRD & RBAC

# 生成 crd 和 rbac yaml 文件
make manifests
# 查看結(jié)構(gòu)
tree
.
├── config
│   ├── crd
│   │   ├── bases
│   │   │   └── demo.sumeng.com_orders.yaml # crd 文件

生成部署文件

修改文件

修改 ./config/crd/kustomization.yaml

resources:  
- bases/demo.sumeng.com_orders.yaml  
#+kubebuilder:scaffold:crdkustomizeresource  
  
patchesStrategicMerge:  
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.  
# patches here are for enabling the conversion webhook for each CRD  
- patches/webhook_in_orders.yaml  
#+kubebuilder:scaffold:crdkustomizewebhookpatch  
  
# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.  
# patches here are for enabling the CA injection for each CRD  
- patches/cainjection_in_orders.yaml  
#+kubebuilder:scaffold:crdkustomizecainjectionpatch  
  
# the following config is for teaching kustomize how to do kustomization for CRDs.  
configurations:  
- kustomizeconfig.yaml

修改 ./config/default/kustomization.yaml

# Adds namespace to all resources.  
namespace: crd-demo-system  
  
# Value of this field is prepended to the  
# names of all resources, e.g. a deployment named  
# "wordpress" becomes "alices-wordpress".  
# Note that it should also match with the prefix (text before '-') of the namespace  
# field above.  
namePrefix: crd-demo-  
  
# Labels to add to all resources and selectors.  
#commonLabels:  
#  someName: someValue  
  
bases:  
- ../crd  
- ../rbac  
- ../manager  
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in  
# crd/kustomization.yaml  
- ../webhook  
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.  
- ../certmanager  
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.  
#- ../prometheus  
  
patchesStrategicMerge:  
# Protect the /metrics endpoint by putting it behind auth.  
# If you want your controller-manager to expose the /metrics  
# endpoint w/o any authn/z, please comment the following line.  
- manager_auth_proxy_patch.yaml  
  
# Mount the controller config file for loading manager configurations  
# through a ComponentConfig type  
#- manager_config_patch.yaml  
  
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in  
# crd/kustomization.yaml  
- manager_webhook_patch.yaml  
  
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.  
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.  
# 'CERTMANAGER' needs to be enabled to use ca injection  
- webhookcainjection_patch.yaml  
  
# the following config is for teaching kustomize how to do var substitution  
vars:  
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.  
- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR  
  objref:  
    kind: Certificate  
    group: cert-manager.io  
    version: v1  
    name: serving-cert # this name should match the one in certificate.yaml  
  fieldref:  
    fieldpath: metadata.namespace  
- name: CERTIFICATE_NAME  
  objref:  
    kind: Certificate  
    group: cert-manager.io  
    version: v1  
    name: serving-cert # this name should match the one in certificate.yaml  
- name: SERVICE_NAMESPACE # namespace of the service  
  objref:  
    kind: Service  
    version: v1  
    name: webhook-service  
  fieldref:  
    fieldpath: metadata.namespace  
- name: SERVICE_NAME  
  objref:  
    kind: Service  
    version: v1  
    name: webhook-service

修改 ./config/default/manager_auth_proxy_patch.yaml

apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: controller-manager  
  namespace: system  
spec:  
  template:  
    spec:  
      containers:  
      - name: manager  
        args:  
        - "--health-probe-bind-address=:8081"  
        - "--metrics-bind-address=127.0.0.1:8080"  
        - "--leader-elect"

修改 ./config/default/webhookcainjection_patch.yaml

apiVersion: admissionregistration.k8s.io/v1  
kind: ValidatingWebhookConfiguration  
metadata:  
  name: validating-webhook-configuration  
  annotations:  
    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)

修改 ./config/webhook/manifests.yaml

---  
apiVersion: admissionregistration.k8s.io/v1  
kind: ValidatingWebhookConfiguration  
metadata:  
  creationTimestamp: null  
  name: validating-webhook-configuration  
webhooks:  
- admissionReviewVersions:  
  - v1  
  clientConfig:  
    service:  
      name: webhook-service  
      namespace: system  
      path: /validate-demo-sumeng-com-v1-order  
  failurePolicy: Fail  
  name: vorder.kb.io  
  rules:  
  - apiGroups:  
    - demo.sumeng.com  
    apiVersions:  
    - v1  
    operations:  
    - CREATE  
    - UPDATE  
    resources:  
    - orders  
  sideEffects: None

生成文件

# 安裝 kustomize
make kustomize
# 生成部署文件
make deploy-yaml REPO=<xxx> TAG=<xxx>
# 生成的部署文件為 ./_output/yamls/deploy.yaml, 我們拿著這個文件去k8s集群部署就可以了

部署服務(wù)

經(jīng)歷了這么多簸州,終于要開始部署服務(wù)了鉴竭。開始之前,我們要先構(gòu)建我們的服務(wù)岸浑。

# 拉取依賴
go mod tidy
go mod vendor
# 生成 deepcopy 文件
make generate
# 制作鏡像
make docker-build REPO=<xxx> TAG=<xxx>
# 上傳鏡像
make docker-push REPO=<xxx> TAG=<xxx>

# 進行制作完成后搏存,為了防止我們的指定的鏡像不對,我們可以重新生成部署文件
make deploy-yaml REPO=<xxx> TAG=<xxx>

# 部署服務(wù)
# 這個是時候矢洲,我們就可以拿著這個文件去我們的集群部署服務(wù)了璧眠。
kubectl apply -f ./_output/yamls/deploy.yaml

# 我們查看pod,會發(fā)現(xiàn)我們的控制器一致起不來读虏,是因為我們添加了webhook服務(wù)责静,需要安裝certmanger
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml
kubectl apply -f ./_output/yamls/deploy.yaml
namespace/crd-demo-system created
customresourcedefinition.apiextensions.k8s.io/orders.demo.sumeng.com created
serviceaccount/crd-demo-controller-manager created
role.rbac.authorization.k8s.io/crd-demo-leader-election-role created
clusterrole.rbac.authorization.k8s.io/crd-demo-manager-role created
clusterrole.rbac.authorization.k8s.io/crd-demo-metrics-reader created
clusterrole.rbac.authorization.k8s.io/crd-demo-proxy-role created
rolebinding.rbac.authorization.k8s.io/crd-demo-leader-election-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/crd-demo-manager-rolebinding created
clusterrolebinding.rbac.authorization.k8s.io/crd-demo-proxy-rolebinding created
configmap/crd-demo-manager-config created
service/crd-demo-controller-manager-metrics-service created
service/crd-demo-webhook-service created
deployment.apps/crd-demo-controller-manager created
certificate.cert-manager.io/crd-demo-serving-cert created
issuer.cert-manager.io/crd-demo-selfsigned-issuer created
validatingwebhookconfiguration.admissionregistration.k8s.io/crd-demo-validating-webhook-configuration created
?  crd-demo k get pod -n crd-demo-system 
NAME                                           READY   STATUS    RESTARTS   AGE
crd-demo-controller-manager-68dc8c65bc-nlkwp   1/1     Running   0          4m5s

經(jīng)過上述步驟,我們的服務(wù)就跑起來了盖桥。

我們可編寫一個資源去驗證一下灾螃。

./config/samples/demo_v1_order.yaml

apiVersion: demo.sumeng.com/v1  
kind: Order  
metadata:  
  name: order-sample  
spec:  
  shop:  
    name: 鮮果市場  
  commodity:  
    - name: 白菜  
      price: 1  
      quantity: 5  
    - name: 黃瓜  
      price: 2  
      quantity: 5  
  totalPrice: 15  
  remark: 送貨上門

觀察她的狀態(tài)變化

 k get orders.demo.sumeng.com order-sample -w
NAME           STATUS   MESSAGE  
order-sample   未接單      Order not accepted
order-sample   已接單      Order accepted
order-sample   制作中      Order in making
order-sample   制作完成     Order make complete
order-sample   待配送      Order wait delivery

這樣我們便完整的進行了一個 CRD 控制器和 webhook 開發(fā)與部署。

附錄

github 項目地址

kububuilder book

cretmanager

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末揩徊,一起剝皮案震驚了整個濱河市腰鬼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塑荒,老刑警劉巖熄赡,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異齿税,居然都是意外死亡彼硫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門凌箕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拧篮,“玉大人,你說我怎么就攤上這事陌知∷校” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵仆葡,是天一觀的道長。 經(jīng)常有香客問我志笼,道長沿盅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任纫溃,我火速辦了婚禮腰涧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘紊浩。我一直安慰自己窖铡,他們只是感情好疗锐,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著费彼,像睡著了一般滑臊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箍铲,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天雇卷,我揣著相機與錄音,去河邊找鬼颠猴。 笑死关划,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翘瓮。 我是一名探鬼主播贮折,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼资盅!你這毒婦竟也來了调榄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤律姨,失蹤者是張志新(化名)和其女友劉穎振峻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體择份,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡扣孟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荣赶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凤价。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拔创,靈堂內(nèi)的尸體忽然破棺而出利诺,到底是詐尸還是另有隱情,我是刑警寧澤剩燥,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布慢逾,位于F島的核電站,受9級特大地震影響灭红,放射性物質(zhì)發(fā)生泄漏侣滩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一变擒、第九天 我趴在偏房一處隱蔽的房頂上張望君珠。 院中可真熱鬧,春花似錦娇斑、人聲如沸策添。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唯竹。三九已至乐导,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摩窃,已是汗流浹背兽叮。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猾愿,地道東北人鹦聪。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像蒂秘,于是被迫代替她去往敵國和親泽本。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

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