De pie sobre los hombros de los gigantes, use etcd para almacenar objetos personalizados como kubernetes

antecedentes

Como todos sabemos, kubernetes usa etcd para almacenar objetos API, como Pod, Deployment, StatefulSet, etc. El autor cree que el diseño del objeto API de kubernetes está actualmente muy avanzado, y la realización del almacenamiento de objetos basado en etcd es la base clave de este diseño. El autor y muchos lectores tienen tales necesidades:

  1. El sistema que diseñé también espera adoptar etcd;
  2. El sistema también quiere adoptar la idea de objetos API diseñados por kubernetes, estos objetos están diseñados para el sistema y no tienen nada que ver con kubernetes;

La carga de trabajo de implementar un conjunto de código propio como kubernetes es asombrosa, incluso copiar una copia es muy difícil, después de todo, hay muchas dependencias de código involucradas. Entonces el autor piensa que será más interesante poder reutilizar el código ya implementado por kubernetes y extender los objetos API personalizados sobre esta base. Entonces, el título de este artículo está sobre los hombros de gigantes, kubernetes es un gigante y hay muchos diseños e implementaciones de código excelentes que se pueden usar como referencia. Cabe señalar que el autor del código de muestra de este artículo no explicará el principio en detalle. Si el lector necesita comprender el principio de implementación específico, puede leer el "Análisis en profundidad de la definición del tipo de objeto de la API de kubernetes" del autor. , "etcd en la aplicación de kubernetes" y "Análisis en profundidad de la implementación de almacenamiento de kubernetes apiserver".

Las dependencias de código de este artículo son las siguientes:

k8s.io/apimachinery v0.0.0-20191017185446-6e68a40eebf9
k8s.io/apiserver v0.0.0-20191018030144-550b75f0da71

lograr

Primero, construya un proyecto, nombraré este proyecto customeapi y crearé dos directorios cmd y pkg en el directorio raíz del proyecto. Luego está el objeto API personalizado. El autor define el objeto API en el paquete customeapi / pkg / api / test / v1 (los lectores deben tener en cuenta que la API en kubernetes se define en un proyecto separado k8s.io/api. Este artículo es para La demostración se define fácilmente en su propio proyecto), como se muestra en el siguiente código:

// 代码源自customeapi/pkg/api/test/v1/types.go
package v1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
)
// 定义自定义API对象Custom.
type Custom struct {
    // 继承metav1.TypeMeta和metav1.ObjectMeta才实现了runtime.Object,这样Custom对象
    // 的yaml的格式就像如下:
    // kind: Custom
    // apiVersion: test/v1
    // metadata: 
    //   labels: 
    //     name: custom
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
    // 为了演示方便,Custom对象的规格和状态都定义为空类型,读者根据自己的业务进行设计
    Spec              CustomSpec
    Status            CustomStatus
}
type CustomSpec struct{}
type CustomStatus struct{}

// DeepCopyObject()是必须要实现的,这是runtime.Objec定义的接口,否则编译就会报错。读者需要注意,
// kubernetes的API对象的DeepCopyObject()函数是代码生成工具生成的,本文的示例是笔者自己写的。
func (in *Custom) DeepCopyObject() runtime.Object {
    if in == nil {
        return nil
    }
    out := new(Custom)
    *out = *in
    return out
}
var _ runtime.Object = &Custom{}

Ok, el objeto personalizado ha sido definido. Ahora necesitas dejar que el códec reconozca el tipo de Custom, de lo contrario la serialización de Custom fallará antes de escribir en etcd. Esta parte del código se implementa de la siguiente manera:

// 代码源自customeapi/pkg/api/test/v1/register.go
package v1

import (
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/schema"
)

// 定义自定义类型组名,因为是测试例子,所以笔者把组名定义为test,这样test/Custom才是类型全称
const GroupName = "test"

// 定义自定义类型的组名+版本,即test v1
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}

// 程序初始化部分需要调用AddToScheme()来实现自定义类型的注册,具体的实现在addKnownTypes()
var (
    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    AddToScheme   = SchemeBuilder.AddToScheme
)

// 把笔者上面定义的Custom添加到scheme中就完成了类型的注册,就是这么简单。读者需要注意,类型注册
// 其实是一个比较复杂的过程,kubernetes把这部分实现全部交给了scheme,把简单的接口留给了使用者。
func addKnownTypes(scheme *runtime.Scheme) error {
    scheme.AddKnownTypes(SchemeGroupVersion,
        &Custom{},
    )

    return nil
}

Lo último es almacenar este objeto en etcd. El autor lo implementa desde el archivo cmd / main.go, como se muestra en el siguiente código:

// 代码源自customeapi/cmd/main.go
package main

import (
    "fmt"
    "context"

    testv1 "customapi/pkg/api/test/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/serializer"
    "k8s.io/apiserver/pkg/storage/storagebackend"
    "k8s.io/apiserver/pkg/storage/storagebackend/factory"
)

func main() {
    // 构造scheme,然后调用刚刚实现的注册函数实现自定义API对象的注册。
    scheme := runtime.NewScheme()
    testv1.AddToScheme(scheme)
    
    // 注册了自定义API对象,就可以构造codec工厂了,在通过codec工厂构造codec。所谓的codec就是
    // 自定义API对象的序列化与反序列化的实现。
    cf := serializer.NewCodecFactory(scheme)
    codec := cf.LegacyCodec(testv1.SchemeGroupVersion)

    // 有了codec就可以创建storagebackend.Config,他是构造storage.Interfaces的必要参数
    config := storagebackend.NewDefaultConfig("", codec)
    // 笔者在本机装了etcd,所以把etcd的地址填写为本机地址,这样方便测试
    config.Transport.ServerList = append(config.Transport.ServerList, "127.0.0.1:2379")
    // 创建storage.Interfaces对象,storage.Interfaces是apiserver对存储的抽象,这样我们
    // 就可以像apiserver一样在etcd上操作自己定义的对象了。
    storage, destroy, err := factory.Create(*config)
    if nil != err {
        fmt.Printf("%v\n", err)
    }
    // 构造Custom对象
    custom := testv1.Custom{}
    // 把Custom对象写入etcd
    if err = storage.Create(context.Background(), "test", &custom, &custom, 0); nil != err {
        fmt.Println(err)
    }
    // 把写入的Custom对象打印出来看看结果
    if data, err := runtime.Encode(codec, &custom); nil == err {
        fmt.Printf("%s\n", string(data))
    }
    // 必要的析构函数
    destroy()
}

Bueno, hasta ahora el autor ha completado la interfaz de storage.Interface que ha sido implementada por apiserver para almacenar objetos personalizados. Los resultados después de compilar y ejecutar son los siguientes:

I1018 11:10:21.782324   25634 client.go:357] parsed scheme: "endpoint"
I1018 11:10:21.782665   25634 endpoint.go:68] ccResolverWrapper: sending new addresses to cc: [{127.0.0.1:2379 0  <nil>}]
I1018 11:10:21.784459   25634 once.go:66] CPU time info is unavailable on non-linux or appengine environment.
I1018 11:10:21.787267   25634 client.go:357] parsed scheme: "endpoint"
I1018 11:10:21.787306   25634 endpoint.go:68] ccResolverWrapper: sending new addresses to cc: [{127.0.0.1:2379 0  <nil>}]
{"kind":"Custom","apiVersion":"test/v1","metadata":{"creationTimestamp":null},"Spec":{},"Status":{}}

Luego, a través de etcdctl --endpoints = http: // localhost: 2379 get / test, los resultados de la verificación son los siguientes:

{"kind":"Custom","apiVersion":"test/v1","metadata":{"creationTimestamp":null},"Spec":{},"Status":{}}

Terminaste, ¿te sorprende? ¿Fue inesperado? Puede escribir un programa que manipule objetos API como kubernetes con solo una pequeña cantidad de código. La clave es que personalizamos este tipo de objeto y lo almacenamos en nuestro propio etcd implementado.

En cuanto a la obtención y observación del objeto, el autor no lo demostrará uno por uno, los lectores pueden probarlo por sí mismos.

para resumir

El contenido anterior solo demuestra que es factible implementar un programa que pueda manipular objetos en etcd como kubernetes usando el código prefabricado de kubernetes. Sin embargo, todavía existen algunas restricciones sobre cuánto se puede usar, como:

  1. El tipo de objeto debe heredar metav1.TypeMeta y metav1.ObjectMeta. Metav1.TypeMeta sigue siendo muy versátil, pero metav1.ObjectMeta es algo redundante o puede que no sea suficiente;
  2. En el ejemplo, la serialización del objeto está en formato json. ¿Qué pasa si el objeto necesita interactuar con otros sistemas a través de rpc (como grpc) y el objeto necesita serialización protobuf?

De hecho, esta parte se puede encontrar en el "Análisis en profundidad de la definición de tipo de objeto de la API de kubernetes" y en el "Análisis en profundidad de la implementación de almacenamiento de kubernetes apiserver" del autor para encontrar la respuesta. Los lectores interesados ​​pueden desear echar un vistazo. En este momento, el autor tiene la sensación de estar sobre los hombros de los kubernetes gigantes.

Supongo que te gusta

Origin blog.csdn.net/weixin_42663840/article/details/102633206
Recomendado
Clasificación