什么是Kubebuilder
Kubebuilder是一个用于在Go中快速构建和发布Kubernetes API的SDK。它建立在用于构建核心Kubernetes API的规范技术之上,以提供简化的抽象来减少开发工作。
与Web开发框架(如Ruby on Rails和SpringBoot)类似,Kubebuilder提高了速度并降低了开发人员管理的复杂性。
包含在Kubebuilder中:
1、使用包括基本结构的项目初始化
- 在规范版本中获取包依赖性。
- 主程序入口点
- 用于格式化,生成,测试和构建的Makefile
- 用于构建容器映像的Dockerfile
2、脚手架API
- 资源(模型)定义
- 控制器实现
- 资源和控制器的集成测试
- CRD定义
3、用于实现API的简单抽象
- Controllers
- Resource Schema Validation
- Validating Webhooks
4、用于发布API以安装到集群中的工件
- Namespace
- CRDs
- RBAC Roles and RoleBindings
- Controller StatefulSet + Service
API参考文档和示例
Kubebuilder是在控制器运行时和控制器工具库之上开发的。
环境准备
Requirements
除了上面的工具和环境以外,需要有一套可连接的kubernetes环境,要求配置好kubectl config,以便能直连进行调试。
由于Feature gates的 --CustomResourceWebhookConversion
参数是在v1.15及以上的版本k8s才默认为true,为了避免版本导致的额外问题,如果是新部署,建议安装v1.15.4 以上的版本,原有的集群版本较低的话请升级。
参考这里:
安装
go
参考这里:Install Go
docker
mac安装包:
wget https://download.docker.com/mac/stable/Docker.dmg
linux根据不同的发行版来安装,网络资料很多,不再赘述
kubebuilder
二进制(推荐):
os=$(go env GOOS) arch=$(go env GOARCH)
curl -L https://go.kubebuilder.io/dl/2.3.1/${os}/${arch} | tar -xz -C /tmp/
sudo mv /tmp/kubebuilder_2.3.1_${os}_${arch} /usr/local/kubebuilder export PATH=$PATH:/usr/local/kubebuilder/bin
通过源码安装:
git clone https://github.com/kubernetes-sigs/kubebuilder
cd kubebuilder
make build
cp bin/kubebuilder $GOPATH/bin
kustomize
curl -s "https://raw.githubusercontent.com/ kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
$ cat install_kustomize.sh
#!/bin/bash # Downloads the most recently released kustomize binary # to your current working directory. # # Fails if the file already exists. where=$PWD if [ -f $where/kustomize ]; then echo "A file named kustomize already exists (remove it first)." exit 1 fi tmpDir=`mktemp -d` if [[ ! "$tmpDir" || ! -d "$tmpDir" ]]; then echo "Could not create temp dir." exit 1 fi function cleanup { rm -rf "$tmpDir" } trap cleanup EXIT pushd $tmpDir >& /dev/null opsys=windows if [[ "$OSTYPE" == linux* ]]; then opsys=linux elif [[ "$OSTYPE" == darwin* ]]; then opsys=darwin fi curl -s https://api.github.com/repos/kubernetes-sigs/kustomize/releases | grep browser_download | grep $opsys | cut -d '"' -f 4 | grep /kustomize/v | sort | tail -n 1 | xargs curl -s -O -L tar xzf ./kustomize_v*_${opsys}_amd64.tar.gz cp ./kustomize $where popd >& /dev/null ./kustomize version echo kustomize installed to current directory.
kubernetes
安装方式众多,文档丰富,不再赘述。
可参考:https://www.cnblogs.com/lizhewei/p/13366172.html
创建项目
查看现有的所有resource:
kubectl api-resources -o wide
查看现有的api groupVersion:
kubectl api-versions
Step 1: 初始化
新建一个 gitlab 项目,运行
mkdir $GOPATH/src/crd-demo
cd $GOPATH/src/crd-demo
export GO111MODULE=on
# 如果路径位于GOPATH/src下,go mod这一步可省略
go mod init ${CRD}
在$GOPATH/src/crd-demo项目目录下执行
kubebuilder init --domain=kruise.io
参数解读:domain 指定了后续注册 CRD 对象的 Group 域名
Step 2: 创建 API
kubebuilder create api --group apps --version v1alpha1 --kind SidecarSet
参数解读:
-
group 加上之前的 domian 即此 CRD 的 Group: apps.kruise.io;
-
version 一般分三种,按社区标准;
-
v1alpha1: 此 api 不稳定,CRD 可能废弃、字段可能随时调整,不要依赖;
-
v1beta1: api 已稳定,会保证向后兼容,特性可能会调整;
-
v1: api 和特性都已稳定;
-
-
kind: 此 CRD 的类型,类似于社区原生的 Service 的概念;
-
namespaced: 此 CRD 是全局唯一还是 namespace 唯一,类似 node 和 Pod。
目录结构
Step 3:定义 CRD
在图 2 中对应的文件定义 Spec 和 Status。
Step 4:编写 Controller 逻辑
在图 3 中对应的文件实现 Reconcile 逻辑。
Step 5: 测试发布
本地测试完之后使用 Kubebuilder 的 Makefile 构建镜像,部署我们的 CRDs 和 Controller 即可。
源码阅读
从 main.go 开始
var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") ) func init() { _ = clientgoscheme.AddToScheme(scheme) _ = extensionsv1alpha1.AddToScheme(scheme) // +kubebuilder:scaffold:scheme } func main() { var metricsAddr string var enableLeaderElection bool flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") flag.Parse() ctrl.SetLogger(zap.New(zap.UseDevMode(true))) // 1.init Manager mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, Port: 9443, LeaderElection: enableLeaderElection, LeaderElectionID: "f5e19998.sncloud.com", }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } // 2.init Reconciler if err = (&controllers.SidecarSetReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("SidecarSet"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SidecarSet") os.Exit(1) } // +kubebuilder:scaffold:builder setupLog.Info("starting manager") // 3.start Manager if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } }
可以看到在 init 方法里面我们将 appsv1alpha1 注册到 Scheme 里面去了,这样一来 Cache 就知道 watch 谁了,main 方法里面的逻辑基本都是 Manager 的:
- 初始化了一个 Manager;
- 将 Manager 的 Client 传给 Controller,并且调用 SetupWithManager 方法传入 Manager 进行 Controller 的初始化;
- 启动 Manager。
我们的核心就是看这 3 个流程。
Manager 初始化
Manager 初始化代码如下
// New returns a new Manager for creating Controllers. func New(config *rest.Config, options Options) (Manager, error) {
...
// 创建Cache对象,用做client的读请求,以及生成informer cache, err := options.NewCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod, Namespace: options.Namespace}) if err != nil { return nil, err }
// 创建读请求的client,即apiReader,读请求走的Cache
apiReader, err := client.New(config, client.Options{Scheme: options.Scheme, Mapper: mapper}) if err != nil { return nil, err }
// 创建写请求的client,写请求直连APIServer writeObj, err := options.NewClient(cache, config, client.Options{Scheme: options.Scheme, Mapper: mapper}) if err != nil { return nil, err }
...
return &controllerManager{ config: config, scheme: options.Scheme, cache: cache, fieldIndexes: cache, client: writeObj, apiReader: apiReader, recorderProvider: recorderProvider, resourceLock: resourceLock, mapper: mapper, metricsListener: metricsListener, internalStop: stop, internalStopper: stop, port: options.Port, host: options.Host, certDir: options.CertDir, leaseDuration: *options.LeaseDuration, renewDeadline: *options.RenewDeadline, retryPeriod: *options.RetryPeriod, healthProbeListener: healthProbeListener, readinessEndpointName: options.ReadinessEndpointName, livenessEndpointName: options.LivenessEndpointName, }, nil }
可以看到主要是创建 Cache 与 Clients。
创建Clients
==> D:codegopkgmodsigs.k8s.iocontroller-runtime@v0.5.0pkgmanagermanager.go
// defaultNewClient creates the default caching client func defaultNewClient(cache cache.Cache, config *rest.Config, options client.Options) (client.Client, error) { // Create the Client for Write operations. c, err := client.New(config, options) if err != nil { return nil, err } return &client.DelegatingClient{ Reader: &client.DelegatingReader{ CacheReader: cache, ClientReader: c, }, Writer: c, StatusClient: c, }, nil }