go, k8s

client-go 管理crd

前文介绍了client-go的一些基本操作,这篇文章介绍下如何通过client-go管理crd。

环境

crd如下:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: crontabs.stable.example.com
spec:
  group: stable.example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
  scope: Namespaced
  names:
    plural: crontabs
    singular: crontab
    kind: CronTab
    shortNames:
    - ct

cr对象如下,创建3个crontab对象。

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object1
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image
---
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object2
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image
---
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: my-new-cron-object3
spec:
  cronSpec: "* * * * */5"
  image: my-awesome-cron-image

file

client

创建DynamicClient客户端。

dynamic.go

package crd

import (
    "k8s.io/client-go/dynamic"
    "k8s.io/client-go/tools/clientcmd"
    "log"
)

func DynamicClient() (*dynamic.DynamicClient, error) {
    var kubeConfig = "E:\\client-go\\config"

    config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
    if err != nil {
        log.Fatal(err)
    }

    client, err := dynamic.NewForConfig(config)
    if err != nil {
        log.Fatal(err)
    }
    return client, nil
}

list

list操作就和上面k get的效果是一样的。

list.go

package crd

import (
    "context"
    "encoding/json"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "log"
)

// gvr的值来源于上面的crd
var gvr = schema.GroupVersionResource{
    Group:    "stable.example.com",
    Version:  "v1",
    Resource: "crontabs",
}

// 定义crontab资源的spec
type CrontabSpec struct {
    CronSpec string `json:"cronSpec"`
    Image    string `json:"image"`
}

// 定义crontab资源
type Crontab struct {
    // 内嵌的TypeMeta字段,包含了资源的类型信息,例如API版本和种类(Kind)。
    metav1.TypeMeta `json:",inline"`
    // 包含了资源的元数据,例如名称、命名空间、标签等。
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              CrontabSpec `json:"spec,omitempty"`
}

// 表示Crontab资源的集合
type CrontabList struct {
    metav1.TypeMeta `json:",inline"`
    // 名为metadata的字段,提供了列表的元数据,如资源版本等,有助于实现资源的列表操作。
    metav1.ListMeta `json:"metadata,omitempty"`
    // 一个Crontab类型的切片,包含了集合中的每个Crontab资源实例。
    Items []Crontab `json:"items"`
}

// 将获取到的资源列表序列化为JSON,然后反序列化到CrontabList类型的变量中。最后遍历Items切片中每个Crontab实例打印输出。
func ListCrontabs(d *dynamic.DynamicClient, ns string) {
    // crd定义的是namespace作用域的,如果是cluster的话,可以不用指定namespace
    list, err := d.Resource(gvr).Namespace(ns).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        log.Fatal(err)
    }
    data, err := list.MarshalJSON()
    if err != nil {
        log.Fatal(err)
    }
    var crontabList CrontabList
    if err = json.Unmarshal(data, &crontabList); err != nil {
        log.Fatal(err)
    }
    for _, crontab := range crontabList.Items {
        fmt.Printf("Crontab: %s %s %s %s\n", crontab.Namespace, crontab.Name, crontab.Spec.CronSpec, crontab.Spec.Image)
    }
}

json标记的作用

结构体字段后面的这些标记(tags)是用来指定JSON编码和解码时的一些特殊行为的。

`json:",inline"`:该标签用于嵌入结构体中的字段,让这些字段出现在外层对象中,而不是嵌套在结构体字段中。
//添加前
{
  "typeMeta": {
    "kind": ...,
    "apiVersion": ...
  },
  "otherFields": ...
}
// 添加后
{
  "kind": ...,
  "apiVersion": ...,
  "otherFields": ...
}
`json:"metadata,omitempty"`:omitempty是一个可选标记,表示如果该字段的值为空(零值),那么在进行JSON编码时,这个字段会被省略,不会出现在编码后的JSON中。

运行后输出如下:

file

get

和list相似。

get.go

package crd

import (
    "context"
    "encoding/json"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "log"
)

var gvr = schema.GroupVersionResource{
    Group:    "stable.example.com",
    Version:  "v1",
    Resource: "crontabs",
}

type CrontabSpec struct {
    CronSpec string `json:"cronSpec"`
    Image    string `json:"image"`
}

type Crontab struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              CrontabSpec `json:"spec,omitempty"`
}

type CrontabList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Crontab `json:"items"`
}

func GetCrontab(d *dynamic.DynamicClient, ns string, name string) {
    get, err := d.Resource(gvr).Namespace(ns).Get(context.TODO(), name, metav1.GetOptions{})
    if err != nil {
        log.Fatal(err)
    }
    data, err := get.MarshalJSON()
    if err != nil {
        log.Fatal(err)
    }
    var crontab Crontab
    if err = json.Unmarshal(data, &crontab); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Crontab: %s %s %s %s\n", crontab.Namespace, crontab.Name, crontab.Spec.CronSpec, crontab.Spec.Image)
}

运行后输出如下:

file

create

根据指定的yaml来创建CronTab对象。

package crd

import (
    "context"
    "encoding/json"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
    "k8s.io/client-go/dynamic"
    "log"
)

var gvr = schema.GroupVersionResource{
    Group:    "stable.example.com",
    Version:  "v1",
    Resource: "crontabs",
}

type CrontabSpec struct {
    CronSpec string `json:"cronSpec"`
    Image    string `json:"image"`
}

type Crontab struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              CrontabSpec `json:"spec,omitempty"`
}

type CrontabList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Crontab `json:"items"`
}

func CreateCrontab(d *dynamic.DynamicClient, ns string, yamldata string) {
    // 创建一个解码器,用于将YAML格式的数据解码成一个unstructured.Unstructured对象。
    decode := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
    obj := &unstructured.Unstructured{}
    // 将传入的YAML数据解码到一个unstructured.Unstructured对象中。如果解码失败,将记录错误并退出。
    if _, _, err := decode.Decode([]byte(yamldata), nil, obj); err != nil {
        log.Fatal(err)
    }
    create, err := d.Resource(gvr).Namespace(ns).Create(context.TODO(), obj, metav1.CreateOptions{})
    if err != nil {
        log.Fatal(err)
    }
    data, err := create.MarshalJSON()
    if err != nil {
        log.Fatal(err)
    }
    var crontab Crontab
    if err = json.Unmarshal(data, &crontab); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Crontab: %s %s %s %s\n", crontab.Namespace, crontab.Name, crontab.Spec.CronSpec, crontab.Spec.Image)
}
func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer
# 将YAML格式的数据解码为JSON序列化数据。
func (Decoder) Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
# 这个方法会解码data字节,并将结果写入into指定的对象中。如果数据中没有指定API版本与种类(例如是未知 CRD),就使用defaults作为默认值。主要作用是将任意结构的数据(JSON/YAML)解码为 Go 对象;解析数据中的 API 版本与种类信息;解码结果写入指定的目标对象。

整个流程就是:yaml–>unstructured.Unstructured(obj)–>json(data)–>crontab

运行后输出如下:

file

update

和上面创建不同的是,在更新资源之前,需要将新对象的资源版本设置为当前获取到的资源版本。这是为了确保更新操作是在最新版本的基础上进行的,避免数据冲突。

package crd

import (
    "context"
    "encoding/json"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer/yaml"
    "k8s.io/client-go/dynamic"
    "log"
)

var gvr = schema.GroupVersionResource{
    Group:    "stable.example.com",
    Version:  "v1",
    Resource: "crontabs",
}

type CrontabSpec struct {
    CronSpec string `json:"cronSpec"`
    Image    string `json:"image"`
}

type Crontab struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              CrontabSpec `json:"spec,omitempty"`
}

type CrontabList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Crontab `json:"items"`
}

func UpdateCrontab(d *dynamic.DynamicClient, ns string, yamldata string) {
    decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
    obj := &unstructured.Unstructured{}
    if _, _, err := decoder.Decode([]byte(yamldata), nil, obj); err != nil {
        log.Fatal(err)
    }
    // 获取对象信息
    update, err := d.Resource(gvr).Namespace(ns).Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
    if err != nil {
        log.Fatal(err)
    }
    // 设置版本号
    obj.SetResourceVersion(update.GetResourceVersion())
    update, err = d.Resource(gvr).Namespace(ns).Update(context.TODO(), obj, metav1.UpdateOptions{})
    if err != nil {
        log.Fatal(err)
    }
    date, err := update.MarshalJSON()
    if err != nil {
        log.Fatal(err)
    }
    var crontab Crontab
    if err = json.Unmarshal(date, &crontab); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Crontab: %s %s %s %s\n", crontab.Namespace, crontab.Name, crontab.Spec.CronSpec, crontab.Spec.Image)
}

运行后输出如下:

file

patch

package crd

import (
    "context"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/types"
    "k8s.io/client-go/dynamic"
    "log"
)

var gvr = schema.GroupVersionResource{
    Group:    "stable.example.com",
    Version:  "v1",
    Resource: "crontabs",
}

type CrontabSpec struct {
    CronSpec string `json:"cronSpec"`
    Image    string `json:"image"`
}

type Crontab struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              CrontabSpec `json:"spec,omitempty"`
}

type CrontabList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Crontab `json:"items"`
}

func PatchCrontab(d *dynamic.DynamicClient, ns string, name string, patchtype types.PatchType, patchdata []byte) {
    if _, err := d.Resource(gvr).Namespace(ns).Patch(context.TODO(), name, patchtype, patchdata, metav1.PatchOptions{}); err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Patched Crontab: %s %s\n", ns, name)
}

报错

patch对象如果写成:patchData := []byte([{"op": "replace", "path": "/spec/image", "value": "my-awesome-cron-image2"}]),
运行会报错:"" is invalid: patch: Invalid value: "[{\"op\":\"replace\",\"path\":\"/spec/image\",\"value\":\"my-awesome-cron-image2\"}]": couldn't get version/kind; json parse error: json: cannot unmarshal array into Go value of type struct { APIVersion string "json:\"apiVersion,omitempty\""; Kind string "json:\"kind,omitempty\"" }
这个错误是因为Patch数据使用了JSON数组的格式,而实际需要使用JSON对象格式。

解决方法有两种,

  • 一种是上面示例代码的形式,patchData := []byte({"spec":{"image":"my-awesome-cron-image3"}}),然后PatchCrontab函数的patch类型使用types.MergePatchType
  • 另外一种是使用patchData := []byte([{"op": "replace", "path": "/spec/image", "value": "my-awesome-cron-image2"}]),然后PatchCrontab函数的patch类型使用types.JSONPatchType

运行后输出如下:

file

delete

package crd

import (
    "context"
    "fmt"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/client-go/dynamic"
    "log"
)

var gvr = schema.GroupVersionResource{
    Group:    "stable.example.com",
    Version:  "v1",
    Resource: "crontabs",
}

type CrontabSpec struct {
    CronSpec string `json:"cronSpec"`
    Image    string `json:"image"`
}

type Crontab struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`
    Spec              CrontabSpec `json:"spec,omitempty"`
}

type CrontabList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Crontab `json:"items"`
}

func DeleteCrontab(d *dynamic.DynamicClient, ns string, name string) {
    err := d.Resource(gvr).Namespace(ns).Delete(context.TODO(), name, metav1.DeleteOptions{})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Crontab %s deleted\n", name)
}

运行后输出如下:

file

main.go

package main

import (
    createcrd "crd/create"
    dclient "crd/client"
    deletecrd "crd/delete"
    getcrd "crd/get"
    listcrd "crd/list"
    patchcrd "crd/patch"
    updatecrd "crd/update"
    "fmt"
    "log"
)

func main() {
    client, err := dclient.DynamicClient()
    if err != nil {
        log.Fatal(err)
    }
    ns := "default"
    fmt.Println("List Crontabs:")
    listcrd.ListCrontabs(client, ns)
    //fmt.Println("Get Crontab:")
    //name := "my-new-cron-object3"
    //getcrd.GetCrontab(client, ns, name)
    //  fmt.Println("Create Crontab:")
    //  yamlData := `
    //apiVersion: stable.example.com/v1
    //kind: CronTab
    //metadata:
    //  name: my-new-cron-object4
    //spec:
    //  cronSpec: "* * * * */5"
    //  image: my-awesome-cron-image`
    //  createcrd.CreateCrontab(client, ns, yamlData)
    //  fmt.Println("Update Crontab:")
    //  yamlData := `
    //apiVersion: stable.example.com/v1
    //kind: CronTab
    //metadata:
    //  name: my-new-cron-object3
    //spec:
    //  cronSpec: "* * * * */5"
    //  image: my-awesome-cron-image2`
    //  updatecrd.UpdateCrontab(client, ns, yamlData)
    //fmt.Println("Patching Crontab:")
    ////patchData := []byte(`{"spec":{"image":"my-awesome-cron-image3"}}`)
    //patchData := []byte(`[{"op": "replace", "path": "/spec/image", "value": "my-awesome-cron-image2"}]`)
    ////patchcrd.PatchCrontab(client, ns, "my-new-cron-object3", types.MergePatchType, patchData)
    //patchcrd.PatchCrontab(client, ns, "my-new-cron-object3", types.JSONPatchType, patchData)
    //fmt.Println("Get Crontab3:")
    //name := "my-new-cron-object3"
    //getcrd.GetCrontab(client, ns, name)
    deletecrd.DeleteCrontab(client, ns, "my-new-cron-object4")
}
0 0 投票数
文章评分
订阅评论
提醒
guest

0 评论
内联反馈
查看所有评论

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部
0
希望看到您的想法,请您发表评论x