使用 client-go 操作 kubernetes 的 crd 资源

最近在开发 k8s crd 相关的操作,这里简单的记录一下通过 client-go 增删改查 k8s crd 资源的方法。

示例CRD

这里我以第三方的crd: kyverno 作为例子。

kyverno 的 crd 结构体定义在这里: pkg/api/kyverno/v1,主要是一下两个文件(为了引入尽可能少的文件,编译报错时可以把某些类型改成interface)

重点的 cluster policy 和 List,以及内部的 Rule 的结构体如下:

type ClusterPolicy struct {
	metav1.TypeMeta   `json:",inline,omitempty" yaml:",inline,omitempty"`
	metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
	Spec Spec `json:"spec" yaml:"spec"`
	Status PolicyStatus `json:"status,omitempty" yaml:"status,omitempty"`
}

type ClusterPolicyList struct {
	metav1.TypeMeta `json:",inline" yaml:",inline"`
	metav1.ListMeta `json:"metadata" yaml:"metadata"`
	Items           []ClusterPolicy `json:"items" yaml:"items"`
}

type Rule struct {
	Name string `json:"name,omitempty" yaml:"name,omitempty"`
	Context []ContextEntry `json:"context,omitempty" yaml:"context,omitempty"`
	MatchResources MatchResources `json:"match,omitempty" yaml:"match,omitempty"`
	ExcludeResources ExcludeResources `json:"exclude,omitempty" yaml:"exclude,omitempty"`
	AnyAllConditions apiextensions.JSON `json:"preconditions,omitempty" yaml:"preconditions,omitempty"`
	Mutation Mutation `json:"mutate,omitempty" yaml:"mutate,omitempty"`
	Validation Validation `json:"validate,omitempty" yaml:"validate,omitempty"`
	Generation Generation `json:"generate,omitempty" yaml:"generate,omitempty"`
}

部署 kyverno 的 crd:

kubectl create -f https://raw.githubusercontent.com/kyverno/kyverno/main/definitions/release/install.yaml

查看 k8s 上的api资源有哪些:

kubectl api-resources

image-20210323090256151

List

相当于 kubectl get xxx 命令,我们通过 k8s.io/client-go/dynamic 里的 Interface 提供的方法来操作 crd 资源。

package main

import (
        "encoding/json"
        "fmt"
        "os"
        "path/filepath"

        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/runtime/schema"
        "k8s.io/client-go/dynamic"
        "k8s.io/client-go/tools/clientcmd"
)

// getGVR :- gets GroupVersionResource for dynamic client
func getGVR(group, version, resource string) schema.GroupVersionResource {
	return schema.GroupVersionResource{Group: group, Version: version, Resource: resource}
}

func listKyverno(client dynamic.Interface, ns string) (*ClusterPolicyList, error) {
	    gvr := getGVR("kyverno.io", "v1", "clusterpolicies")

        if (ns == ''){
        	list, err := dynamicClient.Resource(gvr).List(metav1.ListOptions{})
        } else {
        	list, err := dynamicClient.Resource(gvr).Namespace(namespace).List(metav1.ListOptions{})
        }
        
	
        if err != nil {
                return nil, err
        }
        
        data, err := list.MarshalJSON()
        if err != nil {
                return nil, err
        }
        
        var list ClusterPolicyList
        if err := json.Unmarshal(data, &list); err != nil {
                return nil, err
        }
        
        return &list, nil
}

func main() {
        kubeconfig := filepath.Join(os.Getenv("HOME"), ".kube", "config")
        config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
        if err != nil {
                panic(err)
        }

        client, err := dynamic.NewForConfig(config)
        if err != nil {
                panic(err)
        }
        
        list, err := listKyverno(client, "")
        if err != nil {
                panic(err)
        }
        
        for _, t := range list.Items {
                fmt.Printf("%s %s %s %s\n", t.Namespace, t.Name, t.Spec.CronSpec, t.Spec.Image)
        }
}

Get

func getKyverno (client dynamic.Interface, namespace string, name string) (*ClusterPolicy, error) {
        gvr := getGVR("kyverno.io", "v1", "clusterpolicies")
        
        // Unstructured
        utd, err := client.Resource(gvr).Namespace(namespace).Get(name, metav1.GetOptions{})
        
        // Unstructured -> cpol
        var cpol ClusterPolicy
        runtime.DefaultUnstructuredConverter.FromUnstructured(utd.UnstructuredContent(), &cpol)
        logrus.Info("kube kyverno cpol ", cpol.Name, ", version:", cpol.Generation)
        
        return &cpol, nil
}

Create

前端 Post 传输 yaml 信息,传输过来实际上是字符串。

image-20210323101755887

import "gopkg.in/yaml.v3"

func createKyvernoFrom(client dynamic.Interface, namespace string, yamlData string) (*ClusterPolicy, error) {
    gvr := getGVR("kyverno.io", "v1", "clusterpolicies")
    
    // yaml string -> cpol
	var cpol ClusterPolicy
	err := yaml.Unmarshal([]byte(yamlData), &cpol)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // cpol ->  []byte
	data, err := json.Marshal(&cpol)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// []byte -> Unstructured
	obj := &unstructured.Unstructured{}
	err := json.Unmarshal(data, &obj.Object)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// cpol -> unstructed 也可以直接使用 runtime.DefaultUnstructuredConverter.FromUnstructured 类似的方法做一个反转换,这部分可以从参考我另一篇blog: https://blog.kelu.org/tech/2021/03/14/go-unstructured.html
	
	// Unstructured create k8s 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(obj, metav1.CreateOptions{})
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// unstructured -> []byte
	result, err := utd.MarshalJSON()
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // []byte -> cpol
	var resultCpol ClusterPolicy
	if err := json.Unmarshal(result, &resultCpol); err != nil {
		logrus.Error(err)
		return nil, err
	}
    
    return &resultCpol, nil
}

Update

utd, err = client.Resource(gvr).Namespace(namespace).Update(obj, metav1.UpdateOptions{})

Patch

 _, err := client.Resource(gvr).Namespace(namespace).Patch(name, pt, data, metav1.PatchOptions{})

Delete

client.Resource(gvr).Namespace(namespace).Delete(name, nil)

参考资料


go 主动退出进程 go xorm 入门笔记