阿巩
期待同大家一起学习和交流~
在上一章中阿巩和大家分享了k8s组件之一kube-apiserver,在我自己阅读代码时发现k8s整体结构复杂,而且由于参与的开发者众多代码结构不免有些混乱,我往往容易陷入到某个细节而无法从整体视角梳理流程。在查阅官网文档及相关书籍后,我决定换个思路,先理解k8s核心数据结构设计,这样能够在阅读源码时做到事半功倍。好的,日拱一卒,我们开始吧!
K8s系统虽然功能众多且复杂,但它本质上是一个资源控制系统,即资源是k8s最重要的概念,它包括注册、管理、调度资源并维护资源状态。k8s将资源细分为三种数据结构,分别是:Group(资源组)、Version(资源版本)、Resource(资源)。Kind(资源种类)与Resource同级,用来描述资源的种类。
k8s系统支持多个 Group,每个 Group 支持多个 Version,每个 Version 支持多个 Resource,其中部分资源同时会拥有自己的子资源 SubResource。一个资源对象的表现形式为:
<group>/<version>, Kind=<kind>
例如 apps/v1,Kind=Deployment。
对每个资源都可以进行一系列操作即 Verbs,Verbs 对 Etcd 集群存储中的资源对象做增、删、改、查等操作。k8s资源分为两种:Kubernetes Resource 内置资源和Custom Resource 自定义资源,自定义资源可以通过CRD实现。
每个资源至少有两个版本,External Version 外部版本用于对外暴露给用户请求的接口所使用的资源对象。Internal Version 内部版本不对外暴露,仅在 Kubernetes API Server 内部使用。
资源列表:ResourceList
type APIResourceList struct {
TypeMeta `json:",inline"`
// groupVersion is the group and version this APIResourceList is for.
GroupVersion string `json:"groupVersion" protobuf:"bytes,1,opt,name=groupVersion"`
// resources contains the name of the resources and if they are namespaced.
APIResources []APIResource `json:"resources" protobuf:"bytes,2,rep,name=resources"`
}
我们可以通过APIResourceList数据结构描述所有Group、Version、Resource的结构,以最常用的Pod、Service、Deployment资源为例,APIResourceList Example代码示例如下:
pkg/controller/namespace/deletion/namespaced_resources_deleter_test.go 367
func testResources() []*metav1.APIResourceList {
results := []*metav1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []metav1.APIResource{
{
Name: "pods",
Namespaced: true,
Kind: "Pod",
Verbs: []string{"get", "list", "delete", "deletecollection", "create", "update"},
},
{
Name: "services",
Namespaced: true,
Kind: "Service",
Verbs: []string{"get", "list", "delete", "deletecollection", "create", "update"},
},
},
},
{
GroupVersion: "apps/v1",
APIResources: []metav1.APIResource{
{
Name: "deployments",
Namespaced: true,
Kind: "Deployment",
Verbs: []string{"get", "list", "delete", "deletecollection", "create", "update"},
},
},
},
}
return results
}
每个资源都可以使用metav1.APIResource结构进行描述,它描述资源的基本信息包括:资源名称(Name)、资源所属命名空间(NameSpace)、资源种类(Kind)、资源可操作方法列表(Verbs)。每个资源都属于一个或多个版本,通过metav1.APIVersion结构描述,并使用Versions []string字符串数组进行存储。
示例中的GroupVersion是一个字符串,在资源同时资源版本和资源组时,被设置为<group>/<version>;资源不存在资源组时,被设置为/<version>。
对于一个资源,用来明确标识它的资源组名称、资源版本和资源名称的结构称为GVR,即GroupVersionResource结构体,代码路径在:
vendor/k8s.io/apimachinery/pkg/runtime/schema/group_version.go 96
type GroupVersionResource struct {
Group string
Version string
Resource string
}
//以deployment为例:
type GroupVersionResource{
Group:"apps",
Version:"v1",
Resource:"Deployment"
}
Group、Version、Resource核心数据结构详情如下图:
资源组Group
type APIGroup struct {
TypeMeta `json:",inline"`
// name is the name of the group.
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// versions are the versions supported in this group.
Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"`
// 首选版本
PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty" protobuf:"bytes,3,opt,name=preferredVersion"`
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
资源版本Version
type APIVersions struct {
TypeMeta `json:",inline"`
Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
}
Kubernetes的资源版本控制可分为3种,分别是Alpha、Beta、Stable,它们之间的迭代顺序为Alpha→Beta→Stable,其通常用来表示软件测试过程中的3个阶段。
资源Resource
一个资源被实例化后会表达为一个资源对象(Resource Object),所有资源对象都是实体(Entity),k8s使用这些Entity来表示当前状态。通过kube-apiserver进行查询和更新每一个资源对象。k8s支持两种Entity:
type APIResource struct {
// 资源名称
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// 资源的单数名称,例如pods的单数是pod
SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"`
// 是否有命名空间
Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
// 所在资源组名称
Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"`
// 所在资源版本
Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"`
// 资源种类
Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
// 资源操作方法
Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
// 资源简称
ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
// 该资源所属的分组资源列表
Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"`
}
资源版本(如v1beta1、v1等)与外部版本/内部版本概念不同。外部版本用于对外暴露给用户请求的接口所使用的资源对象,例如,用户在通过YAML或JSON格式的描述文件创建资源对象时,所使用的是外部版本的资源对象;内部版本用于多资源版本的转换,例如将v1beta1版本转换为v1版本。
即拥有资源版本的资源属于外部版本,拥有runtime.APIVersionInternal标识的资源属于内部版本。以资源pod为例:
type Pod struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
type Pod struct {
metav1.TypeMeta
metav1.ObjectMeta
Spec PodSpec
Status PodStatus
}
通过register.go代码文件定义所属的资源组和资源版本时两者的区别:
// 内部版本
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
// 外部版本
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
在每个k8s资源组目录中都有install/install.go代码文件,它负责将资源信息注册到资源注册表(Scheme)中,以core核心资源组为例:
func init() {
Install(legacyscheme.Scheme)
}
// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
utilruntime.Must(core.AddToScheme(scheme))
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion))
}
资源操作方法及接口说明:
kubectl delete pods --all
kubectl delete pod -l $key=$value
通用资源类型runtime.Object
runtime.Object为Interface接口类型,作为资源对象的通用资源对象,这里我们可以把资源对象理解为Go中的Struct类型,它们都实现了runtime.Object中提供的两个方法,分别是GetObjectKind和DeepCopyObject。
Scheme资源注册表
我们都听说过Windows上的注册表,k8s中的资源注册表和它类似,只不过注册的是资源类型,包括内部和外部版本。
Scheme资源注册表通过Go语言的map结构实现映射关系,这些映射关系可以实现高效的正向和反向检索,从Scheme资源注册表中检索某个GVK的Type,它的时间复杂度为O(1)。
Scheme资源注册表数据结构主要由4个map结构组成:
(UnversionedType早期Kubernetes系统中的概念,目前几乎所有的资源对象都拥有版本,即KnownType。但在metav1元数据中还有部分类型如:metav1.Status、 metav1.APIVersions、metav1.APIGroupList、metav1.APIGroup、metav1.APIResourceList)
对于不同的资源类型注册方式也不同,如 AddKnownTypes 注册有版本资源;AddKnownTypeWithName 带着名字注册有版本资源;AddUnversionedTypes 注册无版本资源。以AddKnownTypes,在注册资源类型时,无须指定Kind名称,而是通过reflect机制获取资源类型的名称作为资源种类名称,代码示例如下:
func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
s.addObservedVersion(gv)
for _, obj := range types {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Ptr {
panic("All types must be pointers to structs.")
}
t = t.Elem()
s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
}
}
对于查询注册表可以使用kube-apiserver组件下提供的”scheme.资源类型“的方法查询。
文章最后附上k8s Project Layout结构图:
参考:
《kubernetes源码剖析》
https://blog.51cto.com/daixuan/4976182
END