执行下面的脚本: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的配置,比较费时。