带你读《云原生应用开发:Operator原理与实践》——2.1.1 CRD 介绍

简介: 带你读《云原生应用开发:Operator原理与实践》——2.1.1 CRD 介绍

第2章 Operator原理


Operator 的概念是由 CoreOS 公司的工程师于 2016 年提出的,它可以让工程师根据应用独有的领域逻辑编写自定义的控制器。我们通过一个简单的例子理解 Operator。假设有一个连接数据库的 Go Web 程序,开发者想将其部署到 k8s 集群。在理想情况下,你会希望用 Deployment 部署应用,然后暴露给 Service,对于应用服务的后端则是使用 StatuflSet 部署数据库,所以需要完成两部分的部署才能完成整个应用服务部署,前提条件:

(1)无状态部分,部署 Go Web 应用;(2)有状态部分,部署数据库。

在上面的例子中,我们可以应用自身对应用程序与数据库之间关系的了解创建一个控制器,该控制器将以某种特定方式运行时执行某些操作,比如备份、更新、数据还原这些任务该如何完成取决于应用程序本身和业务限制(领域知识)。这些与应用强相关的操作就是 Kubernetes Operator 要实现的:代替原本需要由网站可靠性工程师(SRE,Site Reliability Enginner) 和 运 维 工 程 师 来 完 成 的 操 作。 在 Kubernetes 中 Operator 就 是Kubernetes API 的客户端,扮演 Controller 的角色管理 CRD。


2.1 Operator 简介


在介绍 Operator 之前,我们先通过 Kubeclt 命令行创建 Kubernetes 的 Namespace,那么,从发送一条创建命令到被 Kubernetes 执行完成这一过程发生了什么。

首先通过执行以下命令创建一个 Namespace。

kubeclt create -f namespace-nginx.yaml

Yaml 文件见代码清单 2-1。

代码清单 2-1

apiVersion: v1
kind: Namespace
metadata:
 name: nginx

这时 Kubectl 会向 API 服务器发送一个 POST 请求,API 请求格式见代码清单 2-2。

代码清单 2-2

curl --request POST \
 --url http://${k8s.host}:${k8s.port}/api/v1/namespaces \
 --header 'content-type: application/json' \
  --data '{
 "apiVersion":"v1",
 "kind":"Namespace",
 "metadata":{
 "name":"nginx"
 }

从中可以看出 Kubernetes 集群的组件交互都是通过 RESTful API 的形式完成的。流程如图 2-1 所示。

image.png

图 2-1 Namespace 资源创建流程

从上面的 cURL 指令可以看出,尽管我们编写的是 Yaml 文件格式,但是 Kubernetes APIServer 接收的是 JSON 数据类型,而并非用户编写的 Yaml,然后创建的所有资源的请求都通过 Kube-APIServer 预处理、检测处理后持久化到 ETCD 组件中。其中APIServer 也是 Kubernetes 集群中与 ETCD 交互的唯一一个组件。如果 Namespace资源被创建在 ETCD 之后,Kubernetes 事件监听机制就会将 Namespace 资源的变化情况发送给监听Namespaces 资源的 Namespace 控制器,最后由 Namespace 控制器执行创建 Nginx 命名空间的具体操作。同理,Kubernetes 中的其他资源操作类似,都会存在对应的资源控制器来处理响应的资源请求。这些控制器共同组成了 Kubernetes API 控制器集合。


我们不难发现,实现 Kubernetes 中某一种资源类型,如 Namespace、ReplicaSet、Pod 等,Kubernetes 中的资源类型需要满足以下要求。

(1)对该领域类型的模型抽象,如上面 namespace-nginx.yaml 文件描述的 Yaml 数据结构,这个抽象决定了 Kubernetes Client 发送到 Kubernetes APIServer 的 RESTful API 请求数据内容,也描述了这个领域类型本身。

(2)实际去处理这个领域类型抽象的控制器,例如 Namespace 控制器、ReplicaSet控制器、Pod 控制器,这些控制器实现了这个抽象描述的具体业务逻辑,并通过 RESTful API 提供这些服务。


我们将这种资源设计方式称为“声明式 API”。而当 Kubernetes 开发者需要扩展Kubernetes 能力时,也可以遵循这种模式,即提供一份对想要扩展的能力的抽象以及实现了这个抽象具体逻辑的控制器。前者称作 CRD,后者称作 Controller。


Operator 就是通过这种方式实现 Kubernetes 扩展性的一种模式,Operator 模式可以将一个领域问题的解决办法想像成一个操作者,这个操作者在用户和集群之间,通过一份份订单去操作集群的 API,来达到满足这个领域各种需求的目的。这里的订单就是 CR(即 CRD 的一个实例),而操作者就是控制器,是具体逻辑的实现者。之所以强调是 Operator,而不是计算机领域里传统的 Server 角色,是因为 Operator 本质上不创造和提供新的服务,它只是已有 Kubernetes APIServer 的组合。下面将着重介绍这些基础概念,什么是 CRD,什么是 Controller。


2.1.1 CRD 介绍


1. 声明式 API

什 么 是 声 明 式 API 呢? 首 先 我 们 需 要 了 解 在 Kubernetes 中, 使 Deployment、DamenSet,StatefulSet 等资源来管理应用 Workload,使用 Service、Ingress 等来管理应用的访问方式,使用 ConfigMap 和 Secret 来管理应用配置。在集群中对这些资源的创建、更新、删除的动作都会被转换为事件(Event),Kubernetes 的 Controller Manager 负责监听这些事件并触发相应的任务来满足用户的期望。这种方式称为声明式,用户只需要关心应用程序的最终状态,其他的过程都通过 Kubernetes 来完成,通过这种方式可以大大简化应用配置管理的复杂度。

声明式 API 指的是用户提交一个定义好的 API 对象来描述所期望的状态。例如上面创建的 Namespace 资源,这个资源类型表明用户期望的结果是创建一个名字为 Nginx 的命名空间。那么用户无须关心如何创建 Namespaces 资源,只需要关注结果即可。

什么是过程式 API 呢?过程式 API 一次只能处理一个写请求,否则可能会产生冲突,已不具备合并操作的能力 。

声明式 API 的特点是允许有多个 API 写端以 PATCH 的方式对 API 对象进行修改,而无须关心本地原始Yaml文件的内容。声明式API才是 Kubernetes 项目编排能力的核心。对于 API 对象的增、删、改、查,可以在完全无须外界干预的情况下,完成对“实际状态”和“期望状态”的调谐(Reconcile)过程。这种调谐过程的实现者则是 Controller 的处理逻辑。

当开发者对 Kubernetes 的使用逐渐增多之后,会发现这些默认的资源不足以支撑我们的系统,以 Nginx Ingress 为例,如果用户想要实现负载均衡限流器功能,目前 NiginxIngress 的配置不支持这样的特性,对于这种非通用的特性,Kubernetes 提供了一种扩展性的支撑方式,即自定义资源。典型地,声明式 API 特点如下。

(1)你的 API 包含相对而言为数不多、尺寸较小的对象(资源)。

(2)对象定义了应用或者基础设施的配置信息。

(3)对象更新操作频率较低。

(4)通常需要人来读取或写入对象。

(5)对象的主要操作是 CRUD 风格的(创建、读取、更新和删除)。

(6)不需要跨对象的事务支持:API 对象代表的是期望状态而非确切实际状态。

命令式 API 与声明式有所不同。 以下迹象表明你的 API 可能不是声明式 API。

(1)客户端发出“做这个操作”的指令,之后在该操作结束时获得同步响应。

(2)客户端发出“做这个操作”的指令,并获得一个操作 ID,之后需要检查一Operation 对象来判断请求是否成功完成。

(3)将你的 API 类比为 RPC。

(4)需要较高的访问带宽(长期保持每秒数十个请求)。

(5)在对象上执行的常规操作并非是 CRUD 。

(6) API 不太容易用对象来建模。

2. CRD 场景

什么是 CRD 呢?为什么要有 CRD 资源呢?通过对下面内容的学习,大家会对 CRD有概念性的认识。首先 Kubernetes 为用户提供了丰富的资源,如资源对象、配置对象、存储对象和策略对象,如表 2-1 所示。

image.png

虽然 Kubernetes 为我们提供了丰富的资源类型,但是在不同应用场景下,某些传统资源类型仍不能满足用户需求,他们对平台可能存在一些特殊的需求,为了满足这些需求,Kubernetes 社区为我们提供了一种抽象 Kubernetes 的扩展资源。这种抽象的资源类型叫作自定义资源定义(CRD,Custom Resource Definition)。CRD 为我们提供资源的快速注册和使用的接口。其实在很早的 k8s 版本中自定义资源就已经被提出,当时叫作TPR(Third Party Resource),这是与 CRD 类似的概念,但是在 1.9 以上的版本中被弃用,而 CRD 则进入 beta 状态。

(1)什么时候需要添加定制资源?

① 你希望使用 Kubernetes 客户端库和 CLI 来创建和更改新的资源。

② 你希望 Kubectl 能够直接支持你的资源。

③ 你希望构造新的自动化机制,监测新对象的更新事件,并对其他对象执行 CRUD (增加、检索、更新、删除)操作,或者监测后者更新前者。

④ 你希望编写自动化组件来处理对对象的更新。

⑤ 你希望使用 Kubernetes API 对诸如 .spec、.status 和 .metadata 等字段进行约定。

⑥ 你希望对象是对一组受控资源的抽象,或者对其他资源的归纳提炼。

(2)如何定义一个 CRD ?

我们通过代码清单 2-3 中的 Yaml 文件来创建一个 Appconfig CRD 资源。

代码清单 2-3

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
 # 名称必须符合下面的格式 :<plural>.<group>
 name: crontabs.stable.example.com
spec:
 # REST API 使用的组名称 :/apis/<group>/<version>
  group: stable.example.com
 # REST API 使用的版本号 :/apis/<group>/<version>
 versions:
 - name: v1
 # 可以通过 served 来开关每个版本
 served: true
 # 有且仅有一个版本开启存储
 storage: true
 schema:
 openAPIV3Schema:
 type: object
 properties:
 spec:
 type: object
 properties:
 cronSpec:
 type: string
 image:
 type: string
 replicas:
 type: integer
 # Namespaced 或 Cluster
 scope: Namespaced
 names:
 # URL 中使用的复数名称 : /apis/<group>/<version>/<plural>
 plural: crontabs
 # CLI 中使用的单数名称
 singular: crontab
 # CamelCased 格式的单数类型。在清单文件中使用
 kind: CronTab
 # CLI 中使用的资源简称
 shortNames:
 - ct

首先我们解释一下创建的 CRD 的参数。

① 第一行和第二行我们定义了 CRD 的版本。

② metadata 定义了访问的 CRD 资源的名称,名称必须与下面的 spec 字段匹配。

③ Spec.group 定义了资源属于什么组,这里我们定义成 stable.example.com。

④ Spec.versions 定义了资源存储的版本,这里定义为 v1。

⑤ scope 定义了我们创建的资源的作用范围?这里定义成 Namespace 范围。

⑥ plural singular 定义了资源的单复数形式,这个根据实际情况命名即可。

我们执行 kubectl apply -f crd.yaml 就可以创建名称为 appconfig 的 CRD 资源了。同 时 我 们 定 义 的 CRD 资 源 RESTful API 将 会 定 义 成 /apis/stable.example.com/v1/namespaces/*/crontabs/。

(3)如何创建一个 CRD 实例对象?

首先定义一个符合上面 CRD 资源的 apiVersion 和资源类型(kind)(见代码清单 2-4)。

代码清单 2-4

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
 name: my-new-cron-object
spec:
 cronSpec: "* * * * */5"
 image: my-awesome-cron-image

创建 CRD 对象后,可以创建自定义对象,自定义对象可包含自定义字段。这些字段可以包含任意 JSON。如代码清单 2-4 所示,cronSpec 和 image 自定义字段在自定义对象中设置 CronTab。CronTab 类型来自上面创建的 CRD 对象的规范,然后执行kubectle apply -f my-crontab.yaml 即可。


3. k8s API 设计规范

Kubernetes API 是集群系统中的重要组成部分,Kubernetes 中各种资源(对象)的数据通过该 API 被传送到后端的持久化存储(ETCD)中。Kubernetes 集群中的各部件之间通过该 API 实现解耦合,Kubernetes API 中的资源对象都拥有通用的元数据,资源对象也可能存在嵌套现象,比如在一个 Pod 中嵌套多个 Container。创建一个 API 对象是指通过 API 调用创建一条有意义的记录,该记录一旦被创建,Kubernetes 将确保对应的资源对象会被自动创建并托管维护。在 Kubernetes 系统中,大多数情况下,API 定义和实现都符合标准的 HTTP REST 格式,如通过标准的 HTTP 动作(POST、PUT、GET、DELETE)来完成对相关资源对象的查询、修改、删除等操作。但同时 Kubernetes 也为某些非标准的 REST 行为提供了附加的 API,例如,监听某个资源变化的 Watch 接口等。另外,某些 API 可能违背严格的 REST 模式,因为接口不是返回单一的 JSON 对象,而是返回其他类型的数据,如 JSON 对象流(Stream)或非结构化的文本日志数据等。 从上面的 CRD 定义中可以发现,在 Kubernetes 中要想完成一个 CRD,需要指定 Group、Version 和 Kind,这在 Kubernetes 的 APIServer 中简称为 GVK。当我们在 Kubernetes 中谈论 API 时,经常会提起 4 个术语:Groups 、Versions、Kinds和Resources。

(1) Groups /Versions

Kubernetes 中的 API 组简单来说就是相关功能的集合。每个组都有一个或多个版本,它允许我们随着时间的推移改变 API 的职责。

(2) Kinds/Resources

每个 API 组 - 版本包含一个或多个 API 类型,我们称之为 Kinds。虽然一个 Kind 可以在不同版本之间改变表单内容,但每个表单必须能够以某种方式存储其他表单的所有数据(我们可以将数据存储在字段或者注释中)。 这意味着,使用旧的 API 版本不会导致新的数据丢失或损坏。

Resources 只是 API 中的一个 Kind 的使用方式。通常情况下,Kind 和 Resources 之间是一对一的映射。 例如,Pods 资源对应于 Pod 种类。但是有时同一类型可能由多个资源返回。例如,Scale Kind 是由所有 Scale 子资源返回的,它由 Deployments/Scale 或 Replicasets/Scale 组成。这就是允许 Kubernetes HPA(Horizontal Pod Autoscaler) 与不同资源交互的原因。然而,使用 CRD 每个 Kind 都将对应一个 Resource。

GVK 是定义一种类型的方式。例如,Daemonsets 就是 Kubernetes 中的一种资源,当我们要求 Kubernetes 创建一个 Daemonsets 的时候,Kubectl 是如何知道该怎么向 APIServer 发送这个信息呢?是所有的不同资源都发向同一个 URL,还是每种资源都是不同的?例如 Daemonsets 资源内容(见代码清单 2-5)。

代码清单 2-5

apiVersion: apps/v1
kind: DaemonSet
metadata:
 name: node-exporter

这里声明了 apiVersion 是 apps/v1,其实就是隐含了 Group 是 apps,Version 是 v1,Kind 就是定义的 DaemonSet,而 Kubectl 接收到这个声明之后,就可以根据这个声明去调用 APIServer 对应的 URL 来获取信息。例如,/api/apps/v1/daemonset 这样的 API 就是由上面的设计规则实现的。Kubernetes 以符合 REST 规范的 URI 来组织资源,组织的路径如图 2-2 所示。

image.png

图 2-2 API 资源格式路径图

前面介绍了 GVK(Group、Version、Kind), 接下来介绍 APIServer 的第二个概念 GVR (Group、Version、Resource)。其实理解了 GVK 之后再理解 GVR 就很容易了,这就是面向对象编程中的类和对象的概念是一样的。Kind 相当于一个类,Resource是具体的 Kind,可以理解为一个类的对象资源。那么 GVR 资源如何对应到 GVK ?这就是 REST Mapping 的功能:REST Mapping 可以将指定的一个 GVR(如 Daemonset 资源)通过转换映射返回对应的 GVK 以及支持的操作等。

(3) API 版本

为了在兼容旧版本的同时不断升级 API,Kubernetes 提供了多版本 API 的支持能力,每个版本的 API 通过一个版本号路径前缀加以区分,例如 /api/v1beta3。通常情况下,新旧几个不同的 API 版本都能涵盖所有的 Kubernetes 资源对象,在不同的版本之间这些 API 存在一些细微差别。Kubernetes 开发团队基于 API 级别选择版本而不是基于资源和域级别来选择版本,是为了确保 API 能够描述一个清晰、连续的系统资源和行为的视图,能够控制访问的整个过程和实验性 API 的访问 。

API 详细说明如下。

① GET /< 资源名的复数格式 >:获得某一类型的资源列表,例如 GET /Pods 返回一个 Pod 资源列表。

② POST /< 资源名的复数格式 >:创建一个资源,该资源来自用户提供的 JSON 对象。

③ GET /< 资源名复数格式 >/< 名字 >:通过给出的名称(Name)获得单个资源,例如 GET /pods/podname 返回一个名称为“podname”的 Pod。

④ DELETE /< 资源名复数格式 >/< 名字 >:通过给出的名字删除单个资源。删除选项(DeleteOptions)中可以指定的优雅删除(Grace Deletion)的时间(Grace Period Seconds),该可选项表明了从服务端接收删除请求到资源被删除的时间间隔(单位为 s)。不同的类别(Kind)可能为优雅删除时间(Grace Period)申明默认值。用户提交的优雅删除时间将覆盖该默认值,包括值为 0 的优雅删除时间。

⑤ PUT /< 资源名复数格式 >/< 名字 >:通过给出的资源名和客户端提供的 JSON 对象来更新或创建资源。

⑥ PATCH /< 资源名复数格式 >/< 名字 >:选择修改资源详细指定的域。

此外,Kubernetes API 添加了资源变动的“观察者”模式的 API 。

① GET /watch/< 资源名复数格式 >:随时间变化,不断接收一连串的 JSON 对象,这些 JSON 对象记录了给定资源类别内所有资源对象的变化情况。

② GET /watch/< 资源名复数格式 >/:随时间变化,不断接收一连串的 JSON 对象,这些 JSON 对象记录了某个给定资源对象的变化情况。

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
云原生实践公开课
课程大纲 开篇:如何学习并实践云原生技术 基础篇: 5 步上手 Kubernetes 进阶篇:生产环境下的 K8s 实践 相关的阿里云产品:容器服务&nbsp;ACK 容器服务&nbsp;Kubernetes&nbsp;版(简称&nbsp;ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情:&nbsp;https://www.aliyun.com/product/kubernetes
相关文章
|
2月前
|
缓存 Java API
【云原生】Spring Cloud Gateway的底层原理与实践方法探究
【云原生】Spring Cloud Gateway的底层原理与实践方法探究
|
2月前
|
Kubernetes Cloud Native 开发工具
带你读《云原生应用开发:Operator原理与实践》精品文章合集
带你读《云原生应用开发:Operator原理与实践》精品文章合集
|
3月前
|
人工智能 缓存 Kubernetes
.NET 9 首个预览版发布:瞄准云原生和智能应用开发
.NET 9 首个预览版发布:瞄准云原生和智能应用开发
|
3月前
|
Prometheus Cloud Native 数据库
Grafana 系列文章(九):开源云原生日志解决方案 Loki 简介
Grafana 系列文章(九):开源云原生日志解决方案 Loki 简介
|
7月前
|
Cloud Native 架构师 Java
谷歌架构师分享gRPC与云原生应用开发Go和Java为例文档
随着微服务和云原生相关技术的发展,应用程序的架构模式已从传统的单体架构或分层架构转向了分布式的计算架构。尽管分布式架构本身有一定的开发成本和运维成本,但它所带来的收益是显而易见的。
|
2月前
|
Kubernetes Cloud Native 微服务
作者推荐|剖析云原生服务框架中服务发现机制的核心原理与实现机制
作者推荐|剖析云原生服务框架中服务发现机制的核心原理与实现机制
45 0
|
2月前
|
Java fastjson 数据安全/隐私保护
【Dubbo3技术专题】「云原生微服务开发实战」 一同探索和分析研究RPC服务的底层原理和实现
【Dubbo3技术专题】「云原生微服务开发实战」 一同探索和分析研究RPC服务的底层原理和实现
44 0
|
2月前
|
运维 Cloud Native 持续交付
云原生技术的未来展望:如何塑造下一代应用开发
【2月更文挑战第30天】 随着云计算的不断发展,云原生技术已经成为推动现代应用开发的重要力量。本文将深入探讨云原生技术的核心概念,分析其在提高开发效率、降低运维成本以及支持复杂业务场景中的作用。同时,文章还将预测云原生技术的发展趋势,并讨论如何在不断变化的技术环境中保持应用的敏捷性和可靠性。
|
2月前
|
消息中间件 存储 Cloud Native
【Spring云原生系列】Spring RabbitMQ:异步处理机制的基础--消息队列 原理讲解+使用教程
【Spring云原生系列】Spring RabbitMQ:异步处理机制的基础--消息队列 原理讲解+使用教程
|
5月前
|
Prometheus Cloud Native 关系型数据库
prometheus|云原生|prometheus项目安装postgres-exporter监视组件的部署简介
prometheus|云原生|prometheus项目安装postgres-exporter监视组件的部署简介
94 0

热门文章

最新文章