本文档面向希望深入了解 Kubernetes API 结构的用户,以及希望扩展 Kubernetes API 的开发人员。有关使用 kubectl 使用资源的介绍,请参阅对象管理概述。
目录
- 類型(類型)
- 不同的表述
- 资源动词
- 简称和类别
- 幂等性
- 可选与必需
- 违约
- 并发控制和一致性
- 序列化格式
- 单位
- 选择字段
- 对象引用
- HTTP 状态代码
- 响应状态类型
- 活动
- 命名约定
- 标签、选择器和注释约定
- WebSockets 和 SPDY
- 验证
- 自动资源分配和释放
- 表示分配的值
Kubernetes API (以及生态系统中的相关 API)的约定旨在简化客户端开发并确保能够实现在各种用例中一致工作的配置机制。
Kubernetes API 的一般样式是 RESTful – 客户端通过标准 HTTP 动词(POST、PUT、DELETE 和 GET)创建、更新、删除或检索对象的描述 – 这些 API 优先接受和返回 JSON。Kubernetes 还为非标准动词公开了其他端点,并允许使用其他内容类型。服务器接受和返回的所有 JSON 都有一个架构,由“kind”和“apiVersion”字段标识。如果存在相关的 HTTP 标头字段,它们应该镜像 JSON 字段的内容,但信息不应仅在 HTTP 标头中表示。
定义以下术语:
- 特定对象模式的名称(例如,“猫”和“狗”类型具有不同的属性和特性)
- 资源是系统实体的表示,以 JSON 形式通过 HTTP 发送或检索到服务器。资源通过以下方式公开:
- 集合 – 同一类型的资源列表,可以查询
- 元素 – 可通过 URL 寻址的单个资源
- API 将一组一起暴露的资源分组,并在“apiVersion”字段中以“GROUP/VERSION”形式暴露版本,例如“policy.k8s.io/v1”。
每种资源通常接受并返回单一类型的数据。一种类型可能被多种反映特定用例的资源接受或返回。例如,类型“Pod”公开为“pods”资源,允许最终用户创建、更新和删除 pod,而单独的“pod 状态”资源(作用于“Pod”类型)允许自动化流程更新该资源中的部分字段。
资源在 API 组中绑定在一起 – 每个组可能有一个或多个独立于其他 API 组发展的版本,并且组内的每个版本都有一个或多个资源。组名通常采用域名形式 – Kubernetes 项目保留对空组、所有单词名称(“extensions”、“apps”)以及以“*.k8s.io”结尾的任何组名的使用权。选择组名时,我们建议选择您的组或组织拥有的子域,例如“widget.mycompany.com”。
版本字符串应该与 DNS_LABEL 格式匹配。
资源集合应全部小写且为复数,而种类应采用驼峰命名法且为单数。组名必须小写且为有效的 DNS 子域。
類型(類型)
种类分为三类:
- 对象代表系统中的持久实体。创建 API 对象是意图的记录 – 一旦创建,系统将努力确保资源存在。所有 API 对象都有通用的元数据。一个对象可能有多个资源,客户端可以使用这些资源执行创建、更新、删除或获取的特定操作。例子:
Pod
,ReplicationController
,Service
,Namespace
,Node
。 - 列表是一种或多种(偶尔)资源的集合。列表类型的名称必须以“List”结尾。列表具有一组有限的通用元数据。所有列表都使用必需的“items”字段来包含它们返回的对象数组。任何具有“items”字段的类型都必须是列表类型。系统中定义的大多数对象都应具有一个返回完整资源集的端点,以及零个或多个返回完整列表子集的端点。某些对象可能是单例(当前用户、系统默认)并且可能没有列表。此外,所有返回带有标签的对象的列表都应支持标签过滤(参见标签文档),并且大多数列表都应支持按字段过滤(参见 字段文档)。例子:
PodList
,ServiceList
,NodeList
。
请注意,kubectl
和其他工具有时会将资源集合输出为kind: List
。请记住,这kind: List
不是 Kubernetes API 的一部分;它公开了这些工具中客户端代码的实现细节,用于处理混合资源组。
- 简单类型用于对对象和非持久实体执行特定操作。由于其范围有限,它们具有与列表相同的一组有限的通用元数据集。例如,当发生错误时返回“状态”类型,并且不会在系统中保留。许多简单资源都是“子资源”,它们根植于特定资源的 API 路径。当资源希望公开与单个资源紧密相关的替代操作或视图时,它们应该使用新的子资源来实现。常见的子资源包括:
/binding
:用于将代表用户请求的资源(例如,Pod,PersistentVolumeClaim)绑定到集群基础设施资源(例如,Node,PersistentVolume)。/status
:用于仅写入status
资源的一部分。例如,/pods
端点仅允许更新metadata
和spec
,因为这些反映了最终用户的意图。自动化流程应该能够通过将更新的 Pod 类型发送到服务器的“/pods/<name>/status”端点来修改用户可查看的状态 – 备用端点允许对更新应用不同的规则,并适当限制访问。/scale
:用于以独立于特定资源模式的方式读取和写入资源的计数。
proxy
和portforward
提供对集群资源的访问,如 访问集群中所述。
标准 REST 动词(定义如下)必须返回单个 JSON 对象。某些 API 端点可能偏离严格的 REST 模式,并返回非单个 JSON 对象的资源,例如 JSON 对象流或非结构化文本日志数据。
所有 API 组都使用一组通用的“元”API 对象,因此它们被视为名为 的 API 组的一部分meta.k8s.io
。这些类型可能独立于使用它们的 API 组而发展,并且 API 服务器可能允许以通用形式处理它们。示例包括ListOptions
、 DeleteOptions
、List
、Status
、WatchEvent
和Scale
。由于历史原因,这些类型是每个现有 API 组的一部分。配额、垃圾收集、自动缩放器等通用工具和 kubectl 等通用客户端利用这些类型来定义跨不同资源类型的一致行为,就像编程语言中的接口一样。
术语“种类”是为这些“顶级”API 类型保留的。术语“类型”应用于区分对象或子对象内的子类别。
资源
API 返回的所有 JSON 对象必须具有以下字段:
- kind:标识此对象应具有的架构的字符串
- apiVersion:一个字符串,用于标识对象应具有的架构版本
这些字段是正确解码对象所必需的。服务器可能默认从指定的 URL 路径填充这些字段,但客户端可能需要知道这些值才能构建 URL 路径。
对象
元数据
每个对象类型必须在名为“元数据”的嵌套对象字段中具有以下元数据:
- 命名空间:命名空间是对象被细分为的 DNS 兼容标签。默认命名空间为“default”。有关更多信息,请参阅 命名空间文档。
- name:在当前命名空间中唯一标识此对象的字符串(请参阅标识符文档)。检索单个对象时,此值将在路径中使用。
- uid:一个在时间和空间上唯一的值(通常是 RFC 4122 生成的标识符,请参阅标识符文档),用于区分已删除和重新创建的同名对象
每个对象都应该在名为“元数据”的嵌套对象字段中具有以下元数据:
- resourceVersion:一个字符串,用于标识此对象的内部版本,客户端可以使用它来确定对象何时发生更改。客户端必须将此值视为不透明的,并将其原封不动地传回服务器。客户端不应假设资源版本在命名空间、不同类型的资源或不同的服务器之间具有意义。(有关更多详细信息,请参阅 下文的并发控制。)
- 代数:表示所需状态的特定代数的序列号。由系统设置,并按资源单调递增。可以进行比较,例如 RAW 和 WAW 一致性。
- creationTimestamp:一个字符串,表示对象创建的日期和时间的 RFC 3339 日期
- 删除时间标记:一个字符串,表示资源将被删除的日期和时间(RFC 3339 日期)。当用户请求正常删除时,服务器会设置此字段,客户端无法直接设置。除非对象已设置终结器,否则资源将在此字段中的时间之后被删除(不再显示在资源列表中,并且无法通过名称访问)。如果设置了终结器,则对象的删除将至少推迟到终结器被移除为止。设置删除时间标记后,此值不能取消设置或设置为更远的时间,但可能会缩短或在此时间之前删除资源。
- 标签:字符串键和值的映射,可用于组织和分类对象(请参阅标签文档)
- 注释:字符串键和值的映射,可由外部工具用来存储和检索有关此对象的任意元数据(请参阅 注释文档)
标签旨在供最终用户进行组织(选择与此标签查询匹配的 pod)。注释使第三方自动化和工具能够使用其他元数据来装饰对象以供自己使用。
规格和状态
按照惯例,Kubernetes API 会区分对象期望状态的规范(名为 的嵌套对象字段spec
)和对象当前状态(名为 的嵌套对象字段 status
)。规范是对期望状态的完整描述,包括用户提供的配置设置、 系统扩展的默认值以及其他生态系统组件(例如调度程序、自动缩放器)创建后初始化或以其他方式更改的属性,并与 API 对象一起持久保存在稳定存储中。如果删除规范,则对象将从系统中清除。
总结status
了系统中对象的当前状态,通常通过自动化流程与对象一起保存,但也可能动态生成。作为一般准则,中的字段status
应该是实际状态的最新观察值,但它们可能包含诸如分配结果或响应对象的执行的类似操作等信息spec
。有关更多详细信息,请参阅下文。
spec
具有和节的类型status
可以(通常应该)具有不同的授权范围。这允许用户被授予对spec
状态的完全写入访问权限和只读访问权限,而相关控制器被授予对spec
状态的只读访问权限和完全写入访问权限。
当对象的新版本被 POST 或 PUT 时,spec
会立即更新并可用。随着时间的推移,系统将努力使status
符合spec
。无论该节的先前版本如何,系统都会朝着最新版本前进spec
。例如,如果在一个 PUT 中将值从 2 更改为 5,然后在另一个 PUT 中将其降回 3,则系统无需在将 更改status
为 3 之前“接触” 5。换句话说,系统的行为是基于级别的,而不是基于边缘的。这使得在错过中间状态更改的情况下也能实现稳健的行为。
Kubernetes API 也是系统声明式配置模式的基础。为了便于基于级别的操作和声明式配置的表达,规范中的字段应该具有声明性而非命令性的名称和语义 —— 它们代表所需状态,而不是旨在产生所需状态的操作。
对象上的 PUT 和 POST 动词必须忽略这些status
值,以避免status
在读取-修改-写入场景中意外覆盖。/status
必须提供子资源,以使系统组件能够更新其管理的资源的状态。
否则,PUT 要求指定整个对象。因此,如果省略某个字段,则假定客户端想要清除该字段的值。PUT 动词不接受部分更新。可以通过 GET 资源、修改部分规范、标签或注释,然后将其 PUT 回去,来修改对象的一部分。使用此模式时,请参阅 下面的并发控制,了解读取-修改-写入一致性。某些对象可能会公开允许状态改变或对对象执行自定义操作的替代资源表示。
所有表示物理资源(其状态可能与用户的期望意图不同)的对象都应具有spec
和status
。状态不能与用户的期望意图不同对象可能只有spec
,并且可以重命名 spec
为更合适的名称。
同时包含spec
和的对象status
不应包含除标准元数据字段之外的其他顶级字段。
一些未在系统中持久化的对象(例如SubjectAccessReview
和其他 webhook 样式的调用)可能会选择添加spec
并status
封装“调用和响应”模式。spec
是请求(通常是信息请求),status
是响应。对于这些类似 RPC 的对象,唯一的操作可能是 POST,但在提交和响应之间拥有一致的架构可以降低这些客户端的复杂性。
典型状态属性
条件为控制器提供更高级别的状态报告标准机制。它们是一种扩展机制,允许工具和其他控制器收集有关资源的摘要信息,而无需了解特定于资源的状态详细信息。条件应该补充有关控制器写入的对象观察到的状态的更详细信息,而不是取代它。例如,可以通过检查 Deployment 的 、 和 等属性来确定 Deployment 的“可用”条件readyReplicas
。replicas
但是,“可用”条件允许其他组件避免在 Deployment 控制器中重复可用性逻辑。
对象可以报告多个条件,并且将来可能会添加新类型的条件,或者由第三方控制器添加。因此,条件使用对象列表/切片表示,其中每个条件具有相似的结构。此集合应被视为具有键的映射type
。
当条件遵循一些一致的约定时,它们最有用:
- 应添加条件来明确传达用户和组件关心的属性,而不是要求从其他观察中推断出这些属性。一旦定义,条件的含义就不能任意更改 – 它成为 API 的一部分,并且具有与 API 的任何其他部分相同的向后和向前兼容性问题。
- 控制器应在首次访问资源时将其条件应用于该资源,即使资源
status
为未知。这样系统中的其他组件就可以知道该条件存在,并且控制器正在协调该资源。- 并非所有控制器都会遵守之前关于报告“未知”或“错误”值的建议。对于已知条件,不存在条件
status
应被解释为Unknown
,并且通常表示协调尚未完成(或资源状态可能尚未可观察)。
- 并非所有控制器都会遵守之前关于报告“未知”或“错误”值的建议。对于已知条件,不存在条件
- 对于某些条件,
True
表示正常运行,而对于某些条件,False
表示正常运行。(“正常-真”条件有时被称为具有“正极性”,而“正常-假”条件被称为具有“负极性”。)如果不进一步了解条件,就不可能计算出资源条件的一般摘要。 - 条件类型名称应该对人类有意义;一般来说,正极性和负极性都不是推荐的。像“MemoryExhausted”这样的负条件可能比“SufficientMemory”更容易让人理解。相反,“Ready”或“Succeeded”可能比“Failed”更容易理解,因为“Failed=Unknown”或“Failed=False”可能会造成双重否定混淆。
- 条件类型名称应描述资源的当前观察状态,而不是描述当前状态转换。这通常意味着名称应为形容词(“Ready”、“OutOfDisk”)或过去时动词(“Succeeded”、“Failed”),而不是现在时动词(“Deploying”)。可以通过将
status
条件的设置为来指示中间状态Unknown
。- 对于需要很长时间(例如超过 1 分钟)的状态转换,将转换本身视为观察到的状态是合理的。在这些情况下,条件(例如“调整大小”)本身不应是瞬态的,而应使用
True
/False
/Unknown
模式发出信号。这允许其他观察者确定控制器的最后更新,无论成功还是失败。在状态转换无法完成且无法继续协调的情况下,应使用原因和消息来表明转换失败。
- 对于需要很长时间(例如超过 1 分钟)的状态转换,将转换本身视为观察到的状态是合理的。在这些情况下,条件(例如“调整大小”)本身不应是瞬态的,而应使用
- 在为资源设计条件时,拥有一个通用的顶级条件来总结更详细的条件会很有帮助。简单的消费者可能只是查询顶级条件。虽然它们不是一致的标准,但API 设计者可以分别将
Ready
和Succeeded
条件类型用于长时间运行和有界执行对象。
条件应遵循k8s.io/apimachinery/pkg/apis/meta/v1/types.go中包含的标准架构。它应作为状态中的顶级元素包含在内,类似于
// +listType=map // +listMapKey=type // +patchStrategy=merge // +patchMergeKey=type // +optional Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
包括metav1.Conditions
以下字段
// type of condition in CamelCase or in foo.example.com/CamelCase. // +required Type string `json:"type" protobuf:"bytes,1,opt,name=type"` // status of the condition, one of True, False, Unknown. // +required Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status"` // observedGeneration represents the .metadata.generation that the condition was set based upon. // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date // with respect to the current state of the instance. // +optional ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,3,opt,name=observedGeneration"` // lastTransitionTime is the last time the condition transitioned from one status to another. // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. // +required LastTransitionTime Time `json:"lastTransitionTime" protobuf:"bytes,4,opt,name=lastTransitionTime"` // reason contains a programmatic identifier indicating the reason for the condition's last transition. // Producers of specific condition types may define expected values and meanings for this field, // and whether the values are considered a guaranteed API. // The value should be a CamelCase string. // This field may not be empty. // +required Reason string `json:"reason" protobuf:"bytes,5,opt,name=reason"` // message is a human readable message indicating details about the transition. // This may be an empty string. // +required Message string `json:"message" protobuf:"bytes,6,opt,name=message"`
将来可能会添加其他字段。
Reason
必须使用该字段。
条件类型应以 PascalCase 命名。最好使用简短的条件名称(例如“Ready”而不是“MyResourceReady”)。
条件status
值可以是True
、False
或Unknown
。条件的缺失应解释为Unknown
。控制器如何处理 Unknown
取决于所讨论的条件。
围绕条件的思考随着时间的推移而不断发展,因此有几个非规范性的例子被广泛使用。
一般来说,条件值可能会来回变化,但有些条件转换可能是单调的,具体取决于资源和条件类型。但是,条件是观察结果,而不是状态机,我们也没有为对象定义全面的状态机,也没有定义与状态转换相关的行为。该系统是基于级别的,而不是边缘触发的,应该假设一个开放的世界。
振荡条件类型的一个例子是Ready
,它表示对象在上次探测时被认为完全可操作。可能的单调条件可能是Succeeded
。True
状态 Succeeded
表示完成并且资源不再处于活动状态。仍然处于活动状态的对象通常具有Succeeded
状态 的条件Unknown
。
v1 API 中的某些资源包含称为 的字段phase
以及相关的 message
、reason
和其他状态字段。使用 的模式phase
已被弃用。较新的 API 类型应改用条件。阶段本质上是一个状态机枚举字段,它与系统设计原则相矛盾并妨碍了发展,因为添加新的枚举值会破坏向后兼容性。我们不希望客户端从阶段推断隐式属性,而是更喜欢明确公开客户端需要监视的各个条件。条件还有一个好处,那就是可以创建一些在所有资源类型中具有统一含义的条件,同时仍然公开特定资源类型所独有的其他条件。有关更多详细信息和讨论,请参阅#7856 。
在条件类型中,以及在 API 中出现的任何其他地方,Reason
旨在用一个单词、CamelCase 来表示当前状态的原因类别,并且Message
旨在成为人类可读的短语或句子,其中可能包含单个发生的具体细节。 Reason
旨在用于简洁的输出,例如单行 kubectl get
输出,以及总结原因的发生,而 Message
旨在以详细的状态解释(例如输出)呈现给用户kubectl describe
。
历史信息状态(例如,上次转换时间、故障计数)仅以合理的努力提供,并且不保证不会丢失。
状态信息可能很大(尤其是与其他资源集合的大小成比例,例如对其他对象的引用列表 – 见下文)和/或快速变化,例如 资源使用情况,应放入单独的对象中,并可能引用原始对象。这有助于确保 GET 和 watch 对于大多数可能不需要该数据的客户端来说保持合理的效率。
某些资源会报告observedGeneration
,这是generation
负责对资源期望状态的变化采取行动的组件最近观察到的。例如,这可用于确保报告的状态反映最新的期望状态。
对相关对象的引用
对松耦合对象集的引用(例如 由复制控制器监督的 pod)通常最好使用 标签选择器来引用。为了确保各个对象的 GET 在时间和空间上保持有界,这些集合可以通过单独的 API 查询进行查询,但不会在引用对象的状态中展开。
有关特定对象的引用,请参阅对象引用。
status
当引用是一对一的并且不需要频繁更新(特别是以基于边的方式)时,可以允许被引用者对引用者的引用。
命名子对象列表优于映射
在#2004和其他地方讨论过。任何 API 对象中都没有子对象的映射。相反,惯例是使用包含名称字段的子对象列表。这些惯例以及如何更改列表、结构和映射的语义在 Kubernetes文档中有更详细的描述 。
例如:
ports: - name: www containerPort: 80
对阵
ports: www: containerPort: 80
此规则保持不变,即所有 JSON/YAML 键都是 API 对象中的字段。唯一的例外是 API 中的纯映射(目前是标签、选择器、注释、数据),而不是子对象集。
原始类型
- 查看 API 中的类似字段(例如端口、持续时间)并遵循现有字段的约定。
- 不要使用枚举。请改用字符串别名(例如
NodeConditionType
)。 - 所有数字字段都应进行边界检查,包括太小或为负以及太大。
- 所有公共整数字段必须使用 Go
int32
或 Goint64
类型,而不是int
(大小不明确,取决于目标平台)。内部类型可以使用int
。 - 对于整数字段,除非需要表示大于的值,否则最好
int32
使用。请参阅有关限制 和语言兼容性的其他指南。int64
int32
int64
- 不要使用无符号整数,因为不同语言和库对无符号整数的支持不一致。如果是这种情况,只需验证整数是否为非负数即可。
- 所有数字(例如
int32
,int64
)都由 Javascript 和其他一些语言转换为float64
,因此任何预计在大小或精度上超过该数字的字段(例如整数值 > 53 位)都应序列化并作为字符串接受。int64
字段必须进行边界检查以在范围内-(2^53) < x < (2^53)
。 - 尽可能避免使用浮点值,并且永远不要在规范中使用它们。浮点值无法在不发生改变的情况下可靠地往返(编码和重新解码),并且在不同语言和架构中具有不同的精度和表示形式。
- 仔细考虑
bool
字段。许多想法都是从布尔值开始的,但最终趋向于一小组互斥的选项。通过将策略选项明确描述为字符串类型别名(例如TerminationMessagePolicy
)来规划未来的扩展。
常量
某些字段将具有允许值(枚举)的列表。这些值将是字符串,并且它们将采用 CamelCase 格式,首字母大写。示例:,,ClusterFirst
。当使用首字母缩略词或缩写时,首字母缩略词中的每个字母都应大写,例如或 。当使用专有名称或命令行可执行文件的名称作为常量时,专有名称应以一致的大小写表示 – 例如:,,,,,(作为通用概念), (作为命令行可执行文件)。如果使用了像这样混合大小写的专有名称,则应保留为更长的常量,例如。Pending
ClientIP
ClientIP
TCPDelay
systemd
iptables
IPVS
cgroupfs
Docker
docker
eBPF
eBPFDelegation
Kubernetes 中的所有 API 都必须利用这种样式的常量,包括标志和配置文件。如果之前使用了不一致的常量,则新标志应仅采用 CamelCase 格式,并且随着时间的推移,旧标志应更新为接受 CamelCase 值以及不一致的常量。示例:Kubelet 接受具有、 、和--topology-manager-policy
值的标志。此标志应继续接受 、、和。如果向标志添加新值,则应支持这两种形式。none
best-effort
restricted
single-numa-node
None
BestEffort
Restricted
SingleNUMANode
工会
有时,最多只能设置一组字段中的一个。例如,PodSpec 的 [volumes] 字段有 17 个不同的卷类型特定字段,例如nfs
和iscsi
。集合中的所有字段都应为 Optional。
有时,当创建新类型时,API 设计者可能会预计将来会需要联合,即使最初只允许一个字段。在这种情况下,请确保将字段设为可选。 在验证中,如果未设置唯一字段,您仍可能会返回错误。不要为该字段设置默认值。
列表和简单类型
每个列表或简单类型都应该在名为“元数据”的嵌套对象字段中具有以下元数据:
- resourceVersion:一个字符串,用于标识列表中返回的对象的通用版本。客户端必须将此值视为不透明的,并将其原封不动地传回服务器。资源版本仅在单一命名空间中的单一资源类型中有效。
服务器返回的每种简单类型以及发送到服务器的任何必须支持幂等性或乐观并发性的简单类型都应返回此值。由于简单资源通常用作修改对象的输入替代操作,因此简单资源的资源版本应与对象的资源版本相对应。
不同的表述
API 可能以不同的方式为不同的客户端表示单个实体,或者在系统中发生某些转换后转换对象。在这些情况下,一个请求对象可能有两种表示形式,可用作不同的资源或不同的种类。
一个例子是服务,它代表用户将一组具有共同行为和共同端口的 pod 分组的意图。当 Kubernetes 检测到某个 pod 与服务选择器匹配时,该 pod 的 IP 地址和端口将添加到该服务的 Endpoints 资源中。Endpoints 资源仅在服务存在时才存在,但仅公开所选 pod 的 IP 和端口。完整的服务由两个不同的资源表示 – 在用户创建的原始服务资源下以及 Endpoints 资源中。
再举一个例子,“pod 状态”资源可能会接受具有“pod”类型的 PUT,并且对于可以更改哪些字段有不同的规则。
Kubernetes 的未来版本可能会允许 JSON 之外的对象的其他编码。
资源动词
API 资源应该使用传统的 REST 模式:
- GET /<resourceNamePlural> – 检索类型 <resourceName> 的列表,例如 GET /pods 返回 Pod 列表。
- POST /<resourceNamePlural> – 从客户端提供的 JSON 对象创建新资源。
- GET /<resourceNamePlural>/<name> – 检索具有给定名称的单个资源,例如 GET /pods/first 返回名为“first”的 Pod。应为常量时间,并且资源的大小应有限制。
- DELETE /<resourceNamePlural>/<name> – 删除具有给定名称的单个资源。DeleteOptions 可以指定 gracePeriodSeconds,即删除对象前的可选持续时间(以秒为单位)。各个种类可以声明提供默认宽限期的字段,不同种类可能具有不同的种类范围默认宽限期。用户提供的宽限期将覆盖默认宽限期,包括零宽限期(“现在”)。
- DELETE /<resourceNamePlural> – 删除类型 <resourceName> 的列表,例如 DELETE /pods Pod 列表。
- PUT /<resourceNamePlural>/<name> – 使用客户端提供的 JSON 对象更新或创建具有给定名称的资源。是否可以使用 PUT 请求创建资源取决于特定资源的存储策略配置,特别是返回值
AllowCreateOnUpdate()
。大多数内置类型不允许这样做。 - PATCH /<resourceNamePlural>/<name> – 选择性地修改资源的指定字段。请参阅下文了解更多信息。
- GET /<resourceNamePlural>?watch=true – 接收与一段时间内对给定类型的任何资源所作的更改相对应的 JSON 对象流。
PATCH 操作
API 支持三种不同的 PATCH 操作,由其对应的 Content-Type 标头决定:
- JSON 补丁,
Content-Type: application/json-patch+json
- 根据RFC6902中的定义,JSON Patch 是在资源上执行的一系列操作,例如
{"op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ]}
。有关如何使用 JSON Patch 的更多详细信息,请参阅 RFC。
- 根据RFC6902中的定义,JSON Patch 是在资源上执行的一系列操作,例如
- 合并补丁,
Content-Type: application/merge-patch+json
- 根据RFC7386中的定义,合并补丁本质上是资源的部分表示。提交的 JSON 与当前资源“合并”以创建新资源,然后保存新资源。有关如何使用合并补丁的更多详细信息,请参阅 RFC。
- 战略合并补丁,
Content-Type: application/strategic-merge-patch+json
- Strategic Merge Patch 是 Merge Patch 的自定义实现。有关其工作原理以及为何需要引入它的详细说明,请参阅 此处。
简称和类别
资源实施者可以选择在为资源类型发布的发现信息中包含“简称”和类别,客户端在解析模糊的用户调用时可以将其用作提示。
ShortNames() []string
对于编译后的资源,这些由 REST 处理程序和Categories() []string
实现控制。
对于自定义资源,这些由CustomResourceDefinition 中的.spec.names.shortNames
和字段控制。.spec.names.categories
简称
注意:由于短名称发生冲突(彼此冲突或与资源类型冲突)时会出现不可预测的行为,除非 API 审阅者明确允许,否则请勿向内置资源添加新的短名称。请参阅问题 #117742和#108573。
客户端可以使用发现中列出的“简称”作为提示,以解决用户对单个资源的模糊调用。
内置短名称的示例包括:
ds
->apps/v* daemonsets
sts
->apps/v* statefulsets
hpa
->autoscaling/v* horizontalpodautoscalers
例如,仅提供内置 API 类型,kubectl get sts
相当于kubectl get statefulsets.v1.apps
。
短名称匹配的优先级可能低于资源类型的精确匹配,因此如果安装了自定义资源的集群中的自定义资源类型与短名称重叠,则使用短名称会增加出现不一致行为的可能性。
继续上面的例子,如果在集群中安装了 .spec.names.plural
设置为的自定义资源,则会切换到检索自定义资源的实例。sts
kubectl get sts
类别
注意:由于类别与资源类型发生冲突时的行为不一致,并且难以知道何时可以安全地将新资源添加到现有类别,因此除非 API 审阅者明确允许,否则请勿将新类别添加到内置资源。请参阅问题#7547 #42885以及添加到“全部”类别的注意事项, 了解遇到的困难示例。
客户端可以使用发现中列出的类别作为提示来解析用户对多个资源的调用。
内置类别及其映射的资源的示例包括:
api-extensions
apiregistration.k8s.io/v* apiservices
admissionregistration.k8s.io/v* mutatingwebhookconfigurations
admissionregistration.k8s.io/v* validatingwebhookconfigurations
admissionregistration.k8s.io/v* validatingadmissionpolicies
admissionregistration.k8s.io/v* validatingadmissionpolicybindings
apiextensions.k8s.io/v* customresourcedefinitions
all
v1 pods
v1 replicationcontrollers
v1 services
apps/v* daemonsets
apps/v* deployments
apps/v* replicasets
apps/v* statefulsets
autoscaling/v* horizontalpodautoscalers
batch/v* cronjobs
batch/v* jobs
有了上述类别,并且只提供内置 API 类型,kubectl get all
就相当于 kubectl get pods.v1.,replicationcontrollers.v1.,services.v1.,daemonsets.v1.apps,deployments.v1.apps,replicasets.v1.apps,statefulsets.v1.apps,horizontalpodautoscalers.v2.autoscaling,cronjobs.v1.batch,jobs.v1.batch,
。
幂等性
所有兼容的 Kubernetes API 都必须支持“名称幂等性”,并在发出 POST 请求以发布与系统中现有对象同名的对象时以 HTTP 状态代码 409 响应。有关详细信息,请参阅 标识符文档 。
可以使用 来请求系统生成的名称metadata.generateName
。GenerateName 表示在持久化名称之前,服务器应将其设置为唯一。字段的非空值表示服务器应尝试使名称唯一(并且返回给客户端的名称将与传递的名称不同)。如果未提供 Name 字段,则此字段的值将与服务器上的随机后缀相结合。提供的值必须符合 Name 规则,并且可能会被后缀的长度截断。如果指定了此字段,并且 Name 不存在,则服务器将返回带有 Reason 的 409 AlreadyExists
,并且客户端应重试(至少等待 Retry-After 标头(如果存在)中指示的时间)。
可选与必需
字段必须是可选的或必填的。
可选字段具有以下属性:
+optional
在 Go 中它们有注释标签。- 它们是 Go 定义中的指针类型(例如
AwesomeFlag *SomeFlag
)或具有内置nil
值(例如映射和切片)。 - API 服务器应该允许在未设置此字段的情况下 POST 和 PUT 资源。
在大多数情况下,可选字段也应具有omitempty
struct 标记(该 omitempty
选项指定如果字段具有空值,则应从 json 编码中省略该字段)。但是,如果您希望对未提供的可选字段和提供空值的可选字段使用不同的逻辑,请不要使用omitempty
(例如kubernetes/kubernetes#34641)。
请注意,为了向后兼容,任何具有omitempty
结构标记的字段都将被视为可选的,但是这可能会在将来发生变化,因此+optional
强烈建议使用注释标记。
必填字段具有相反的属性,即:
- 他们没有
+optional
评论标签。 - 它们没有
omitempty
结构标签。 - 它们不是 Go 定义中的指针类型(例如
AnotherFlag SomeFlag
)。 - API 服务器不应允许在未设置此字段的情况下 POST 或 PUT 资源。
使用+optional
或omitempty
标签会导致 OpenAPI 文档反映该字段是可选的。
使用指针可以区分该类型的未设置值和零值。在某些情况下,原则上,可选字段不需要指针,因为零值是被禁止的,因此意味着未设置。代码库中有这样的例子。但是:
- 对于实现者来说,预测所有需要将空值与零值区分开的情况可能很困难
- 即使指定了 omitempty,结构也不会从编码器输出中省略,这很混乱;
- 对于 Go 语言客户端的用户以及使用相应类型的任何其他客户端来说,使用指针始终意味着可选,这更加清晰
因此,我们要求指针始终与没有内置值的可选字段一起使用nil
。
违约
通常,我们希望在 API 中明确表示默认值,而不是断言“未指定的字段将获得默认行为”。这很重要,因为:
- 在较新的 API 版本中,默认值可能会发生变化
- 存储的配置描述了完整的期望状态,使系统更容易确定如何实现该状态,也使用户知道预期会发生什么
创建或更新(包括修补和应用)资源时,有 3 种不同的方式可以应用默认值:
- 静态:根据请求的 API 版本以及资源中可能的其他字段,可以在 API 调用期间为字段分配值
- 准入控制:根据配置的准入控制器以及集群内外的其他可能状态,可以在 API 调用期间为字段分配值
- 控制器:API 调用完成后,可以对资源进行任意更改(在允许的范围内)
在决定使用哪种机制和管理语义时需要小心谨慎。
静态默认值
静态默认值特定于每个 API 版本。使用“v1”API 创建对象时应用的默认字段值可能与使用“v2”API 时应用的值不同。在大多数情况下,这些值由 API 版本定义为文字值(例如“如果未指定此字段,则默认为 0”)。
在某些情况下,这些值可能是有条件的或确定性地从其他字段派生而来(例如,“如果 otherField 是 X,则此字段默认为 0”或“此字段默认为 otherField 的值”)。请注意,这种派生默认值在更新时存在危险 – 如果“其他”字段发生变化,则派生字段可能也必须更改。静态默认逻辑不知道更新,也没有“以前的值”的概念,这意味着这种字段间关系成为用户的问题 – 他们必须同时更新他们关心的字段和“其他”字段。
在极少数情况下,这些值可能从某个池中分配或由其他方法确定(例如,服务的 IP 和 IP 系列相关字段需要考虑其他配置设置)。
在解码版本化数据时,API 服务器会同步应用这些值。对于 CREATE 和 UPDATE 操作,这相当简单 – 当 API 服务器收到(版本化)请求时,会立即应用默认值,然后再进行任何进一步处理。当 API 调用完成时,所有静态默认值都已设置并存储。资源的后续 GET 将明确包含默认值。但是,从存储中读取对象(即 GET 操作)时,静态默认值也适用。这意味着当有人 GET 一个“较旧”的存储对象时,自该对象存储以来添加到 API 中的任何字段都将被设置为默认值,并根据存储的 API 版本返回。
对于逻辑上必需的值,但其值对大多数用户来说都适用,静态默认值是最佳选择。静态默认值不得考虑除操作对象之外的任何状态(服务 API 的复杂性就是原因的一个例子)。
可以使用+default=
标签在字段上指定默认值。原始值将直接分配,而结构将经过 JSON 解组过程。omitempty
如果没有分配默认值,没有 json 标签的字段将默认为其相应类型的零值。
请参阅默认文档 以了解更多信息。
准入控制默认值
在某些情况下,设置一个不是从相关对象派生的默认值很有用。例如,在创建 PersistentVolumeClaim 时,必须指定存储类。对于许多用户来说,最好的答案是“集群管理员决定的默认值”。StorageClass 是与 PersistentVolumeClaim 不同的 API,哪一个被标记为默认值可能会随时更改。因此,这不符合静态默认值的条件。
相反,我们可以提供内置准入控制器或 MutatingWebhookConfiguration。与静态默认值不同,这些默认值在决定默认值时可能会考虑外部状态(例如 StorageClass 对象上的注释),并且必须处理竞争条件等问题(例如,StorageClass 被指定为默认值,但准入控制器尚未看到该更新)。这些准入控制器是严格可选的,可以禁用。因此,以这种方式初始化的字段必须是严格可选的。
与静态默认值一样,这些默认值与相关 API 操作同步运行,当 API 调用完成时,所有静态默认值都已设置。资源的后续 GET 将明确包含默认值。
控制器分配的默认值(又称延迟初始化)
后期初始化是指在创建/更新对象(异步)后,系统控制器设置资源字段。例如,调度程序pod.spec.nodeName
在创建 pod 后设置该字段。称其为“默认”有点牵强,但由于它非常常见且有用,因此将其包括在此处。
与准入控制默认值一样,这些控制器在决定设置哪些值时可能会考虑外部状态,必须处理竞争条件,并且可以禁用。以这种方式初始化的字段必须严格为可选(这意味着观察者将看到未设置这些字段的对象,这是允许的并且语义上是正确的)。
与所有控制器一样,必须注意不要破坏不相关的字段或值(例如数组中的值)。建议使用 patch 或 apply 机制之一来促进控制器的组合和并发。
哪些情况可能会被违约
所有形式的违约都应仅进行以下类型的修改:
- 设置先前未设置的字段
- 向地图添加键
- 向具有可合并语义的数组添加值(类型定义中的
+listType=map
标签或属性)patchStrategy:"merge"
特别是我们永远不想改变或覆盖用户提供的值。如果他们请求了无效的内容,他们应该得到一个错误。
这些规则确保:
- 用户(具有足够权限)可以通过明确设置原本默认的字段来覆盖任何系统默认行为
- 用户的更新可以与默认值合并
PUT 操作注意事项
一旦创建了对象并应用了默认值,随着时间的推移,更新是很常见的。Kubernetes 提供了几种更新对象的方法,这些方法保留了除正在更新的字段之外的字段中的现有值(例如战略合并修补程序和服务器端应用)。然而,还有一种不太明显的更新对象的方法,这种方法可能与默认值产生不良交互 – PUT(又名kubectl replace
)。
目标是,对于给定的输入(例如 YAML 文件),对现有对象执行 PUT 应产生与使用该输入创建对象相同的结果。使用相同输入第二次调用 PUT 应是幂等的,并且不应更改资源。即使是读取-修改-写入循环,在版本偏差的情况下也不是完美的解决方案。
当使用 PUT 更新对象时,API 服务器将看到具有先前分配的默认值的“旧”对象和具有新分配的默认值的“新”对象。对于静态默认值,如果 CREATE 和 PUT 使用不同的 API 版本,则这可能是一个问题。例如,API 的“v1”可能将字段默认为false
,而“v2”将其默认为true
。如果对象是通过 API v1(字段 = false
)创建的,然后通过 API v2 替换,则该字段将尝试更改为true
。当值是从相关对象之外的源(例如服务 IP)分配或派生时,这也可能是一个问题。
对于某些 API,这是可以接受的,也是可操作的。对于其他 API,验证可能会禁止这样做。在后一种情况下,用户将收到有关尝试更改其 YAML 中不存在的字段的错误。这在添加新字段时尤其危险 – 较旧的客户端甚至可能不知道该字段的存在,甚至导致读取-修改-写入循环失败。虽然这是“正确的”(从某种意义上说,这确实是他们使用 PUT 要求的),但它没有帮助,并且是一种糟糕的用户体验。
当添加具有静态或准入控制默认值的字段时,必须考虑到这一点。如果字段在创建后是不可变的,请考虑添加逻辑,以便在“未设置”时手动将“旧”对象的值“修补”到“新”对象中,而不是返回错误或分配不同的值(例如服务 IP)。这通常是用户的意思,即使这不是他们所说的。这可能需要以不同的方式设置默认值(例如,在理解更新的注册表代码中,而不是在不理解更新的版本化默认代码中)。请小心检测并报告指定了“新”值但与“旧”值不同的合法错误。
对于控制器默认字段,情况更加令人不快。控制器没有机会在提交 API 操作之前“修补”该值。如果允许“取消设置”值,则将保存该值,并且将通知任何监视客户端。如果不允许“取消设置”值或不允许突变,则用户将收到错误,我们对此无能为力。
并发控制和一致性
Kubernetes 利用资源版本的概念来实现乐观并发。所有 Kubernetes 资源的元数据中都有一个“resourceVersion”字段。此 resourceVersion 是一个字符串,用于标识对象的内部版本,客户端可以使用它来确定对象何时发生更改。当记录即将更新时,会根据预先保存的值检查其版本,如果不匹配,则更新失败并显示 StatusConflict(HTTP 状态代码 409)。
每次修改对象时,服务器都会更改 resourceVersion。如果 PUT 操作中包含 resourceVersion,系统将通过验证 resourceVersion 的当前值是否与指定值匹配来验证在读取/修改/写入周期中是否还有其他成功的资源变更。
resourceVersion 目前由etcd 的 mod_revision支持。但是,需要注意的是,应用程序不应依赖于 Kubernetes 维护的版本控制系统的实现细节。我们可能会在未来更改 resourceVersion 的实现,例如将其更改为时间戳或每个对象计数器。
客户端了解 resourceVersion 预期值的唯一方法是从服务器接收该值以响应先前的操作(通常是 GET)。客户端必须将此值视为不透明的,并将其原封不动地传回服务器。客户端不应假设资源版本在命名空间、不同类型的资源或不同的服务器之间具有意义。目前,resourceVersion 的值设置为与 etcd 的序列器匹配。您可以将其视为 API 服务器可用于对请求进行排序的逻辑时钟。但是,我们预计 resourceVersion 的实现将来会发生变化,例如在我们按类型和/或命名空间对状态进行分片或移植到另一个存储系统的情况下。
如果发生冲突,此时正确的客户端操作是再次获取资源,重新应用更改,然后尝试再次提交。此机制可用于防止以下争用:
Client #1 Client #2
GET Foo GET Foo
Set Foo.Bar = "one" Set Foo.Baz = "two"
PUT Foo PUT Foo
当这些序列并行发生时,对 Foo.Bar 的更改或对 Foo.Baz 的更改可能会丢失。
另一方面,当指定 resourceVersion 时,其中一个 PUT 将失败,因为无论哪个写入成功都会改变 Foo 的 resourceVersion。
resourceVersion 将来可用作其他操作(例如 GET、DELETE)的先决条件,例如在存在缓存的情况下实现读写一致性。
“监视”操作使用查询参数指定 resourceVersion。它用于指定开始监视指定资源的点。这可用于确保在资源(或资源列表)的 GET 与后续监视之间不会错过任何变化,即使资源的当前版本较新。这是目前列表操作(集合上的 GET)返回 resourceVersion 的主要原因。
序列化格式
API 可能会响应 Accept 标头或在替代端点下返回任何资源的替代表示,但 API 响应的输入和输出的默认序列化必须是 JSON。
内置资源也接受 protobuf 编码。由于 proto 不是自描述的,因此有一个信封包装器来描述内容的类型。
所有日期都应序列化为 RFC3339 字符串。
单位
单位必须在字段名称中明确显示(例如timeoutSeconds
),或者必须指定为值的一部分(例如resource.Quantity
)。目前我们使用约定的持续时间,但哪种方法更可取尚待确定fooSeconds
。
Duration 字段必须表示为整数字段,单位是字段名称的一部分(例如leaseDurationSeconds
)。我们不在 API 中使用 Duration,因为这需要客户端实现与 go 兼容的解析。
选择字段
某些 API 可能需要识别 JSON 对象中的哪个字段无效,或者引用要从单独资源中提取的值。当前建议使用标准 JavaScript 语法来访问该字段,假设 JSON 对象已转换为 JavaScript 对象,不带前导点,例如metadata.name
。
例子:
- 在数组“fields”的第二项中查找对象“state”中的字段“current”:
fields[1].state.current
对象引用
命名空间类型上的对象引用通常应仅引用同一命名空间中的对象。由于命名空间是安全边界,因此跨命名空间引用可能会产生意外影响,包括:
- 将有关一个命名空间的信息泄漏到另一个命名空间。将状态消息甚至有关引用对象的内容片段放在原始位置是很自然的。这是一个跨命名空间的问题。
- 可能入侵其他命名空间。引用通常会提供对所引用信息的访问权限,因此,在没有额外检查权限或从相关命名空间中选择加入的情况下,能够跨命名空间表达“给我那边的那个”是危险的。
- 一方无法解决的引用完整性问题。从命名空间/A 引用命名空间/B 并不意味着有权控制另一个命名空间。这意味着您可以引用您无法创建或更新的内容。
- 删除语义不明确。如果命名空间资源被其他命名空间引用,删除引用的资源是否会导致移除,还是应强制保留引用的资源。
- 创建时语义不明确。如果引用的资源是在引用之后创建的,则无法知道它是预期的资源还是使用相同名称创建的其他资源。
内置类型和所有者引用不支持跨命名空间引用。如果非内置类型选择进行跨命名空间引用,则应明确描述上述边缘情况的语义,并解决权限问题。这可以通过双重选择加入(推荐人和被推荐人都选择加入)或在准入时执行二次权限检查来实现。
参考字段的命名
参考字段的名称应采用“{field}Ref”格式,后缀始终包含“Ref”。
“{field}”组件的命名应表明引用的目的。例如,端点中的“targetRef”表示对象引用指定了目标。
让“{field}”组件指示资源类型是可以的。例如,引用机密时使用“secretRef”。但是,如果将字段扩展为引用多种类型,则存在字段名称不当的风险。
对于对象引用列表,字段应采用“{field}Refs”格式,与上面的单数情况指导相同。
引用多个版本的资源
大多数资源都会有多个版本。例如,核心资源在从 alpha 过渡到 GA 时会经历版本变更。
控制器应该假设资源的版本可能会改变,并包括适当的错误处理。
处理不存在的资源
在多种情况下,所需资源可能不存在。示例包括:
- 所需资源的版本不存在。
- 集群引导过程中的竞争条件导致资源尚未添加。
- 用户错误。
控制器应在引用资源可能不存在的假设下编写,并包含错误处理以便让用户清楚地了解问题。
字段验证
对象引用中使用的许多值都用作 API 路径的一部分。例如,对象名称在路径中用于标识对象。未经清理的这些值可用于尝试检索其他资源,例如通过使用具有语义含义(如 ..
或 )的值/
。
让控制器在将字段用作 API 请求中的路径段之前验证字段,并发出事件以告知用户验证失败。
有关合法对象名称的更多信息,请参阅对象名称和 ID 。
不修改引用的对象
为了最大限度地减少潜在的权限提升媒介,请不要修改所引用的对象,或者将修改限制在同一命名空间中的对象并限制允许的修改类型(例如,HorizontalPodAutoscaler 控制器仅写入子资源/scale
)。
尽量减少将值复制或打印到 referrer 对象
由于控制者的权限可能与控制者所管理对象的作者的权限不同,因此对象的作者可能没有查看引用对象的权限。因此,将引用对象的任何值复制到引用者对象可视为权限升级,使用户能够读取他们以前无法访问的值。
将有关所引用对象的信息写入事件也是同样的情况。
一般来说,不要将从引用对象检索到的信息写入或打印到规范、其他对象或日志中。
当有必要时,请考虑这些值是否是引用对象的作者可以通过其他方式访问的值(例如,已经需要正确填充对象引用)。
对象引用示例
以下部分说明了针对各种对象引用场景的推荐架构。
下面概述的模式旨在随着可引用对象类型的扩展而启用纯粹的附加字段,因此向后兼容。
例如,可以从单一资源类型转变为多种资源类型,而无需对架构进行重大更改。
单一资源引用
单一类型的对象引用很简单,因为控制器可以对识别对象所需的大多数限定符进行硬编码。例如,需要提供的唯一值是名称(和命名空间,尽管不鼓励跨命名空间引用):
# for a single resource, the suffix should be Ref, with the field name # providing an indication as to the resource type referenced. secretRef: name: foo # namespace would generally not be needed and is discouraged, # as explained above. namespace: foo-namespace
仅当希望始终只引用单一资源时才应使用此架构。如果可以扩展到多种资源类型,请使用多资源引用。
控制器行为
操作员应该知道需要从中检索值的对象的版本、组和资源名称,并且可以使用发现客户端或直接构建 API 路径。
多资源引用
当引用可以指向一组有限的有效资源类型时,使用多种对象引用。
与单一类型的对象引用一样,操作员可以提供缺失的字段,前提是现有的字段足以在受支持的类型集合中唯一地标识对象资源类型。
# guidance for the field name is the same as a single resource. fooRef: group: sns.services.k8s.aws resource: topics name: foo namespace: foo-namespace
虽然“组”并非总是帮助控制器识别资源类型的必要元素,但当资源存在于多个组中时,它是为了避免歧义而添加的。它还为最终用户提供了清晰度,并允许复制粘贴引用,而不会因为处理引用的不同控制器而导致引用类型发生变化。
种类与资源
对象引用中一个常见的混淆点是,是否使用“kind”或“resource”字段来构造引用。从历史上看,Kubernetes 中的大多数对象引用都使用“kind”。这不像“resource”那么精确。尽管“group”和“resource”的每个组合在 Kubernetes 中必须是唯一的,但“group”和“kind”并不总是如此。多个资源可以使用相同的“kind”。
通常,Kubernetes 中的所有对象都有一个规范的主要资源 – 例如“pod”,它代表创建和删除“Pod”架构资源的方式。虽然可能无法直接创建资源架构,例如仅在多个工作负载的“scale”子资源中使用的“Scale”对象,但大多数对象引用通过其架构来处理主要资源。在对象引用的上下文中,“种类”指的是架构,而不是资源。
如果对象引用的实现始终有明确的方式将类型映射到资源,则在对象引用中使用“类型”是可以接受的。通常,这要求实现在类型和资源之间具有预定义的映射(对于使用“类型”的内置引用而言就是这种情况)。依赖动态类型到资源的映射并不安全。即使“类型”最初仅动态映射到单个资源,也有可能安装引用相同“类型”的另一个资源,从而可能破坏任何动态资源映射。
如果对象引用可用于引用任意类型的资源,并且类型和资源之间的映射可能不明确,则应在对象引用中使用“资源”。
Ingress API 提供了一个很好的例子,说明“kind”在对象引用中是可接受的。API 支持将后端引用作为扩展点。实现可以使用它来支持将流量转发到自定义目标(例如存储桶)。重要的是,API 的每个实现都明确定义了支持的目标类型,并且对于某种资源映射到哪种资源没有任何歧义。这是因为每个 Ingress 实现都有一个从类型到资源的硬编码映射。
如果使用“kind”而不是“resource”,上面的对象引用将如下所示:
fooRef: group: sns.services.k8s.aws kind: Topic name: foo namespace: foo-namespace
控制器行为
操作员可以将 (group,resource) 的映射存储到所需资源的版本。从那里,它可以构建资源的完整路径并检索对象。
也可以让控制器选择通过发现客户端找到的版本。但是,由于资源的不同版本之间的架构可能会有所不同,因此控制器也必须处理这些差异。
通用对象引用
当需要提供指向某个对象的指针以简化用户的发现时,可以使用通用对象引用。例如,这可用于引用core.v1.Event
发生的目标对象。
使用通用对象引用,除了标准信息(例如 ObjectMeta)之外,无法提取有关引用对象的任何信息。由于任何版本的资源中都存在任何标准字段,因此在这种情况下可以不包含版本:
fooObjectRef: group: operator.openshift.io resource: openshiftapiservers name: cluster # namespace is unset if the resource is cluster-scoped, or lives in the # same namespace as the referrer.
控制器行为
操作员应该通过发现客户端找到资源(因为未提供版本)。由于任何可检索字段对于所有对象都是通用的,因此资源的任何版本都应该可以。
字段引用
当需要从引用对象中的特定字段提取值时,使用字段引用。
字段引用与其他引用类型不同,因为操作员在引用之前对对象一无所知。由于对象的架构可能因资源的不同版本而不同,这意味着这种引用类型需要“版本”。
fooFieldRef: version: v1 # version of the resource # group is elided in the ConfigMap example, since it has a blank group in the OpenAPI spec. resource: configmaps fieldPath: data.foo
fieldPath 应该指向单个值,并使用推荐的字段选择器符号来表示字段路径。
控制器行为
在此场景中,用户将提供所有必需的路径元素:组、版本、资源、名称以及可能的命名空间。因此,控制器可以构造 API 前缀并在不使用发现客户端的情况下对其进行查询:
/apis/{group}/{version}/{resource}/
HTTP 状态代码
服务器将使用符合 HTTP 规范的 HTTP 状态代码进行响应。请参阅以下部分,详细了解服务器将发送的状态代码类型。
API 可能会返回以下 HTTP 状态代码。
成功代码
200 StatusOK
- 表示请求已成功完成。
201 StatusCreated
- 表示创建类型的请求已成功完成。
204 StatusNoContent
- 表示请求成功完成,并且响应不包含任何正文。
- 返回对 HTTP OPTIONS 请求的响应。
错误代码
307 StatusTemporaryRedirect
- 表示请求的资源的地址已改变。
- 建议的客户端恢复行为:
- 按照重定向。
400 StatusBadRequest
- 表示请求无效。
- 建议的客户端恢复行为:
- 请勿重试。修复请求。
401 StatusUnauthorized
- 表示服务器可以访问并理解请求,但拒绝采取任何进一步的操作,因为客户端必须提供授权。如果客户端已经提供了授权,则服务器指示提供的授权不合适或无效。
- 建议的客户端恢复行为:
- 如果用户未提供授权信息,则提示他们输入适当的凭证。如果用户提供了授权信息,则通知他们凭证被拒绝,并可选择再次提示他们。
403 StatusForbidden
- 表示服务器可以到达并理解请求,但拒绝采取任何进一步的操作,因为它被配置为由于某种原因拒绝客户端访问所请求的资源。
- 建议的客户端恢复行为:
- 请勿重试。修复请求。
404 StatusNotFound
- 表示请求的资源不存在。
- 建议的客户端恢复行为:
- 请勿重试。修复请求。
405 StatusMethodNotAllowed
- 表示代码不支持客户端尝试对资源执行的操作。
- 建议的客户端恢复行为:
- 请勿重试。修复请求。
409 StatusConflict
- 表示客户端尝试创建的资源已存在,或者由于冲突,请求的更新操作无法完成。
- 建议的客户端恢复行为:
- 如果创建新资源:
- 要么更改标识符并重试,要么 GET 并比较预先存在的对象中的字段并发出 PUT/update 来修改现有对象。
- 如果更新现有资源:
- 请参阅
Conflict
下面status
的响应部分,了解如何检索有关冲突性质的更多信息。 - GET 并比较预先存在的对象中的字段,合并更改(如果根据先决条件仍然有效),然后使用更新的请求重试(包括
ResourceVersion
)。
- 请参阅
- 如果创建新资源:
410 StatusGone
- 表示该项目在服务器上不再可用,并且不知道转发地址。
- 建议的客户端恢复行为:
- 请勿重试。修复请求。
422 StatusUnprocessableEntity
- 表示由于请求中提供的数据无效,因此无法完成请求的创建或更新操作。
- 建议的客户端恢复行为:
- 请勿重试。修复请求。
429 StatusTooManyRequests
- 表示客户端速率已超出限制或服务器收到的请求数超出其处理能力。
- 建议的客户端恢复行为:
Retry-After
从响应中读取HTTP 标头,并至少等待那么长时间再重试。
500 StatusInternalServerError
- 表示可以访问服务器并理解请求,但发生意外的内部错误且调用结果未知,或者服务器无法在合理的时间内完成操作(这可能是由于服务器临时负载或与另一台服务器的瞬时通信问题)。
- 建议的客户端恢复行为:
- 使用指数退避算法重试。
503 StatusServiceUnavailable
- 表示所需服务不可用。
- 建议的客户端恢复行为:
- 使用指数退避算法重试。
504 StatusServerTimeout
- 表示请求无法在给定时间内完成。客户端只有在请求中指定超时参数时才能获得此响应。
- 建议的客户端恢复行为:
- 增加超时参数的值并使用指数退避算法重试。
响应状态类型
Status
发生错误时,Kubernetes 将始终从任何 API 端点返回类型。客户端应在适当的时候处理这些类型的对象。
Status
在两种情况下,API 将返回一种类型:
- 当操作不成功时(即当服务器返回非 2xx HTTP 状态代码时)。
- 当 HTTP
DELETE
调用成功时。
状态对象被编码为 JSON 并作为响应主体提供。状态对象包含字段,供 API 的人类和机器消费者获取有关故障原因的更详细信息。状态对象中的信息补充了 HTTP 状态代码的含义,但不会覆盖。当状态对象中的字段与通常定义的 HTTP 标头具有相同的含义并且该标头随响应一起返回时,应认为该标头具有更高的优先级。
例子:
$ curl -v -k -H "Authorization: Bearer WhCDvq4VPpYhrcfmF6ei7V9qlbqTubUc" https://10.240.122.184:443/api/v1/namespaces/default/pods/grafana > GET /api/v1/namespaces/default/pods/grafana HTTP/1.1 > User-Agent: curl/7.26.0 > Host: 10.240.122.184 > Accept: */* > Authorization: Bearer WhCDvq4VPpYhrcfmF6ei7V9qlbqTubUc > < HTTP/1.1 404 Not Found < Content-Type: application/json < Date: Wed, 20 May 2015 18:10:42 GMT < Content-Length: 232 < { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "pods \"grafana\" not found", "reason": "NotFound", "details": { "name": "grafana", "kind": "pods" }, "code": 404 }
status
字段包含以下两个可能值之一:
Success
Failure
message
可能包含人类可读的错误描述
reason
可能包含一个机器可读的、单词的 CamelCase 描述,说明此操作处于此Failure
状态的原因。如果此值为空,则没有可用信息。阐明reason
HTTP 状态代码,但不会覆盖它。
details
可能包含与原因相关的扩展数据。每个原因可以定义自己的扩展详细信息。此字段是可选的,并且返回的数据不保证符合除原因类型定义的模式之外的任何模式。
reason
和字段的可能值details
:
BadRequest
- 表示请求本身无效,因为该请求没有任何意义,例如删除只读对象。
- 这与
status reason
Invalid
上面的不同,上面的情况表明 API 调用可能成功,但数据无效。 - 返回 BadRequest 的 API 调用永远不会成功。
- Http 状态代码:
400 StatusBadRequest
Unauthorized
- 表示服务器可以访问并理解请求,但如果客户端未提供适当的授权,则拒绝采取任何进一步的操作。如果客户端已提供授权,则此错误表示提供的凭据不足或无效。
- 详细信息(可选):
kind string
- 未授权资源的 kind 属性(某些操作可能与请求的资源不同)。
name string
- 未授权资源的标识符。
- HTTP 状态代码:
401 StatusUnauthorized
Forbidden
- 表示服务器可以到达并理解请求,但拒绝采取任何进一步的操作,因为它被配置为由于某种原因拒绝客户端访问所请求的资源。
- 详细信息(可选):
kind string
- 禁止资源的种类属性(某些操作可能与请求的资源不同)。
name string
- 禁止资源的标识符。
- HTTP 状态代码:
403 StatusForbidden
NotFound
- 表示无法找到此操作所需的一个或多个资源。
- 详细信息(可选):
kind string
- 缺失资源的种类属性(某些操作可能与请求的资源不同)。
name string
- 缺失资源的标识符。
- HTTP 状态代码:
404 StatusNotFound
AlreadyExists
- 表示您正在创建的资源已经存在。
- 详细信息(可选):
kind string
- 冲突资源的种类属性。
name string
- 冲突资源的标识符。
- HTTP 状态代码:
409 StatusConflict
Conflict
- 表示请求的更新操作由于冲突而无法完成。客户端可能需要更改请求。每个资源都可以定义指示冲突性质的自定义详细信息。
- HTTP 状态代码:
409 StatusConflict
Invalid
- 表示由于请求中提供的数据无效,因此无法完成请求的创建或更新操作。
- 详细信息(可选):
kind string
- 无效资源的 kind 属性
name string
- 无效资源的标识符
causes
- 一个或多个
StatusCause
条目表明所提供资源中的数据无效。将设置reason
、message
和属性。field
- 一个或多个
- HTTP 状态代码:
422 StatusUnprocessableEntity
Timeout
- 表示请求无法在给定时间内完成。如果服务器决定限制客户端的速率,或者服务器超载且此时无法处理请求,客户端可能会收到此响应。
- Http 状态代码:
429 TooManyRequests
- 服务器应设置HTTP 标头并在对象的详细信息字段中
Retry-After
返回 。默认值为。retryAfterSeconds
0
ServerTimeout
- 表示服务器可以访问并理解请求,但无法在合理的时间内完成操作。这可能是由于服务器临时负载或与其他服务器的短暂通信问题。
- 详细信息(可选):
kind string
- 正在操作的资源的种类属性。
name string
- 正在尝试的操作。
- 详细信息(可选):
- 服务器应设置HTTP 标头并在对象的详细信息字段中
Retry-After
返回 。默认值为。retryAfterSeconds
0
- Http 状态代码:
504 StatusServerTimeout
- 表示服务器可以访问并理解请求,但无法在合理的时间内完成操作。这可能是由于服务器临时负载或与其他服务器的短暂通信问题。
MethodNotAllowed
- 表示代码不支持客户端尝试对资源执行的操作。
- 例如,尝试删除只能创建的资源。
- 返回 MethodNotAllowed 的 API 调用永远不会成功。
- Http 状态代码:
405 StatusMethodNotAllowed
InternalError
- 表示发生了内部错误,这是意外的并且调用的结果未知。
- 详细信息(可选):
causes
- 原始错误。
- Http 状态代码:
500 StatusInternalServerError
code
可能包含此状态的建议 HTTP 返回代码。
活动
事件是状态信息的补充,因为除了当前或以前的状态外,它们还可以提供有关状态和发生事件的一些历史信息。针对用户或管理员应收到警报的情况生成事件。
为每个事件类别选择一个独特、具体、简短的 CamelCase 原因。例如,FreeDiskSpaceInvalid
是一个很好的事件原因,因为它可能只指一种情况,但Started
不是一个好的原因,因为它不能充分表明事件的发生,即使与其他事件字段结合使用也是如此。
Error creating foo
或Error creating foo %s
适合事件消息,但后者更可取,因为它具有更多信息。
在客户端中积累重复事件,特别是频繁事件,以减少数据量、系统负载和向用户暴露的噪音。
命名约定
- Go 字段名称必须采用 PascalCase 格式。JSON 字段名称必须采用 camelCase 格式。除了首字母大写外,两者几乎总是一致的。两者之间不能有下划线或破折号。
- 字段和资源名称应该是声明性的,而不是命令性的(SomethingDoer、DoneBy、DoneAt)。
- 使用
Node
where 来指代集群上下文中的节点资源。使用Host
where 来指代单个物理/虚拟系统的属性,例如hostname
、hostPath
、hostNetwork
等。 FooController
是已弃用的命名约定。请改用受控事物的名称来命名类型(例如,Job
而不是JobController
)。- 指定发生时间的字段名称
something
应称为somethingTime
。请勿使用stamp
(例如creationTimestamp
)。 - 我们使用
fooSeconds
持续时间的惯例,如单位小节中讨论的那样。fooPeriodSeconds
对于周期间隔和其他等待期(例如超过fooIntervalSeconds
)是首选。fooTimeoutSeconds
是不活动/无响应期限的首选。fooDeadlineSeconds
是活动完成期限的首选。
- 不要在 API 中使用缩写,除非它们非常常用,例如“id”、“args”或“stdin”。
- 同样,缩写词也只应在非常常见的情况下使用。缩写词中的所有字母应具有相同的大小写,并使用适合情况的大小写。例如,在字段名称的开头,缩写词应全部小写,例如“httpGet”。在用作常量时,所有字母都应大写,例如“TCP”或“UDP”。
- 引用另一个按
Foo
名称类型的资源的字段名称应称为。 引用另一个按 ObjectReference 类型(或其子集)fooName
的资源的字段名称 应称为。Foo
fooRef
- 更一般地,如果单位和/或类型可能产生歧义并且未由值或值类型指定,则在字段名称中包含单位和/或类型。
- 表达名为“fooable”的布尔属性的字段名称应该称为
Fooable
,而不是IsFooable
。
命名空间名称
标签、选择器和注释约定
标签属于用户领域。它们旨在使用对用户有意义(而不是对系统有意义的)的属性来促进 API 资源的组织和管理。可以将它们视为用户创建的 mp3 或电子邮件收件箱标签,而不是程序用于存储其数据的目录结构。前者使用户能够应用任意本体,而后者则以实现为中心且不灵活。用户将使用标签来选择要操作的资源、在 CLI/UI 列中显示标签值等。用户应始终对其命名空间中应用于标签的标签模式拥有完全的权力和灵活性。
但是,我们应该默认支持常见情况的便利性。例如,我们现在在 ReplicationController 中所做的是,默认情况下,如果尚未设置,则自动将 RC 的选择器和标签设置为 pod 模板中的标签。这确保选择器将与模板匹配,并且可以使用与其创建的 pod 相同的标签来管理 RC。请注意,一旦我们概括了选择器,就不一定能够明确地生成与任意选择器匹配的标签。
如果用户想要将额外的标签应用于未选择的 Pod,例如为了便于采用 Pod,或者预计某些标签值会发生变化,他们可以将选择器设置为 Pod 标签的子集。同样,RC 的标签可以初始化为 Pod 模板标签的子集,也可以包含额外的/不同的标签。
对于在自己的命名空间内管理资源的自律用户来说,一致地应用确保唯一性的模式并不难。只需确保某些标签键的至少一个值与所有其他可比较资源不同。我们可以/应该提供验证工具来检查这一点。但是,制定类似于 标签中示例的约定可以使唯一性变得简单。此外,可以使用相对狭窄使用的命名空间(例如,每个环境、每个应用程序)来减少可能导致重叠的资源集。
如果用户可能运行架构不一致的其他示例,或者工具或组件需要以编程方式生成要选择的新对象,则需要有一种直接的方法来生成唯一的标签集。确保集合唯一性的一种简单方法是确保单个标签值的唯一性,例如使用资源名称、uid、资源哈希或生成编号。
但是,uid 和 hashes 的问题在于,它们对用户没有语义含义,难以记忆,难以识别,并且不可预测。缺乏可预测性会阻碍一些用例,例如从 pod 创建复制控制器(例如人们在探索系统时想要执行的操作)、引导自托管集群,或者删除并重新创建采用前一个 pod 的新 RC(例如重命名它)。假设存在逻辑顺序,则代数更可预测且更清晰。幸运的是,对于部署来说就是这样。对于作业,内部使用创建时间戳很常见。用户应该始终能够关闭自动生成,以允许上述某些场景。请注意,自动生成的标签也将成为在命名空间内、新命名空间中、新集群中等克隆资源时需要删除的另一个字段,并且在通过修补或读取-修改-写入序列更新资源时需要忽略该字段。
在标签键中包含系统前缀对用户体验非常不利。只有在用户无法选择标签键的情况下才需要前缀,以避免与用户定义的标签发生冲突。不过,我坚信应该始终允许用户选择要在其资源上使用的标签键,因此应该始终能够覆盖默认标签键。
因此,支持自动生成唯一标签的资源应该有一个 uniqueLabelKey
字段,以便用户可以根据需要指定键,但如果未指定,则可以默认设置,例如设置为资源类型,如作业、部署或复制控制器。该值至少需要在空间上唯一,在作业的情况下可能需要在时间上唯一。
注释的预期用途与标签大不相同。它们主要由工具和系统扩展生成和使用,或由最终用户用来参与组件的非标准行为。例如,注释可用于指示资源实例需要非 kubernetes 控制器进行额外处理。注释可以携带任意有效负载,包括 JSON 文档。与标签一样,注释键可以以管理域作为前缀(例如example.com/key-name
)。无前缀的键(例如key-name
)是为最终用户保留的。第三方组件必须使用带前缀的键。“kubernetes.io”和“k8s.io”域下的键前缀是为 kubernetes 项目保留的,第三方不得使用。
在 Kubernetes 的早期版本中,一些正在开发的功能将新的 API 字段表示为注释,通常采用something.alpha.kubernetes.io/name
或 形式something.beta.kubernetes.io/name
(取决于我们对它的信心)。这种模式已被弃用。一些这样的注释可能仍然存在,但不能定义新的注释。新的 API 字段现在被开发为常规字段。
关于 Kubernetes 组件和工具使用标签、注释、污点和其他通用映射键的其他建议:
- 键名应全部小写,单词之间用破折号分隔,而不是使用驼峰式命名法
- 例如,优先选择
foo.kubernetes.io/foo-bar
,foo.kubernetes.io/fooBar
优先desired-replicas
选择DesiredReplicas
- 例如,优先选择
- 无前缀的键是为最终用户保留的。所有其他标签和注释都必须加前缀。
- “kubernetes.io”和“k8s.io”下的键前缀是为 Kubernetes 项目保留的。
- 这些密钥实际上是 kubernetes API 的一部分,并且可能会受到弃用和兼容性政策的影响。
- “kubernetes.io” 是标签和注释的首选形式,“k8s.io”不应用于新映射键。
- 键名(包括前缀)应该足够精确,以便用户能够合理地理解它来自哪里以及用于什么用途。
- 关键前缀应承载尽可能多的上下文。
- 例如,优先
subsystem.kubernetes.io/parameter
于kubernetes.io/subsystem-parameter
- 例如,优先
- 使用注释来存储负责资源的控制器不需要了解的 API 扩展、不打算作为一般使用的 API 字段的实验字段等。请注意,注释不会由 API 转换机制自动处理。
WebSockets 和 SPDY
Kubernetes 公开的一些 API 操作涉及客户端和容器之间的二进制流传输,包括附加、执行、端口转发和日志记录。因此,API 通过 WebSocket 和 SPDY 协议通过可升级的 HTTP 连接(在 RFC 2817 中描述)公开某些操作。这些操作作为子资源及其相关动词(执行、日志、附加和端口转发)公开,并通过 GET(以支持浏览器中的 JavaScript)和 POST(语义准确)进行请求。
目前使用的主要协议有两种:
- 流媒体频道在处理多个独立的二进制数据流(例如远程执行 shell 命令(写入 STDIN、从 STDOUT 和 STDERR 读取)或转发多个端口)时,可以将这些流多路复用到单个 TCP 连接上。Kubernetes 支持基于 SPDY 的帧协议(该协议利用 SPDY 通道)和 WebSocket 帧协议(该协议通过在每个二进制块前面加上一个表示其通道的字节来将多个通道多路复用到同一个流上)。WebSocket 协议支持可选子协议,该子协议处理来自客户端的 base64 编码字节并返回来自服务器的 base64 编码字节和基于字符的通道前缀(“0”、“1”、“2”),以便于在浏览器中通过 JavaScript 使用。
- 流式响应流数据通道的默认日志输出是 HTTP 分块传输编码,它可以从服务器返回任意二进制数据流。基于浏览器的 JavaScript 在访问分块响应中的原始数据方面能力有限,尤其是在返回大量日志时,并且在未来的 API 调用中可能需要传输大文件。流式 API 端点支持可选的 WebSocket 升级,它提供从服务器到客户端的单向通道,并将数据分块为二进制 WebSocket 帧。公开了一个可选的 WebSocket 子协议,该协议在将流返回到客户端之前对其进行 base64 编码。
如果客户端具有本机支持,则应使用 SPDY 协议,否则应使用 WebSockets 作为后备。请注意,WebSockets 容易受到队头阻塞的影响,因此客户端必须按顺序读取和处理每条消息。将来,将推出弃用 SPDY 的 HTTP/2 实现。
验证
API 服务器收到 API 对象后,会对其进行验证。验证错误会被标记,并以设置为 的 Failure
状态返回给调用者。为了便于提供一致的错误消息,我们要求验证逻辑尽可能遵循以下准则(尽管会存在例外情况)。reason
Invalid
- 尽可能精确。
- 告诉用户他们能做什么比告诉他们不能做什么更有用。
- 当以肯定的方式断言要求时,请使用“必须”。例如:“必须大于 0”、“必须匹配正则表达式‘[az]+’”。“应该”等词语暗示断言是可选的,必须避免使用。
- 当以否定形式断言格式要求时,请使用“不得”。例如:“不得包含‘..’”。“不应”等词语暗示断言是可选的,必须避免使用。
- 当以否定形式断言行为要求时,使用“不可以”。例如:“当 otherField 为空时,不可以指定”、“只能
name
指定”。 - 引用文字字符串值时,请用单引号指明文字。例如:“不得包含‘..’”。
- 引用其他字段名称时,请在反引号中指明名称。例如:“必须大于`request`”。
- 指定不等式时,请使用文字而不是符号。例如:“必须小于 256”、“必须大于或等于 0”。请勿使用“大于”、“大于”、“多于”、“高于”等词语。
- 指定数字范围时,尽可能使用包含范围。
自动资源分配和释放
API 对象通常是包含以下内容的联合对象:
- 一个或多个字段用于标识
Type
特定于 API 对象(又名discriminator
)。 - 一组 N 个字段,在任何给定时间只能设置其中一个 – 实际上是联合。
在 API 类型上运行的控制器通常根据Type
和/或用户提供的一些附加数据来分配资源。一个典型的例子是Service
API 对象,其中 IP 和网络端口等资源将根据 设置在 API 对象中Type
。当用户未指定资源时,它们将被分配,而当用户指定确切值时,它们将被保留或拒绝。
当用户选择更改discriminator
值(例如,从Type X
到Type Y
)而不更改任何其他字段时,系统应清除用于Type X
在联合中表示的字段以及释放附加到 的资源Type X
。 无论这些值和资源如何分配(即由用户保留还是由系统自动分配),这都应该自动发生。Service
API 就是一个具体的例子。 系统分配诸如NodePorts
和 之类的资源ClusterIPs
,并在服务类型为NodePort
或 的情况下自动填充代表它们的字段ClusterIP
(discriminator
值)。 当用户将服务类型更改为ExternalName
这些资源和字段值不再适用时,这些资源和代表它们的字段将被自动清除。
表示分配的值
许多 API 类型都包含代表用户从某个较大空间分配的值(例如,某个范围内的 IP 地址或存储桶名称)。这些分配通常由控制器以异步方式驱动,与用户的 API 操作无关。有时,用户可以请求特定值,控制器必须确认或拒绝该请求。Kubernetes 中有很多这样的例子,并且有几种模式用于表示它。
所有这些的共同主题是,系统不应该信任具有此类字段的用户,并且必须在使用之前验证或以其他方式确认此类请求。
一些例子:
- 服务
clusterIP
:用户可以在 中请求特定 IP,spec
或者将分配一个 IP(在同一spec
字段中)。如果请求特定 IP,apiserver 将确认该 IP 可用,或者如果 IP 不可用,将同步拒绝 API 操作(罕见)。消费者从 中读取结果spec
。这是安全的,因为该值要么有效,要么永远不会被存储。 - 服务
loadBalancerIP
:用户可能在 中请求特定 IP,spec
或者将分配一个在 中报告的status
IP。如果请求特定 IP,LB 控制器将确保 IP 可用或异步报告失败。消费者从 读取结果status
。这是安全的,因为大多数用户无权写入status
。 - PersistentVolumeClaims:用户可以请求一个特定的 PersistentVolume,
spec
或者将分配一个(在同一个spec
字段中)。如果请求特定的 PV,卷控制器将确保卷可用或异步报告故障。消费者通过检查 PVC 和 PV 来读取结果。这比其他方法更复杂,因为值spec
在确认之前就被存储了,这可能(假设,由于额外的检查)导致用户访问其他人的 PV。 - VolumeSnapshots:用户可以在 中请求对特定源进行快照
spec
。生成的快照的详细信息反映在 中status
。
反例:
- 服务
externalIPs
:用户必须在 中指定一个或多个特定 IPspec
。系统无法轻松验证这些 IP(根据其定义,它们是外部的)。消费者从 读取结果spec
。这是不安全的,并且已导致不受信任的用户出现问题。
过去,API 惯例规定status
字段始终来自观察,这使得其中一些情况变得比必要的更复杂。这些惯例已更新,允许status
保存此类分配的值。不过,这并不是一个万能的解决方案。
何时使用spec
字段
新的 API 几乎不应该这样做。相反,它们应该使用status
。如果我们这样做了,PersistentVolumes 可能会更简单。
何时使用status
字段
将这些值存储在status
是最简单、最直接的模式。这适用于以下情况:
- 分配的值与对象的其余部分高度耦合(例如 pod 资源分配)
- 分配的值总是或几乎总是需要的(即,这种类型的大多数实例都会有一个值)
- 模式和控制器是先验已知的(即它不是扩展)
- 允许控制器写入是“安全的”
status
(即,它们通过其他字段导致问题的风险很低status
)。
这些值的消费者可以查看该status
字段以寻找“最终”值或指示为什么无法执行分配的错误或条件。
排序操作
由于几乎所有事情都是异步发生的,因此控制器实现应该注意操作的顺序。例如,控制器是status
在触发更改之前还是之后更新字段取决于需要向系统观察者做出什么保证。在某些情况下,写入字段status
表示确认或接受值spec
,并且可以在触发之前写入。但是,如果客户端在触发之前观察值会有问题, status
则控制器必须先触发,然后再更新status
。在少数情况下,控制器需要先确认,然后触发,然后更新为“最终”值。
控制器必须小心考虑status
在控制循环中断(例如控制器崩溃和重启)的情况下如何处理字段,并且必须幂等且一致地采取行动。这在使用通知器馈送的缓存时尤其重要,因为缓存可能不会使用最近的写入进行更新。在这种情况下,使用 resourceVersion 前提条件来检测“冲突”是常见模式。请参阅此问题以获取示例。
何时使用其他类型
将分配的值存储在不同类型中更复杂,但也更灵活。这在以下情况下最合适:
- 分配的值是可选的(即,这种类型的许多实例根本没有值)
- 模式和控制器不是先验已知的(即,它是一个扩展)
- 该模式足够复杂(即,没有必要用它来增加主要类型的负担)
- 这种类型的访问控制需要比“所有状态”更细的粒度
- 分配值的生命周期与分配持有者的生命周期不同
服务和端点可以被视为这种模式的一种形式,持久卷和持久卷声明也可以被视为这种模式的一种形式。
当使用此模式时,您必须考虑分配对象的生命周期(谁清理它们以及何时清理它们)以及它们与主类型之间的“链接”(通常使用相同的名称、对象引用字段或选择器)。
总会有一些情况可以遵循任何一种路径,而这些情况需要人工评估才能决定。例如,服务clusterIP
与服务的其余部分高度耦合,大多数实例都使用它。但它也是严格可选的,并且具有越来越复杂的相关字段架构。可以为任何一条路径提出争论。