【k8s基础篇】k8s scheme3 之序列化

本文深入解析Kubernetes中的Scheme机制,探讨如何通过GVK实现不同版本API数据结构映射,并介绍反序列化过程,包括内容编码、模型构建和版本转换。重点讲解了如何通过Scheme解决多版本API的兼容性问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考

利用 Scheme 反序列化的理解

0 | 前置知识 —— restful 访问 k8s 资源

  • 集群作用域的资源:

    • GET /apis/GROUP/VERSION/RESOURCETYPE - 返回指定资源类型的资源的集合
    • GET /apis/GROUP/VERSION/RESOURCETYPE/NAME - 返回指定资源类型下名称为 NAME 的资源
  • 名字空间作用域的资源:

    • GET /apis/GROUP/VERSION/RESOURCETYPE - 返回所有名字空间中指定资源类型的全部实例的集合
    • GET /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE - 返回名字空间 NAMESPACE 内给定资源类型的全部实例的集合
    • GET /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME - 返回名字空间 NAMESPACE 中给定资源类型的名称为 NAME 的实例
  • 某些资源类型有一个或多个子资源(Sub-resource),表现为对应资源下面的子路径:

    • 集群作用域的子资源:GET /apis/GROUP/VERSION/RESOURCETYPE/NAME/SUBRESOURCE
    • 名字空间作用域的子资源:`GET /apis/GROUP/VERSION/namespaces/NAMESPACE/RESOURCETYPE/NAME/SUBRESOURCE
  • 例子

    • 列举给定名字空间中的所有 Pods:

    • GET /api/v1/namespaces/test/pods
      ---
      200 OK
      Content-Type: application/json
      {
        "kind": "PodList",
        "apiVersion": "v1",
        "metadata": {"resourceVersion":"10245"},
        "items": [...]
      }
      
    • 从访问的 url GET /api/v1/namespaces/test/pods 即可得知资源的 GVK(core(核心组忽略显示,就是这么规定的),v1,pod)

    • 可以看出发起请求,相应给出了相关 Pod 信息,而其信息的存储是通过相应的数据结构存储的

      • 不难看出,不同资源就会有不同的数据存储结构
      • 那么如何从 url 获取的 GVK 获取到响应的数据信息呢?就是如何将 GVK 与 响应的资源数据结构做对应呢
        • 答:Scheme ,下面将进行详细讲解

1 | 为什么需要 Scheme

  • 因为在web开发中随着版本的更新迭代,通常要在系统中维护多个版本的api,多个版本的api在数据结构上往往也各不相同
  • 为了解决上述问题 —— 出现了 Scheme —— 实现 GVK 与 api数据结构的对应

2 | web 请求的处理流程

使用kubernetes中的Scheme机制进行反序列化的操作

  1. 收到请求后,通常首先是webServer先进行Http协议的处理
    • 解析成基础的webServer内部的一个Http请求对象
    • 该 Http 请求对象持有对应请求的请求头和底层对应的字节序列(从socket流中读取)
  2. 接着根据对应的编码格式来进行反序列化
    • 完成从字节序列到当前接口的业务模型的映射, 然后在交给业务逻辑处理
    • 从而最终进行持久化存储, 本文的重点也就在反序列化部分

3 | 模型映射的实现

3.1 | 描述资源版本信息

/api/{version}/{resource}/{action}
  • 上面是一个基础的web url通常我们都会为每个版本注册一个对应的URL
    • 其中会包含很关键的两个信息即version与resource
    • 通过这两个信息,通常我们就可以知道这可能是某个资源的那个版本
    • 如果我们把后面的action也包裹进来,我们通常就可以知道对应的资源的那个具体操作

3.2 | Group 组信息

使用kubernetes中的Scheme机制进行反序列化的操作

在微服务流行的今天我们通常会为按照业务功能来进行微服务的切分,本质上一个微服务可能就是实现某个具体业务场景的功能集合,比如用户系统通常会包含用户的所有相关操作,在kubernetes中也有类似的概念就是所谓的Group

POST /apis/batch/v1beta1/namespaces/{namespace}/cronjobs
POST /apis/apps/v1/namespaces/{namespace}/daemonsets

我们来看一个实例这是一个创建daemonsets和cronjobs的url, 如果按照Group、resource、version来进行拆分可以拆成如下:batch、v1beta1、cronjobs和apps、v1、daemonsets,也就是大家尝试的GroupVersionKind,其中kind对应的就是resource

3.3 | 模型映射的实现

使用kubernetes中的Scheme机制进行反序列化的操作

  • 我们通过url里面获取到资源的GroupVersionKind信息,如何将其映射为一个具体的类型呢?
    • 这貌似就很简单了结合反射和map来进行就可以了,我们通过url获取到对应想的GVK信息,然后在通过我们的映射表,就知道对应的模型是哪个,接下来就只需要进行转换就行了
gvkToType map[schema.GroupVersionKind]reflect.Type

4 | 反序列化实现

4.1 | 解码机制

  • 那如何将对应的Http里面的数据流反序列化成内部的一个对象呢?
    • 别忘记了是Http协议, 肯定就是header头里面的信息了
    • 通过header头里面的序列化就可以知道对应的编码格式,只需要调用对应格式的解码就可以完成了
Content-Type: "application/json"

4.2 | 默认对象

使用kubernetes中的Scheme机制进行反序列化的操作

  • 如果要将json格式的字节数组进行解码通常要进行如下操作:
    • 需要传入一个目标对象的指针,然后由json将对应的字节数据解析到目标对象中,我们也需要这样一个对象,用于存储反序列化的结果
func Unmarshal(data []byte, v interface{}) error {}
  • 该目标存储对象如何获取呢?
    • 那只要我再提供一个当前版本对应的对象构造函数是不是就可以呢?答案是的
    • 构造函数,通过 GVK 调用相应的构造函数,即可构造出对应版本的目标对象
func() Object{ return 目标对象 },

5 | 总览

使用kubernetes中的Scheme机制进行反序列化的操作

  1. 在 webserver 上注册 url 时,即完成了 url 与 资源的版本信息GroupVersionKind(GVK) 映射关系的构建
  2. 之后收到请求进行处理时
    1. 通过 url 获取到 GVK
    2. 根据 GVK 调用相应构造函数,创建目标资源对象
    3. 根据请求的 Header 得知编码格式,获取对应的解码器
    4. 将Body里面的字节序列进行解码到目标对象
  3. 以上流程就可以实现多版本资源的映射和反序列化操作了

Scheme 的其他作用

  • schema 是 kubernetes 资源管理的核心数据结构。由以前文章我们了解到 kubernetes 会将其管理的资源划分为 group/version/kind 的概念,scheme 可以利用进行 GVK 进行一下操作:
    • 可以将资源在内部版本和其他版本中相互转化
    • 可以序列化和反序列化的过程中识别资源类型,创建资源对象,设置默认值等等
    • 这些 group/version/kind 和资源 model 的对应关系,资源 model 的默认值函数,不同版本之间相互转化的函数等等全部由 schema 维护。可以说 schema 是组织 kubernetes 资源的核心

具体讲解见下一节

### 使用 Golang 对 Kubernetes 组件进行定制化开发 #### 自定义组件开发概述 Kubernetes 提供了强大的可扩展机制,其中 CRD (Custom Resource Definitions)[^2] 和 Operator 是实现自定义功能的核心工具。CRD 允许开发者定义新的资源类型,而 Operator 则通过代码逻辑维护这些资源的状态。 为了简化复杂性的开发工作,社区提供了多种框架支持 CRD 和 Operator 的开发,比如 **Operator SDK** 和 **Kubebuilder**[^1]。这些工具帮助开发者专注于业务逻辑而非底层细节。 --- #### 创建 Custom Resource Definition (CRD) CRD 是一种声明式的资源配置方式,用于定义新类型的 Kubernetes 资源。以下是创建 CRD 的基本流程: 1. 定义一个新的资源对象结构。 2. 编写 YAML 文件描述该资源的字段及其行为模式。 3. 应用到集群中以使 kube-apiserver 认识此资源。 下面是一个简单的 CRD 示例: ```yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: myresources.example.com spec: group: example.com versions: - name: v1alpha1 served: true storage: true scope: Namespaced names: plural: myresources singular: myresource kind: MyResource ``` 上述文件定义了一个名为 `MyResource` 的新资源类型。 --- #### 开发 Operator 来管理 CRD 一旦 CRD 注册成功,就需要编写对应的 Controller 或者 Operator 来监控和响应其生命周期事件。以下是基于 Go 语言使用 **Controller Runtime** 实现的一个简单例子: ##### 初始化项目 假设已经安装好 Operator SDK 工具链,则可以通过以下命令快速搭建环境: ```bash operator-sdk init --domain=example.com --repo=github.com/example/my-operator ``` 接着为特定 CRD 添加控制器支持: ```bash operator-sdk create api --group=mygroup --version=v1alpha1 --kind=MyKind --controller=true ``` 这会生成必要的 scaffold 文件夹结构以及样板代码。 --- ##### 修改 Main 函数启动 Manager 在入口函数 `main.go` 中初始化并运行 Manager 实例负责协调整个操作过程。例如: ```go package main import ( "os" ctrl "sigs.k8s.io/controller-runtime" ) func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: ":8080", Port: 9443, HealthProbeBindAddress: ":8081", }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) } if err = (&myv1.MyKindReconciler{}).SetupWithManager(mgr); err != nil { setupLog.Error(err, "无法设置 Reconciler") os.Exit(1) } log.Info("开始监听...") if err := mgr.Start(signals.SetupSignalHandler()); err != nil { setupLog.Error(err, "manager stopped") } } ``` 这里设置了多个选项参数控制服务端口、健康检查地址等内容[^3]。 --- ##### 处理核心逻辑——Reconcile 方法 每个 controller 至少要实现一个 reconcile 循环方法处理实际事务。它接收输入请求后决定下一步动作直到达到期望状态为止。如下所示伪代码片段展示了典型场景下的更新策略: ```go // MyKindReconciler reconciles a MyKind object type MyKindReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme } func (r *MyKindReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { instance := &myv1.MyKind{} err := r.Get(context.TODO(), req.NamespacedName, instance) if errors.IsNotFound(err) { return ctrl.Result{}, nil } else if err != nil { r.Log.Error(err, fmt.Sprintf("获取实例失败 %s/%s", req.Namespace, req.Name)) return ctrl.Result{}, err } // 执行具体任务... newPodSpec := corev1.PodSpec{/*...*/} pod := new(corev1.Pod) pod.GenerateName = "mypod-" pod.Spec = newPodSpec if resultErr := r.Create(context.TODO(), pod); client.IgnoreAlreadyExists(resultErr) != nil { r.Log.Error(resultErr, "创建 Pod 错误") return ctrl.Result{}, resultErr } return ctrl.Result{RequeueAfter: time.Minute}, nil } ``` 以上代码片段说明当检测到某个条件满足时触发重新排队等待下一轮循环继续执行。 --- #### 结合案例分析:Spark Operator 作为真实世界中的应用范例之一,Apache Spark 社区贡献出了官方版本的 Spark Operator[^4]。借助于这个插件,用户无需手动调用传统 CLI 方式即可完成批处理或者流计算任务调度至云端平台之上无缝衔接现有基础设施架构优势最大化利用硬件性能指标提升效率降低成本开销等方面均表现出色值得借鉴学习研究价值极高! --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值