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
三个文件。
<group>/<version>
下所有的资源类型和 codegen 注释;deepcopy-gen
工具创建的、定义各资源类型 DeepCopyObject()
方法的文件;AddToScheme()
函数,用于将本 <group>/<version>
下的各资源类型注册到 Clientset 使用的 Scheme 对象中(k8s.io/client-go/kubernetes/scheme/);该文件包含资源类型的 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"`
}
所有注册到 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
}
使用 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
}
需要将 k8s.io/api/<group>/<version>
目录下的各资源类型注册到全局 Scheme 对象,这样 Clienset 才能识别和使用它们。
client-go 的 scheme package
(k8s.io/client-go/kubernetes/scheme/)定义了这个全局 Scheme
对象,并将各 k8s.io/api/<Group>/<Version>
目录下的资源类型注册到它上面。
Scheme 对象还被用于创建另外两个外部对象:
Codecs
,后续使用它配置 rest.Config.NegotiatedSerializer
;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:
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)
k8s.io/client-go/kubernetes/clientset.go
文件中创建的 Clientset 实际上是对各资源类型的 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 定义位于 k8s.io/client-go/kubernetes/typed/<group>/<version>/<plug>_client.go
文件中,如
k8s.io/client-go/kubernetes/typed/extensions/v1beta1/extensions_client.go
。
比较关键的是 setConfigDefaults()
函数,它负责为 Clientset 配置参数:
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)
}
使用各资源类型的 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
}
目录结构:
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