"K8S API规范约束"

  "kubernetes"

Posted by Xu on October 11, 2018

K8S API规范约束

该文档是面向那些想要深入理解K8S API结构的用户,和那些想要扩展K8S api的开发者,一个kubectl来使用资源的介绍文档可以在下一篇文档中看到概览,这一章主要介绍API的一些约束:

Table of Contents

K8S API的约束主要是为了简化客户端的开发并且确保配置机制可以被实现来持续工作在不同的用例

K8S API 的总体风格是满足RESTful风格的,客户端创建、更新、删除或获取一个资源对象的描述信息,都是通过标准的HTTP的动作(POST,PUT,DELETE,和GET),这些API更喜欢接受或返回JSON数据,K8S也会暴露额外的endpoints来满足不标准的请求动作,并且允许可选的内容类型,所有被接受和返回的JSON数据都有一个体制,被”kind”和”apiVersion字段所标示,相关HTTP header字段存在的地方,他们应该反应出JSON字段的内容。但这些信息不能仅仅在HTTP header中被展现出来。

下面的术语被定义:

  • Kind,一个特殊对象体制的名字,Pod/Service…
  • Resource,系统服务实体的表示,通过HTTP作为JSON数据来发送给服务器端或,从服务器端获取,资源通过如下两种方式暴露:(RESTful风格,将所有服务器端服务都看作资源对象)
    • Collections - 相同类型Kind的资源列表,可以被查询
    • Elements - 一个独立的资源,可以通过URL定位的资源
  • API Group ,一个资源对象的集合,被一起暴露,随着API版本被暴露在apiVersion字段作为”GROUP/VERSION’,例如“policy.k8s.io/v1”

每个资源Resources通常是接受并返回单个Kind的数据,一个Kind可能会通过多种Resources(对应不同的使用场景)被接受和返回。例如,Kind “Pod”被”pods”资源暴露,使得用户可以 创建、更新和删除pods。对另一个资源”pod status”则是允许进程来更新该资源部分字段的数据

所有资源被绑定在API groups,每一个group也许有一个或多个版本,和其它API Group独立发展,迭代。并且每一个groups的版本有一个或多个resource资源,Group names通常是域名的格式-K8S项目保留来空group的使用,并且所有单个单词的名字(“extensions”,”apps”),和任何以”*.k8s.io结尾”的group name来保存它的基础功能的使用,当选择一个group name时,我们推荐选择一个你们团队或组织拥有的子域名,例如“widget.mycompany.com”

Resource collections应该全是小写并且是复数形式

Types(Kinds)

Kinds被分为三大类:

  • Objects:代表一个系统中持久稳固的对象实体
    • 创建一个API对象只是一个”意图”的记录-一旦创建,系统将工作确保资源存在,所有的API对象有相同的元数据
    • 一个对象可能会有多个客户端可以使用的资源来执行特定的动作如,创建、更新,删除或获取
    • 例如:Pod,ReplicationController,Service,Namespace,Node
  • Lists是一个或多个Kinds的资源集合
    • 一个kind list的名称必须要以”List”结尾,List有一个受限制的公共元数据,所有lists使用被要求的”required”的”items”字段来包含它们返回的对象数组,任何拥有”items”字段的kind必须是一个list kind
    • 在系统中的定义的大多数Object都应该有一个端口endpoint返回其拥有的资源集合
    • 此外,所有的lists返回带有标签label应该支持标签label过滤,并且大多数lists应该支持字段fields过滤
    • 例如:PodList,ServiceList,NodeLists
  • Simple:用于对对象object的特殊操作或非持久性实体的类型Kind
    • 给它们受限制的范围scope,它们会像lists一样有相同的受限制的元数据集合
      • 例如,”Status” kind 会被返回,当错误发生时并且该kind不会持久性保存在系统中
      • 许多简单的资源是“子资源”,它们的来源于特定资源的API路径,当资源希望暴露一些单个资源相关的可选的动作actions或视图view的时候,它们应该使用新的子资源来做这些操作。一些公共的子资源包括:
        • /binding:用于绑定一个代表用户请求的资源到集群cluster的infrastucture资源
        • /status:用于写一个资源的状态部分,例如:/pods接口endpoint仅仅允许更新数据到metadata和spec,因为这些信息会反应到用户的意图。一个自动的进程应该可以去修改用户查看的状态信息,通过发送一个被更新的Pod Kind到服务器端的”/pods/< name >/status”的资源endpoint
        • /scale:用于读写一个资源的数量用一个独立于特指的资源机制的方式
        • 还有两个额外的子资源:proxy和portforward,提供访问“集群资源”的方法

标准的REST动作verbs必须返回单一的JSON对象,一些API endpoints可能会违反一些严格的REST模式并且会返回不是单个JSON对象的资源,如JSON对象流或无结构化的文本日志数据

公共部分的元数据meta API对象被用于在所有API Group并因此被看做名称为”meta.k8s.io”的server group的一部分,这写类型可能会不断迭代独立于所有使用它们的API Group,并且API Server可能允许它们以它们通用的方式被寻址定位。例如:ListOptions,DeleteOptions,List,Status,WatchEvent和Scale.处于历史遗留的原因,这些类型types是每个已存API Group的一部分。通用的工具像 quota,garbage clooection,autoscalers和通用的客户端如kubectl都会利用这些types来在不同的资源类型中定义一致的行为,就像编程中接口的概念一样。

Resources

  • 所有的JSON对象都是通过一个API MUST(必须)返回,有如下字段:
    • kind:一个字符串来区分这个对象拥有的机制scheme(纲要?不知道怎么翻译)
    • apiVersion:一个字符串区分该对象应该拥有的机制scheme版本

Objects

Metadata元数据

每个对象kind MUST(必须)有如下元数据metadata在一个嵌套的对象,叫做”metadata”:

  • namespace:namespace是一个DNS兼容的标签,对象会根据该标签划分到不同的子区域,默认的namespace是”default”。更多的信息可以查看namespace doc
  • name:一个字符串唯一在当前namespace区分该对象,该名称会被使用到获取一个单个对象的路径
  • uid:在当前时间和空间的唯一值,用于区分拥有相同name的不同对象

每个对象都SHOULD(应该)有如下元数据,在一个嵌套的对象”metadata”中

  • resourceVersion:一个字符串来标示对象的内部版本,该版本信息可以被客户端使用来判断该对象应该何时被改变
  • generation:序列号代表目标状态的特定generation,由系统设置和监控递增。
  • creationTimeStamp:创建时间
  • deletionTimestamp:删除时间
  • labels:一组KV数据,可以被用来组织并展示对象
  • annotations:一组KV数据被外部工具使用来存储并获取关于该对象任意的元数据

Labels为了用户的对象组织的需求,Annotation使得第三方自动化的目的来使用额外的元数据装饰对象

Spec和Status

按照规范,K8S API在一个对象的目标状态的规范specification(嵌套字段spec)和对象当时的状态(嵌套对象字段status)做出一个区别:

  • specification(spec)是一个目标状态的完全的描述,包括用户提供的配置信息的设置,默认值有系统来补充,并被初始化的属性或者被其它子系统组件(如调度器,自动扩展器)创建后改动的属性会用API对象被保存在稳定的存储器中。如果specification被删除,该对象会从系统中删除
  • status:总结该系统中的对象当时的状态,并且通常被保存到一个自动处理的对象,但可能on the fly的过程中被产生。在一些成本或也许一个临时的行为降级,如果status丢失的话,状态status会根据观察被重构。

k8s API可以作为系统声明式配置机制的基础来提供服务,为了利用分层式的操作和声明式配置的表达,该specification中的字段应该有声明式而不是命令式的名字和含义,它们代表渴望的状态。

对对象的PUT和POST动作必须忽略”status”对象字段,来避免在读-修改-写的场景中复写status。一个/status子资源必须被提供来使得系统组件可以更新他们所管理的系统资源。

此外PUT期望整个对象都被指定,所以当一个字段被遗漏掉时,就会认为客户端想要清楚该字段的值。所以PUT动作不接受部分更新,部分更新会通过先Getting资源,然后修改spec,labels,annotations的部分数据,然后PUTing回系统。

一些对象不被存储在系统,例如SubjectAccessReview并且其它的webhook风格的调用可能回选择通过添加spec和status字段来包含”call and response”模式。spec是request,status是response.对于像对象一样的RPC,唯一的操作可能只有POST,但在提交和响应之间有一个一致的机制来减少客户端的复杂度

典型的status 属性:

Condition代表对象当前状态最新可获取的观测数据。对象可能会报告多个conditions,并且新类型的conditions可能会在将来被添加。

对一些资源类型Foo的FooCondition类型可能包含下面字段的子集,但一定要包含type和status字段:

Type               FooConditionType  `json:"type" description:"type of Foo condition"`
  Status             ConditionStatus   `json:"status" description:"status of the condition, one of True, False, Unknown"`
  // +optional
  LastHeartbeatTime  unversioned.Time  `json:"lastHeartbeatTime,omitempty" description:"last time we got an update on a given condition"`
  // +optional
  LastTransitionTime unversioned.Time  `json:"lastTransitionTime,omitempty" description:"last time the condition transit from one status to another"`
  // +optional
  Reason             string            `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"`
  // +optional
  Message            string            `json:"message,omitempty" description:"human-readable message indicating details about last transition"`

额外的字段可能会在未来被添加,Conditions应该被显示的添加来传送用户和组件关心的属性信息,而不是通过其它观测数据推断出来的属性。

Condition status的值可能是”True”,”False”,”Unknown”,一个condition字段的缺失被理解为Unknown。

在v1 API中一些资源包含一个叫“phase”的字段,该字段还与message,reson字段和其它的一些status字段相关联。使用”phase”字段的模式并不被推荐,新的API类型应该使用conditions作为替代,Phase是一个状态机的列举enumeration字段,这违背了系统定义原则并且阻碍了迭代更新。因为添加一个新的enum值,打破了向前的兼容性。相较于从phases中推断隐式的属性,我们更倾向于显式暴露这些客户端需要监控的conditions。

在Condition类型结构中,Reason往往是一个单词,骆驼式拼写法的单词代表当前状态的原因列表。并且”Message”字段往往是一个人类可读的短语或句子,包含一些特定的细节。Reason往往用在一些简要的输出,例如kubectl get。总结事件发生的原因,与此同时,Message用于表达细节性的状态解释,例如kubectl describe等

不同的表示方式

一个API可能用不同的方式为不同的客户端表达一个单个实体 ,或在系统中发生的某个转换步骤后转变一个对象,一个请求对象可能会有两种可获取的表达方式作为不同的资源resource或不同的kinds。

一个例子就是Service,它表明了用户在相同的端口上用相同的行为管理一组pods的意图。当k8s监测到一个pod满足service的selector,该IP地址和Pod端口会为该Service被添加到一个Endpoints Resource,该Endpoints resource仅在Service存在的时候存在。仅仅暴露满足selector的IP和port。所以完整的servcie由两个独立的资源resource来表达:Service resource和Endpoint resource.

对资源的动作Verbs

所有的资源应该使用如下常动的REST模式:

  • GET/< resourceNamePlural > - 获取指定资源名称列表
  • POST/< resourceNamePlural > - 根据一个客户端提供的JSON对象创建一个新的资源
  • GET/< resourceNamePlural >/< name > - 根据name获取单个资源
  • DELETE/< resourceNamePlural >/< name > - 根据name删除单个资源,删除选项可能会指定gracePeriodSeconds。
  • PUT/< resourceNamePlural >/< name > - 根据给出的name使用JSON对象更新或创建资源
  • PATCH/< resourceNamePlural >/< name > - 选择性修改资源指定的字段
  • GET/< resourceNamePlural >&watch=true- 获取JSON对象的流对应对指定kind的任何改动信息(listandwatch)

PATCH operations

JSON PATCH是一个操作序列,来对相应JSON对象进行修改: {“op”: “add”, “path”: “/a/b/c”, “value”: [ “foo”, “bar” ]}

并发控制和一致性

k8s利用resource versions的概念来获取乐观性并发能力,所有k8s资源都有一个resourceVersion字段作为元数据的一部分。该resourceVersion是一个字符串来确定内部的对象版本可以被客户端使用来决定一个对象何时被改变。当一个record将被更新时,会先检查之前保存的版本信息,如果不匹配则更新失败。

resourceVersion会被服务器每次修改对象时改变。如果resourceVersion呗包含在PUT操作中,系统将验证,是否有其它对该资源成功的改动在读-修改-写循环过程中,通过验证当前resourceVersion值是否匹配指定值。