【学习client-go源码,了解管理kubernetes集群的奥秘】—— 连接集群

学习源码最头疼的是找到入口开始学习,刚开始学习client-go源码的时候,感觉无从下手,后面参考了很多博客,慢慢摸索,从连接k8s集群开始,逐步展开,就可以轻松读懂client-go源码。当然,在学习源码的过程中,也要学习源码的编码风格,学习源码的编程思路,对自己的编程能力也有很大的提高,开始第一篇文章,找到client-go的入口,连接k8s集群。

一、获取源码

1、第一种,在github上面获取如图,直接搜索client-go就可以找到,然后将源码clone到本地就可以了。

在Github中下载client-go

2、第二种执行下面的命令即可。
go  get  k8s.io/client-go

二、探秘

管理k8s的第一步先要连接集群,因此我们先找到client-go源码的入口。 在Github官网上,我们可以找到官方给的example程序。

1、在README中我们可以看到目录中有一个How to use it,点击链接。

How to use it

2、这里有两种选择,第一种是你的程序运行在一个pod中,另一种是你的程序运行在集群外,这里我选择第二种。

example
点击后可以看到下面的界面,里面有一个main.go,这就是我们要找到的入口了。
main.go
点击main.go就可以看到一个示例程序。

package main

import (
	"context"
	"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()

	// use the current context in kubeconfig
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// create the clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	for {
		pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
		if err != nil {
			panic(err.Error())
		}
		fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

		// Examples for error handling:
		// - Use helper functions like e.g. errors.IsNotFound()
		// - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
		namespace := "default"
		pod := "example-xxxxx"
		_, err = clientset.CoreV1().Pods(namespace).Get(context.TODO(), 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、分析代码,找到入口
在示例代码中有两个函数我们先从main函数开始分析。

(1) main函数中先定义了一个字符串指针变量kubeconfig,接着是调用homeDir函数获取当前家目录,并判断家目录是否为空,如果为空则kubeconfig也为空,不为空则kubeconfig为k8s配置文件目录。从这里看出,client-go应该是通过config文件连接k8s集群的。

(2)接着调用了BuildConfigFromFlags("", *kubeconfig)函数。我们看一下BuildConfigFromFlags函数做了什么。

// BuildConfigFromFlags is a helper function that builds configs from a master
// url or a kubeconfig filepath. These are passed in as command line flags for cluster
// components. Warnings should reflect this usage. If neither masterUrl or kubeconfigPath
// are passed in we fallback to inClusterConfig. If inClusterConfig fails, we fallback
// to the default config.
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
	if kubeconfigPath == "" && masterUrl == "" {
		klog.Warningf("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")
		kubeconfig, err := restclient.InClusterConfig()
		if err == nil {
			return kubeconfig, nil
		}
		klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
	}
	return NewNonInteractiveDeferredLoadingClientConfig(
		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}

从函数的注释可以看出他的主要功能是根据config文件或者masterUrl构建一个Config结构体,该结构体包含了,集群的主要配置信息,原来除了通过配置文件的方式连接k8s集群外,还可以通过masterurl连接集群,官方比较推荐的是通过config文件连接集群。感兴趣的可以看一下restclient.Config,这里不做赘述。

(3)接着调用kubernetes.NewForConfig(config),通过config结构体与k8s集群建立连接。
(4)最后是一个每十秒采集一次pod信息的for循环,在例子中有两个需要注意的点。

第一个是获取集群中的所有pod:

pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
if err != nil {
	panic(err.Error())
}
fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

从上面的第一行代码就可以看出,这行代码的意思是列出集群中的pod,List方法有两个参数,第一个是context上下文,这个可以使用context.TODO生成一个空的上下文,第二个参数,相当于k8s中的Selector,在metav1.ListOptions中你可以进行定义。另外Pods方法中有一个参数是用来筛选NameSpace的,这个Pods方法调用的是newPods:

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

可以看到nowPods返回的是一个pods类型的指针,pods中有两个参数一个是命名空间,一个是连接k8s集群的指针client。
接下来我们看List方法的源码:

// List takes label and field selectors, and returns the list of Pods that match those selectors.
func (c *pods) List(ctx context.Context, 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(ctx).
		Into(result)
	return
}

可以看到List调用了六个方法,我们来逐一分析:

// Get begins a GET request. Short for c.Verb("GET").
func (c *RESTClient) Get() *Request {
	return c.Verb("GET")
}

Get方法调用Verb方法返回一个Request对象,我们看一下Verb方法:

func (c *RESTClient) Verb(verb string) *Request {
	return NewRequest(c).Verb(verb)
}
// NewRequest creates a new request helper object for accessing runtime.Objects on a server.
func NewRequest(c *RESTClient) *Request {
	var backoff BackoffManager
	if c.createBackoffMgr != nil {
		backoff = c.createBackoffMgr()
	}
	if backoff == nil {
		backoff = noBackoff
	}

	var pathPrefix string
	if c.base != nil {
		pathPrefix = path.Join("/", c.base.Path, c.versionedAPIPath)
	} else {
		pathPrefix = path.Join("/", c.versionedAPIPath)
	}

	var timeout time.Duration
	if c.Client != nil {
		timeout = c.Client.Timeout
	}

	r := &Request{
		c:           c,
		rateLimiter: c.rateLimiter,
		backoff:     backoff,
		timeout:     timeout,
		pathPrefix:  pathPrefix,
		maxRetries:  10,
	}

	switch {
	case len(c.content.AcceptContentTypes) > 0:
		r.SetHeader("Accept", c.content.AcceptContentTypes)
	case len(c.content.ContentType) > 0:
		r.SetHeader("Accept", c.content.ContentType+", */*")
	}
	return r
}

我们可以看到Get方法返回的Request对象其实是NewRequest方法构建的连接API接口的请求参数。
接着看Namespace方法:

// Namespace applies the namespace scope to a request (<resource>/[ns/<namespace>/]<name>)
func (r *Request) Namespace(namespace string) *Request {
	if r.err != nil {
		return r
	}
	if r.namespaceSet {
		r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace)
		return r
	}
	if msgs := IsValidPathSegmentName(namespace); len(msgs) != 0 {
		r.err = fmt.Errorf("invalid namespace %q: %v", namespace, msgs)
		return r
	}
	r.namespaceSet = true
	r.namespace = namespace
	return r
}

这里其实是编辑API请求中的命名空间参数,因此我们可以猜出Resource、VersionParams、Timeout、Do都是用来设置请求参数的,Into则返回请求结果。

第二个需要注意的点是,在源码的pod.go文件中有一个PodInterface接口,在这个接口中定义了pod进行增删改查的方法,也是我们需要关注的点,通过这些方法,我们就可以轻松的管理k8s集群中的pod了。

// PodInterface has methods to work with Pod resources.
type PodInterface interface {
	Create(ctx context.Context, pod *v1.Pod, opts metav1.CreateOptions) (*v1.Pod, error)
	Update(ctx context.Context, pod *v1.Pod, opts metav1.UpdateOptions) (*v1.Pod, error)
	UpdateStatus(ctx context.Context, pod *v1.Pod, opts metav1.UpdateOptions) (*v1.Pod, error)
	Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error
	DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error
	Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Pod, error)
	List(ctx context.Context, opts metav1.ListOptions) (*v1.PodList, error)
	Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error)
	Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Pod, err error)
	GetEphemeralContainers(ctx context.Context, podName string, options metav1.GetOptions) (*v1.EphemeralContainers, error)
	UpdateEphemeralContainers(ctx context.Context, podName string, ephemeralContainers *v1.EphemeralContainers, opts metav1.UpdateOptions) (*v1.EphemeralContainers, error)

	PodExpansion
}

从eample中我们可以看到与k8s建立连接的步骤如下:

初始化
config文件
生成restclient.Config
根据配置生成Clientset
管理集群pod

在后面的博客中我会和大家一起学习如何使用client-go管理k8s集群的pod和node,希对你有所帮助。

猜你喜欢

转载自blog.csdn.net/random_w/article/details/105286038