導讀: 作為云原生的從業(yè)者,多多少少也會聽說CRD(Custom Resource Definition)和operator,但是說實話開發(fā)起來挺繁瑣的,而且對初學者也不友好。還好社區(qū)目前有好使的腳手架:kubebuilder和operator-sdk,目前個人感覺大家會往前者站隊,畢竟k8s自家的,索性就學著用它來嘗試開發(fā)operator了??紤]到篇幅和觀感問題,這里就只介紹最基本的使用,像finalizer和webhook建議直接查官方文檔,code邏輯另外寫一篇心得。
1.安裝
安裝參考官方指南, 由于github下載太慢我就用碼云fork了源文件來安裝的:
git clone https://gitee.com/henrywangx/kubebuilder.git
cd kubebuilder
make build
cp bin/kubebuilder $GOPATH/bin
2.使用
kubebuilder依賴go module所以要打開go module環(huán)境變量:export GO111MODULE=on, 另外proxy或者墻的原因,先設一下go mod的proxy:export GOPROXY=https://goproxy.io, 然后就可以開始使用了??偨Y就是要:
export GO111MODULE=on
export GOPROXY=https://goproxy.io
2.1創(chuàng)建project
mkdir $GOPATH/src/demo
cd $GOPATH/src/demo
kubebuilder init --domain demo.com --license apache2 --owner "xiong"
然后呢check一下當前文件夾:
tree .
.
├── Dockerfile
├── Makefile
├── PROJECT
├── bin
│ └── manager
├── config
│ ├── certmanager
│ │ ├── certificate.yaml
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ ├── manager_webhook_patch.yaml
│ │ └── webhookcainjection_patch.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── 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
│ └── webhook
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── service.yaml
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
└── main.go
這里kubebuilder幫我們生成了一下模板文件夾,包括解決crd的rbac, cert, webhook的文件。暫時不用管他們,這時需要保證你的終端能訪問k8s的測試集群,簡單就是用kubectl cluster-info看看是否出錯,如果不出錯,就可以run起來main.go了
kubectl cluster-info
go run main.go
可以看到終端輸出的log:
2019-12-28T00:22:09.789+0800 INFO controller-runtime.metrics metrics server is starting to listen {"addr": ":8080"}
2019-12-28T00:22:09.789+0800 INFO setup starting manager
2019-12-28T00:22:09.790+0800 INFO controller-runtime.manager starting metrics server {"path": "/metrics"}
從main.go里面可以看出其實kubebuilder幫我們生成一個管理controller的manager的代碼,但是還沒添加controller(controller是指管理crd的控制器):
func main() {
...
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
LeaderElection: enableLeaderElection,
Port: 9443,
})
...
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
...
}
}
ok, 接下來我們就可以用kubebuilder幫我們創(chuàng)建一個我們想要的crd,我就叫這個crd為Object吧:
kubebuilder create api --group infra --version v1 --kind Object
這里簡單注意一下, group, version, kind這三個屬性組合起來來標識一個k8s的crd。另外就是kind要首字母大寫而且不能有特殊符號。
執(zhí)行上面的命令之后,kubebuilder就幫我們創(chuàng)建了兩個文件api/v1/object_types.go和controllers/object_controller.go, 前者是這個crd需要定義哪些屬性,而后者是對crd的reconsile的處理邏輯(也就是增刪改crd的邏輯), 我們后面再講這兩個文件。最后呢,在main.go里面,我們定義的Object對應的controller會注冊到之前生成的manager里:
function main(){
...
// 注冊Object的controller到manager里
if err = (&controllers.ObjectReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Object"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
...
}
...
聰明的你一定能猜到,我們反復執(zhí)行kubebuilder create api xxx這條命令就會幫我們創(chuàng)建和注冊不同的controller到manager里面。
回過頭我們再看一下api/v1/object_types.go,這里我在spec里面加一個Detail,在status里面加一個Created:
// ObjectSpec defines the desired state of Object
type ObjectSpec 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 Object. Edit Object_types.go to remove/update
Foo string `json:"foo,omitempty"`
Detail string `json:"detail,omitempty"`
}
// ObjectStatus defines the observed state of Object
type ObjectStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Created bool `json:"created,omitempy"`
}
實際上kubebuilder就是幫我們生成Object的spec和status的模板,從注釋就也可以看出來spec是我們期望的crd狀態(tài),而status就是觀測到的狀態(tài),具體也可以參見k8s對一個對象的定義??梢钥吹侥J定義下面,kubebuilder會為我們生成對應的yaml文件在config/samples/infra_v1_object.yaml:
---
apiVersion: infra.demo.com/v1
kind: Object
metadata:
name: object-sample
spec:
# Add fields here
foo: bar
detail: "detail for demo"
在controllers/object_controller.go,也就是kubebuilder幫我們生成的Reconcile代碼里面,我添加了打印Detail的信息,并且把Created改成true:
//此method在controllers/object_controller.go
func (r *ObjectReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
_ = r.Log.WithValues("object", req.NamespacedName)
// your logic here
// 1. Print Spec.Detail and Status.Created in log
obj := &infrav1.Object{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
fmt.Errorf("couldn't find object:%s", req.String())
} else {
//打印Detail和Created
r.Log.V(1).Info("Successfully get detail", "Detail", obj.Spec.Detail)
r.Log.V(1).Info("", "Created", obj.Status.Created)
}
// 2. Change Created
if !obj.Status.Created {
obj.Status.Created = true
r.Update(ctx, obj)
}
return ctrl.Result{}, nil
}
由于kubebuilder要用到kustomize,所以先確保先裝好了, mac的安裝方式:
brew install kustomize
然后就install生成的crd并且運行修改代碼后的manager:
make install
go run main.go
現(xiàn)在我們就可以用config/samples/infra_v1_object.yaml創(chuàng)建一個Object:
kubectl create -f config/samples/infra_v1_object.yaml
在運行manager的終端里面可以看到我們剛才添加的代碼打印出來的log:
2019-12-28T23:39:34.963+0800 DEBUG controllers.Object Successfully get detail {"Detail": "detail for demo"}
2019-12-28T23:39:34.963+0800 DEBUG controllers.Object {"Created": false}
2019-12-28T23:39:35.019+0800 DEBUG controller-runtime.controller Successfully Reconciled {"controller": "object", "request": "default/object-sample"}
2019-12-28T23:39:35.019+0800 DEBUG controllers.Object Successfully get detail {"Detail": "detail for demo"}
2019-12-28T23:39:35.019+0800 DEBUG controllers.Object {"Created": true}
再看看從k8s里面看到的這個Object的狀態(tài):
kubectl get object object-sample -o yaml
apiVersion: infra.demo.com/v1
kind: Object
metadata:
creationTimestamp: "2019-12-28T15:39:34Z"
generation: 2
name: object-sample
namespace: default
resourceVersion: "433782"
selfLink: /apis/infra.demo.com/v1/namespaces/default/objects/object-sample
uid: 3f4b368d-2988-11ea-a544-080027d6a71e
spec:
detail: detail for demo
foo: bar
status:
Created: true #此處是我們修改成true的狀態(tài)
可以看到這個object-sample的status.Created已經(jīng)被修改為true了
3.其他
關于沒有介紹的crt,webhook和finalizer,官網(wǎng)手冊有比較詳細的用法介紹。我就簡單談一下我的理解,如果有錯誤請糾正:
- webhook,其實就是當我們添加和修改一個Object時,我們需要對Object的合法性進行判斷,所以可以通過webhook的framework來進行合法性的判定,所以kubebuilder可以生成對應的webhook代碼;
- crt,用于解決webhook訪問k8s時所需要的證書問題,官網(wǎng)也建議使用crt-manager解決證書問題;
- finalizer,就是在刪除Object時,由于這個Object可能創(chuàng)建一些其他的resource比如pod之類的,又或者在刪除之前,我們需要做一些清理工作,finalizer就是實現(xiàn)這個清理的framework代碼;
另外kubebuilder也是支持把這個manager部署為deployment,但是調(diào)試起來比較麻煩,所以就只用go run的形式演示了。
小結
這篇blog簡單的介紹了一下kubebuilder開發(fā)crd的基本過程,沒有深入過多的代碼原理,可能也有不少錯誤地方,麻煩幫忙糾正。另外大家可能跟我剛學習kubebuilder的時候一樣,只能照著官網(wǎng)教程敲命令,kubebuilder生成的代碼就像一個黑盒一樣,接下來目標就是專門整理一下kubebuilder生成crd代碼流程和結構。