什么是Kubebuilder
Kubebuilder是一个用于在Go中快速构建和发布Kubernetes API的SDK。它建立在用于构建核心Kubernetes API的规范技术之上,以提供简化的抽象来减少开发工作。
与Web开发框架(如Ruby on Rails和SpringBoot)类似,Kubebuilder提高了速度并降低了开发人员管理的复杂性。
包含在Kubebuilder中:
- 使用包括基本结构的项目初始化
- 在规范版本中获取包依赖性。
- 主程序入口点
- 用于格式化,生成,测试和构建的Makefile
- 用于构建容器映像的Dockerfile
- 脚手架API
- 资源(模型)定义
- 控制器实现
- 资源和控制器的集成测试
- CRD定义
- 用于实现API的简单抽象
- Controllers
- Resource Schema Validation
- Validating Webhooks
- 用于发布API以安装到集群中的工件
- Namespace
- CRDs
- RBAC Roles and RoleBindings
- Controller StatefulSet + Service
- API参考文档和示例
Kubebuilder是在控制器运行时和控制器工具库之上开发的。
快速开始
本快速入门指南将涵盖:
- 创建一个项目
- 创建一个API
- 在本地运行
- 在集群中运行
- 构建文档
安装
- 安装dep
- 安装kustomize
- 安装kubebuilder
version=1.0.8 # latest stable version
arch=amd64
# download the release
curl -L -O "https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${version}/kubebuilder_${version}_darwin_${arch}.tar.gz"
# extract the archive
tar -zxvf kubebuilder_${version}_darwin_${arch}.tar.gz
sudo mv kubebuilder_${version}_darwin_${arch} /usr/local/kubebuilder
# update your PATH to include /usr/local/kubebuilder/bin
export PATH=$PATH:/usr/local/kubebuilder/bin
创建一个新的API
项目创建
初始化项目目录。
kubebuilder init --domain k8s.io --license apache2 --owner "The Kubernetes Authors"
API创建
创建一个名为Sloop的新API 。将创建文件供您在pkg/apis/<group>/<version>
其下 编辑pkg/controller/<kind>
。
kubebuilder create api --group ships --version v1beta1 --kind Sloop
本地运行API
通过将CRD安装到集群中并在开发计算机上作为本地进程启动控制器来构建和运行API。
创建API的新实例并查看命令输出。
将CRD安装到群集中
make install
针对远程群集在本地运行该命令。
make run
在新的终端中 - 创建一个实例并期望Controller获取它
kubectl apply -f config/samples/ships_v1beta1_sloop.yaml
添加架构和业务逻辑
编辑您的API架构和控制器,然后重新运行make
。
nano -w pkg/apis/ship/v1beta1/sloop_types.go
...
nano -w pkg/controller/sloop/sloop_controller.go
...
make
发布
Controller-Manager容器和清单安装
- 构建并推送容器图像。
- 为您的API创建安装清单manifests
- 使用kubectl apply在集群中运行
make
export IMG=gcr.io/kubeships/manager:v1
gcloud auth configure-docker
make docker-build
make docker-push
make deploy
API文档
生成文档:
- 创建API的示例
- 生成文档
- 查看生成的文档
docs/reference/build/index.html
kubebuilder create example --version v1beta1 --group ships.k8s.io --kind Sloop
nano -w docs/reference/examples/sloop/sloop.yaml
...
kubebuilder docs
源码分析
从入口函数入手,一步一步抽丝剥茧:
func main() {
//检测kubebuilder是否工作在GOPATH/src目录下
gopath := os.Getenv("GOPATH")
if len(gopath) == 0 {
gopath = gobuild.Default.GOPATH
}
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
if !toolsutil.IsUnderGoSrcPath(wd) {
log.Fatalf("kubebuilder must be run from the project root under $GOPATH/src/<package>. "+
"\nCurrent GOPATH=%s. \nCurrent directory=%s", gopath, wd)
}
util.Repo, err = toolsutil.DirToGoPkg(wd)
if err != nil {
log.Fatal(err)
}
re := regexp.MustCompile(`(^.*\/src)(\/.*$)`)
util.GoSrc = re.ReplaceAllString(wd, "$1")
rootCmd := defaultCommand()
rootCmd.AddCommand(
newInitProjectCmd(), //添加初始化命令行flag
newAPICommand(), //添加API操作命令行flag
version.NewVersionCmd(), //添加版本命令行flag
newDocsCmd(), //添加文档命令行flag
newVendorUpdateCmd(),
newAlphaCommand(),
)
//构建CLI工具
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}
现在我们看看newInitProjectCmd()干些什么:
//projectOptions结构
type projectOptions struct {
prj *project.Project
bp *project.Boilerplate
gopkg *project.GopkgToml
mgr *manager.Cmd
dkr *manager.Dockerfile
dep bool
depFlag *flag.Flag
depArgs []string
skipGoVersionCheck bool
}
//init flag的run属性的主要实现逻辑如下:
func (o *projectOptions) runInit() {
if !o.skipGoVersionCheck {
ensureGoVersionIsCompatible()
}
if !depExists() {
log.Fatalf("Dep is not installed. Follow steps at: https://golang.github.io/dep/docs/installation.html")
}
if util.ProjectExist() {
fmt.Println("Failed to initialize project bacause project is already initialized")
return
}
// Boilerplate和Project文件夹已经创建完毕
s := &scaffold.Scaffold{
BoilerplateOptional: true,
ProjectOptional: true,
}
//根据project配置文件返回一个Input实例
p, err := o.prj.GetInput()
if err != nil {
log.Fatal(err)
}
//根据Boilerplate配置文件返回一个Input实例
b, err := o.bp.GetInput()
if err != nil {
log.Fatal(err)
}
//按照相应配置生成相应文件目录(项目基础)
err = s.Execute(input.Options{ProjectPath: p.Path, BoilerplatePath: b.Path}, o.prj, o.bp)
if err != nil {
log.Fatal(err)
}
// default controller manager image name
imgName := "controller:latest"
s = &scaffold.Scaffold{}
err = s.Execute(input.Options{ProjectPath: p.Path, BoilerplatePath: b.Path},
o.gopkg,
o.mgr,
&project.Makefile{Image: imgName},
o.dkr,
&manager.APIs{},
&manager.Controller{},
&manager.Webhook{},
&manager.Config{Image: imgName},
&project.GitIgnore{},
&project.Kustomize{},
&project.KustomizeImagePatch{},
&project.KustomizePrometheusMetricsPatch{},
&project.KustomizeAuthProxyPatch{},
&project.AuthProxyService{},
&project.AuthProxyRole{},
&project.AuthProxyRoleBinding{})
if err != nil {
log.Fatal(err)
}
//安装包依赖
if !o.depFlag.Changed {
reader := bufio.NewReader(os.Stdin)
fmt.Println("Run `dep ensure` to fetch dependencies (Recommended) [y/n]?")
o.dep = util.Yesno(reader)
}
if o.dep {
c := exec.Command("dep", "ensure") // #nosec
if len(o.depArgs) > 0 {
c.Args = append(c.Args, o.depArgs...)
}
c.Stderr = os.Stderr
c.Stdout = os.Stdout
fmt.Println(strings.Join(c.Args, " "))
if err := c.Run(); err != nil {
log.Fatal(err)
}
fmt.Println("Running make...")
c = exec.Command("make") // #nosec
c.Stderr = os.Stderr
c.Stdout = os.Stdout
fmt.Println(strings.Join(c.Args, " "))
if err := c.Run(); err != nil {
log.Fatal(err)
}
} else {
fmt.Println("Skipping `dep ensure`. Dependencies will not be fetched.")
}
fmt.Printf("Next: Define a resource with:\n" +
"$ kubebuilder create api\n")
}
接下来我们看看newAPICommand()这个命令干些什么:
//apiOptions结构
type apiOptions struct {
r *resource.Resource
resourceFlag, controllerFlag *flag.Flag
doResource, doController, doMake bool
}
//init flag的run属性的主要实现逻辑如下:
func (o *apiOptions) runAddAPI() {
dieIfNoProject()
reader := bufio.NewReader(os.Stdin)
if !o.resourceFlag.Changed {
fmt.Println("Create Resource under pkg/apis [y/n]?")
o.doResource = util.Yesno(reader)
}
if !o.controllerFlag.Changed {
fmt.Println("Create Controller under pkg/controller [y/n]?")
o.doController = util.Yesno(reader)
}
if o.r.Group == "" {
log.Fatalf("Must specify --group")
}
if o.r.Version == "" {
log.Fatalf("Must specify --version")
}
if o.r.Kind == "" {
log.Fatalf("Must specify --kind")
}
fmt.Println("Writing scaffold for you to edit...")
r := o.r
if o.doResource {
fmt.Println(filepath.Join("pkg", "apis", r.Group, r.Version,
fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind))))
fmt.Println(filepath.Join("pkg", "apis", r.Group, r.Version,
fmt.Sprintf("%s_types_test.go", strings.ToLower(r.Kind))))
//根据配置参数生成CRDs
err := (&scaffold.Scaffold{}).Execute(input.Options{},
&resource.Register{Resource: r},
&resource.Types{Resource: r},
&resource.VersionSuiteTest{Resource: r},
&resource.TypesTest{Resource: r},
&resource.Doc{Resource: r},
&resource.Group{Resource: r},
&resource.AddToScheme{Resource: r},
&resource.CRDSample{Resource: r},
)
if err != nil {
log.Fatal(err)
}
} else {
// disable generation of example reconcile body if not scaffolding resource
// because this could result in a fork-bomb of k8s resources where watching a
// deployment, replicaset etc. results in generating deployment which
// end up generating replicaset, pod etc recursively.
r.CreateExampleReconcileBody = false
}
//根据配置参数生成Controller
if o.doController {
fmt.Println(filepath.Join("pkg", "controller", strings.ToLower(r.Kind),
fmt.Sprintf("%s_controller.go", strings.ToLower(r.Kind))))
fmt.Println(filepath.Join("pkg", "controller", strings.ToLower(r.Kind),
fmt.Sprintf("%s_controller_test.go", strings.ToLower(r.Kind))))
err := (&scaffold.Scaffold{}).Execute(input.Options{},
&controller.Controller{Resource: r},
&controller.AddController{Resource: r},
&controller.Test{Resource: r},
&controller.SuiteTest{Resource: r},
)
if err != nil {
log.Fatal(err)
}
}
if o.doMake {
fmt.Println("Running make...")
cm := exec.Command("make") // #nosec
cm.Stderr = os.Stderr
cm.Stdout = os.Stdout
if err := cm.Run(); err != nil {
log.Fatal(err)
}
}
}
到此kubebuilder的基本功能就已经实现了。剩下的version、doc等flag可自行解析。