怎么使用kubebuilder构建CRD及相关controller的项目基础

什么是Kubebuilder

Kubebuilder是一个用于在Go中快速构建和发布Kubernetes API的SDK。它建立在用于构建核心Kubernetes API的规范技术之上,以提供简化的抽象来减少开发工作。

与Web开发框架(如Ruby on RailsSpringBoot)类似,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
  • 在本地运行
  • 在集群中运行
  • 构建文档

安装

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可自行解析。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值