[k8s源码分析][client-go] client之clientset

1. 前言

转载请说明原文出处, 尊重他人劳动成果!

源码位置: https://github.com/nicktming/client-go/tree/tming-v13.0/tools/cache
分支: tming-v13.0 (基于v13.0版本)

2. 概括

client-go中提供了三种client可以去访问api-server中的资源.
clientset: 提供集群外部的服务用来访问, 只能访问集群中已有的资源, 对于自定义资源crd无法访问. (除非自己编写相关方法)
dynamic: 提供集群外部的服务用来访问, 可以访问第三方资源crd, 无须再添加方法.
In-cluster: 用于集群里面的pod访问集群资源.

不过这三种client都依赖rest.

3. clientset

3.1 例子

import (
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "time"

    "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/clientcmd"
)

func main() {
    var kubeconfig *string
    if home := homeDir(); home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    } else {
        kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    }
    flag.Parse()
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err.Error())
    }
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }
    for {
        pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
        if err != nil {
            panic(err.Error())
        }
        fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))
        namespace := "default"
        pod := "example-xxxxx"
        _, err = clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{})
        if errors.IsNotFound(err) {
            fmt.Printf("Pod %s in namespace %s not found\n", pod, namespace)
        } else if statusError, isStatus := err.(*errors.StatusError); isStatus {
            fmt.Printf("Error getting pod %s in namespace %s: %v\n",
                pod, namespace, statusError.ErrStatus.Message)
        } else if err != nil {
            panic(err.Error())
        } else {
            fmt.Printf("Found pod %s in namespace %s\n", pod, namespace)
        }
        time.Sleep(10 * time.Second)
    }
}
func homeDir() string {
    if h := os.Getenv("HOME"); h != "" {
        return h
    }
    return os.Getenv("USERPROFILE") // windows
}

3.2 分析

接口与实现类
type Interface interface {
    Discovery() discovery.DiscoveryInterface
    ...
    CoreV1() corev1.CoreV1Interface
    ...
}

Interface接口定义了获得k8s中所有资源的方法, 比如获得CoreV1中资源的可以调用该接口的CoreV1()方法.

type Clientset struct {
    *discovery.DiscoveryClient
    ...
    coreV1                       *corev1.CoreV1Client
    ...
}

Clientset结构体是Interface的实现类. 既然是实现类, 那需要实现该Interface中的每一个方法, 接下来看看它的CoreV1方法是如何实现的.

CoreV1方法
func (c *Clientset) CoreV1() corev1.CoreV1Interface {
    return c.coreV1
}

可以看到该实现方法非常简单, 直接返回该Clientset对象的coreV1属性. 那就说明该属性在哪里初始化过了, 接下来看一下Clientset的生成方法, 有三种方法:

1. New(c rest.Interface)传入的是一个可以操作api-serverrest接口实现类, 前面说过, 三种client本质上都是依赖rest接口去操作api-server的.
2. NewForConfigOrDie(c *rest.Config)传入的是一个可以生成rest接口实现类的配置, 根据该配置自己生成一个rest接口的实现类. 但是中途生成rest client过程中出现错误, 程序直接panic退出.
3. NewForConfig(c *rest.Config)NewForConfigOrDie基本一样, 只是出现错误时返回错误并不会退出程序.

NewForConfig为例:

func NewForConfig(c *rest.Config) (*Clientset, error) {
    configShallowCopy := *c
    if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
        if configShallowCopy.Burst <= 0 {
            return nil, fmt.Errorf("Burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
        }
        configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
    }
    var cs Clientset
    var err error
    ...
    cs.coreV1, err = corev1.NewForConfig(&configShallowCopy)
    if err != nil {
        return nil, err
    }
    ...
    return &cs, nil
}

可以看到调用corev1.NewForConfig生成cs.coreV1, 如下所示:

// kubernetes/typed/core/v1/core_client.go
func NewForConfig(c *rest.Config) (*CoreV1Client, error) {
    config := *c
    if err := setConfigDefaults(&config); err != nil {
        return nil, err
    }
    client, err := rest.RESTClientFor(&config)
    if err != nil {
        return nil, err
    }
    return &CoreV1Client{client}, nil
}

根据config配置生成一个与api-server打交道的rest client, 进而由CoreV1Client包装起来. 现在很清晰了cs.corev1 = &CoreV1Client{client}.

CoreV1Client

接下来看一下CoreV1Client的结构是如何的.

// kubernetes/typed/core/v1/core_client.go
type CoreV1Interface interface {
    RESTClient() rest.Interface
    ComponentStatusesGetter
    ConfigMapsGetter
    EndpointsGetter
    EventsGetter
    LimitRangesGetter
    NamespacesGetter
    NodesGetter
    PersistentVolumesGetter
    PersistentVolumeClaimsGetter
    PodsGetter
    PodTemplatesGetter
    ReplicationControllersGetter
    ResourceQuotasGetter
    SecretsGetter
    ServicesGetter
    ServiceAccountsGetter
}
type CoreV1Client struct {
    restClient rest.Interface
}

可以看到CoreV1Interface定义了corev1下所有资源的获得接口. 而CoreV1Client就是该接口的实现类. 看一个pods方法

// kubernetes/typed/core/v1/core_client.go
func (c *CoreV1Client) Pods(namespace string) PodInterface {
    return newPods(c, namespace)
}

可见Pods方法返回一个可以操作podPodInterface. 该PodInterface接口如下所示:

// kubernetes/typed/core/v1/pod.go
type PodInterface interface {
    Create(*v1.Pod) (*v1.Pod, error)
    Update(*v1.Pod) (*v1.Pod, error)
    UpdateStatus(*v1.Pod) (*v1.Pod, error)
    Delete(name string, options *metav1.DeleteOptions) error
    DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
    Get(name string, options metav1.GetOptions) (*v1.Pod, error)
    List(opts metav1.ListOptions) (*v1.PodList, error)
    Watch(opts metav1.ListOptions) (watch.Interface, error)
    Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error)
    GetEphemeralContainers(podName string, options metav1.GetOptions) (*v1.EphemeralContainers, error)
    UpdateEphemeralContainers(podName string, ephemeralContainers *v1.EphemeralContainers) (*v1.EphemeralContainers, error)

    PodExpansion
}

type pods struct {
    client rest.Interface
    ns     string
}

// newPods returns a Pods
func newPods(c *CoreV1Client, namespace string) *pods {
    return &pods{
        client: c.RESTClient(),
        ns:     namespace,
    }
}

可以看到pods结构体就是PodInterface的实现类, pods的一个对象表示可以操作该ns这个namespace下面的所有pods. 可以看一下list方法.

// kubernetes/typed/core/v1/pod.go
func (c *pods) List(opts metav1.ListOptions) (result *v1.PodList, err error) {
    var timeout time.Duration
    if opts.TimeoutSeconds != nil {
        timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
    }
    result = &v1.PodList{}
    err = c.client.Get().
        Namespace(c.ns).
        Resource("pods").
        VersionedParams(&opts, scheme.ParameterCodec).
        Timeout(timeout).
        Do().
        Into(result)
    return
}

调用rest clientapi-server中获得该namespace下所有的pods.

4. 总结

flow.png

整体的调用关系如上图所示.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容