Kubernetes API 是通过 HTTP 提供的基于资源 (RESTful) 的编程接口。
它支持通过标准 HTTP 动词(POST、PUT、PATCH、DELETE、GET)检索、创建、更新和删除主要资源。
对于某些资源,API 包括额外的子资源,允许细粒度授权(例如:将 Pod 的详细信息与检索日志分开),
为了方便或者提高效率,可以以不同的表示形式接受和服务这些资源。
几乎所有对象资源类型都支持标准 HTTP 动词 - GET、POST、PUT、PATCH 和 DELETE。
Kubernetes 也使用自己的动词,这些动词通常写成小写,以区别于 HTTP 动词。
你可以将这两种技术结合使用,利用 Kubernetes 的 Protobuf 编码与所有支持的 API 进行读写交互。
只有某些 API 资源类型与 Protobuf 兼容。
封套格式如下:
四个字节的特殊数字前缀:
字节 0-3: "k8s\x00" [0x6b, 0x38, 0x73, 0x00]
使用下面 IDL 来编码的 Protobuf 消息:
message Unknown {
// typeMeta 应该包含 "kind" 和 "apiVersion" 的字符串值,就像
// 对应的 JSON 对象中所设置的那样
optional TypeMeta typeMeta = 1;
// raw 中将保存用 protobuf 序列化的完整对象。
// 参阅客户端库中为指定 kind 所作的 protobuf 定义
optional bytes raw = 2;
// contentEncoding 用于 raw 数据的编码格式。未设置此值意味着没有特殊编码。
optional string contentEncoding = 3;
// contentType 包含 raw 数据所采用的序列化方法。
// 未设置此值意味着 application/vnd.kubernetes.protobuf,且通常被忽略
optional string contentType = 4;
}
message TypeMeta {
// apiVersion 是 type 对应的组名/版本
optional string apiVersion = 1;
// kind 是对象模式定义的名称。此对象应该存在一个 protobuf 定义。
optional string kind = 2;
}
说明:
收到 application/vnd.kubernetes.protobuf
格式响应的客户端在响应与预期的前缀不匹配时应该拒绝响应,
因为将来的版本可能需要以某种不兼容的方式更改序列化格式,并且这种更改是通过变更前缀完成的。
与 Kubernetes Protobuf 的兼容性
并非所有 API 资源类型都支持 Protobuf;具体来说,Protobuf
不适用于定义为 CustomResourceDefinitions
或通过聚合层提供服务的资源。
作为客户端,如果你可能需要使用扩展类型,则应在请求 Accept
请求头中指定多种内容类型以支持回退到 JSON。例如:
Accept: application/vnd.kubernetes.protobuf, application/json
高效检测变更
Kubernetes API 允许客户端对对象或集合发出初始请求,然后跟踪自该初始请求以来的更改:watch。
客户端可以发送 list 或者 get 请求,然后发出后续 watch 请求。
为了使这种更改跟踪成为可能,每个 Kubernetes 对象都有一个 resourceVersion
字段,
表示存储在底层持久层中的该资源的版本。在检索资源集合(名字空间或集群范围)时,
来自 API 服务器的响应包含一个 resourceVersion
值。
客户端可以使用该 resourceVersion
来启动对 API 服务器的 watch。
当你发送 watch 请求时,API 服务器会响应更改流。
这些更改逐项列出了在你指定为 watch 请求参数的 resourceVersion
之后发生的操作
(例如 create、delete 和 update)的结果。
整个 watch 机制允许客户端获取当前状态,然后订阅后续更改,而不会丢失任何事件。
如果客户端 watch 连接断开,则该客户端可以从最后返回的 resourceVersion
开始新的 watch 请求;
客户端还可以执行新的 get/list 请求并重新开始。有关更多详细信息,请参阅资源版本语义。
例如:
列举给定名字空间中的所有 Pod:
GET /api/v1/namespaces/test/pods
---
200 OK
Content-Type: application/json
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {"resourceVersion":"10245"},
"items": [...]
}
从资源版本 10245 开始,接收影响 test 名字空间中 Pod 的所有 API 操作
(例如 create、delete、patch 或 update)的通知。
每个更改通知都是一个 JSON 文档。
HTTP 响应正文(用作 application/json
)由一系列 JSON 文档组成。
GET /api/v1/namespaces/test/pods?watch=1&resourceVersion=10245
---
200 OK
Transfer-Encoding: chunked
Content-Type: application/json
{
"type": "ADDED",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "10596", ...}, ...}
}
{
"type": "MODIFIED",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "11020", ...}, ...}
}
...
给定的 Kubernetes 服务器只会保留一定的时间内发生的历史变更列表。
使用 etcd3 的集群默认保存过去 5 分钟内发生的变更。
当所请求的 watch 操作因为资源的历史版本不存在而失败,
客户端必须能够处理因此而返回的状态代码 410 Gone
,清空其本地的缓存,
重新执行 get 或者 list 操作,
并基于新返回的 resourceVersion
来开始新的 watch 操作。
对于订阅集合,Kubernetes 客户端库通常会为 list -然后- watch 的逻辑提供某种形式的标准工具。
(在 Go 客户端库中,这称为 反射器(Reflector)
,位于 k8s.io/client-go/tools/cache
包中。)
监视书签
为了减轻短历史窗口的影响,Kubernetes API 提供了一个名为 BOOKMARK
的监视事件。
这是一种特殊的事件,用于标记客户端请求的给定 resourceVersion
的所有更改都已发送。
代表 BOOKMARK
事件的文档属于请求所请求的类型,但仅包含一个 .metadata.resourceVersion
字段。例如:
GET /api/v1/namespaces/test/pods?watch=1&resourceVersion=10245&allowWatchBookmarks=true
---
200 OK
Transfer-Encoding: chunked
Content-Type: application/json
{
"type": "ADDED",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "10596", ...}, ...}
}
...
{
"type": "BOOKMARK",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "12746"} }
}
作为客户端,你可以在 watch 请求中设置 allowWatchBookmarks=true
查询参数来请求 BOOKMARK
事件,
但你不应假设书签会在任何特定时间间隔返回,即使要求时,客户端也不能假设 API 服务器会发送任何 BOOKMARK
事件。
流式列表
特性状态:
Kubernetes v1.27 [alpha]
(enabled by default: false)
在大型集群检索某些资源类型的集合可能会导致控制平面的资源使用量(主要是 RAM)显著增加。
为了减轻其影响并简化 list + watch 模式的用户体验,
Kubernetes 1.27 版本引入了一个 alpha 功能,支持在 watch 请求中请求初始状态
(之前在 list 请求中请求)。
如果启用了 WatchList
特性门控,
可以通过在 watch 请求中指定 sendInitialEvents=true
作为查询字符串参数来实现这一功能。
如果指定了这个参数,API 服务器将使用合成的初始事件(类型为 ADDED
)来启动监视流,
以构建所有现有对象的完整状态;如果请求还带有 allowWatchBookmarks=true
选项,
则继续发送 BOOKMARK
事件。
BOOKMARK 事件包括已被同步的资源版本。
发送 BOOKMARK 事件后,API 服务器会像处理所有其他 watch 请求一样继续执行。
当你在查询字符串中设置 sendInitialEvents=true
时,
Kubernetes 还要求你将 resourceVersionMatch
的值设置为 NotOlderThan
。
如果你在查询字符串中提供 resourceVersion
而没有提供值或者根本没有提供这个参数,
这一请求将被视为 一致性读(Consistent Read) 请求;
当状态至少被同步到开始处理一致性读操作时,才会发送 BOOKMARK 事件。
如果你(在查询字符串中)指定了 resourceVersion
,则只要需要等状态同步到所给资源版本时,
BOOKMARK 事件才会被发送。
示例
举个例子:你想监视一组 Pod。对于该集合,当前资源版本为 10245,并且有两个 Pod:foo
和 bar
。
接下来你发送了以下请求(通过使用 resourceVersion=
设置空的资源版本来明确请求一致性读),
这样做的结果是可能收到如下事件序列:
GET /api/v1/namespaces/test/pods?watch=1&sendInitialEvents=true&allowWatchBookmarks=true&resourceVersion=&resourceVersionMatch=NotOlderThan
---
200 OK
Transfer-Encoding: chunked
Content-Type: application/json
{
"type": "ADDED",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "8467", "name": "foo"}, ...}
}
{
"type": "ADDED",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "5726", "name": "bar"}, ...}
}
{
"type": "BOOKMARK",
"object": {"kind": "Pod", "apiVersion": "v1", "metadata": {"resourceVersion": "10245"} }
}
...
<followed by regular watch stream starting from resourceVersion="10245">
响应压缩
特性状态:
Kubernetes v1.16 [beta]
(enabled by default: true)
APIResponseCompression
是一个选项,允许 API 服务器压缩 get 和 list 请求的响应,
减少占用的网络带宽并提高大规模集群的性能。此选项自 Kubernetes 1.16 以来默认启用,
可以通过在 API 服务器上的 --feature-gates
标志中包含 APIResponseCompression=false
来禁用。
特别是对于大型资源或集合,
API 响应压缩可以显著减小其响应的大小。例如,针对 Pod 的 list 请求可能会返回数百 KB 甚至几 MB 的数据,
具体大小取决于 Pod 数量及其属性。通过压缩响应,可以节省网络带宽并降低延迟。
要验证 APIResponseCompression
是否正常工作,你可以使用一个 Accept-Encoding
头向 API 服务器发送一个 get 或 list 请求,并检查响应大小和头信息。例如:
GET /api/v1/pods
Accept-Encoding: gzip
---
200 OK
Content-Type: application/json
content-encoding: gzip
...
content-encoding
头表示响应使用 gzip
进行了压缩。
分块检视大体量结果
特性状态:
Kubernetes v1.29 [stable]
(enabled by default: true)
在较大规模集群中,检索某些资源类型的集合可能会导致非常大的响应,从而影响服务器和客户端。
例如,一个集群可能有数万个 Pod,每个 Pod 大约相当于 2 KiB 的编码 JSON。
跨所有名字空间检索所有 Pod 可能会导致非常大的响应(10-20MB)并消耗大量服务器资源。
Kubernetes API 服务器支持将单个大型集合请求分解为许多较小块的能力,
同时保持总体请求的一致性。每个块都可以按顺序返回,这既减少了请求的总大小,
又允许面向用户的客户端增量显示结果以提高响应能力。
你可以请求 API 服务器通过使用页(Kubernetes 将其称为“块(Chunk)”)的方式来处理 list,
完成单个集合的响应。
要以块的形式检索单个集合,针对集合的请求支持两个查询参数 limit
和 continue
,
并且从集合元 metadata
字段中的所有 list 操作返回响应字段 continue
。
客户端应该指定他们希望在每个带有 limit
的块中接收的条目数上限,如果集合中有更多资源,
服务器将在结果中返回 limit
资源并包含一个 continue
值。
作为 API 客户端,你可以在下一次请求时将 continue
值传递给 API 服务器,
以指示服务器返回下一页(块)结果。继续下去直到服务器返回一个空的 continue
值,
你可以检索整个集合。
与 watch 操作类似,continue
令牌也会在很短的时间(默认为 5 分钟)内过期,
并在无法返回更多结果时返回 410 Gone
代码。
这时,客户端需要从头开始执行上述检视操作或者忽略 limit
参数。
例如,如果集群上有 1253 个 Pod,客户端希望每次收到包含至多 500 个 Pod
的数据块,它应按下面的步骤来请求数据块:
列举集群中所有 Pod,每次接收至多 500 个 Pod:
GET /api/v1/pods?limit=500
---
200 OK
Content-Type: application/json
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion":"10245",
"continue": "ENCODED_CONTINUE_TOKEN",
"remainingItemCount": 753,
...
},
"items": [...] // returns pods 1-500
}
继续前面的调用,返回下一组 500 个 Pod:
GET /api/v1/pods?limit=500&continue=ENCODED_CONTINUE_TOKEN
---
200 OK
Content-Type: application/json
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion":"10245",
"continue": "ENCODED_CONTINUE_TOKEN_2",
"remainingItemCount": 253,
...
},
"items": [...] // returns pods 501-1000
}
继续前面的调用,返回最后 253 个 Pod:
GET /api/v1/pods?limit=500&continue=ENCODED_CONTINUE_TOKEN_2
---
200 OK
Content-Type: application/json
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion":"10245",
"continue": "", // continue token is empty because we have reached the end of the list
...
},
"items": [...] // returns pods 1001-1253
}
请注意,集合的 resourceVersion
在每个请求中保持不变,
这表明服务器正在向你显示 Pod 的一致快照。
在版本 10245
之后创建、更新或删除的 Pod 将不会显示,
除非你在没有继续令牌的情况下发出单独的 list 请求。
这使你可以将大请求分成更小的块,然后对整个集合执行 watch 操作,而不会丢失任何更新。
remainingItemCount
是集合中未包含在此响应中的后续项目的数量。
如果 list 请求包含标签或字段选择器,
则剩余项目的数量是未知的,并且 API 服务器在其响应中不包含 remainingItemCount
字段。
如果 list 是完整的(因为它没有分块,或者因为这是最后一个块),没有更多的剩余项目,
API 服务器在其响应中不包含 remainingItemCount
字段。
remainingItemCount
的用途是估计集合的大小。
集合
在 Kubernetes 术语中,你从 list 中获得的响应是一个“集合(Collections)”。
然而,Kubernetes 为不同类型资源的集合定义了具体类型。
集合的类别名是针对资源类别的,并附加了 List
。
当你查询特定类型的 API 时,该查询返回的所有项目都属于该类型。
例如,当你 list Service 对象时,集合响应的 kind
设置为
ServiceList
;
该集合中的每个项目都代表一个 Service。例如:
GET /api/v1/services
{
"kind": "ServiceList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "2947301"
},
"items": [
{
"metadata": {
"name": "kubernetes",
"namespace": "default",
...
"metadata": {
"name": "kube-dns",
"namespace": "kube-system",
...
Kubernetes API 中定义了数十种集合类型(如 PodList
、ServiceList
和 NodeList
)。
你可以从 Kubernetes API 文档中获取有关每种集合类型的更多信息。
一些工具,例如 kubectl
,对于 Kubernetes 集合的表现机制与 Kubernetes API 本身略有不同。
因为 kubectl
的输出可能包含来自 API 级别的多个 list 操作的响应,
所以 kubectl
使用 kind: List
表示项目列表。例如:
kubectl get services -A -o yaml
apiVersion: v1
kind: List
metadata:
resourceVersion: ""
selfLink: ""
items:
- apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2021-06-03T14:54:12Z"
labels:
component: apiserver
provider: kubernetes
name: kubernetes
namespace: default
...
- apiVersion: v1
kind: Service
metadata:
annotations:
prometheus.io/port: "9153"
prometheus.io/scrape: "true"
creationTimestamp: "2021-06-03T14:54:14Z"
labels:
k8s-app: kube-dns
kubernetes.io/cluster-service: "true"
kubernetes.io/name: CoreDNS
name: kube-dns
namespace: kube-system
说明:
请记住,Kubernetes API 没有名为 List
的 kind
。
kind: List
是一个客户端内部实现细节,用于处理可能属于不同类别的对象的集合。
在自动化或其他代码中避免依赖 kind: List
。
以表格形式接收资源
当你执行 kubectl get
时,默认的输出格式是特定资源类型的一个或多个实例的简单表格形式。
过去,客户端需要重复 kubectl
中所实现的表格输出和描述输出逻辑,以执行简单的对象列表操作。
该方法的一些限制包括处理某些对象时的不可忽视逻辑。
此外,API 聚合或第三方资源提供的类型在编译时是未知的。
这意味着必须为客户端无法识别的类型提供通用实现。
为了避免上述各种潜在的局限性,客户端可以请求服务器端返回对象的表格(Table)
表现形式,从而将打印输出的特定细节委托给服务器。
Kubernetes API 实现标准的 HTTP 内容类型(Content Type)协商:为 GET
调用传入一个值为 application/json;as=Table;g=meta.k8s.io;v=v1
的 Accept
头部即可请求服务器以 Table 的内容类型返回对象。
例如,以 Table 格式列举集群中所有 Pod:
GET /api/v1/pods
Accept: application/json;as=Table;g=meta.k8s.io;v=v1
---
200 OK
Content-Type: application/json
{
"kind": "Table",
"apiVersion": "meta.k8s.io/v1",
...
"columnDefinitions": [
...
]
}
对于在控制平面上不存在定制的 Table 定义的 API 资源类型而言,服务器会返回一个默认的
Table 响应,其中包含资源的 name
和 creationTimestamp
字段。
GET /apis/crd.example.com/v1alpha1/namespaces/default/resources
---
200 OK
Content-Type: application/json
...
{
"kind": "Table",
"apiVersion": "meta.k8s.io/v1",
...
"columnDefinitions": [
{
"name": "Name",
"type": "string",
...
},
{
"name": "Created At",
"type": "date",
...
}
]
}
并非所有 API 资源类型都支持 Table 响应;
例如,CustomResourceDefinitions 可能没有定义字段到表的映射,
扩展核心 Kubernetes API
的 APIService 可能根本不提供 Table 响应。
如果你正在实现使用 Table 信息并且必须针对所有资源类型(包括扩展)工作的客户端,
你应该在 Accept
请求头中指定多种内容类型的请求。例如:
Accept: application/json;as=Table;g=meta.k8s.io;v=v1, application/json
资源删除
当你 delete 资源时,操作将分两个阶段进行。
- 终结(finalization)
- 移除
{
"kind": "ConfigMap",
"apiVersion": "v1",
"metadata": {
"finalizers": ["url.io/neat-finalization", "other-url.io/my-finalizer"],
"deletionTimestamp": nil,
}
}
当客户端第一次发送 delete 请求删除资源时,.metadata.deletionTimestamp
设置为当前时间。
一旦设置了 .metadata.deletionTimestamp
,
作用于终结器的外部控制器可以在任何时间以任何顺序开始执行它们的清理工作。
终结器之间不存在强制的执行顺序,因为这会带来卡住 .metadata.finalizers
的重大风险。
.metadata.finalizers
字段是共享的:任何有权限的参与者都可以重新排序。
如果终结器列表是按顺序处理的,那么这可能会导致这样一种情况:
在列表中负责第一个终结器的组件正在等待列表中稍后负责终结器的组件产生的某些信号
(字段值、外部系统或其他),从而导致死锁。
如果没有强制排序,终结者可以在它们之间自由排序,并且不易受到列表中排序变化的影响。
当最后一个终结器也被移除时,资源才真正从 etcd 中移除。
单个资源 API
Kubernetes API 动词 get、create、update、patch、delete 和 proxy 仅支持单一资源。
这些具有单一资源支持的动词不支持在有序或无序列表或事务中一起提交多个资源。
当客户端(包括 kubectl)对一组资源进行操作时,客户端会发出一系列单资源 API 请求,
然后在需要时聚合响应。
相比之下,Kubernetes API 动词 list 和 watch 允许获取多个资源,
而 deletecollection 允许删除多个资源。
字段校验
Kubernetes 总是校验字段的类型。例如,如果 API 中的某个字段被定义为数值,
你就不能将该字段设置为文本类型的值。如果某个字段被定义为字符串数组,你只能提供数组。
有些字段可以忽略,有些字段必须填写。忽略 API 请求中的必填字段会报错。
如果请求中带有集群控制面无法识别的额外字段,API 服务器的行为会更加复杂。
默认情况下,如果接收到的输入信息中含有 API 服务器无法识别的字段,API 服务器会丢弃该字段
(例如:PUT
请求中的 JSON 主体)。
API 服务器会在两种情况下丢弃 HTTP 请求中提供的字段。
这些情况是:
- 相关资源的 OpenAPI 模式定义中没有该字段,因此无法识别该字段(有种例外情形是,
CRD
通过
x-kubernetes-preserve-unknown-fields
显式选择不删除未知字段)。
- 字段在对象中重复出现。
检查无法识别或重复的字段
特性状态:
Kubernetes v1.27 [stable]
(enabled by default: true)
从 1.25 开始,当使用可以提交数据的 HTTP 动词(POST
、PUT
和 PATCH
)时,
将通过服务器上的校验检测到对象中无法识别或重复的字段。
校验的级别可以是 Ignore
、Warn
(默认值) 和 Strict
之一。
Ignore
- 使 API 服务器像没有遇到错误字段一样成功处理请求,丢弃所有的未知字段和重复字段,并且不发送丢弃字段的通知。
Warn
:(默认值)使 API 服务器成功处理请求,并向客户端发送告警信息。告警信息通过 Warning:
响应头发送,
并为每个未知字段或重复字段添加一条告警信息。有关告警和相关的 Kubernetes API 的信息,
可参阅博文告警:增加实用告警功能。
Strict
- API 服务器检测到任何未知字段或重复字段时,拒绝处理请求并返回 400 Bad Request 错误。
来自 API 服务器的响应消息列出了 API 检测到的所有未知字段或重复字段。
字段校验级别可通过查询参数 fieldValidation
来设置。
说明:
如果你提交的请求中设置了一个无法被识别的字段,并且该请求存在因其他原因引起的不合法
(例如,请求为某已知字段提供了一个字符串值,而 API 期望该字段为整数),
那么 API 服务器会以 400 Bad Request 错误作出响应,但不会提供有关未知或重复字段的任何信息
(仅提供它首先遇到的致命错误)。
在这种情况下,不管你设置哪种字段校验级别,你总会收到出错响应。
向服务器提交请求的工具(例如 kubectl
)可能会设置自己的默认值,与 API 服务器默认使用的 Warn
校验层级不同。
kubectl
工具使用 --validate
标志设置字段校验层级。
该字段可取的值包括 ignore
、warn
和 strict
,同时还接受值 true
(相当于 strict
)和
false
(相当于 ignore
)。
kubectl 默认的校验设置是 --validate=true
,这意味着执行严格的服务端字段校验。
当 kubectl 无法连接到启用字段校验的 API 服务器(Kubernetes 1.27 之前的 API 服务器)时,
将回退到使用客户端的字段校验。
客户端校验将在 kubectl 未来版本中被完全删除。
说明:
在 Kubernetes 1.25 之前,kubectl --validate
是用来开启或关闭客户端校验的布尔标志的命令。
试运行
特性状态:
Kubernetes v1.19 [stable]
(enabled by default: true)
当你使用可以修改资源的 HTTP 动词(POST
、PUT
、PATCH
和 DELETE
)时,
你可以在 试运行(dry run) 模式下提交你的请求。
试运行模式有助于通过典型的请求阶段(准入链、验证、合并冲突)评估请求,直到将对象持久化到存储中。
请求的响应正文尽可能接近非试运行响应。Kubernetes 保证试运行请求不会被持久化存储或产生任何其他副作用。
发起试运行请求
通过设置 dryRun
查询参数触发试运行。此参数是一个字符串,用作枚举,唯一可接受的值是:
- [未设置值]
- 允许副作用。你可以使用
?dryRun
或 ?dryRun&pretty=true
之类的查询字符串请求此操作。
响应是最终会被持久化的对象,或者如果请求不能被满足则会出现一个错误。 All
- 每个阶段都正常运行,除了防止副作用的最终存储阶段。
当你设置 ?dryRun=All
时,将运行任何相关的准入控制器,
验证准入控制器检查经过变更的请求,针对 PATCH
请求执行合并、设置字段默认值等操作,并进行模式验证。
更改不会持久化到底层存储,但本应持久化的最终对象仍会与正常状态代码一起返回给用户。
如果请求的非试运行版本会触发具有副作用的准入控制器,则该请求将失败,而不是冒不希望的副作用的风险。
所有内置准入控制插件都支持试运行。
此外,准入 Webhook 还可以设置配置对象
的 sideEffects
字段为 None
,借此声明它们没有副作用。
说明:
如果 webhook 确实有副作用,则应该将 sideEffects
字段设置为 “NoneOnDryRun”。
如果还修改了 webhook 以理解 AdmissionReview 中的 DryRun 字段,
并防止对标记为试运行的任何请求产生副作用,则该更改是适当的。
这是一个使用 ?dryRun=All
的试运行请求的示例:
POST /api/v1/namespaces/test/pods?dryRun=All
Content-Type: application/json
Accept: application/json
响应会与非试运行模式请求的响应看起来相同,只是某些生成字段的值可能会不同。
生成值
对象的某些值通常是在对象被写入数据库之前生成的。很重要的一点是不要依赖试运行请求为这些字段所设置的值,
因为试运行模式下所得到的这些值与真实请求所获得的值很可能不同。这类字段有:
name
:如果设置了 generateName
字段,则 name
会获得一个唯一的随机名称creationTimestamp
/ deletionTimestamp
:记录对象的创建/删除时间UID
:唯一标识对象,
取值随机生成(非确定性)resourceVersion
:跟踪对象的持久化(存储)版本- 变更性准入控制器所设置的字段
- 对于
Service
资源:kube-apiserver
为 Service
对象分配的端口和 IP 地址
试运行的授权
试运行和非试运行请求的鉴权是完全相同的。因此,要发起一个试运行请求,
你必须被授权执行非试运行请求。
例如,要在 Deployment 对象上试运行 patch 操作,你必须具有对 Deployment 执行 patch 操作的访问权限,
如下面的 RBAC 规则所示:
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["patch"]
参阅鉴权概述以了解鉴权细节。
更新现有资源
Kubernetes 提供了多种更新现有对象的方式。
你可以阅读选择更新机制以了解哪种方法可能最适合你的用例。
你可以使用 HTTP PUT 覆盖(update)ConfigMap 等现有资源。
对于 PUT 请求,客户端需要指定 resourceVersion
(从要更新的对象中获取此项)。
Kubernetes 使用该 resourceVersion
信息,这样 API 服务器可以检测丢失的更新并拒绝对集群来说过期的客户端所发出的请求。
如果资源已发生变化(即客户端提供的 resourceVersion
已过期),API 服务器将返回 409 Conflict
错误响应。
客户端除了发送 PUT 请求之外,还可以发送指令给 API 服务器对现有资源执行 patch 操作。
patch 通常适用于客户端希望进行的更改并不依赖于现有数据的场景。
需要有效检测丢失更新的客户端应该考虑根据现有 resourceVersion
来进行有条件的请求
(HTTP PUT 或 HTTP PATCH),并在存在冲突时作必要的重试。
Kubernetes API 支持四种不同的 PATCH 操作,具体取决于它们所对应的 HTTP Content-Type
标头:
application/apply-patch+yaml
- Server Side Apply YAML(基于 YAML 的 Kubernetes 扩展)。
所有 JSON 文档都是有效的 YAML,因此你也可以使用此媒体类型提交 JSON。
更多细节参阅服务器端应用序列化。
对于 Kubernetes,这一 PATCH 请求在对象不存在时成为 create 操作;在对象已存在时成为 patch 操作。
application/json-patch+json
- JSON Patch,如 RFC6902 中定义。
JSON Patch 是对资源执行的一个操作序列;例如
{"op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ]}
。
对于 Kubernetes,这一 PATCH 请求即是一个 patch 操作。使用 application/json-patch+json
的 patch 可以包含用于验证一致性的条件,
如果这些条件不满足,则允许此操作失败(例如避免丢失更新)。
application/merge-patch+json
- JSON Merge Patch,如 RFC7386 中定义。
JSON Merge Patch 实质上是资源的部分表示。提交的 JSON 与当前资源合并以创建一个新资源,然后将其保存。
对于 Kubernetes,这个 PATCH 请求是一个 patch 操作。
application/strategic-merge-patch+json
- Strategic Merge Patch(基于 JSON 的 Kubernetes 扩展)。
Strategic Merge Patch 是 JSON Merge Patch 的自定义实现。
你只能在内置 API 或具有特殊支持的聚合 API 服务器中使用 Strategic Merge Patch。
你不能针对任何使用 CustomResourceDefinition
定义的 API 来使用
application/strategic-merge-patch+json
。说明:
Kubernetes 服务器端应用机制已取代 Strategic Merge Patch。
Kubernetes 的服务器端应用功能允许控制平面跟踪新创建对象的托管字段。
服务端应用为管理字段冲突提供了清晰的模式,提供了服务器端 apply 和 update 操作,
并替换了 kubectl apply
的客户端功能。
对于服务器端应用,Kubernetes 在对象尚不存在时将请求视为 create,否则视为 patch。
对于其他在 HTTP 层面使用 PATCH 的请求,逻辑上的 Kubernetes 操作始终是 patch。
更多细节参阅服务器端应用。
选择更新机制
HTTP PUT 替换现有资源
update(HTTP PUT
)操作实现简单且灵活,但也存在一些缺点:
- 你需要处理对象的
resourceVersion
在客户端读取和写回之间发生变化所造成的冲突。
Kubernetes 总是会检测到此冲突,但你作为客户端开发者需要实现重试机制。 - 如果你在本地解码对象,可能会意外丢失字段。例如你在使用 client-go 时,
可能会收到客户端不知道如何处理的一些字段,而客户端在构造更新时会将这些字段丢弃。
- 如果对象上存在大量争用(即使是在你不打算编辑的某字段或字段集上),你可能会难以发送更新。
对于体量较大或字段较多的对象,这个问题会更为严重。
使用 JSON Patch 的 HTTP PATCH
patch 更新很有帮助,因为:
- 由于你只发送差异,所以你在
PATCH
请求中需要发送的数据较少。 - 你可以依赖于现有值进行更改,例如将特定字段的值复制到注解中。
- 与 update(HTTP
PUT
)不同,即使存在对无关字段的频繁更改,你的更改也可以立即生效:
你通常无需重试。- 如果你要特别小心避免丢失更新,仍然可能需要指定
resourceVersion
(以匹配现有对象)。 - 编写一些重试逻辑以处理错误仍然是一个良好的实践。
- 你可以通过测试条件来精确地构造特定的更新条件。
例如,如果现有值与你期望的值匹配,你可以递增计数器而无需读取它。
即使自上次写入以来对象以其他方式发生了更改,你也可以做到这一点而不会遇到丢失更新的风险。
(如果测试条件失败,你可以回退为读取当前值,然后写回更改的数字)。
然而:
- 你需要更多本地(客户端)逻辑来构建补丁;如果你拥有实现了 JSON Patch 的库,
或者针对 Kubernetes 生成特定的 JSON Patch 的库,将非常有帮助。
- 作为客户端软件的开发者,你在构建补丁(HTTP 请求体)时需要小心,避免丢弃字段(操作顺序很重要)。
使用服务器端应用的 HTTP PATCH
服务器端应用(Server-Side Apply)具有一些明显的优势:
- 仅需一次轮询:通常无需先执行
GET
请求。- 并且你仍然可以检测到意外更改造成的冲突
- 合适的时候,你可以选择强制覆盖冲突
- 客户端实现简单。
- 你可以轻松获得原子级别的 create 或 update 操作,无需额外工作
(类似于某些 SQL 语句中的
UPSERT
)。
然而:
- 服务器端应用不适合依赖对象当前值的字段更改。
- 你只能更新对象。Kubernetes HTTP API 中的某些资源不是对象(它们没有
.metadata
字段),
并且服务器端应用只能用于 Kubernetes 对象。
资源版本
资源版本是标识服务器内部对象版本的字符串。
客户端可以使用资源版本来确定对象何时更改,
或者在获取、列出和监视资源时表达数据一致性要求。
资源版本必须被客户端视为不透明的,并且未经修改地传回服务器。
你不能假设资源版本是数字的或可排序的。
API 客户端只能比较两个资源版本的相等性(这意味着你不能比较资源版本的大于或小于关系)。
客户端在资源中查找资源版本,这些资源包括来自用于 watch 的响应流资源,或者使用 list 枚举的资源。
v1.meta/ObjectMeta -
资源的 metadata.resourceVersion
值标明该实例上次被更改时的资源版本。
v1.meta/ListMeta - 资源集合即
list 操作的响应)的 metadata.resourceVersion
所标明的是 list 响应被构造时的资源版本。
查询字符串中的 resourceVersion
参数
get、list 和 watch 操作支持 resourceVersion
参数。
从 v1.19 版本开始,Kubernetes API 服务器支持 list 请求的 resourceVersionMatch
参数。
API 服务器根据你请求的操作和 resourceVersion
的值对 resourceVersion
参数进行不同的解释。
如果你设置 resourceVersionMatch
那么这也会影响匹配发生的方式。
get 和 list 语义
对于 get 和 list 而言,resourceVersion
的语义为:
get:
如果你请求 API 服务器无法识别的资源版本,
kube-apiserver 还会使用 “Too large resource version” 消息额外标识其错误响应。