在上文中对k8s中自定义资源进行了介绍,同时也介绍了如何使用client-go通过非结构化的数据类型来访问自定义资源。使用非结构化的数据类型并不是十分友好,因此本文将介绍如何使用结构化的数据结构来访问自定义资源
准备
参考如何使用CustomResourceDefinition介绍的方式,在k8s上创建自定义资源,同时创建一个实例,以方便下文使用
使用结构化数据访问自定义资源
简介
对k8s的资源访问都是通过k8s的API Server来进行的,client-go就是对API Server的一层封装,也就是说client-go
通过访问API Server来获取数据,并将获取到的数据解析成结构化的数据类型,如Pod
、Node
等,方便用户使用。对于自定义资源类型,我们也可以通过访问API Server来获取到相关数据,参考如下步骤
建立本地到API Server的映射
kubectl proxy
访问
http://127.0.0.1:8001/apis/test.wuming.com/v1/namespaces/default/crontabs
即可获取到自定义资源的相关数据
从结果中可以看出,所获取到的数据为json
结构,将其按照特定的格式进行解析便可获取到结构化的数据类型了
构建结构化的数据类型
创建文件
api\types\v1\crontab.go
,该文件主要就是将获取到的json
数据解析成结构化的数据package v1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
type CrontabSpec struct {
Replicas int `json:"replicas"`
Spec string `json:"spec"`
Image string `json:"Image"`
}
//+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type CronTab struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CrontabSpec `json:"spec"`
}
//+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type CronTabList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CronTab `json:"items"`
}实现接口
k8s.io/apimachinery/pkg/runtime.Object
, 该接口是所有API类型都要实现的,主要是考虑到对资源的修改需要写回到API Server,详细信息可参考文档2。该接口可通过工具controller-gen自动实现,我们只需给类型加上注释+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
# Install controller-gen
go get -u github.com/kubernetes-sigs/controller-tools/cmd/controller-gen
# Implement interface runtime.Object automatically
controller-gen object paths=.\api\types\v1\crontab.go创建文件
api\types\v1\register.go
,该文件主要用于将上面定义的数据类型注册到client-go
,之后便可使用client-go
对获取到的CronTab
类型进行解析。通过方法v1.AddToScheme(scheme.Scheme)
完成注册package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
const GroupName = "test.wuming.com"
const GroupVersion = "v1"
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&CronTab{},
&CronTabList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
构建对API Server的访问接口
创建文件
clientset\v1\api.go
,该文件主要用于对k8s配置文件进行解析,创建访问API Server的客户端package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
v1 "main.go/api/types/v1"
)
type CrontabV1Interface interface {
Crontabs(namespace string) CronTabInterface
}
type CrontabV1Client struct {
restClient rest.Interface
}
func NewForConfig(c *rest.Config) (*CrontabV1Client, error) {
config := *c
config.ContentConfig.GroupVersion = &schema.GroupVersion{Group: v1.GroupName, Version: v1.GroupVersion}
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
config.UserAgent = rest.DefaultKubernetesUserAgent()
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &CrontabV1Client{restClient: client}, nil
}
func (c *CrontabV1Client) Crontabs(namespace string) CronTabInterface {
return &CronTabClient{
restClient: c.restClient,
ns: namespace,
}
}创建文件
clientset\v1\crontabs.go
,该文件主要用于访问API Server,并将返回的数据解析成已定义的结构化数据类型package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
v1 "main.go/api/types/v1"
)
type CronTabInterface interface {
List(opts metav1.ListOptions) (*v1.CronTabList, error)
Get(name string, options metav1.GetOptions) (*v1.CronTab, error)
}
type CronTabClient struct {
restClient rest.Interface
ns string
}
func (c *CronTabClient) List(opts metav1.ListOptions) (*v1.CronTabList, error) {
result := v1.CronTabList{}
err := c.restClient.
Get().
Namespace(c.ns).
Resource("crontabs").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(&result)
return &result, err
}
func (c *CronTabClient) Get(name string, opts metav1.GetOptions) (*v1.CronTab, error) {
result := v1.CronTab{}
err := c.restClient.
Get().
Namespace(c.ns).
Resource("crontabs").
Name(name).
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(&result)
return &result, err
}
构建主程序
上述程序完成了自定义资源的结构化解析,以及访问API Server获取到相关的数据,接下来的程序将使用已创建好的接口来完成对自定义资源的访问,同时所获取到的资源信息将以结构化的数据类型呈现
package main
import (
"flag"
"fmt"
"os"
v1 "main.go/api/types/v1"
clientV1 "main.go/clientset/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func buildConfig() (*rest.Config, error) {
config, err := rest.InClusterConfig()
if err == nil {
return config, nil
}
var kubeconfig = flag.String("kubeconfig", "C:/Users/wumin/.kube/config", "kubeconfig file")
flag.Parse()
return clientcmd.BuildConfigFromFlags("", *kubeconfig)
}
func main() {
config, err := buildConfig()
if err != nil {
fmt.Printf("The kubeconfig cannot be loaded: %v\n", err)
os.Exit(1)
}
v1.AddToScheme(scheme.Scheme)
clientSet, err := clientV1.NewForConfig(config)
if err != nil {
panic(err)
}
crontabs, err := clientSet.Crontabs("default").List(metav1.ListOptions{})
if err != nil {
panic(err)
}
for _, crontab := range crontabs.Items {
item, err := clientSet.Crontabs("default").Get(crontab.Name, metav1.GetOptions{})
if err != nil {
panic(err)
}
fmt.Printf("crontab : %+v\n", item)
}
fmt.Printf("crontabs found: %+v\n", crontabs)
}
结果如下:
Q&A
Q:直接写代码访问API Server获取到所需数据,之后将获取到的数据结构化不也可以吗?为什么还要使用
client-go
,按照它的规范来实现?A: 访问API Server是需要证书密钥等信息的,这些信息配置在k8s的config文件中,直接码代码访问API Server首先需要对该文件进行解析,之后才能对API Server进行访问。而这个工作已经在
client-go
中做好了,使用client-go
可以减少该部分工作量。当然如果想自己写代码访问API Server也是可以的
参考
- https://www.martin-helmich.de/en/blog/kubernetes-crd-client.html
- https://pkg.go.dev/k8s.io/apimachinery@v0.17.0/pkg/runtime?tab=doc#Object