天天看點

Kubernetes ClientsetKubernetes Clientset

Kubernetes Clientset

  • Kubernetes Clientset
    • 資源類型 Scheme
      • types.go 檔案
      • zz_generated.deepcopy.go 檔案
      • register.go 檔案
    • 注冊所有内置資源類型到 Scheme 對象
    • 建立和使用 Kubernetes Clientset
    • 建立支援所有資源類型的全局 Clientset
    • 各資源類型的 Clientset
    • 各資源類型的 RestFul 方法
    • 使用資源類型的 Clientset 建立 Informer 和 Lister
    • 使用 codegen 工具生成資源類型的 Clientset、Informer 和 Lister
    • 參考

資源類型 Scheme

Clienset 和 apiserver 通信時,需要根據資源對象的類型生成 Resource URL、對 Wire-data 進行編解碼(序列化/反序列化)。

資源類型的 Group、Version、Kind、go struct 定義、編解碼(序列化/反序列化) 等内容構成了它的

Scheme

K8S 内置資源類型的 Scheme 位于

k8s.io/api/<group>/<version>

目錄下,以

Deployment

為例:

$ pwd
/Users/zhangjun/go/src/gitlab.4pd.io/pht3/aol/vendor/k8s.io/api/extensions/v1beta1

$ ls -l
total 1048
-rw-r--r--  1 zhangjun  staff     642 Jan 22 15:16 doc.go
-rw-r--r--  1 zhangjun  staff  308747 Jan 22 15:16 generated.pb.go
-rw-r--r--  1 zhangjun  staff   49734 Jan 22 15:16 generated.proto
-rw-r--r--  1 zhangjun  staff    2042 Jan 22 15:16 register.go
-rw-r--r--  1 zhangjun  staff   69022 Jan 23 22:30 types.go
-rw-r--r--  1 zhangjun  staff   47996 Jan 22 15:16 types_swagger_doc_generated.go
-rw-r--r--  1 zhangjun  staff   41555 Jan 22 15:16 zz_generated.deepcopy.go
           

可以暫時忽略無關的檔案,我們主要分析

types.go

zz_generated.deepcopy.go

register.go

三個檔案。

  1. types.go:定義本

    <group>/<version>

    下所有的資源類型和 codegen 注釋;
  2. zz_generated.deepcopy.go:

    deepcopy-gen

    工具建立的、定義各資源類型

    DeepCopyObject()

    方法的檔案;
  3. register.go:定義了

    AddToScheme()

    函數,用于将本

    <group>/<version>

    下的各資源類型注冊到 Clientset 使用的 Scheme 對象中(k8s.io/client-go/kubernetes/scheme/);
  4. 對于自定義資源類型,需要在 doc.go 中添加注釋 // +groupName=<group_name>,否則後續自動生成的 fake clientset 的 Group 不對;

types.go 檔案

該檔案包含資源類型的 go struct 定義及 codegen 指令行工具使用的注釋:

// 來源于:k8s.io/api/extensions/v1beta1/types.go

// +genclient
// +genclient:method=GetScale,verb=get,subresource=scale,result=Scale
// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for
// more information.
// Deployment enables declarative updates for Pods and ReplicaSets.
type Deployment struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object metadata.
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Specification of the desired behavior of the Deployment.
	// +optional
	Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

	// Most recently observed status of the Deployment.
	// +optional
	Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

// DeploymentSpec is the specification of the desired behavior of the Deployment.
type DeploymentSpec struct {
	Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"`
	Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
	Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"`
	Strategy DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys" protobuf:"bytes,4,opt,name=strategy"`
	MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"`
	RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"`
	Paused bool `json:"paused,omitempty" protobuf:"varint,7,opt,name=paused"`
	RollbackTo *RollbackConfig `json:"rollbackTo,omitempty" protobuf:"bytes,8,opt,name=rollbackTo"`
	ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"`
}
           

zz_generated.deepcopy.go 檔案

所有注冊到 Scheme 的資源類型都要實作

runtime.Object

接口:

// 來源于:k8s.io/apimachinery/pkg/runtime/interfaces.go
type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}
           

K8S 各資源類型的 go struct 定義都嵌入了

metav1.TypeMeta

類型,而該類型實作了

GetObjectKind()

方法,故各資源類型隻需要實作

DeepCopyObject()

方法。

我們不需要手動為各資源類型定義

DeepCopyObject()

方法,而是使用

deepcopy-gen

工具指令統一、自動地生成該方法。

deepcopy-gen 工具讀取

types.go

檔案中的

+k8s:deepcopy-gen

注釋,如:

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
           

然後将生成的結果儲存到

zz_generated.deepcopy.go

檔案。

Deployment

類型為例:

// 來源于:k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Deployment.
func (in *Deployment) DeepCopy() *Deployment {
	if in == nil {
		return nil
	}
	out := new(Deployment)
	in.DeepCopyInto(out)
	return out
}

// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Deployment) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}
           

register.go 檔案

使用

deepcopy-gen

工具指令統一、自動地為各資源類型生成

DeepCopyObject()

方法後,各資源類型就滿足了

runtime.Object

接口,進而可以注冊到 Scheme 中。

各 API Group/Version 目錄下都有一個

register.go

檔案,該檔案對外提供的

AddToScheme()

方法用于将本 Group/Version 下的各資源類型注冊到傳入的 Scheme 對象中(k8s.io/client-go/kubernetes/scheme/register.go 中建立該 Scheme 對象),然後 Clientset 就可以使用它進行 Wired-data 和對象之間的轉換了。

// 來源于:k8s.io/api/extensions/v1beta1/register.go
// 本 package 的 Group 名稱
const GroupName = "extensions"

// 注冊時提供的 Group/Version 資訊
// 一個 Group 目錄下,有可能有多個 Version 的子目錄
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}

// Resource 實際就是資源類型的完整路徑 <Group>/<Version>/<Plural>,如 extensions/v1beta1/deployments
// Plural 是資源類型的複數形式
func Resource(resource string) schema.GroupResource {
	return SchemeGroupVersion.WithResource(resource).GroupResource()
}

var (
	SchemeBuilder      = runtime.NewSchemeBuilder(addKnownTypes)
	localSchemeBuilder = &SchemeBuilder
	// 對外暴露的 AddToScheme() 方法用于注冊該 Group/Verion 下的所有資源類型
	AddToScheme        = localSchemeBuilder.AddToScheme
)

// 将本 Group/Version 下的所有資源類型注冊到傳入的 scheme
func addKnownTypes(scheme *runtime.Scheme) error {
	scheme.AddKnownTypes(SchemeGroupVersion,
		&Deployment{},
		&DeploymentList{},
		...
	)
	// Add the watch version that applies
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}
           

注冊所有内置資源類型到 Scheme 對象

需要将

k8s.io/api/<group>/<version>

目錄下的各資源類型注冊到全局 Scheme 對象,這樣 Clienset 才能識别和使用它們。

client-go 的

scheme package

(k8s.io/client-go/kubernetes/scheme/)定義了這個全局

Scheme

對象,并将各

k8s.io/api/<Group>/<Version>

目錄下的資源類型注冊到它上面。

Scheme 對象還被用于建立另外兩個外部對象:

  1. 對資源類型對象進行編解碼(序列化/反序列化)的工廠對象

    Codecs

    ,後續使用它配置

    rest.Config.NegotiatedSerializer

  2. 參數編解碼對象

    ParameterCodec

    ,後續調用 RestFul 的方法時使用,如

    VersionedParams(&options, scheme.ParameterCodec)

// 來源于 k8s.io/client-go/kubernetes/scheme/register.go
// 建立一個 Scheme,後續所有 K8S 類型均添加到該 Scheme;
var Scheme = runtime.NewScheme()
// 為 Scheme 中的所有類型建立一個編解碼工廠;
var Codecs = serializer.NewCodecFactory(Scheme)
// 為 Scheme 中的所有類型建立一個參數編解碼工廠
var ParameterCodec = runtime.NewParameterCodec(Scheme)

// 将各 `k8s.io/api/<Group>/<Version>` 目錄下的資源類型的 AddToScheme() 方法注冊到 SchemeBuilder 中
var localSchemeBuilder = runtime.SchemeBuilder{
    ...
	extensionsv1beta1.AddToScheme,
    ...
}
var AddToScheme = localSchemeBuilder.AddToScheme

func init() {
    v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
	// 調用 SchemeBuilder 中各資源對象的 AddToScheme() 方法,将它們注冊到到 Scheme 對象。
	utilruntime.Must(AddToScheme(Scheme))
}
           

建立和使用 Kubernetes Clientset

經過前面的鋪墊分析後,我們開始分析 Kubernetes Clientset 的建立過程。

先從使用者的角度看看如何建立和使用 Kubernetes Clientset:

var err error
var config *rest.Config
// 使用 ServiceAccount 建立叢集配置
if config, err = rest.InClusterConfig(); err != nil {
	// 使用 kubeConfig 指向的配置檔案建立叢集配置
	if config, err = clientcmd.BuildConfigFromFlags("", *kubeConfig); err != nil {
		panic(err.Error())
	}
}

// 建立 k8s clientset
clientset, err = kubernetes.NewForConfig(config)
if err != nil {
	panic(err.Error())
}

// 使用 clienset 建立一個 Deploy
deploy, err := c.kubeclientset.ExtensionsV1beta1().Deployments(aolDeploy.ObjectMeta.Namespace).Create(myDeploy)
           
  1. 使用 Kubeconfig 檔案或 ServiceAccount 建立 Kubernetes 的 RestFul 配置參數;
  2. 使用 Kubernetes 的 RestFul 配置參數,建立 Clientset;
  3. 調用 Clientset 的方法對資源對象進行 CRUD;

建立支援所有資源類型的全局 Clientset

k8s.io/client-go/kubernetes/clientset.go

檔案中建立的 Clientset 實際上是對各資源類型的 Clientset 做了一次封裝:

  1. 調用各資源類型的 NewForConfig() 函數建立對應的 Clientset;
  2. 後續可以使用 Clientset.(),如 Clientset.ExtensionsV1beta1() 來調用具體資源類型的 Clientset;
// 來源于 k8s.io/client-go/kubernetes/clientset.go
// 傳入的 rest.Config 包含 apiserver 伺服器和認證資訊
func NewForConfig(c *rest.Config) (*Clientset, error) {
	configShallowCopy := *c
    ...
    // 透傳 rest.Config,調用具體分組和版本的資源類型的 ClientSet 構造函數
	cs.extensionsV1beta1, err = extensionsv1beta1.NewForConfig(&configShallowCopy)
	if err != nil {
		return nil, err
	}
    ...
	cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
	if err != nil {
		return nil, err
	}
	return &cs, nil
}

// ExtensionsV1beta1 retrieves the ExtensionsV1beta1Client
func (c *Clientset) ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface {
	return c.extensionsV1beta1
}
           

各資源類型的 Clientset

各資源類型的 Clientset 定義位于

k8s.io/client-go/kubernetes/typed/<group>/<version>/<plug>_client.go

檔案中,如

k8s.io/client-go/kubernetes/typed/extensions/v1beta1/extensions_client.go

比較關鍵的是

setConfigDefaults()

函數,它負責為 Clientset 配置參數:

  1. 資源對象的 GroupVersion;
  2. 資源對象的 root path;
  3. 對 wired data 進行編解碼(序列化/反序列化)的

    NegotiatedSerializer

    ,使用的

    scheme.Codecs

    為前面介紹過的

    scheme package

RESTClient 根據配置的 root path 和 GroupVersion,構造 Resource 位址(格式為

/apis/<group>/<version>/<name>

)。

// 來源于 k8s.io/client-go/kubernetes/typed/extensions/v1beta1/extensions_client.go
// 傳入的 rest.Config 包含 apiserver 伺服器和認證資訊
func NewForConfig(c *rest.Config) (*ExtensionsV1beta1Client, error) {
    config := *c
    // 為 rest.Config 設定資源對象相關的參數
	if err := setConfigDefaults(&config); err != nil {
		return nil, err
    }
    // 建立 ExtensionsV1beta1 的 RestClient
	client, err := rest.RESTClientFor(&config)
	if err != nil {
		return nil, err
	}
	return &ExtensionsV1beta1Client{client}, nil
}

func setConfigDefaults(config *rest.Config) error {
    // 資源對象的 GroupVersion
	gv := v1beta1.SchemeGroupVersion
    config.GroupVersion = &gv
    // 資源對象的 root path
    config.APIPath = "/apis"
    // 使用注冊的資源類型 Schema 對請求和響應進行編解碼
    // scheme 為前面分析過的 k8s.io/client-go/kubernetes/scheme package
	config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}

	if config.UserAgent == "" {
		config.UserAgent = rest.DefaultKubernetesUserAgent()
	}

	return nil
}

func (c *ExtensionsV1beta1Client) Deployments(namespace string) DeploymentInterface {
	return newDeployments(c, namespace)
}
           

各資源類型的 RestFul 方法

使用各資源類型的 Clientset 建立特定資源類型的 RestFul 方法,參數的編解碼工廠

scheme.ParameterCodec

來源于前面介紹的

scheme package

中。

// 來源于 k8s.io/client-go/kubernetes/typed/extensions/v1beta1/deployment.go
// newDeployments returns a Deployments
func newDeployments(c *ExtensionsV1beta1Client, namespace string) *deployments {
	return &deployments{
		client: c.RESTClient(),
		ns:     namespace,
	}
}

// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any.
func (c *deployments) Get(name string, options v1.GetOptions) (result *v1beta1.Deployment, err error) {
	result = &v1beta1.Deployment{}
	// 發起實際的 RestFul 請求;
	err = c.client.Get().
		Namespace(c.ns).
		Resource("deployments").
		Name(name).
		VersionedParams(&options, scheme.ParameterCodec).
		Do().
		Into(result)
	return
}
           

使用資源類型的 Clientset 建立 Informer 和 Lister

使用 codegen 工具生成資源類型的 Clientset、Informer 和 Lister

目錄結構:

zhangjun:core zhangjun$ pwd
/Users/zhangjun/go/src/gitlab.4pd.io/pht3/aol/pkg/apis/core/v1alpha1

zhangjun:v1alpha1 zhangjun$ ls -l
total 976
-rw-r--r--  1 zhangjun  staff     643 Jan 28 21:14 doc.go
-rw-r--r--  1 zhangjun  staff    1200 Jan 28 21:37 objectreference.go
-rw-r--r--  1 zhangjun  staff    2741 Jan 28 21:39 register.go
-rw-r--r--  1 zhangjun  staff  273720 Jan 28 21:40 types.go
-rw-r--r--  1 zhangjun  staff  154116 Jan 28 21:41 zz_generated.deepcopy.go
           

doc.go

檔案裡添加

// +k8s:deepcopy-gen=package

,否則後續不會為

types.go

中的類型生成

DeepCopy()

方法:

// 來源于 doc.go

// +k8s:deepcopy-gen=package
// +k8s:openapi-gen=true

package v1alpha1
           

參考

Kubernetes Deep Dive: Code Generation for CustomResources

繼續閱讀