kubernetes CRD 开发入门指南
CRD是什么?
CRD全称为Custom Resource Definition,是kubernetes提供的开放的扩展api方式。
下面是我的理解:
通常我们说的Operator指的是CRD + Controller。CRD也是kubernetes提供的一种资源类型,我们通过CRD向kubernetes注册自定义的资源类型。空有定义好的CRD没有任何作用,要让自定义的资源类型像kubernetes中的资源一样工作,需要开发一个Controller来控制、调度、实现该资源中定义的状态。而我们真正使用的则是CR(Custom Resource)。
举个例子
现在很多项目都大量使用到CRD, 为了能有更清楚的理解,下面以matrix项目为例:
[root@centos01 ~]# kubectl get crd | grep crd.cxwen.com
dns.crd.cxwen.com 2020-06-30T06:21:09Z
etcdclusters.crd.cxwen.com 2020-06-30T06:21:09Z
masters.crd.cxwen.com 2020-06-30T06:21:09Z
matrices.crd.cxwen.com 2020-06-30T06:21:09Z
networkplugins.crd.cxwen.com 2020-06-30T06:21:09Z
可以看到matrix定义了许多的CRD。以masters.crd.cxwen.com
为例,来看看CRD里面到底定义了哪些东西。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: masters.crd.cxwen.com
spec:
conversion:
strategy: None
group: crd.cxwen.com
names:
kind: Master
listKind: MasterList
plural: masters
singular: master
preserveUnknownFields: true
scope: Namespaced
versions:
- additionalPrinterColumns: # 定义kubectl get时打印出的字段
- description: version
jsonPath: .spec.version
name: VERSION
type: string
- description: pod replicas
jsonPath: .spec.replicas
name: REPLICAS
type: string
- description: etcdcluster name
jsonPath: .spec.etcdCluster
name: ETCD
type: string
- description: expose type
jsonPath: .spec.expose.method
name: EXPOSETYPE
type: string
- description: expose node
jsonPath: .spec.expose.node
name: EXPOSENODE
type: string
- description: expose port
jsonPath: .spec.expose.port
name: EXPOSEPORT
type: string
- description: phase
jsonPath: .status.phase
name: PHASE
type: string
- jsonPath: .metadata.creationTimestamp
name: AGE
type: date
name: v1
schema: # 定义CRD的schema
openAPIV3Schema:
description: Master is the Schema for the masters API
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
description: MasterSpec defines the desired state of Master
properties:
etcdCluster:
type: string
expose:
properties:
method:
type: string
node:
items:
type: string
type: array
port:
type: string
type: object
imageRegistry:
type: string
imageRepo:
properties:
apiserver:
type: string
controllerManager:
type: string
proxy:
type: string
scheduler:
type: string
type: object
replicas:
type: integer
version:
description: Foo is an example field of Master. Edit Master_types.go
to remove/update
type: string
type: object
status:
description: MasterStatus defines the observed state of Master
properties:
adminKubeconfig:
type: string
exposeUrl:
items:
type: string
type: array
phase:
description: 'INSERT ADDITIONAL STATUS FIELD - define observed state
of cluster Important: Run "make" to regenerate code after modifying
this file'
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
CRD里面主要定义了两部分内容:
1、additionalPrinterColumns
顾名思意,additional Printer Columns
就是额外的打印列的意思,即设置使用kubectl get
命令去查看自定义资源时会打印哪些字段,例如:
[root@centos01 ~]# kubectl get master
NAME VERSION REPLICAS ETCD EXPOSETYPE EXPOSENODE EXPOSEPORT PHASE AGE
example-km v1.15.12 1 example-ec NodePort [192.168.83.128] 31299 Ready 12m
这里就打印出了上面CRD中定义的字段。
2、schema
定义Custom Resource的模式或者说规范,这里面定义一个CR的各个属性的数据类型,CR一定要遵循CRD里面的定义才能创建成功。
有以下两种情况:
- 属性是一个基本数据类型
type直接指定属性的类型,如apiserver
为string
类型:
apiserver:
type: string
- 属性是一个结构体类型
type值为object, properties中描述其它属性的类型,如imageRepo属性:
imageRepo:
type: object
properties:
apiserver:
type: string
controllerManager:
type: string
proxy:
type: string
scheduler:
type: string
总结一下:
类型 | 说明 |
---|---|
Operator | CRD + Controller |
CRD (Custom Resource Definition) | 定义自定义资源的各种属性并向kubernetes中注册 |
CR (Custom Resource) | 通过CRD定义好的真正可以在k8s中使用的资源,类似于pod,deployment这样的k8s中定义好的资源 |
Controller | 监听CRD的CRUD事件并添加自定义业务逻辑,负责确保其追踪的资源对象的当前状态接近期望状态 |
如何开发?
了解了CRD的是什么,那如何来开发一个CRD呢?
从上文看起来CRD的定义文件这么长,似乎很复杂。不要怕,这些都可以用工具生成,不需要咱们手动编写的。正所谓工欲善其事,必先利其器。下面就来了解下CRD的开发利器: kubebuilder。有时间的也可以研究下kubebuilder的文档, 里面有详细的介绍。
使用kubebuilder构建CRD基本代码框架
环境安装
可以直接在机器上进行安装,或者使用构建好kubebuilder镜像运行一个容器在容器执行操作:推荐使用容器方式,方便很多。
机器直接安装
安装go环境
首先机器上得安装go环境,不会安装的可以看这个教程:Go语言环境安装。 如果因为墙的原因无法从go官网下载安装包,可以访问go语言中文网进行下载。
安装kubebuilder
接着需要在机器上安装kubebuilder, linux可以使用下面命令安装:
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.3.1/kubebuilder_2.3.1_linux_amd64.tar.gz
tar -zxvf kubebuilder_2.3.1_linux_amd64.tar.gz
cp kubebuilder_2.3.1_linux_amd64/bin/kubebuilder /usr/local/bin/
其它版本和架构的机器安装方法一样,可以根据需要下载相应的安装包。
安装kustomize
kustomize是一个yaml渲染工具,kubebuilder依赖它进行yaml文件的渲染。
curl https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv3.6.1/kustomize_v3.6.1_linux_amd64.tar.gz | tar -zxv -C /usr/local/bin/
容器中运行
拉取镜像 xwcheng/kubebuilder:2.3.1
docker pull xwcheng/kubebuilder:2.3.1
运行容器
docker run -d --name kubebuilder -v /go/src/github.com/cxwen/:/go/src/github.com/cxwen -e GOPATH=/go -e GOROOT=/usr/local/go xwcheng/kubebuilder:2.3.1 sh -c "while true; do sleep 1000000000; done"
可以将宿主机的目录挂载到容器中。
进入容器
docker exec -ti kubebuilder bash
构建代码框架
1、初始化代码框架
国内因为墙的关系,最好执行 export GOPROXY=https://goproxy.io
。
mkdir -p crd/
cd crd/
kubebuilder init --domain cxwen.com --license apache2 --owner "cxwen"
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.5.0
go: finding sigs.k8s.io/controller-runtime v0.5.0
......
Update go.mod:
$ go mod tidy
go: downloading github.com/go-logr/zapr v0.1.0
......
Running make:
$ make
go: creating new go.mod: module tmp
go: finding sigs.k8s.io v0.2.5
......
/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
Next: define a resource with:
$ kubebuilder create api
生成完之后,会出现如下这些文件机目录:
[root@centos01 ~]# tree
.
├── bin
│ └── manager
├── config
│ ├── certmanager
│ │ ├── certificate.yaml
│ │ ├── kustomization.yaml
│ │ └── kustomizeconfig.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ ├── manager_webhook_patch.yaml
│ │ └── webhookcainjection_patch.yaml
│ ├── manager
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ └── role_binding.yaml
│ └── webhook
│ ├── kustomization.yaml
│ ├── kustomizeconfig.yaml
│ └── service.yaml
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT
9 directories, 30 files
2、创建CRD
[root@centos01 ~]# kubebuilder create api --group crd --version v1 --kind TestCrd
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing scaffold for you to edit...
api/v1/testcrd_types.go
controllers/testcrd_controller.go
Running make:
$ make
/go/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
执行完后可以看到目录下出现api和controllers这两个新的目录。
[root@centos01 ~]# tree api/
api/
└── v1
├── groupversion_info.go
├── testcrd_types.go
└── zz_generated.deepcopy.go
1 directory, 3 files
[root@centos01 ~]# tree controllers/
controllers/
├── suite_test.go
└── testcrd_controller.go
0 directories, 2 files
代码解析
结构体: CRD的血肉
api目录中存放的是Custom Resource的结构体。 如下所示,TestCrdSpec结构体中可以自定义yaml文件spec属性下需要的字段。TestCrdStatus结构体中可以自定义yaml文件status属性下需要的字段。
// TestCrdSpec defines the desired state of TestCrd
type TestCrdSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of TestCrd. Edit TestCrd_types.go to remove/update
Foo string `json:"foo,omitempty"`
}
// TestCrdStatus defines the observed state of TestCrd
type TestCrdStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
// +kubebuilder:object:root=true
// TestCrd is the Schema for the testcrds API
type TestCrd struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec TestCrdSpec `json:"spec,omitempty"`
Status TestCrdStatus `json:"status,omitempty"`
}
spec和status有什么区别呢?
可以查看kubernetes的官方文档对象规约(Spec)与状态(Status)
每个 Kubernetes 对象包含两个嵌套的对象字段,它们负责管理对象的配置:对象 spec 和 对象 status 。 spec 是必需的,它描述了对象的 期望状态(Desired State) —— 希望对象所具有的特征。 status 描述了对象的 实际状态(Actual State) ,它是由 Kubernetes 系统提供和更新的。在任何时刻,Kubernetes 控制面一直努力地管理着对象的实际状态以与期望状态相匹配。
对于我们开发的CRD来说,可以像这样理解。
spec是预先定义的期望状态,也就是控制器 (controller) 里面可以获取到的预置信息,并根据这些信息进行处理调度以达到这个期望状态,并且spec里面的信息不能被控制器里面的代码更改的。
status里面的字段里面的信息是标志当前实际状态。比如,一个Custom Resource生命周期有三个状态:initializing、ready、teminating, 可以在status中加一个phase字段来表示;当这个CR刚创建时,控制器将phase字段值更新为initializin;CR初始化完成,健康检查等通过,已经可以正常提供服务了,控制器就可以将phase字段置为ready;当这个CR使命已经完成,进入结束阶段,控制器就将phase字段置为teminating, 然后再执行资源清理操作。当然,这些状态的转换,都是需要在控制器代码里来实现的。
Reconcile:CRD的大脑
如果说结构体是CRD的血肉,那么controller里面的Reconcile方法就是CRD的大脑,因为CRD的所有行为,都是通过这个方法来控制的,这个方法也是我们代码实现的关键所在。每一个CRD都会在controllers目录中生成一个以{CRD名称}_controller.go
格式命名的代码文件, Reconcile方法即在这个文件中。
func (r *TestCrdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("testcrd", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}
下面是Reconcile实现的一个模板:
func (r *TestCrdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
var err error
ctx := context.Background() // 获取context
log := r.Log.WithValues("master", req.NamespacedName) // 获取日志对象
log.V(1).Info("TestCrd reconcile triggering")
// 从kubernetes中获取crd对象
testCrd := crdv1.TestCrd{}
if err = r.Get(ctx, req.NamespacedName, &testCrd); err != nil {
if IgnoreNotFound(err) != nil {
log.Error(err, "unable to fetch testCrd")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
testCrdFinalizer := "crd.cxw.com"
// 通过DeletionTimestamp字段来判断是删除还是创建更新操作
if testCrd.ObjectMeta.DeletionTimestamp.IsZero() {
// 判断是否是创建
if ! ContainsString(master.ObjectMeta.Finalizers, testCrdFinalizer) {
testCrd.ObjectMeta.Finalizers = append(testCrd.ObjectMeta.Finalizers, testCrdFinalizer)
// 你的创建处理代码
} else {
// 你的更新处理代码
}
} else {
if ContainsString(master.ObjectMeta.Finalizers, masterFinalizer) {
// 你的删除处理代码
testCrd.ObjectMeta.Finalizers = RemoveString(testCrd.ObjectMeta.Finalizers, testCrdFinalizer)
if err = r.Update(ctx, &testCrd); err != nil {
return ctrl.Result{}, err
}
}
}
return ctrl.Result{}, nil
}
注释也是代码
可以直接在代码里通过格式化的注释来实现授权、添加额外打印字段功能。具体使用可以参考matrix
1、给控制器授权
在Reconcile方法前面添加, 例如:
// +kubebuilder:rbac:groups=crd.cxwen.com,resources=testcrds,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=crd.cxwen.com,resources=testcrds/status,verbs=get;update;patch
func (r *TestCrdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("testcrd", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}
2、添加额外的打印字段
在CRD结构体前面添加,例如:
// +kubebuilder:printcolumn:name="VERSION",type="string",JSONPath=".spec.version",description="version"
// +kubebuilder:printcolumn:name="REPLICAS",type="string",JSONPath=".spec.replicas",description="pod replicas"
// +kubebuilder:printcolumn:name="ETCD",type="string",JSONPath=".spec.etcdCluster",description="etcdcluster name"
// +kubebuilder:printcolumn:name="EXPOSETYPE",type="string",JSONPath=".spec.expose.method",description="expose type"
// +kubebuilder:printcolumn:name="EXPOSENODE",type="string",JSONPath=".spec.expose.node",description="expose node"
// +kubebuilder:printcolumn:name="EXPOSEPORT",type="string",JSONPath=".spec.expose.port",description="expose port"
// +kubebuilder:printcolumn:name="PHASE",type="string",JSONPath=".status.phase",description="phase"
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// Master is the Schema for the masters API
type Master struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MasterSpec `json:"spec,omitempty"`
Status MasterStatus `json:"status,omitempty"`
}
Finalizer
Finalizer的作用是基于kubernetes的处理机制。
当api接收到一个资源对象删除操作,Finalizer为空时,kubernetes会直接将这个资源对象删掉,删掉之后,etcd中就不存在改资源对象了,通过api也无法查询到了。
当Finalizer不为空时,kubernetes不会直接删除改资源对象,而是在对象ObjectMeta中添加DeletionTimestamp这么个字段,一直要等到Finalizer为空时才把资源对象删除。
所以,当我们的CRD在删除时有需要进行资源清理操作时,就可以在创建时添加上Finalizer,当检测到DeletionTimestamp不为空时,就知道该资源对象处于删除状态了,然后执行资源清理操作,最后移除Finalizer即可。
Finalizer的添加很简单,它的值可以是任何字符串。当然,为了起到一些标志作用,可以使用有意义的字符串。
OwnerReference
kubernetes GC在删除一个对象时,任何 ownerReference 是该对象的对象都会被清除。
下面是kubernetes官方文档中的描述。
某些 Kubernetes 对象是其它一些对象的所有者。例如,一个 ReplicaSet 是一组 Pod 的所有者。 具有所有者的对象被称为是所有者的 附属 。 每个附属对象具有一个指向其所属对象的 metadata.ownerReferences 字段。
有时,Kubernetes 会自动设置 ownerReference 的值。 例如,当创建一个 ReplicaSet 时,Kubernetes 自动设置 ReplicaSet 中每个 Pod 的 ownerReference 字段值。 在 Kubernetes 1.8 版本,Kubernetes 会自动为某些对象设置 ownerReference 的值,这些对象是由 ReplicationController、ReplicaSet、StatefulSet、DaemonSet、Deployment、Job 和 CronJob 所创建或管理。 也可以通过手动设置 ownerReference 的值,来指定所有者和附属之间的关系。
添加OwnerReference, 以deployment为例,在ObjectMeta下面添加OwnerReferences即可:
Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: test,
Namespace: test,
OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(app, schema.GroupVersionKind{
Group: v1.SchemeGroupVersion.Group,
Version: v1.SchemeGroupVersion.Version,
Kind: "TestCrd",
}),
},
},
......
}
小结
进行CRD开发时
- 使用注释的方法来为controller配置权限,以及添加额外打印字段
- 使用Finalizer来做资源的清理
- 使用OwnerReference进行对象之间依赖关系的管理
其它代码开发技巧可以研究下kubebuilder的文档。
源码小窥
kubebuilder生成的源码架构,主要是基于controller-runtime这个go代码库。这个代码库对controller操作做了很好的封装,基于它开发CRD非常方便。
main.go
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
// 注册scheme
func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = crdv1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
func main() {
......
// 初始化manager
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
LeaderElection: enableLeaderElection,
LeaderElectionID: "6ed5364d.cxwen.com",
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}
// 初始化controller
if err = (&controllers.TestCrdReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("TestCrd"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "TestCrd")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
// 启动manager
setupLog.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
main.go中主要做了下面两件事:
1、注册scheme
在controller中,如果需要使用manager提供的client操作某种类型的资源,需要将资源类型注册到scheme中。从代码中的init函数可以看到,crdv1中的类型被注册到了scheme中。
2、创建并启动manager
这个manager就是管理controller的manager, 一个manager中可以管理多个controller。
创建manager过程中,创建了两个很重要的对象:cache和client。这两个对象是manager中所有controller共享的。
下面是创建Manager的函数:
// New returns a new Manager for creating Controllers.
func New(config *rest.Config, options Options) (Manager, error) {
// Initialize a rest.config if none was specified
if config == nil {
return nil, fmt.Errorf("must specify Config")
}
// Set default values for options fields
options = setOptionsDefaults(options)
// Create the mapper provider
mapper, err := options.MapperProvider(config)
if err != nil {
log.Error(err, "Failed to get API Group-Resources")
return nil, err
}
// Create the cache for the cached read client and registering informers
cache, err := options.NewCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod, Namespace: options.Namespace})
if err != nil {
return nil, err
}
apiReader, err := client.New(config, client.Options{Scheme: options.Scheme, Mapper: mapper})
if err != nil {
return nil, err
}
writeObj, err := options.NewClient(cache, config, client.Options{Scheme: options.Scheme, Mapper: mapper})
if err != nil {
return nil, err
}
// Create the recorder provider to inject event recorders for the components.
// TODO(directxman12): the log for the event provider should have a context (name, tags, etc) specific
// to the particular controller that it's being injected into, rather than a generic one like is here.
recorderProvider, err := options.newRecorderProvider(config, options.Scheme, log.WithName("events"), options.EventBroadcaster)
if err != nil {
return nil, err
}
// Create the resource lock to enable leader election)
resourceLock, err := options.newResourceLock(config, recorderProvider, leaderelection.Options{
LeaderElection: options.LeaderElection,
LeaderElectionID: options.LeaderElectionID,
LeaderElectionNamespace: options.LeaderElectionNamespace,
})
if err != nil {
return nil, err
}
// Create the metrics listener. This will throw an error if the metrics bind
// address is invalid or already in use.
metricsListener, err := options.newMetricsListener(options.MetricsBindAddress)
if err != nil {
return nil, err
}
// Create health probes listener. This will throw an error if the bind
// address is invalid or already in use.
healthProbeListener, err := options.newHealthProbeListener(options.HealthProbeBindAddress)
if err != nil {
return nil, err
}
stop := make(chan struct{})
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
cache用到了kubernetes中一个重要的工具包:informer。
cache主要就是创建了InformersMap,scheme里面的每个GVK (GroupVersionKind结构体,包含Group、Version、Kind三个字段,可以唯一确定一个资源) 都创建了对应的 Informer,通过 informersByGVK这个map来存放GVK和Informer的映射关系,每个 Informer会根据ListWatch 函数对对应的GVK进行List和Watch。我们为controller开发的Reconcile方法最终都会注册到informer的handler中,这样利用informer就可以达到监控资源的事件并触发Reconcile目的。
// newSpecificInformersMap returns a new specificInformersMap (like
// the generical InformersMap, except that it doesn't implement WaitForCacheSync).
func newSpecificInformersMap(config *rest.Config,
scheme *runtime.Scheme,
mapper meta.RESTMapper,
resync time.Duration,
namespace string,
createListWatcher createListWatcherFunc) *specificInformersMap {
ip := &specificInformersMap{
config: config,
Scheme: scheme,
mapper: mapper,
informersByGVK: make(map[schema.GroupVersionKind]*MapEntry), // schema GVK和informer映射
codecs: serializer.NewCodecFactory(scheme),
paramCodec: runtime.NewParameterCodec(scheme),
resync: resync,
startWait: make(chan struct{}),
createListWatcher: createListWatcher,
namespace: namespace,
}
return ip
}
MapEntry中包含最终创建的informer对象。
// MapEntry contains the cached data for an Informer
type MapEntry struct {
// Informer is the cached informer
Informer cache.SharedIndexInformer
// CacheReader wraps Informer and implements the CacheReader interface for a single type
Reader CacheReader
}
- client
从下面的代码可以看出,读操作使用上面创建的 cache,写操作使用client直连kubernetes。
// 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
}
CRD Reconcile执行
在main.go中, 通过下面的代码把Manager中的client、scheme以及日志对象传递给相应的CRD对象。
if err = (&controllers.TestCrdReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("TestCrd"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "TestCrd")
os.Exit(1)
}
// TestCrdReconciler reconciles a TestCrd object
type TestCrdReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
然后在Reconcile方法中就可以直接使用client来进行CURD操作。
总结
kubernetes的强大之处之一就是支持CRD对API进行扩展,当今很多项目都大量使用到CRD,像calico、istio以及kubevirt等等。如果有在kubernetes上层进行二次开发需求,可以优先考虑CRD,这是一种非常优雅的扩展方式,也是kubernetes生态的发展趋势。除此之外,有kubebuilder这个CRD开发利器,也能让我们的开发工作事半功倍。
资源链接
matrix github:https://github.com/cxwen/matrix
kubebuilder github: https://github.com/kubernetes-sigs/kubebuilder
kubebuilder的文档: https://book.kubebuilder.io/
Go语言环境安装: https://www.runoob.com/go/go-environment.html
go语言中文网go安装包下载: https://studygolang.com/dl
kustomize github: https://github.com/kubernetes-sigs/kustomize
kubernetes的官方文档对象规约(Spec)与状态(Status): https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/kubernetes-objects/#%E5%AF%B9%E8%B1%A1%E8%A7%84%E7%BA%A6-spec-%E4%B8%8E%E7%8A%B6%E6%80%81-status
controller-runtime github: https://github.com/kubernetes-sigs/controller-runtime
Copyright © 2015 Powered by MWeb, Theme used GitHub CSS.