前文介绍了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
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中。
运行后输出如下:
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)
}
运行后输出如下:
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
运行后输出如下:
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)
}
运行后输出如下:
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
运行后输出如下:
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)
}
运行后输出如下:
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")
}