Kubernetes进阶实战(第2版)
上QQ阅读APP看书,第一时间看更新

3.2 对象类资源配置规范

创建Kubernetes对象时,必须提供描述该对象期望状态的规范以及相关的基本信息,例如对象名称、标签和注解等元数据,并以JSON格式提交给API Server。在大多数情况下,用户以YAML格式的配置清单定义对象的相关信息,并由kubectl在发起API请求时将其自动转换为JSON格式。

API Server接收和返回的所有JSON对象都遵循同一个模式,即它们都具有kind和apiVersion字段,分别用于标识对象所属的资源类型、API群组及相关的版本,可合称为类型元数据(TypeMeta)。同时,大多数的对象或列表类型的资源还会有metadata、spec和status这3个嵌套型的字段。其中metadata字段为资源提供元数据信息,例如名称、隶属的名称空间和标签等,因而也称为对象元数据(ObjectMeta);spec字段则是由用户负责声明对象期望状态的字段,不同资源类型的期望状态描述方式各不相同,因此其嵌套支持的字段也不尽相同;而status字段则记录活动对象的当前状态信息,也称为观察状态,它由Kubernetes系统自行维护,对用户来说为只读字段,不需要在配置清单中提供,而是在查询集群中的对象时由API Server在响应中返回。

每个资源类型代表一种特定格式的数据结构,它接收并返回该格式的对象数据,同时,一个对象也可嵌套多个独立的“小”对象,并支持每个小对象的单独管理操作。例如对Pod类型的资源来说,用户可创建、更新或删除Pod对象,而每个Pod对象的metadata、spec和status字段的值又是各自独立的对象型数据,所以它们可被单独操作。需要注意的是,status对象由Kubernetes系统单独进行自动更新,且不支持用户手动操作。

3.2.1 定义资源对象

相较于使用curl命令直接向API发起请求来说,kubectl命令或其他UI工具(例如Dashboard)更为便捷和高效。例如,kubectl get TYPE/NAME -o yaml命令能获取到任何一个对象的YAML格式的配置清单,也可使用kubectl get TYPE/NAME -o json命令获取JSON格式的配置清单。例如,下面的命令能够打印出Namespace对象kube-system的详细数据:


~$ kubectl get namespace kube-system -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2020-05-16T06:23:03Z"
  name: kube-system
  resourceVersion: "4"
  selfLink: /api/v1/namespaces/kube-system
  uid: 9db2a2a3-c0bc-4501-bc42-8cd5815abdac
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

除了极少数的资源外,Kubernetes系统上的绝大多数资源都是由用户创建。用户只需要定义出声明式的对象配置清单,而后由相应的控制器通过控制循环来确保对象的运行状态与用户定义的期望状态无限接近或等同。承载对象定义的配置文件称为配置清单(manifest),虽然可直接以JSON格式描述对象,但更常见的还是以.yaml或.yml结尾的YAML文件。除了status字段,该类文件的格式类似kubectl get命令获取到的YAML或JSON形式的输出结果。例如,下面就是一个创建Namespace资源时提供的资源配置清单示例,它仅提供了几个必要的字段。


apiVersion: v1
kind: Namespace
metadata:
  name: dev
spec:
  finalizers:
  - kubernetes

把上面配置清单中的内容保存到文件中,使用kubectl create -f /PATH/TO/FILE命令即可将其提交至集群。创建完成后打印其YAML或JSON格式的结果时可以看到Kubernetes自动补全了未定义的字段,并提供了相应的数据,这部分功能由第9章讲到的准入控制器实现。事实上,Kubernetes的大多数资源都能够以相同的方式创建和查看,而且它们几乎都遵循类似的组织结构。下面的命令显示了第2章中使用kubectl run命令创建的Deployment资源对象demoapp的状态信息。


~]$ kubectl get deployment demoapp -o yaml 
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
……
  name: demoapp
spec:
  replicas: 3
  selector:
    matchLabels:
      run: demoapp
  ……
status: 
  ……

为了节省篇幅,上面的输出结果中省去了大部分的内容,仅保留了其主体结构。命令结果显示出,它同样遵循Kubernetes API标准的资源组织格式,由apiVersion、kind、metadata、spec和status这5个核心字段组成。事实上,对大多数资源来说,apiVersion、kind和metadata字段的功能基本相同,spec则是资源的期望状态,而资源之所以存在不同类型,是因为它们在该字段内的嵌套属性存在显著差别。

3.2.2 对象元数据

metadata字段内嵌多个字段以定义对象的元数据,这些字段大体可分为必要字段和可选字段两类。名称空间级别的资源的必选字段如下。

▪namespace:当前对象隶属的名称空间,默认值为default。

▪name:当前对象的名称标识,同一名称空间下同一类型的对象名称必须唯一。

▪uid:当前对象的唯一标识符,其唯一性仅发生在特定的时间段和名称空间中,主要用于区别拥有同样名字的“已删除”和“重新创建”的同一个名称的对象。

可选字段通常是指那些或由Kubernetes系统自行维护和设置,或存在默认值,或本身允许使用空值等一类的字段。常用的可选字段有如下几个。

▪labels:该资源对象的标签,键值型数据,常被用作标签选择器的挑选条件。

▪annotations:非标识目的的元数据,键值格式,但不可作为标签选择器的挑选条件,其意义或解释方式一般由客户端自行定义。

▪resourceVersion:对象的内部版本标识符,用于让客户端确定对象变动与否。

▪generation:用于标识当前对象目标状态的代别。

▪creationTimestamp:当前对象创建时的时间戳。

▪deletionTimestamp:当前对象删除时的时间戳。

配置清单中未在metadata字段中明确定义的可选嵌套字段,会由一系列的finalizer组件予以自动填充。而当用户需要强制对资源创建的目标资源对象进行校验或者修改时,则要使用initializer组件完成,例如,为每个待创建的Pod对象添加一个Sidecar容器。另外,不同的资源类型也会存在一些其专有的嵌套字段,例如ConfigMap资源还支持使用clusterName等。

3.2.3 资源的期望状态

Kubernetes API中定义的大部分资源都有spec和status两个字段:前一个是声明式API风格的期望状态,由用户负责定义而由系统读取;后一个是系统写入的实际观测到的状态,可被用户读取,以了解API对象的实际状况。控制器是Kubernetes的核心组件之一,负责将用户通过spec字段声明的API对象状态“真实”反映到集群之上,尤其是创建和更新操作,并持续确保系统观测到并写入status的实际状态符合用户期望的状态。

例如,Deployment是负责编排无状态应用的声明式控制器,当创建一个具体的Deployment对象时,需要在其spec字段中指定期望运行的Pod副本数量、匹配Pod对象的标签选择器以及创建Pod的模板等。Deployment控制器读取该对象的spec字段中的定义,过滤出符合标签选择器的所有Pod对象,并对比该类Pod对象的总数与用户期望的副本数量,进行“多退少补”操作,多出的Pod会被删除,少的将根据Pod模板进行创建,而后将结果保存于Deployment对象的status字段中。随后,Deployment控制器在其控制循环中持续监控status字段与spec的差异,任何不同都将由控制器启动相应操作进行对齐。

然而,出于不同目的分别设计的API资源功能不尽相同,它们的期望状态也会有所不同,因而不同资源类型的spec字段的定义格式也必然各不相同,具体使用时需要参照Kubernetes API参考手册进行了解,核心资源对象的常用配置字段将会在本书后面章节进行讲解。

3.2.4 获取资源配置清单格式文档

在定义资源配置清单时,尽管apiVersion、kind和metadata有据可循,但spec字段对不同的资源来说千差万别,用户难以事先全部掌握,这就不得不参考Kubernetes API文档了解各字段的详细使用说明。Kubernetes的设计和开发人员显然也考虑到了这点,他们直接在系统中内置了这些文档,并提供了具体的打印命令kubectl explain,该命令将根据给出的对象类型或相应的嵌套字段来显示相关下一级文档。例如,下面的命令能够打印出Pod资源的一级字段及其使用格式。


~$ kubectl explain pods

每个对象的spec字段的文档通常会包含KIND、VERSION、RESOURCE、DESCRIPTION和FIELDS几个小节,其中FIELDS一节中给出了可嵌套使用的字段、数据类型及功能描述。各字段的数据类型遵循JSON规范,包括数值型、字符串、布尔型、数组或列表、对象和空值等,其中数值型还可分为整型、分数(Fraction)和指数(Exponent)3种。其中对象类型的字段还可以嵌套其他字段,且每个对象的文档也支持单独打印。例如,查看Pod资源内嵌的Spec对象支持嵌套使用的二级字段,可使用类似如下命令。


~$ kubectl explain pods.spec
KIND:     Pod
VERSION:  v1
RESOURCE: spec <Object>

DESCRIPTION:
     Specification of the desired behavior of the pod. ……

     PodSpec is a description of a pod.

FIELDS:
……
   containers   <[]Object> -required-
     List of containers belonging to the pod. Containers cannot currently be
     added or removed. There must be at least one container in a Pod. Cannot be updated.
……

上面命令结果显示,containers字段的数据类型是对象列表([]Object),也是一个必选字段(带有-required-标记)。任何值为对象类型数据的字段都会嵌套一到多个下一级字段,例如Pod对象中的每个容器也是对象型数据,它同样包含嵌套字段,但Kubernetes不支持单独创建容器对象,而是容器对象必须包含在Pod对象的上下文中。容器的配置文档可通过三级字段获取,命令及其结果示例如下:


~$ kubectl explain pods.spec.containers
KIND:     Pod
VERSION:  v1
RESOURCE: containers <[]Object>

DESCRIPTION:
     List of containers belonging to the pod. Containers cannot currently be added 
     or removed. There must be at least one container in a Pod. Cannot be updated.

     A single application container that you want to run within a pod.

FIELDS:
   args <[]string>
     Arguments to the entrypoint. The docker image's CMD is used if this is not provided. ……

   ……

内置文档大大降低了用户手动创建资源配置清单的难度,explain也的确是用户的常用命令之一。更便捷的配置方式,是以同类型的现有活动对象的清单为模板生成目标资源的配置文件。以活动对象生成配置模板的命令格式为kubectl get TYPE NAME -o yaml --export,其中--export选项用于省略输出由系统生成的信息,主要是指那些应该由系统生成并写入的Status字段的信息。例如,下面的命令能够基于现有的Deployment资源对象demoapp生成配置模板deploy-demo.yaml文件,随后修改该文件中的核心定义即可定义出新的控制器资源:


~$ kubectl get deployment demoapp -o yaml --export > deploy-demo.yaml

通过资源清单文件管理资源对象较之直接通过命令行进行操作有着诸多优势:命令行的操作方式仅支持部分资源对象的部分属性,而资源清单支持配置资源的所有属性字段,且配置清单文件还能够进行版本追踪和配置复审等高级功能。本书后续章节中的大部分资源管理操作都会借助于资源配置文件进行。

注意

kubectl get命令的--export选项在Kubernetes v1.18版本中正式废弃,但在此前的版本中依然可用。

3.2.5 资源对象管理方式

Kubernetes的API Server遵循声明式编程范式而设计,侧重于构建程序逻辑而无须描述其实现流程,用户只需要设定期望的状态,系统即能依赖相应的控制器自行确定需要执行的操作,以确保达到用户期望的状态。事实上,声明式API和控制器模式也是Kubernetes的编排等功能赖以实现的根本。

另一种对应的类型称为命令式编程的范式,代码侧重于通过创建一种告诉计算机如何执行操作的算法或步骤来更改程序状态的语句,它与硬件的工作方式密切相关,通常代码将使用条件语句、循环和类继承等控制结构。为了便于用户使用,kubectl命令也支持命令式的编排功能,用户直接通过命令及其选项就能完成对象管理操作,前面用到的run、expose、delete和get等命令都属于此类,它们在执行时需要用户告诉系统要做的事情的具体步骤。例如,使用run命令创建一个有着3个Pod对象副本的Deployment对象或通过delete命令删除一个名为demoapp的Service对象等。

kubectl的命令也可大体分为3类:命令式命令(imperative command)、命令式对象配置(imperative object configuration)和声明式对象配置(declarative object configuration)。

(1)命令式命令

命令式命令是指将实施于目标对象的操作以选项及选项参数的方式提供给kubectl命令,并直接操作Kubernetes集群中的活动对象,因而无法提供之前配置的历史记录。这是在集群中运行“一次性”任务的最简单方法。常用的命令式命令如下。

▪创建对象:run、expose、autoscale和create <objecttype> [<subtype>] <instancename>。

▪更新对象:scale、annotate、label、set <field>、edit和patch。

▪删除对象:delete <type>/<name>。

▪查看对象:get、describe和logs。

命令式命令简单易用,但无法实现代码复用、修改复审及审计日志等功能。配置跟踪和配置复用的功能要依赖由配置清单承载的“对象配置”,如此用户才能够像管理代码文件一样来管理这类配置清单。写好配置清单并非易事,需要用户深入学习Kubernetes API,理解并掌握常用对象的常用字段的使用方法,这也是熟练使用Kubernetes系统的基础。

配置清单本质上是一个JSON或YAML格式的文本文件,由资源对象的配置信息组成,支持使用Git等进行版本控制。而用户能够以资源清单为基础,在Kubernetes系统上选择以命令式或声明式进行资源对象管理,如图3-4所示。

图3-4 基于资源配置清单管理对象

(2)命令式对象配置

命令式对象配置管理方式包括create、delete、get和replace等命令。与命令式命令不同,它通过资源配置清单读取要管理的目标资源对象,通用格式为kubectl create|delete| replace|get -f <filename|url>,其中的filename和url分别是指以本地文件路径或URL来指定配置清单文件。命令式对象配置的管理操作直接作用于活动对象,因而即使修改配置清单中极小的一部分内容,在使用replace命令进行对象更新时也将会导致整个对象被完全替换。所以,混合使用命令式命令进行清单文件带外修改时,必然导致用户丢失活动对象的当前状态。

(3)声明式对象配置

声明式对象配置并不直接指明要进行的对象管理操作,而是提供配置清单文件给Kubernetes系统,并委托系统来跟踪活动对象的状态变动。资源对象的创建、删除及修改操作全部通过唯一的命令kubectl apply完成,且每次操作时,提供给命令的配置信息都将保存在对象的注解信息(kubectl.kubernetes.io/last-applied-configuration)中,它通过对比检查活动对象的当前状态、注解中的配置信息及资源清单中的配置信息进行变更合并,从而实现仅修改变动字段的高级补丁机制。

声明式对象配置支持针对目录进行操作,通过在目录中存储多个对象配置文件,然后由apply命令递归地创建、更新或删除这些对象,它保留对活动对象所做的更改操作,但并不会将更改合并回对象配置文件中。另外,kubectl diff命令提供了预览要更改的配置的方法。

▪创建:kubectl apply -f <directory>/。

▪更新:kubectl apply -f <directory>/,可以先使用kubectl diff -f <directory>/命令预览。

▪删除:kubectl apply -f <directory/> --prune -l your=label,建议使用命令式的方法kubectl delete -f <filename>。

▪查看:命令式的方法kubectl get -f <filename|url> -o yaml。

命令式对象配置相较于声明式对象配置来说,其缺点还在于同一目录下的配置文件必须同时进行同一种操作,例如要么都创建,要么都更新等,而且其他用户的更新也必须一并反映在配置文件中,否则将在下一次的变动中被覆盖。但声明式对象配置并无此要求或限制,它仅作用于自己声明的各对象,事先不存在的对象会被创建,而已存在对象则会被保留或者修改以吻合声明中的定义,具体操作取决于活动对象的当前状态与用户声明状态的对比结果。

声明式对象配置是优先推荐用户使用的管理机制,但对于新手来说,、命令式命令的配置方式更易于上手,对系统有所了解后也易于切换为配置清单管理方式。因此,仅推荐高级用户使用声明式配置,建议同时使用版本控制系统存储期望的状态和跨对象的历史信息。