从零实现一个operator(1)-kubebuilder实现redis operator

operator实现步骤

正式开始使用kubebuilder来实现operator,关于operator原理可以看之前文章介绍,根据官网教程中的步骤来初始化一个operator项目:

1
kubebuilder init --domain my.domain --repo my.domain/guestbook #修改这里的my.domain和my.domain/guestbook

之后kubebuidler会帮助我们生成相关代码:

image-20240115173047116

  • PROJECT: 关于项目的一些元数据,比如domain、projectName、repo等信息。
  • config: 一些关于RBAC权限的yaml文件,以及Prometheus监控发现的相关yaml文件,还有控制器部署的yaml文件。
  • Dockerfile: 整个代码完成之后,打包成镜像使用。
  • Makefile: 整个程序的编译构建,镜像推送、部署、卸载等操作。

定义CRD

1
kubebuilder create api --group redis  --version v1 --kind Standalone

通过以上命令创建CRD,之后可以看到相关yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: redis.cola.redis/v1
z
metadata:
labels:
app.kubernetes.io/name: standalone
app.kubernetes.io/instance: standalone-sample
app.kubernetes.io/part-of: kubebuilder
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: kubebuilder
name: standalone-sample
spec:
# TODO(user): Add fields here

yaml中字段正好对应着命令中的各种信息,api/v1/standalone_types.go中有关于CRD的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// StandaloneSpec defines the desired state of Standalone
type StandaloneSpec 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 Standalone. Edit standalone_types.go to remove/update
Foo string `json:"foo,omitempty"`
}

// StandaloneStatus defines the observed state of Standalone
type StandaloneStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// Standalone is the Schema for the standalones API
type Standalone struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec StandaloneSpec `json:"spec,omitempty"`
Status StandaloneStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// StandaloneList contains a list of Standalone
type StandaloneList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Standalone `json:"items"`
}

func init() {
SchemeBuilder.Register(&Standalone{}, &StandaloneList{})
}

部署CRD

当CRD代码实现之后,可以通过make manifests来生成ClusterRole和CustomResourceDefinition配置。

  • config/crd/bases/crdFile.yaml
  • config/rbac/role.yaml

相关CRD文件

image-20240115174420263

之后通过make install来部署这个CRD,之后通过api-resource或者kubectl get crd都能够看到这个CRD。

控制器(Controller)实现

虽然CR对应的yaml可以被创建,但是相关的pod是不会运行的,因为没有控制器,等控制器实现之后,整个operator就算是完成了。

控制器部署

demo实现

根据之前步骤,可以明白一个operator是怎么开发出来的,这里参考ot-redis-operator中的Standalone,来开发一个单例redis operator。先来看ot-redis-operator的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---
apiVersion: redis.redis.opstreelabs.in/v1beta1
kind: Redis
metadata:
name: redis-standalone
spec:
kubernetesConfig:
image: quay.io/opstree/redis:v7.0.5
imagePullPolicy: IfNotPresent
storage:
volumeClaimTemplate:
spec:
# storageClassName: standard
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
securityContext:
runAsUser: 1000
fsGroup: 1000

这一块对应了Sepc代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// RedisSpec defines the desired state of Redis
type RedisSpec struct {
KubernetesConfig KubernetesConfig `json:"kubernetesConfig"`
RedisExporter *RedisExporter `json:"redisExporter,omitempty"`
RedisConfig *RedisConfig `json:"redisConfig,omitempty"`
Storage *Storage `json:"storage,omitempty"`
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"`
SecurityContext *corev1.SecurityContext `json:"securityContext,omitempty"`
PriorityClassName string `json:"priorityClassName,omitempty"`
Affinity *corev1.Affinity `json:"affinity,omitempty"`
Tolerations *[]corev1.Toleration `json:"tolerations,omitempty"`
TLS *TLSConfig `json:"TLS,omitempty"`
ACL *ACLConfig `json:"acl,omitempty"`
// +kubebuilder:default:={initialDelaySeconds: 1, timeoutSeconds: 1, periodSeconds: 10, successThreshold: 1, failureThreshold:3}
ReadinessProbe *Probe `json:"readinessProbe,omitempty" protobuf:"bytes,11,opt,name=readinessProbe"`
// +kubebuilder:default:={initialDelaySeconds: 1, timeoutSeconds: 1, periodSeconds: 10, successThreshold: 1, failureThreshold:3}
LivenessProbe *Probe `json:"livenessProbe,omitempty" protobuf:"bytes,11,opt,name=livenessProbe"`
InitContainer *InitContainer `json:"initContainer,omitempty"`
Sidecars *[]Sidecar `json:"sidecars,omitempty"`
ServiceAccountName *string `json:"serviceAccountName,omitempty"`
TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty" protobuf:"varint,4,opt,name=terminationGracePeriodSeconds"`
EnvVars *[]corev1.EnvVar `json:"env,omitempty"`
}

这里可以从使用者角度出发,你可以想象一下,如果你需要一个redis实例,你希望填写哪些参数就可以创建出一个redis?这里贴一下我的spec,后续有需要可以在修改,也可以参考redis config中哪些参数是需要的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: redis.cola.redis/v1
kind: Standalone
metadata:
labels:
app.kubernetes.io/name: standalone
app.kubernetes.io/instance: standalone-sample
app.kubernetes.io/part-of: kubebuilder
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: kubebuilder
name: standalone-sample
spec:
# TODO(user): Add fields here
#image,imagePullPolicy,resources,
redisConfigFile: #user.monitoring,storage
port:
version:
resourceReqs:

完成spec相关代码编写之后,使用make install,之后通过kubectl api-resource |grep redis来查看刚刚部署的CRD:

image-20240117223521762

之后apply一下我们的yaml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: redis.cola.redis/v1
kind: Standalone
metadata:
labels:
app.kubernetes.io/name: standalone
app.kubernetes.io/instance: standalone-sample
app.kubernetes.io/part-of: kubebuilder
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: kubebuilder
name: standalone-sample
spec:
RedisConfig:
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
KubernetesConfig:
image: redis
imagePullPolicy: Always # 或其他拉取策略
storage:
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
volumeMount:
volume:
- name: your-volume-name
persistentVolumeClaim:
claimName: your-pvc-name
mountPath:
- /your/mount/path

需要注意这里的yaml并不能直接使用,需要根据集群情况去调整,这里我对代码进行了调整:

1
2
3
4
5
6
7
type StandaloneSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
RedisConfig v1.ConfigMap `json:"RedisConfig,omitempty"`
KubernetesConfig kubernetes.KubernetesConfig `json:"KubernetesConfig,omitempty"`
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: redis.cola.redis/v1
kind: Standalone
metadata:
labels:
app.kubernetes.io/name: standalone
app.kubernetes.io/instance: standalone-sample
app.kubernetes.io/part-of: kubebuilder
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/created-by: kubebuilder
name: standalone-sample
spec:
RedisConfig:

KubernetesConfig:

我们可以省去这两个字段apply了,但是不建议这样做,只是为了展示。可以看到应该再集群中运行了,但是相关pod没有被创建,这是因为控制器代码并没有被实现,当控制器实现的时候,我们就能够创建对应pod了。

image-20240117225317633

参考链接


从零实现一个operator(1)-kubebuilder实现redis operator
http://example.com/2024/01/11/从零实现一个operator-1-kubebuilder实现redis-operator/
Author
John Doe
Posted on
January 11, 2024
Licensed under