将 Kubernetes unstructured 类型转为对象

使用代码和k8s交互,主要用到 client-go 这个库中的两个主要 APIs: kubernetes.Interface API 和非结构化的 dynamic.Interface API。

在使用 dynamic.Interface 时,我们经常需要在 string,unstructured ,对象, map[string] interface{} 转来转去,本文介绍将非结构化的类型 (unstructured) 与对象互转的两个方法。

方法一:json

使用 json 包将 unstructured 序列化为 []byte ,再反序列化为对象,这是 golang 里最通用的做法了。不过这个做法有点局限性,如果内容存在一些特殊字符容易在 json.Unmarshal 的时候报错,这时候还是使用方法二,官方的比较靠谱。

unstructured -> []byte -> object

import "encoding/json"
...

    // 从k8s返回值里构造一个 unstructured 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(xxxUnstructured, 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 -> obj
	var obj XxxObject
	if err := json.Unmarshal(result, &obj); err != nil {
		logrus.Error(err)
		return nil, err
	}

object -> []byte -> unstructured

ps: 对象转为[]byte时需要注意的一点是,object里不能存在 map[interface{}] interface{} 类型,如果存在此种类型,一定要在源头将其转换为 map[string]interface 类型,否则无法使用json包。

json: unsupported type: map[interface {}]interface {}

相关 issue参考:map[interface{}]interface{} on a map[string]interface{}

import "gopkg.in/yaml.v3"
...

    //// 从k8s返回值里构造一个 object 
	var obj XxxObj
	err := yaml.Unmarshal([]byte(yamlData), &obj)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

    // obj ->  []byte
	data, err := json.Marshal(&obj)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
	
	// []byte -> Unstructured
	utd := &unstructured.Unstructured{}
	err := json.Unmarshal(data, &utd.Object)
	if err != nil {
		logrus.Error(err)
		return nil, err
	}

方法二:runtime.UnstructuredConverter

runtime.DefaultUnstructuredConverter 基本上满足了我们大部分场景。

unstructured -> obj


import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/cluster-api/api/v1alpha2"
)
...

    // 从k8s返回值里构造一个 unstructured 
    var utd *unstructured.Unstructured
	utd, err = dynamicClient.Resource(gvr).Create(xxxUnstructured, metav1.CreateOptions{})
	if err != nil {
		logrus.Error(err)
		return nil, err
	}
    
    // unstructured -> obj
	var obj XxxObject
	runtime.DefaultUnstructuredConverter.FromUnstructured(utd.UnstructuredContent(), &cpol)

obj -> unstructured

utd, err := runtime.UnstructuredConverter.ToUnstructured(obj)

objOther -> objK8sVx

假定c是一个第三方封装的object,内容包含了v1node的数据。

		node := c.(interface{})
		utd, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&node)
		if err != nil {
			logrus.Error(err)
		}

		v1node := &v1.Node{}
		err = runtime.DefaultUnstructuredConverter.FromUnstructured(utd, v1node)
		if err != nil {
			logrus.Error(err)
		}

参考资料


go xorm 入门笔记

xorm 是一个简单而强大的Go语言 ORM 库,目前支持了众多的数据库,包括:

最近使用它进行数据库操作,这篇文章简单记录使用情况。

官方网址:https://xorm.io/,官方手册:https://gobook.io/read/gitea.com/xorm/manual-zh-CN/

安装

go get xorm.io/xorm

使用相应的数据库,需要安装相应的驱动,如果使用的是MySQL,需要如下安装:

go get -u github.com/go-sql-driver/mysql

基本使用

package main

import (
    "fmt"
    "github.com/go-xorm/xorm"
    _ "github.com/go-sql-driver/mysql"  // 默认会执行init初始化一些操作的
    "xorm.io/core"
)

func main(){
    //1.创建数据库引擎对象
    engine,err := xorm.NewEngine("mysql","root:root@(127.0.0.1:3306)/elmcms?charset=utf8");
    if err != nil {
        panic(err.Error())
    }
    
    //2.数据库引擎关闭
    defer engine.Close()
    
    //3.数据库引擎设置
    engine.ShowSQL(true)  //设置显示sql语句  会在控制台当中展示
    engine.Logger().SetLevel(core.LOG_DEBUG)   //设置日志级别
    engine.SetMaxOpenConns(10)    //设置最大链接数量
    
    loc, _ := time.LoadLocation("Asia/Shanghai") //设置时区
  	Engine.DatabaseTZ = loc // 设置时区
  	Engine.TZLocation = loc // 设置时区

    //3.简单的一些使用
    //判断表结构是否存在
    exist, err := engine.IsTableExist(XxxObj{})
    if err != nil {
        panic(err.Error())
    }
    
    if ! exist {
        engine.CreateTables(XxxObj{})
    }
}

结构体映射

通过结构体映射自动创建数据库表以及判断表是否为空或者是否存在.

参考:https://gobook.io/read/gitea.com/xorm/manual-zh-CN/chapter-02/1.mapping.html

type XxxObj struct {
    Id         int64     `xorm:"pk autoincr"`   //主键自增
    PersonName string    `xorm:"varchar(24)"`   //可变字符
    PersonAge  int       `xorm:"int default 0"` //默认值
    PersonSex  int       `xorm:"notnull"`       //不能为空
    City       CityTable `xorm:"-"`             //不映射该字段 那就不会在数据库里面创建该字段
}

简单用法

查询

    var obj XxxObj
    var objList []XxxObj
    
    // id查询
    engine.Id(1).Get(&obj)

	// where多条件查询
 	engine.Where(" a = ? and b = ?", 30, 1).Get(&obj)
 	
 	// and or 查询
 	engine.Where(" a = ?", 30).And("b = ?", 1).Find(&objList)
 	engine.Where(" a = ?", 30).Or("b = ?", 1).Find(&objList)
 	
 	// 原生sql语句查询 支持like
 	engine.SQL(" select * from xxx_table where a like '%i%' ").Find(&objList)
 	
 	// orderby
 	engine.OrderBy(" a desc ").Find(&objList)
 	
 	// 特定字段
 	engine.Cols("a", "b").Find(&obj)

更新

 	//更新操作
    xxxUpdate := XxxObj{
        a: "name",
        b:  30,
        c:  1,
    }
    rowNum2, err := engine.Id(1).Cols("xxx","yyy").Update(&personUpdate) //bool类型的更新,有时候不能写入,所以要加上 Cols 强制指定更新的字段。
    fmt.Println(rowNum2) // rowNum2也是表示受影响的行数

    //统计功能count
    count, err := engine.Count(new(XxxObj))
    fmt.Println("persontable表总记录数:",count)

事务操作

    //事务操作
    xxxArray := []XxxObj{
        XxxObj{
            a: "Jack",
            b:  28,
            c:  1,
        },
        XxxObj{
            a: "Mali",
            b:  28,
            c:  1,
        },
        XxxObj{
            a: "Ruby",
            b:  28,
            c:  1,
        },
    }

    session := engine.NewSession()
    session.Begin()
    for i:=0;i<len(xxxArray);i++{
        _, err = session.Insert(xxxArray[i])
        if err != nil {
            session.Rollback()
            session.Close()
        }
    }
    err = session.Commit()
    session.Close()
    if err != nil {
        panic(err.Error())
    }

分页

count, err := models.Engine.Cols("id", "name", "create_time").OrderBy("create_time").Limit(pageSize, skip).FindAndCount(&lists)

参考资料


使用 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 test,可以简单的使用 stdlib os 包,在main函数中直接退出应用程序。

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Println("Hello World")

	// 正常退出
	os.Exit(0)
}


跳出Go module的泥潭 —— colobu.com

转自: https://colobu.com/2018/08/27/learn-go-module/,

参考资料https://roberto.selbach.dev/intro-to-go-modules/

官方的wiki: go/wiki/Modules

最新扩展阅读(go 1.13):Go module 再回顾

Go 1.11 前天已经正式发布了,这个版本包含了两个最重要的feature就是 moduleweb assembly。虽然也有一些简单的教程介绍了go module的特性,但是基本上都是hello world的例子,在实践的过程中, 很多人都在“拼命的挣扎”,包括我自己, 从一些qq群、github的issue, twitter上都可以看到大家茫然或者抱怨的语句。

虽然有三个帮助文件go help modgo help modulesgo help module-get可以了解一些go module的用法,但是感觉Go开发组对module这一特性还是没有很好的做一个全面的介绍,很多情况还得靠大家看源代码或者去猜,比如module下载的文件夹、版本格式的完整声明,module的最佳实践等,并且当前Go 1.11的实现中还有一些bug,给大家在使用的过程中带来了很大的困难。

我也在摸索中前行, 记录了摸索过程中的一些总结,希望能给还在挣扎中的Gopher一些帮助。

Introduction to Go Modules 是一篇很好的go module 入门介绍, 如果你仔细阅读了它,应该就不需要看本文了。

GO111MODULE

要使用go module,首先要设置GO111MODULE=on,这没什么可说的,如果没设置,执行命令的时候会有提示,这个大家应该都了解了。

既有项目

假设你已经有了一个go 项目, 比如在$GOPATH/github.com/smallnest/rpcx下, 你可以使用go mod init github.com/smallnest/rpcx在这个文件夹下创建一个空的go.mod (只有第一行 module github.com/smallnest/rpcx)。

然后你可以通过 go get ./...让它查找依赖,并记录在go.mod文件中(你还可以指定 -tags,这样可以把tags的依赖都查找到)。

通过go mod tidy也可以用来为go.mod增加丢失的依赖,删除不需要的依赖,但是我不确定它怎么处理tags

执行上面的命令会把go.modlatest版本换成实际的最新的版本,并且会生成一个go.sum记录每个依赖库的版本和哈希值。

新的项目

你可以在GOPATH之外创建新的项目。

go mod init packagename可以创建一个空的go.mod,然后你可以在其中增加require github.com/smallnest/rpcx latest依赖,或者像上面一样让go自动发现和维护。

go mod download可以下载所需要的依赖,但是依赖并不是下载到$GOPATH中,而是$GOPATH/pkg/mod中,多个项目可以共享缓存的module。

go mod命令

download    download modules to local cache (下载依赖的module到本地cache))
edit        edit go.mod from tools or scripts (编辑go.mod文件)
graph       print module requirement graph (打印模块依赖图))
init        initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件))
tidy        add missing and remove unused modules (增加丢失的module,去掉未用的module)
vendor      make vendored copy of dependencies (将依赖复制到vendor下)
verify      verify dependencies have expected content (校验依赖)
why         explain why packages or modules are needed (解释为什么需要依赖)

有些命令还有bug, 比如go mod download -dir:

go mod download -dir /tmp
flag provided but not defined: -dir
usage: go mod download [-dir] [-json] [modules]
Run 'go help mod download' for details.

帮助里明明说可以设置dir,但是实际却不支持dir参数。

看这些命令的帮助已经比较容易了解命令的功能。

翻墙

在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。

replace (
	golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
	golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
	golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)

依赖库中的replace对你的主go.mod不起作用,比如github.com/smallnest/rpcxgo.mod已经增加了replace,但是你的go.mod虽然requirerpcx的库,但是没有设置replace的话, go get还是会访问golang.org/x

所以如果想编译那个项目,就在哪个项目中增加replace

版本格式

下面的版本都是合法的:

gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
gopkg.in/yaml.v2 <=v2.2.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
latest

go get 升级

  • 运行 go get -u 将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号)
  • 运行 go get -u=patch 将会升级到最新的修订版本
  • 运行 go get package@version 将会升级到指定的版本号version

go mod vendor

go mod vendor 会复制modules下载到vendor中, 貌似只会下载你代码中引用的库,而不是go.mod中定义全部的module。

go module, vendor 和 Travis CI

https://arslan.io/2018/08/26/using-go-modules-with-vendor-support-on-travis-ci/