【k8s源码阅读】核心数据结构

k8s的本质是一个资源控制系统,即围绕着资源做注册、管理、调度等操作来维护资源状态。但是因为资源系统的复杂性,k8s把资源进行分组和版本化,产生了Group(资源组)、Version(资源版本)、Resource(资源)的数据结构。同时,为了描述资源种类,也引入了Kind,这和Resource是同一级别的。

资源的管理是层级递进的。资源组、资源版本、资源和子资源的完成形式是目录形式来进行定位的,即:

sql 复制代码
Group/Version/Resource/SubResource

比如说常用的Deployment资源,它的完整表现形式就是apps/v1/deployments/status

另外,资源对象也是一个常见的概念,这里指的是经过实例化之后的资源 ,它通过资源组+资源版本+资源种类(Group/Version, Kind) 进行限制。比如Deployment资源,在实例化之后,表现形式就是 apps/v1,Kind=Deployments

每一种资源都有一定数量的操作方法,资源操作方法会用于Etcd集群中对资源进行增删改查。常见的8种操作方法是create、delete、deletecollection、get、list、patch、update、watch

每一种资源都有一定的版本,至少有两个,一个是外部版本,一个是内部版本。外部版本用于对外暴露请求的接口所用的资源对象,内部版本只在api server内部使用。

资源从定义上也可以分为两个,一个是内置资源,一个是Custom Resources(用户自定义资源)。开发者可以通过CRD实现自定义资源,并且把资源添加到系统中使用。

一、资源大图

获取内置资源的主要方式有两种:

  • kubectl api-versions:列出当前支持的资源组和资源版本
  • kubectl api-resources:列出当前支持的Resource资源列表

下表列举了一些常用的资源类型,完整请看官方文档:

kubernetes.io/zh-cn/docs/...

二、资源元信息

k8s的核心数据结构放在 vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go 里面,首先先看一下描述:

golang 复制代码
// Package v1 contains API types that are common to all versions.
// 包 v1 包含所有版本通用的 API 类型。
// The package contains two categories of types:
//   - external (serialized) types that lack their own version (e.g TypeMeta)
//   - internal (never-serialized) types that are needed by several different
//     api groups, and so live here, to avoid duplication and/or import loops
//     (e.g. LabelSelector).
//
// In the future, we will probably move these categories of objects into
// separate packages.
package v1

API版本控制

REST API 是 Kubernetes 的基础架构。组件之间的所有操作和通信,以及外部用户命令都是 API Server 处理的 REST API 调用。因此,Kubernetes 平台中的所有资源被视为 API 对象,并且在 API 中都有对应的定义项。

TypeMeta用来表示API请求和响应当中的元信息,包含了资源类型和资源版本。

当然,每一个资源都可能属于一个或者多个资源版本,资源所属的版本会通过APIVersions来描述,通过Versions []string字段来进行存储,它列举了资源所有支持的版本,并且暴露给clients。

我们也可以通过GroupVersion字段来描述资源组和版本,这个是一个字符串,被设置为Group/Version。当资源不存在资源组的时候,会被设置为/Version

另外,也可以用GroupVersionResource(GVR)字段来没有歧义地明确表示一个资源的资源组、版本号以及名称。

golang 复制代码
type GroupVersionResource struct {
    Group    string
    Version  string
    Resource string
}

vendor/k8s.io/apimachinery/pkg/runtime/schema/ 中定义了常用的资源数据结构

ObjectMeta

ObjectMeta 是所有持久化资源必须具有的元数据,其中包括用户必须创建的所有对象。

存放位置:vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go:111

golang 复制代码
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
    //name 在命名空间内必须是唯一的。创建资源时需要,尽管某些资源可能允许客户端请求自动地生成适当的名称。 名称主要用于创建幂等性和配置定义。无法更新。
    Name string 

    //generateName 是一个可选前缀,由服务器使用,仅在未提供 name 字段时生成唯一名称。 
    GenerateName string 

    //namespace 定义了一个值空间,其中每个名称必须唯一。空命名空间相当于 "default" 命名空间,但 "default" 是规范表示。
    Namespace string 

    // 可用于组织和分类(确定范围和选择)对象的字符串键和值的映射。 可以匹配 ReplicationController 和 Service 的选择算符。
    Labels map[string]string 

    // annotations 是一个非结构化的键值映射,存储在资源中,可以由外部工具设置以存储和检索任意元数据。 它们不可查询,在修改对象时应保留。
    Annotations map[string]string 

    //ResourceVersion是同名、同类型对象,同时间下唯一
    //因为同名对象在不同时间可能会更新、删除再添加
    //用于比较两个对象谁比较新的情况
    ResourceVersion string
    ........
}

参考文章:blog.csdn.net/qq_24433609...

三、Resource

在整个体系架构中,资源是k8s最重要的概念。一个资源被实例化之后会表达为一个资源对象。在k8s系统中定义了许多资源对象,所有的资源对象都是Entity。可以通过api-server来进行查询和更新每一个资源对象。

  • 持久性实体(Persistent Entity):在资源对象被创建后,Kubernetes会持久确保该资源对象存在。大部分资源对象属于持久性实体,例如deployment资源对象。
  • 短暂性实体(Ephemeral Entity):也可称其为非持久性实体(Non-Peprsistent Entity)。在资源对象被创建后,如果出现故障或调度失败,不会重新创建该资源对象,例如Pod资源对象。

存放位置:vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go

在k8s中,每个资源都可以用metav1.APIResource来进行描述,它描述了资源的基本信息:

  • Name:资源名称
  • Namespaced:bool值,是否被分配到了某个ns中
  • Group:group 是资源的首选组。 空表示包含资源列表的组。
  • Version:version 是资源的首选版本。
  • Kind:资源种类
  • Verbs:[]string类型,定义资源的可操作方法

资源定义

k8s的资源定义代码在pkg/apis的目录下面,同一个资源对应着内部版本和外部版本。

资源的内部版本定义了所支持的资源类型(types.go)、资源验证方法(validation.go)、资源注册至资源注册表的方法(install/install.go)等。而资源的外部版本定义了资源的转换方法(conversion.go)、资源的默认值(defaults.go)等。

以Pod资源为例,它的版本定义在pkg/apis/core/下面。

内部版本的资源代码结构说明如下

  • doc.go:GoDoc文件,定义了当前包的注释信息。在Kubernetes资源包中,它还担当了代码生成器的全局Tags描述文件。
  • register.go:定义了资源组、资源版本及资源的注册信息。
  • types.go:定义了在当前资源组、资源版本下所支持的资源类型。
  • v1、vlbeta1、v1beta2:定义了资源组下拥有的资源版本的资源(即外部版本)。
  • install:把当前资源组下的所有资源注册到资源注册表中。
  • validation:定义了资源的验证方法。
  • zz_generated.deepcopy.go:定义了资源的深复制操作,该文件P由代码生成器自动生成。

每一个资源目录都会通过registry定义所属的资源组和资源版本,内部版本资源对象通过runtime.APIVersionInternal标识。

go 复制代码
// GroupName is the group name use in this package
const GroupName = ""

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}

在每一个资源目录下面,都通过type代码文件定义当前的资源组/资源版本下面支持的资源类型。

以Pod资源为例,它的外部资源定义在pkg/apis/core/v1/下面,项目结构如下:

  • conversion.go:定义了资源的转换函数(默认转换函数),并将默认转换函数注册到资源注册表中。
  • zz_generated.conversion.go:定义了资源的转换函数(自动生成的转换函数),并将生成的转换函数注册到资源注册表中。该文件由代码生成器自动生成。
  • defaults.go:定义了资源的默认值函数,并将默认值函数注册到资源注册表中。
  • zz_generated.defaults.go:定义了资源的默认值函数(自动生成的默认值函数),并将生成的默认值函数注册到资源注册表中。该文件由代码生成器自动生成。

外部版本和内部版本资源类型是相通的,外部版本会通过资源版本来进行标识

go 复制代码
// GroupName is the group name use in this package
const GroupName = ""

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}

// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
    return SchemeGroupVersion.WithResource(resource).GroupResource()
}

在这里我们也可以看到Pod的GroupName = "" ,这个是因为k8s支持有组名的资源组和没有组名的资源组。没有组名的资源组,被称为CoreGroups(即核心资源组)或LegacyGroups,也可被称为GroupLess(即无组)。其表现形式为/<version>/<ressource>,例如/v1/pods

资源注册

注册指的是把资源注册到资源注册表中。在每一个资源组目录下面,都拥有一个install/install.go代码文件,负责注册资源信息到注册表 (scheme)

github.com/kubernetes/...

go 复制代码
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))
}

legacyscheme.Scheme是kube-apiserver组件的全局资源注册表,Kubernetes的所有资源信息都交给资源注册表统一管理。core.AddToScheme函数注册core资源组内部版本的资源。v1.AddToScheme函数注册 core资源组外部版本的资源。scheme.SetVersionPriority函数注册资源组的版本顺序,如有多个资源版本,排在最前面的为资源首选版本。

目前,k8s所有资源类型都已经注册到scheme资源注册表中,这是一个内存型的注册表:

  • 支持注册多种资源类型,包括内部版本和外部版本。
  • 支持多种版本转换机制。
  • 支持不同资源的序列化/反序列化机制。

scheme支持两种资源类型的注册,分别是有版本资源和无版本资源。其中,无版本资源的资源对象并不需要进行转换(但是这个类型已经被弱化了)

在Scheme资源注册表中,UnversionedType资源类型的为对象通过scheme.AddUnversionedTypes方法进行注册,KnownType资源类型的对象通过scheme.AddKnownTypes方法进行注册。

在运行过程中,kube-apiserver组件常对Scheme资源注册表进行行查询,它提供了如下方法。

  • scheme.KnownTypes:查询注册表中指定GV下的资源类型。
  • scheme.AllKnownTypes:查询注册表中所有GVK下的资源类型。
  • scheme.ObjectKinds:查询资源对象所对应的GVK,一个资源对象可能存在多个GVK。
  • scheme.New:查询GVK所对应的资源对象。
  • scheme.IsGroupRegistered:判断指定的资源组是否已经注册。
  • scheme.IsVersionRegistered:判断指定的GV是否已经注册。
  • scheme.Recognizes:判断指定的GVK是否已经注册。
  • scheme.IsUnversioned:判断指定的资源对象是否属于UnversionedType类型。

scheme在基于 k8s 做二次开发的项目中还是一个非常常用的组件❗️

首选版本

metav1.APIResource引入了一个首选版本的概念,现在做一下解答。一个资源组可能会有多个版本,比如我们可以看到apps资源组下面就有三个版本。那当我们使用apps资源组下的某个资源,比如Deployment的时候,在一些场景下不指定资源版本,就使用首选版本。

版本的定义我们可以在install里面看到,首选版本的存在也说明scheme.SetVersionPriority的版本注册顺序是很重要的。

github.com/kubernetes/...

go 复制代码
func init() {
    Install(legacyscheme.Scheme)
}

// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
    utilruntime.Must(apps.AddToScheme(scheme))
    utilruntime.Must(v1beta1.AddToScheme(scheme))
    utilruntime.Must(v1beta2.AddToScheme(scheme))
    utilruntime.Must(v1.AddToScheme(scheme))
    utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
}

再通过注册表scheme.PreferredVersionAllGroups函数获取所有资源组下面的首选版本的时候,会把位于最前面的资源版本作为首选版本

go 复制代码
func (s *Scheme) PreferredVersionAllGroups() []schema.GroupVersion {
    ret := []schema.GroupVersion{}
    for group, versions := range s.versionPriority {
       for _, version := range versions {
          ret = append(ret, schema.GroupVersion{Group: group, Version: version})
          break
       }
    }
    ....
  }

资源操作方法

通过metav1.Verbs可以实现对每一个资源支持的操作方法来进行定义。比如对于Pod资源对象,可以通过kubectl命令行工具对其执行creatte、delete、get等操作。Kubernetes系统所支持的操作方法目前有8种操作,分别是create、delete、deletecollection、get、list、patch、update、watch。这些操作方法可分为四大类,分别属于增、删、改、查,对资源进行创建、删除、更新和子查询。

如何了解一个资源对象拥有哪些可操作的方法呢?需要查看与存储相关联的源码包registry,其定义在vendor/k8s.io/apiserver/pkg/registry/目录下。每种操作方法对应一个操作方法接口。

以get、create操作方法为例,rest.Getter接口定义了Get方法,rest.Creater接口定义了New和Create方法。如果某个资源对象在存储(Storage)上实现了Get、New及Create方法,就可以认为该资源对象同时拥有了get和create操作方法。

vendor/k8s.io/apiserver/pkg/registry/rest/rest.go

go 复制代码
// Getter is an object that can retrieve a named RESTful resource.
type Getter interface {
    // Get finds a resource in the storage by name and returns it.
    // Although it can return an arbitrary error value, IsNotFound(err) is true for the
    // returned error value err when the specified resource is not found.
    Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error)
}

// Creater is an object that can create an instance of a RESTful object.
type Creater interface {
    // New returns an empty object that can be used with Create after request data has been put into it.
    // This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
    New() runtime.Object

    // Create creates a new version of a resource.
    Create(ctx context.Context, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
}

以Pod资源对象为例,Pod资源对象的存储(Storage)实现了以上接口的方法,Pod资源对象继承了genericregistry.Store,该对象可以管理存储(Sttorage)的增、删、改、查操作,代码示例如下:

github.com/kubernetes/...

golang 复制代码
// PodStorage includes storage for pods and all sub resources
type PodStorage struct {
    Pod                 *REST
    Binding             *BindingREST
    LegacyBinding       *LegacyBindingREST
    Eviction            *EvictionREST
    Status              *StatusREST
    EphemeralContainers *EphemeralContainersREST
    Log                 *podrest.LogREST
    Proxy               *podrest.ProxyREST
    Exec                *podrest.ExecREST
    Attach              *podrest.AttachREST
    PortForward         *podrest.PortForwardREST
}

// Create ensures a pod is bound to a specific host.
func (r *LegacyBindingREST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (out runtime.Object, err error) {
    metadata, err := meta.Accessor(obj)
    if err != nil {
       return nil, errors.NewBadRequest(fmt.Sprintf("not a Binding object: %T", obj))
    }
    return r.bindingRest.Create(ctx, metadata.GetName(), obj, createValidation, options)
}

// REST implements a RESTStorage for pods
type REST struct {
    *genericregistry.Store
    proxyTransport http.RoundTripper
}

在这里我们还需要明确一个概念,资源对象的操作方法与存储相关联,增删改查实际上都是针对于存储相关的操作。所以具体去看资源支持的操作类型,可以重点关注pkg/registry

以pod/logs子资源对象为例,该资源对象只实现了get操作方法,代码示例如下:

github.com/kubernetes/...

golang 复制代码
// LogREST implements the log endpoint for a Pod
type LogREST struct {
    KubeletConn client.ConnectionInfoGetter
    Store       *genericregistry.Store
}

// Get retrieves a runtime.Object that will stream the contents of the pod log
func (r *LogREST) Get(ctx context.Context, name string, opts runtime.Object) (runtime.Object, error) {
    ...
}

命名空间

每个命名空间相当于一个虚拟集群了,不同命名空间之间可以进行隔离,当然也可以通过某种方式跨命名空间通信。将Kubernetes系统划分为3个环境,分别是pro生产环境、test测试环境及dev开发环境,它们之间相互隔离,admin管理员用户对3个环境竟都拥有权限,而dev作为开发者只对dev开发环境拥有权限。

Kubernetes系统中默认内置了4个命名空间

  • default:所有未指定命名空间的资源对象都会被分配给该命名空间。
  • kube-system:所有由Kubernetes系统创建的资源对象都会被分配纪给该命名空间。
  • kube-public:此命名空间下的资源对象可以被所有人访问(包括未认证用户)。
  • kube-node-lease:此命名空间下存放来自节点的心跳记录(节点租约信息)。

描述某个资源对象属于某个命名空间,就要用到之前说到的metav1.ObjectMeta,比如pod资源

github.com/kubernetes/...

golang 复制代码
// Pod is a collection of containers, used as either input (create, update) or as output (list, get).
type Pod struct {
    metav1.TypeMeta
    // +optional 其中有ns信息
    metav1.ObjectMeta

    // Spec defines the behavior of a pod.
    // +optional
    Spec PodSpec

    // Status represents the current information about a pod. This data may not be up
    // to date.
    // +optional
    Status PodStatus
}

查看哪些k8s资源对象属于命名空间可以用以下命令

ini 复制代码
kubectl api-resources --namespaced=true|false

自定义资源&资源描述

Custom Resource支持把自定义资源加入到系统里面。目前使用的是CRD的方式。

一个资源对象需要用5个字段来描述它,分别是Group/Version、Kind、MetaData、Spec、Status。这些字段定义在YAML或JSON文件中。Kubeernetes系统中的所有的资源对象都可以采用YAML或JSON格式的描述文件来定义,下面是某个Pod文件的资源对象描述文件。

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
  - name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  - name: log-aggregator
    image: images.my-company.example/log-aggregator:v6
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
  • apiVersion:指定创建资源对象的资源组和资源版本,其表现现形式为<group>/<version>,若是core资源组(即核心资源组)下的资油原对象,其表现形式为<version>

  • kind:指定创建资源对象的种类。

  • metadata:描述创建资源对象的元数据信息,例如名称、命名空间等。(对应ObjectMeta

  • spec:对应PodSpec,包含有关Deployment资源对象的核心信息,告诉Kuberneetes期望的资源状态、副本数量、环境变量、卷等信息。

    • 地址:vendor/k8s.io/api/core/v1/types.go:3516
  • status:对应PodStatus,包含有关正在运行的Deployment资源对象的信息。

    • 地址:vendor/k8s.io/api/core/v1/types.go:4451

四、运行时

参考资料:blog.csdn.net/qq_24433609...

以资源对象Pod为例,该资源对象可以转换成runtime.Object通用资源对象,也可以从runtime.Object通用资源对象转换成Pod资源对象。runtime.Object结构如下:

go 复制代码
type Object interface {
    GetObjectKind() schema.ObjectKind
    DeepCopyObject() Object
}

需要注意的是,一般DeepCopyObject由代码生成,在生成文件当中。

所以,Kubernetes的任意资源对象都可以通过runtime.Object存储它的类型并允许深复制操作。通过runtime.Object Example代码示例,可以将资源对象转换成通用资源对象并再次转换回资源对象。runtime.Object Example代码示例如下:

go 复制代码
pod := &corev1.Pod{
    TypeMeta :.......
}
obj := runtime.Object(pod)
pod2, ok := obj.(*core.Pod)
if !ok {
    panic()
}
相关推荐
条纹布鲁斯1 小时前
dockerdsktop修改安装路径/k8s部署wordpress和ubuntu
docker·kubernetes
登云时刻4 小时前
Kubernetes集群外连接redis集群和使用redis-shake工具迁移数据(一)
redis·kubernetes·bootstrap
吴半杯5 小时前
gateway漏洞(CVE-2022-22947)
docker·kubernetes·gateway
灼烧的疯狂9 小时前
K8S + Jenkins 做CICD
容器·kubernetes·jenkins
wenyue112110 小时前
Revolutionize Your Kubernetes Experience with Easegress: Kubernetes Gateway API
容器·kubernetes·gateway
Python私教13 小时前
ubuntu搭建k8s环境详细教程
linux·ubuntu·kubernetes
O&REO14 小时前
单机部署kubernetes环境下Overleaf-基于MicroK8s的Overleaf应用部署指南
云原生·容器·kubernetes
politeboy14 小时前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
运维小文15 小时前
K8S资源限制之LimitRange
云原生·容器·kubernetes·k8s资源限制
登云时刻15 小时前
Kubernetes集群外连接redis集群和使用redis-shake工具迁移数据(二)
redis·容器·kubernetes