使用 client-go 操作 kubernetes 的 crd 资源
2021-03-12 tech go kubernetes 17 mins 2 图 6010 字

最近在开发 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

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 信息,传输过来实际上是字符串。

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)
参考资料
- 在不生成 crd client 代码的情况下通过 client-go 增删改查 k8s crd 资源
- An example of using dynamic client of k8s.io/client-go
- https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured#Unstructured.MarshalJSON
- GoLang : Dynamic JSON Parsing using empty Interface and without Struct in Go Language
- Kubernetes CRD 系列:Client-Go 的使用
- 使用client-go 进行k8s相关操作-dynamicclient(二)
- Kubernetes client-go实现yaml创建Deployment
