How to write a simple k8s operator

Table of Contents

本文参照 k8s 版本为 v.1.18

1. 写在前面

关于 operator 几年前已经在一篇博客中批判过了,但是到了如今 Operator 可以称的上百花齐放,脚手架都开始整上了。

首先 Kubernetes 不是银弹,Operator 就更不是了。并不是什么东西火,什么东西就牛逼。很多人怎么就不明白这个道理呢?

现在你一个项目没有个 Operator 都不好意思说自己是“云原生”。

呕,不好意思。现在云原生 PTSD,呕。

重复一下观点,Operator 是锦上的那朵花,不是救急的那口粮。

您要是没落的贵族,到死都要秉着气节,当我没说。

接下来就看看这朵花怎么绣。

2. CustomResource 与 Operator

自定义资源是 k8s API 的扩展,通过自定义资源可以使用 k8s api 来控制自己的服务

Operator 最早是 coreOS 提出的一种模式,通过代码的方式来简单的运维服务

2.1. 为什么 Operator 离不开 CustomResource

Operators are software extensions to Kubernetes that make use of custom resources to manage applications and their components.

CustomResource 是 Operator 用来与 k8s 沟通的桥梁

3. 如何实现一个自定义的资源

实现一个自定义的资源有两种方式

3.1. CRD

下方是通过 yaml 创建的 CRD

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: milvuses.zilliz.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: zilliz.com
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # Each version can be enabled/disabled by Served flag.
      served: true
      # One and only one version must be marked as the storage version.
      storage: true
      schema:
	openAPIV3Schema:
	  type: object
	  properties:
	    spec:
	      type: object
	      properties:
		milvusSpec:
		  type: string
		image:
		  type: string
		replicas:
		  type: integer
  # either Namespaced or Cluster
  scope: Namespaced
  names:
    # plural name to be used in the URL: /apis/<group>/<version>/<plural>
    plural: milvuses
    # singular name to be used as an alias on the CLI and for display
    singular: milvus
    # kind is normally the CamelCased singular type. Your resource manifests use this.
    kind: Milvus
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - mi

3.2. AA (Aggregated API)

CRD 是将资源注册到 k8s 当中,AA 则是将自定义的资源放到自己实现的 API Server 中

AA 必须使用 go 开发,并且提供相应的镜像

AA 可以提供更丰富的功能,可以看作独立实现的 API 服务,提供更丰富的功能:如改变存储方式、提供更多命令的支持、不同的协议

4. 在集群中创建一个 milvus

apiVersion: "zilliz.com/v1"
kind: Milvus
metadata:
  name: milvus-crd-test
spec:
  milvusSpec: "this is milvus spec"
  image: milvusdb/milvus

5. 如何实现一个 Operator

在实现一个 operator 之前先想一个问题,这个 operator 的功能,这一次我们要做的是最简单的一个功能

当创建了一个 milvus 资源的时候,在默认的 namespace 下启动一个 milvus 实例

实现一个 Operator 有两种方式

  1. 使用框架如 kubebuilder operator-framework
  2. 使用 dynamicClient 写一个

先说为什么有两种方式: 由于 k8s 官方钦点的 go-client 中如果想要识别自定义资源,有两种方式: 1.按照格式来生成一堆代码与结构,并将资源注册到 apiserver。这种方式需要生成很多代码,现有的框架也都是为了这种模式服务的 2.使用 dynamicClient 可以最快速度的调试代码,因为 dynamicClient 本质上是对 k8s REST client 的封装

关于 kubebuilder 框架之类的内容会在之后讲到,这里来讲如何实现一个最小的 operator 详细代码可以参考 https://github.com/41tair/milvus-operator-example

1.从 k8s 集群中获取到 CR 的信息

func milvusGVR() schema.GroupVersionResource{
	return schema.GroupVersionResource{
		Group: "zilliz.com",
		Version: "v1",
		Resource: "milvuses",
	}
}

func milvusList(client dynamic.Interface) []unstructured.Unstructured{
	milvusInstances, _ := client.Resource(milvusGVR()).Namespace("default").List(context.TODO(), metav1.ListOptions{})
	return milvusInstances.Items
}

2.从 k8s 集群中获取到当前的 pod

func milvusPods(client *kubernetes.Clientset) []v1.Pod{
	pods, _ := client.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{})
	return pods.Items
}

3.在启动时开启循环定时进行比对并创建没有被创建的 pod

func mainLoop(clientset *kubernetes.Clientset, milvusList []unstructured.Unstructured, pods []v1.Pod) {
	var waitForCreate []MilvusIns
	for _, milvus := range milvusList {
		name := milvus.Object["metadata"].(map[string]interface{})["name"].(string)
		image := milvus.Object["spec"].(map[string]interface{})["image"].(string)
		if existInPods(name, pods) != true {
			waitForCreate = append(waitForCreate, MilvusIns{
				Name: name,
				Image: image,
			})
		}
	}
	for _, m := range waitForCreate {
		createMilvusPod(clientset, m.Name, m.Image)
	}

}

Created: 2023-11-24 Fri 09:50

Validate