换个角度看Kubernetes 的核心
本文来自微信公共号:分布式实验室。论述了 Kubernetes 的核心价值是其通用、跨厂商和平台、可灵活扩展的声明式 API 框架, 而不是容器(虽然容器是它成功的基础);然后手动创建一个 API Extension(CRD), 通过测试和类比来对这一论述有一个更直观的理解。
例子及测试基于 Kubernetes v1.21.0。
容器是基础
时间回到 2013 年。当一条简单的 docker run postgre 命令就能运行起 Postgre 这样复杂的传统服务时,开发者在震惊之余犹如受到天启;以 Docker 为代表的实用容器技术的横空出世,也预示着一扇通往敏捷基础设施的大门即将打开。此后,一切都在往好的方向迅速发展:
-
越来越多的开发者开始采用容器作为一种标准构建和运行方式。
-
业界也意识到,很容易将这种封装方式引入计算集群,通过 Kubernetes 或 Mesos 这样的编排器来调度计算任务 —— 自此,容器便成为这些调度器最重要的 Workload 类型。
但本文将要说明,容器并非 Kubernetes 最重要、最有价值的地方,Kubernetes 也并非仅仅是一个更广泛意义上的 Workload 调度器 —— 高效地调度不同类型的 Workload 只是 Kubernetes 提供的一种重要价值,但并不是它成功的原因。
API 才是核心
“等等 —— Kubernetes 只是一堆 API?”
“不好意思,一直都是!”
Kubernetes 的成功和价值在于,提供了一种标准的编程接口(API),可以用来编写和使用软件定义的基础设施服务(本文所说的“基础设施”,范围要大于 IaaS):
-
Specification + Implementation 构成一个完整的 API 框架 —— 用于设计、实现和使用各种类型和规模的基础设施服务;
-
这些 API 都基于相同的核心结构和语义:typed resources watched and reconciled by controllers (资源按类型划分,控制器**相应类型的资源,并将其实际 status 校准到 spec 里期望的状态)。
为了进一步解释这一点,考虑下 Kubernetes 出现之前的场景。
Kubernetes 之前:各自造轮子,封装厂商 API 差异
Kubernetes 之前,基础设施基本上是各种不同 API、格式和语义的“云”服务组成的大杂烩:
-
云厂商只提供了计算实例、块存储、虚拟网络和对象存储等基础构建模块,开发者需要像拼图一样将它们拼出一个相对完整的基础设施方案;
-
对于其他云厂商,重复过程 1,因为各家的 API、结构和语义并不相同,甚至差异很大。
虽然 Terraform 等工具的出现,提供了一种跨厂商的通用格式,但原始的结构和语义仍然是五花八门的,—— 针对 AWS 编写的 Terraform descriptor 是无法用到 Azure 的。
Kubernetes 面世:标准化、跨厂商的 API、结构和语义
现在再来看 Kubernetes 从一开始就提供的东西:描述各种资源需求的标准 API。例如:
-
描述 Pod、Container 等计算需求的 API;
-
描述 Service、Ingress 等虚拟网络功能的 API;
-
描述 Volumes 之类的持久存储的 API;
-
甚至还包括 Service Account 之类的服务身份的 API 等等。
这些 API 是跨公有云/私有云和各家云厂商的,各云厂商会将 Kubernetes 结构和语义对接到它们各自的原生 API。因此我们可以说,Kubernetes 提供了一种管理软件定义基础设施(也就是云)的标准接口。或者说,Kubernetes 是一个针对云服务(Cloud Services)的标准 API 框架。
Kubernetes API 扩展:CRD
提供一套跨厂商的标准结构和语义来声明核心基础设施(Pod/Service/Volume/ServiceAccount/……), 是 Kubernetes 成功的基础。在此基础上,它又通过 CRD(Custom Resource Definition), 将这个结构扩展到任何/所有基础设施资源。
CRD 在 1.7 引入,允许云厂商和开发者自己的服务复用 Kubernetes 的 spec/impl 编程框架。
-
有了 CRD,用户不仅能声明 Kubernetes API 预定义的计算、存储、网络服务,还能声明数据库、task runner、消息总线、数字证书……任何云厂商能想到的东西!
-
Operator Framework 以及 SIG API Machinery 等项目的出现,提供了方便地创建和管理这些 CRD 的工具,最小化用户工作量,最大程度实现标准化。
例如,Crossplane 之类的项目,将厂商资源 RDS 数据库、SQS queue 资源映射到 Kubernetes API,就像核心 Kubernetes controller 一样用自己的 controller 来管理网卡、磁盘等自定义资源。Google、RedHat 等 Kubernetes 发行商也在它们的基础 Kubernetes 发行版中包含越来越多的自定义资源类型。
小结
我们说 Kubernetes 的核心是其 API 框架,但并不是说这套 API 框架就是完美的。事实上,后一点并不(非常)重要,因为 Kubernetes 模型已经成为一个事实标准:开发者理解它、大量工具主动与它对接、主流厂商也都已经原生支持它。用户认可度、互操作性 经常比其他方面更能决定一个产品能否成功。
随着 Kubernetes 资源模型越来越广泛的传播,现在已经能够 用一组 Kubernetes 资源来描述一整个软件定义计算环境。就像用 docker run 可以启动单个程序一样,用 kubectl apply -f 就能部署和运行一个分布式应用, 而无需关心是在私有云还是公有云以及具体哪家云厂商上,Kubernetes 的 API 框架已经屏蔽了这些细节。
因此,Kubernetes 并不是关于容器的,而是关于 API。
可以通过 GET/LIST/PUT/POST/DELETE 等 API 操作,来创建、查询、修改或删除集群中的资源。各 controller **到资源变化时,就会执行相应的 reconcile 逻辑,来使 status 与 spec 描述相符。
标准 API(针对内置资源类型)
Namespaced 类型,这种类型的资源是区分 namespace,也就是可以用 namespace 来隔离。大部分内置资源都是这种类型,包括:
-
pods
-
services
-
networkpolicies
API 格式:
-
格式:/api/{version}/namespaces/{namespace}/{resource}
-
举例:/api/v1/namespaces/default/pods
Un-namespaced 类型,这种类型的资源是全局的,不能用 namespace 隔离,例如:
-
nodes
-
clusterroles(clusterxxx 一般都是,表示它是 cluster-scoped 的资源)
API 格式:
-
格式:/api/{version}/{resource}
-
举例:/api/v1/nodes
扩展 API(apiextension)
1、Namespaced 类型
API 格式:
-
格式:/apis/{apiGroup}/{apiVersion}/namespaces/{namespace}/{resource}
-
举例:/apis/cilium.io/v2/namespaces/kube-system/ciliumnetworkpolicies
2、Un-namespaced 类型
CRD
用户发现了 Kubernetes 的强大之后,希望将越来越多的东西(数据)放到 Kubernetes 里面, 像内置的 Pod、Service、NetworkPolicy 一样来管理,因此出现了两个东西:
-
CRD:用来声明用户的自定义资源,例如它是 namespace-scope 还是 cluster-scope 的资源、有哪些字段等等,Kubernetes 会自动根据这个定义生成相应的 API;官方文档的例子[1], 后文也将给出一个更简单和具体的例子。CRD 是资源类型定义,具体的资源叫 CR。
-
Operator 框架:“Operator” 在这里的字面意思是“承担运维任务的程序”, 它们的基本逻辑都是一样的:时刻盯着资源状态,一有变化马上作出反应(也就是 reconcile 逻辑)。
这就是扩展 API 的(最主要)声明和使用方式。
至此,我们讨论的都是一些比较抽象的东西,接下来通过一些例子和类比来更直观地理解一下。
在本节中,我们将创建一个名为 fruit 的 CRD,它有 name/sweet/weight 三个字段, 完整 CRD 如下:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: fruits.example.org # CRD 名字
spec:
conversion:
strategy: None
group: example.org # REST API: /apis/<group>/<version>
names:
kind: Fruit
listKind: FruitList
plural: fruits
singular: fruit
scope: Namespaced # Fruit 资源是区分 namespace 的
versions:
- name: v1 # REST API: /apis/<group>/<version>
schema:
openAPIV3Schema:
properties:
spec:
properties:
comment: # 字段 1,表示备注
type: string
sweet: # 字段 2,表示甜否
type: boolean
weight: # 字段 3,表示重量
type: integer
type: object
type: object
served: true # 启用这个版本的 API(v1)
storage: true
additionalPrinterColumns: # 可选项,配置了这些 printer columns 之后,
- jsonPath: .spec.sweet # 命令行 k get <crd> <cr> 时,能够打印出下面这些字段,
name: sweet # 否则,k8s 默认只打印 CRD 的 NAME 和 AGE
type: boolean
- jsonPath: .spec.weight
name: weight
type: integer
- jsonPath: .spec.comment
name: comment
type: string
后面会解释每个 section 都是什么意思。在此之前,先来做几个(直观而粗糙的)类比。
Kubernetes 是个数据库
像其他数据库技术一样,它有自己的持久存储引擎(etcd),以及构建在存储引擎之上的 一套 API 和语义。这些语义允许用户创建、读取、更新和删除(CURD)数据库中的数据。下面是一些概念对应关系:
关系型数据库 | Kubernetes(as a database) | 说明 |
---|---|---|
DATABASE | cluster | 一套 Kubernetes 集群就是一个 database 【注 1】 |
TABLE | Kind | 每种资源类型对应一个表;分为内置类型和扩展类型 【注 2】 |
COLUMN | property | 表里面的列,可以是 string、boolean 等类型 |
rows | resources | 表中的一个具体 record表中的一个具体 record |
【注 1】 如果只考虑 namespaced 资源的话,也可以说一个 namespace 对应一个 database。
【注 2】 前面已经介绍过,
-
内置 Kind:Job、Service、Deployment、Event、NetworkPolicy、Secret、ConfigMap 等等;
-
扩展 Kind:各种 CRD,例如 CiliumNetworkPolicy。
所以,和其他数据库一样,本质上 Kubernetes 所做的不过是以 schema 规定的格式来处理 records。
另外,Kubernetes 的表都有自带文档:
$ k explain fruits
KIND: Fruit
VERSION: example.org/v1
DESCRIPTION:
<empty>
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
spec <Object>
另外,Kubernetes API 还有两大特色:
-
极其可扩展:声明 CRD 就会自动创建 API;
-
支持事件驱动。
CRD 是一张表
CRD 和内置的 Pod、Service、NetworkPolicy 一样,不过是数据库的一张表。例如,前面给出的 fruit CRD,有 name/sweet/weight 列,以及 “apple”、“banana” 等 entry。
用户发现了 Kubernetes 的强大,希望将越来越多的东西(数据)放到 Kubernetes 里面来管理。数据类 型显然多种多样的,不可能全部内置到 Kubernetes 里。因此,一种方式就是允许用户创建自己的 “表”,设置自己的“列” —— 这正是 CRD 的由来。
1、定义表结构(CRD spec)
CRD(及 CR)描述格式可以是 YAML 或 JSON。CRD 的内容可以简单分为三部分:
常规 Kubernetes metadata:每种 Kubernetes 资源都需要声明的字段,包括 apiVersion、kind、metadata.name 等。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: fruits.example.org # CRD 名字
Table-level 信息:例如表的名字,最好用小写,方便以后命令行操作;
spec:
conversion:
strategy: None
group: example.org # REST API: /apis/<group>/<version>
names:
kind: Fruit
listKind: FruitList
plural: fruits
singular: fruit
scope: Namespaced # Fruit 资源是区分 namespace 的
Column-level 信息:列名及类型等等,遵循 OpenAPISpecification v3 规范。
versions:
- name: v1 # REST API: /apis/<group>/<version>
schema:
openAPIV3Schema:
properties:
spec:
properties:
comment: # 字段 1,表示备注
type: string
sweet: # 字段 2,表示甜否
type: boolean
weight: # 字段 3,表示重量
type: integer
type: object
type: object
served: true # 启用这个版本的 API(v1)
storage: true
additionalPrinterColumns: # 可选项,配置了这些 printer columns 之后,
- jsonPath: .spec.sweet # 命令行 k get <crd> <cr> 时,能够打印出下面这些字段,
name: sweet # 否则,k8s 默认只打印 CRD 的 NAME 和 AGE
type: boolean
- jsonPath: .spec.weight
name: weight
type: integer
- jsonPath: .spec.comment
name: comment
type: string
2、测试:CR 增删查改 vs. 数据库 SQL
创建 CRD:这一步相当于 CREATE TABLE fruits ...;。
$ kubectl create -f fruits-crd.yaml
customresourcedefinition.apiextensions.k8s.io/fruits.example.org created
创建 CR:相当于 INSERT INTO fruits values(...);。
apple-cr.yaml:
apiVersion: example.org/v1
kind: Fruit
metadata:
name: apple
spec:
sweet: false
weight: 100
comment: little bit rotten
banana-cr.yaml:
apiVersion: example.org/v1
kind: Fruit
metadata:
name: banana
spec:
sweet: true
weight: 80
comment: just bought
创建:
$ kubectl create -f apple-cr.yaml
fruit.example.org/apple created
$ kubectl create -f banana-cr.yaml
fruit.example.org/banana created
查询 CR:相当于 SELECT * FROM fruits ... ; 或 SELECT * FROM fruits WHERE name='apple';。
$ k get fruits.example.org # or kubectl get fruits
NAME SWEET WEIGHT COMMENT
apple false 100 little bit rotten
banana true 80 just bought
$ kubectl get fruits apple
NAME SWEET WEIGHT COMMENT
apple false 100 little bit rotten
删除 CR:相当于 DELETE FROM fruits WHERE name='apple';。
$ kubectl delete fruit apple
可以看到,CRD/CR 的操作都能对应到常规的数据库操作。
API 是 SQL
上一节我们是通过 kubectl 命令行来执行 CR 的增删查改,它其实只是一个外壳,内部 调用的是 Kubernetes 为这个 CRD 自动生成的 API —— 所以又回到了本文第一节论述的内容:Kubernetes 的核心是其 API 框架。
只要在执行 kubectl 命令时指定一个足够大的 loglevel,就能看到背后的具体 API 请求。例如:
$ kubectl create -v 10 -f apple-cr.yaml
...
Request Body: {"apiVersion":"example.org/v1","kind":"Fruit",\"spec\":{\"comment\":\"little bit rotten\",\"sweet\":false,\"weight\":100}}\n"},"name":"apple","namespace":"default"},"spec":{"comment":"little bit rotten","sweet":false,"weight":100}}
curl -k -v -XPOST 'https://127.0.0.1:6443/apis/example.org/v1/namespaces/default/fruits?fieldManager=kubectl-client-side-apply'
POST https://127.0.0.1:6443/apis/example.org/v1/namespaces/default/fruits?fieldManager=kubectl-client-side-apply 201 Created in 25 milliseconds
...
给 CR 打标签(label),根据 label 过滤
和内置资源类型一样,Kubernetes 支持对 CR 打标签,然后根据标签做过滤:
# 查看所有 frutis
$ k get fruits
NAME SWEET WEIGHT COMMENT
apple false 100 little bit rotten
banana true 80 just bought
# 给 banana 打上一个特殊新标签
$ k label fruits banana tastes-good=true
fruit.example.org/banana labeled
# 按标签筛选 CR
$ k get fruits -l tastes-good=true
NAME SWEET WEIGHT COMMENT
banana true 80 just bought
# 删除 label
$ k label fruits banana tastes-good-
fruit.example.org/banana labeled
Kubernetes API 与鉴权控制(RBAC)
不管是内置 API,还是扩展 API,都能用 Kubernetes 强大的 RBAC 来做鉴权控制。
关于如何使用 RBAC 网上已经有大量文档;但如果想了解其设计,可参考 Cracking Kubernetes RBAC Authorization Model[2], 它展示了如何从零开始设计出一个 RBAC 鉴权模型(假设 Kubernetes 里还没有)。
相关链接:
参考链接:
-
https://blog.joshgav.com/2021/12/16/kubernetes-isnt-about-containers.html
-
https://github.com/gotopple/k8s-for-users-intro/blob/master/database.md
-
https://itnext.io/crd-is-just-a-table-in-kubernetes-13e15367bbe4
- 点赞
- 收藏
- 关注作者
评论(0)