【kubebuilder2.0】安装、源码分析

什么是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 以上的版本,原有的集群版本较低的话请升级。

参考这里:

Feature gates

安装

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

实际上不仅会创建 API,也就是 CRD,还会生成 Controller 的框架

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 的:

  1. 初始化了一个 Manager;
  2. 将 Manager 的 Client 传给 Controller,并且调用 SetupWithManager 方法传入 Manager 进行 Controller 的初始化;
  3. 启动 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
}
原文地址:https://www.cnblogs.com/lizhewei/p/13214785.html