介绍 Kubernetes 开发中用到的不同类型的 Client
1. 概述
在设计 Kubernetes 的控制器时,我们需要通过 client 对 Kubernetes 的原生资源或者自定义资源执行一些 CURD 操作。
使用 Golang 进行开发时,kubernetes 官方提供了不同类型的 Client 以应对不同场景,本文以下内容一一介绍这些 Client。
2. Client
1. REST Client
RESTClient 是 k8s.io/client-go 中用于和 kube-apiserver 进行 restful 交互的基础类,client-go 中其他类型的 Client 类都是基于该类型的二次封装。
RESTClient 中封装了 golang 的 http.Client 对象,但是设计它并非纯粹为了发送 HTTP 请求,同时还具备以下功能:
- 序列化和反序列化
- 封装 GVR、GVK、Schema 之间的映射关系,即自动填写访问指定资源的URL
- 处理 HTTP 连接的超时、重试等机制
- 封装 Watch、Stream 接口
以下是使用 REST Client 获取命名空间中所有 Pod 的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
package main
import (
"context"
corev1 "k8s.io/api/core/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"
"k8s.io/klog/v2"
"time"
)
func main() {
kubeconfig := "./kubeconfig"
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
klog.Fatalf("Error building kubernetes clientset: %s", err.Error())
}
gv := corev1.SchemeGroupVersion
config.GroupVersion = &gv
// 只有 k8s.io/api/core/v1 包中的 APIPath 指定为 /api,其余所有 Resource 的 APIPath 均为 /apis
config.APIPath = "/api"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() // 指定序列化类型
config.UserAgent = rest.DefaultKubernetesUserAgent()
coreV1Cli, err := rest.RESTClientFor(config)
if err != nil {
klog.Fatalf("Error building rest client: %s", err.Error())
}
pods := &corev1.PodList{}
opts := &metav1.ListOptions{}
if err := coreV1Cli.Get().
Resource("pods").
Namespace("rccp").
VersionedParams(opts, scheme.ParameterCodec).
Timeout(time.Second * 60).
Do(context.Background()).
Into(pods); err != nil {
klog.Fatalf("Error list pod: %s", err.Error())
}
for _, pod := range pods.Items {
klog.Info(pod.Name)
}
}
|
2. ClientSet
ClientSet 是进行控制器开发时最常用的 Client。无论是 k8s.io/client-go 或者自定义项目,ClientSet 的代码通常都是使用 k8s.io/code-generator 自动生成的,其本质上是对 rest.RESTClient 的二次封装。
从上一小节可以看到,创建 RESTClient 时我们需要手工指定 SchemeGroupVersion 、APIPath 、NegotiatedSerializer 等参数,发送请求时还需要通过 Resource 函数手工指定资源名称。
ClientSet 为我们屏蔽了这些细节,用户只需要针对指定 GVR 调用 GET/LIST/CREATE/DELETE/UPDATE 方法即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
type Interface interface {
Discovery() discovery.DiscoveryInterface
SamplecontrollerV1alpha1() samplecontrollerv1alpha1.SamplecontrollerV1alpha1Interface
}
type SamplecontrollerV1alpha1Interface interface {
RESTClient() rest.Interface
FoosGetter
}
// FoosGetter has a method to return a FooInterface.
// A group's client should implement this interface.
type FoosGetter interface {
Foos(namespace string) FooInterface
}
// FooInterface has methods to work with Foo resources.
type FooInterface interface {
Create(ctx context.Context, foo *v1alpha1.Foo, opts v1.CreateOptions) (*v1alpha1.Foo, error)
Update(ctx context.Context, foo *v1alpha1.Foo, opts v1.UpdateOptions) (*v1alpha1.Foo, error)
UpdateStatus(ctx context.Context, foo *v1alpha1.Foo, opts v1.UpdateOptions) (*v1alpha1.Foo, error)
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Foo, error)
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.FooList, error)
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Foo, err error)
FooExpansion
}
|
上述接口是 k8s.io/sample-controller 项目自定义类型的 ClientSet 接口,其中 SamplecontrollerV1alpha1Interface 包含了 samplecontroller/v1alpha1
中所有类型的 CURD 接口。
SamplecontrollerV1alpha1Interface 的实现类包含了一个 rest.RESTClient 对象,并且通过 NewForConfig 和 setConfigDefaults 接口为 rest.RESTClient 执行了必要的初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
// SamplecontrollerV1alpha1Client is used to interact with features provided by the samplecontroller.k8s.io group.
type SamplecontrollerV1alpha1Client struct {
restClient rest.Interface
}
// NewForConfig creates a new SamplecontrollerV1alpha1Client for the given config.
func NewForConfig(c *rest.Config) (*SamplecontrollerV1alpha1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &SamplecontrollerV1alpha1Client{client}, nil
}
func setConfigDefaults(config *rest.Config) error {
gv := v1alpha1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
|
3. Dynamic Client
Dynamic Client 是一种类型无关的 Client,定义在 k8s.io/client-go/dynamic
包中,只处理 Unstructured 类型的对象。
Unstructured 类型是 json.Unmarshal 的包装类,使用 map[string]interface{}
类型保存 Object,使用 []]interface{}
保存数组:
1
2
3
4
5
6
|
type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{}
// children.
Object map[string]interface{}
}
|
k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
定义了大量 Unstructured 的辅助函数,用户可以通过以下接口快速获取 json.Unmarshal 中的指定属性:
1
2
3
4
5
6
7
8
9
10
11
|
func NestedFieldCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error)
func NestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error)
func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error)
func NestedMap(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error)
func nestedMapNoCopy(obj map[string]interface{}, fields ...string) (map[string]interface{}, bool, error)
...
|
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
package main
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2"
)
func main() {
kubeconfig := "./kubeconfig"
master := ""
config, err := clientcmd.BuildConfigFromFlags(master, kubeconfig)
if err != nil {
klog.Fatalf("Error building kubernetes clientset: %s", err.Error())
}
dyClient := dynamic.NewForConfigOrDie(config)
podGVR := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "pods",
}
pods, err := dyClient.Resource(podGVR).Namespace("rccp").List(context.Background(), metav1.ListOptions{})
if err != nil {
klog.Fatalf("Error get pods: %s", err.Error())
}
for _, pod := range pods.Items {
name := pod.GetName()
podIp, _, _ := unstructured.NestedString(pod.Object, "status", "podIP")
klog.Infof("Pod %-60s : %s", name, podIp)
}
}
|
client-go 同样提供了 Unstructured 类型的 Informer 和 Lister ,使用例子参考 sample/dynamic
4. 其他 Client
除了上述 kubernetes 项目提供的原生客户端,sigs.k8s.io/controller-runtime 包中也提供了不同类型的 client。
sigs.k8s.io/controller-runtime 是 Kubebuilder 和 Operator SDK 的基础依赖,该项目提供了大量开发 controller 时的便利接口。
参考:sigs.k8s.io/controller-runtime
参考