執行下面的腳本:html
os=$(go env GOOS) arch=$(go env GOARCH) # download kubebuilder and extract it to tmp curl -sL https://go.kubebuilder.io/dl/2.0.0-beta.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.0.0-beta.0_${os}_${arch} /usr/local/kubebuilder export PATH=$PATH:/usr/local/kubebuilder/bin
便可git
使用kubebuilder前,必需要確保本地的go版本爲1.11或以上,開啓了go module (export GO111MODULE=on
)github
咱們在GOPATH
下新建一個目錄。並在其中執行kubebuilder init --domain netease.com
。
會發現報錯:golang
go: sigs.k8s.io/controller-runtime@v0.2.0-beta.4: unrecognized import path "sigs.k8s.io/controller-runtime" (https fetch: Get https://sigs.k8s.io/controller-runtime?go-get=1: dial tcp 35.201.71.162:443: i/o timeout) go: error loading module requirements
沒法直接clone sigs.k8s.io/controller-runtime
,那麼咱們能夠經過對go mod配置proxy,走代理下載: export GOPROXY=https://goproxy.io
web
將目錄清理,並從新執行kubebuilder init --domain netease.com
:docker
# kubebuilder init --domain my.domain go mod tidy Running make... make go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.4 go: finding sigs.k8s.io/controller-tools/cmd/controller-gen v0.2.0-beta.4 go: finding sigs.k8s.io/controller-tools/cmd v0.2.0-beta.4 /root/mygo/bin/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./api/... go fmt ./... go vet ./... go build -o bin/manager main.go Next: Define a resource with: $ kubebuilder create api # ls bin config Dockerfile go.mod go.sum hack main.go Makefile PROJECT
init以後生成了一些代碼、hack腳本、dockerfile、makefile,這意味着kubebuilder幫咱們生成了一個operator須要的基礎代碼內容。另外bin目錄下編譯出了一個manager二進制文件。這就是kubebuilder最終幫咱們製做的程序,它包括:json
執行:kubebuilder create api --group ops --version v1 --kind Playbook
api
# kubebuilder create api --group ops --version v1 --kind Playbook Create Resource [y/n] y Create Controller [y/n] # 若是咱們只須要建立一個api,不須要開發一個控制器,這裏選n便可 y Writing scaffold for you to edit... api/v1/guestbook_types.go controllers/guestbook_controller.go Running make... go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.0-beta.4 go: finding sigs.k8s.io/controller-tools/cmd/controller-gen v0.2.0-beta.4 go: finding sigs.k8s.io/controller-tools/cmd v0.2.0-beta.4 /root/mygo/bin/controller-gen object:headerFile=./hack/boilerplate.go.txt paths=./api/... go fmt ./... go vet ./... go: downloading github.com/onsi/ginkgo v1.8.0 go: downloading github.com/onsi/gomega v1.5.0 go: extracting github.com/onsi/gomega v1.5.0 go: extracting github.com/onsi/ginkgo v1.8.0 go build -o bin/manager main.go # ls api bin config controllers Dockerfile go.mod go.sum hack main.go Makefile PROJECT
上述操做執行完後,須要關注兩個地方數組
這個文件包含了對Playbook這個CRD的定義,咱們能夠在裏面進行修改,加上咱們須要的字段。併發
好比咱們給Playbook.Spec
增長一個字段Abstract,類型爲string,表示該Playbook的摘要;給Playbook.Status
增長一個字段Progress,類型爲int32,表示這本Playbook看到百分幾。
這個文件裏,有以下函數:
func (r *PlaybookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("playbook", req.NamespacedName) // your logic here return ctrl.Result{}, nil }
這就是控制器的處理函數,每當集羣中有Playbook資源的變更(CRUD),都會觸發這個函數進行協調。
好比咱們在Reconcile中獲取進入協調的Playbook對象,檢查它的status.progress是否等於100,若是是,就直接調用Delete接口刪除它。
咱們開發完畢後,能夠直接運行make run
, 會從新編譯出一個/bin/manager並運行,因爲咱們在一開始指定要生成controller,kubebuilder會在manager中注入controller,當manager運行起來後,會運行起咱們開發的controller。
須要注意的是,直接make run可能會報錯提示8080端口已經被佔用,檢查是否你的apisever監聽在該端口,或者直接修改main.go中的代碼,將metrics接口服務監聽在別的端口上
當咱們看到以下的日誌時,說明manager中的控制器已經在工做了:
2019-09-09T17:43:35.290+0800 INFO controller-runtime.controller Starting Controller {"controller": "playbook"}
咱們若是須要在Playbook CRUD 時進行操做合法性檢查, 能夠開發一個webhook實現。webhook的腳手架同樣能夠用kubebuilder生成:
# kubebuilder create webhook --group ops --version v1 --kind Playbook --programmatic-validation --defaulting Writing scaffold for you to edit... api/v1/playbook_webhook.go
執行後會生成api/v1/playbook_webhook.go
文件,因爲咱們指定了參數--programmatic-validation
(能夠在建立、更新guestbook時觸發的字段驗證函數)和--defaulting
(建立或更新時,用於配置guestbook某些字段默認值的函數)
咱們能夠對上述函數進行自定義的修改。體如今代碼中就是以下三個方法:
func (r *Playbook) ValidateCreate() error func (r *Playbook) ValidateUpdate(old runtime.Object) error func (r *Playbook) Default()
好比咱們在這裏對playbook.Spec.Abstract
的內容進行檢查,若是包含了單詞"yellow",咱們認爲這是本黃書,進行報錯。
測試和部署的前提是咱們在開發環境中安裝了k8s。這一步不作說明。
可是webhook如今編譯會失敗,報錯是找不到webhook server使用的證書文件。咱們須要用k8s的ca簽發一個webhookserver的證書和key,固然咱們也能夠直接用k8s的管理員證書。
cp /etc/kubernetes/pki/apiserver.crt /tmp/k8s_webhook-server/serving-certs/tls.crt cp /etc/kubernetes/pki/apiserver.key /tmp/k8s_webhook-server/serving-certs/tls.key
再次make run,這下運行起來了。咱們看到終端打印出了以下兩行日誌:
2019-09-09T17:48:06.967+0800 INFO controller-runtime.builder Registering a mutating webhook {"GVK": "ops.netease.com/v1, Kind=Playbook", "path": "/mutate-ops-netease-com-v1-playbook"} 2019-09-09T17:48:06.967+0800 INFO controller-runtime.builder Registering a validating webhook {"GVK": "ops.netease.com/v1, Kind=Playbook", "path": "/validate-ops-netease-com-v1-playbook"}
但此時apiserver並不知道運行了這麼一個webserver,webhook又如何能被調用呢?
咱們須要建立webhookcfg,讓apiserver本身感知到某些資源(Playbook)的某些請求(CREATE,UPDATE)須要訪問某個服務(service,或者一個url)
幸運的是,kubebuilder幫咱們生成了webhookcfg。在/config/webhook/
目錄下,記錄了manifests.yaml 和 service.yaml。
service.yaml是一個service模板,對應的是咱們設計的webhook
manifests.yaml的內容以下:
--- apiVersion: admissionregistration.k8s.io/v1beta1 kind: MutatingWebhookConfiguration metadata: creationTimestamp: null name: mutating-webhook-configuration webhooks: - clientConfig: caBundle: Cg== service: name: webhook-service namespace: system path: /mutate-ops-netease-com-v1-playbook failurePolicy: Fail name: mplaybook.kb.io rules: - apiGroups: - ops.netease.com apiVersions: - v1 operations: - CREATE - UPDATE resources: - playbooks --- apiVersion: admissionregistration.k8s.io/v1beta1 kind: ValidatingWebhookConfiguration metadata: creationTimestamp: null name: validating-webhook-configuration webhooks: - clientConfig: caBundle: Cg== service: name: webhook-service namespace: system path: /validate-ops-netease-com-v1-playbook failurePolicy: Fail name: vplaybook.kb.io rules: - apiGroups: - ops.netease.com apiVersions: - v1 operations: - CREATE - UPDATE resources: - playbooks
很顯然,這裏記錄的就是webhookcfg。由於webhook有多種,咱們上面建立時指定了兩種(--programmatic-validation
和--defaulting
),對應建立了一個ValidatingWebhookConfiguration
和一個MutatingWebhookConfiguration
。
在本地測試的過程當中,咱們可能不想每次代碼修改都要打包成鏡像,併發布成deployment+service。那麼咱們就能夠修改以此處的
clientConfig: caBundle: Cg== service: name: webhook-service namespace: system path: /validate-ops-netease-com-v1-playbook
url: https://${server}:443/validate-ops-netease-com-v1-playbook
,注意與service.path對應。kubectl config view --raw -o json | jq -r '.clusters[0].cluster."certificate-authority-data"' | tr -d '"'
拿到)填入到caBundle的值中;注意到,不論是ValidatingWebhookConfiguration
仍是MutatingWebhookConfiguration
,他們的webhooks
是一個數組,咱們若是在開發過程當中須要設計多個CRD,那麼屢次執行kubebuilder create api ****
後,咱們能夠按照本身的設計,在兩種(或其中一種)WebhookConfitration的webhooks
裏增長一個項目,只要填充正確的name
,clientConfig.url
,rules
便可。
這裏介紹的是本地開發環境便於調試的部署方式,測試、線上集羣的部署,應該作到標準化和規範化,那麼如何進行標準化部署呢?
進行dockerbuild。
修改Dockerfile。一是在拷貝go.mod, go.sum後增長兩個env:
ENV GO111MODULE=on ENV GOPROXY=https://goproxy.io
二是將FROM gcr.io/distroless/static:latest
替換爲FROM golang:1.12.5
;三是若是咱們只寫了個webhook,沒有寫controller,那麼Dockerfile中的COPY controllers/ controllers/
要去掉
patches
改成patchesStrategicMerge
config/crd/kustomization.yaml
,將webhook、certmanager相關的註釋去掉config/default/manager_auth_proxy_patch.yaml
文件中的image,改成quay.io/coreos/kube-rbac-proxy:v0.4.0
進行deploy:
執行完畢後,咱們在集羣中能夠找到${項目名}-system
namespace下運行了咱們開發的服務,包括一個deployment,兩個service:
# kubectl get all -n testop3-system NAME READY STATUS RESTARTS AGE pod/testop3-controller-manager-6f44ddbd68-q7m24 1/2 ErrImagePull 0 15s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/testop3-controller-manager-metrics-service ClusterIP 10.107.195.212 <none> 8443/TCP 15s service/testop3-webhook-service ClusterIP 10.110.243.20 <none> 443/TCP 15s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/testop3-controller-manager 0/1 1 0 15s NAME DESIRED CURRENT READY AGE replicaset.apps/testop3-controller-manager-6f44ddbd68 1 1 0 15s
咱們從上文中瞭解到,要讓一個webhook在k8s集羣內可用,須要建立對應的webhookconfiguration,即validatingwebhookconfiguration
或mutatingwebhookconfiguration
.k8s apiserver
會根據集羣中已經建立的全部validatingwebhookconfiguration
或mutatingwebhookconfiguration
逐一進行調用,因此,當咱們建立一個crd對象失敗,且日誌顯示訪問某個webhook失敗時,咱們能夠:
確認集羣中的webhookconfiguration是否爲咱們須要的webhook而且要檢查每個webhookconfiguration中,配置的服務地址是否可達。
特別是:當咱們使用本地make run
測試,且使用make deploy
測試,會須要不一樣的webhookconfiguration,兩種測試方式建議只選擇一種,不要來回切換,不然要確保webhookconfiguration的配置,比較費時。