Kubernetes快速进阶与实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 Kubernetes核心概念

本节介绍Kubernetes核心概念,包括resource、object、Pod、deployment和service等,它们将为读者后续深入学习Kubernetes打下基础。

1.2.1 resource——Kubernetes的组成元素

resource并不是Kubernetes新创的概念,它来源于REST(Representational State Transfer,表现层状态转化)。REST是网络应用程序架构的一种设计风格(或原则),凡是符合REST原则的架构,就称之为RESTful架构。Kubernetes就是基于REST设计的,因此它是RESTful架构。

有关REST的详细信息,可以参考REST提出者Fielding的博士论文http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm。

按照REST的观点,网络应用程序中一切需要被外部所访问的事物,都将被抽象成resource,网络应用程序就是由各种resource组合而成的。resource可以是文件、图片等实体,也可以是统计数据,如某个时间段内的访客人数,还可以是某种概念,例如多个容器的组合等。总之,一个网络应用程序就是一组resource的集合,用户同网络应用程序之间的交互,就是对各类resource的操作。Kubernetes也是RESTful架构,因此,resource也是Kubernetes的基本组成元素,它的地位就如同Linux中文件的地位一样,Kubernetes中一切皆resource。

Kubernetes是RESTful架构,它的交互风格和Web网站类似:Kubernetes中的resource就如同网站中的网页,每个resource在Kubernetes上的位置使用REST路径来表示,例如Pod resource的REST路径就是/api/v1/pods。使用REST路径,再加上HTTP的POST、PUT、PATCH、DELETE和GET操作,就可以完成指定resource的创建、更新、部分更新、删除和读取,就如同操作网页一样。

1.REST API

因为Kubernetes遵循REST原则,所以Kubernetes和外部以及内部组件之间的交互,都采用了统一方式的接口,称之为“REST API”,对这些接口的调用则称之为“REST调用”或“REST操作”。用户平时使用的kubectl等命令调用的就是“REST API”,可以按照REST操作规则直接访问这些API,或者调用客户端函数库中的接口来访问API。

“REST API”和传统网络应用程序的API(简称传统API)是不一样的。传统API是面向函数接口的,每增加一个功能,就会增加一个或若干个函数,这种方式的优点是灵活,可以通过函数的组合来实现各种复杂功能;缺点是对函数库开发者的要求极高,接口抽象的好坏直接关系到开发的难度和工作量,同时对使用者来说,需要熟悉大量的函数接口而且它们之间的调用顺序也是有难度的。

“REST API”则是面向resource的,Kubernetes每增加一个功能,就会增加一个新的resource。而每个resource所支持的操作很有限,就是创建、更新、删除和读取等几个操作,通过HTTP的POST、PUT、DELETE和GET操作来完成。这样对Kubernetes的使用者来说,只需要关注Kubernetes提供了哪些resource,每个resource的作用是什么,至于resource上的操作,就是有限的几种通用操作,再加上REST API的调用是无状态的,它们之间没有顺序关系,因此,对于使用者来说大大降低了使用难度。

Kubernetes REST API不是HTTP之上的封装,而是直接使用HTTP,因此它是非常轻量级的。

2.API object

综上所述,Kubernetes就是一组resource的集合,用户通过“REST API”去操作这些resource。而“REST API”在调用过程中会使用“API object”来表示resource,也就是说,“API object”是resource在“REST API”调用中的序列化数据。因此,从“REST API”的角度来看,所有的resource都是API object,每个resource在API中都有对应的条目来描述。

3.Object

Object(首字母O大写)是REST API中描述resource结构的数据类型,Object由多个FIELD(成员或字段)组成,每个成员的类型可以是string、boolean、integer等基本类型,也可以是数组(用[]表示),甚至可以是Object类型自身。

每个成员都有名字和对应的值,名字和值是一一对应的,通常也用KV(Key Value)键值对来描述每个成员,Key就是成员的名字,Value则是成员的值。Object非常重要,我们创建resource时,要依据每个resource的Object结构,来填充各个成员的值。

图1-2就是一个典型的Object,它描述了Pod这个resource的结构信息。

图1-2 Pod结构图

图1-2中的成员说明如下。

第一个成员的Key是apiVersion,Value类型是string。

第二个成员的Key是kind,Value类型是string。

第三个成员的Key是metadata,Value类型是Object。

第四个成员的Key是spec,Value类型是Object。

第五个成员的Key是status,Value类型是Object。

Kubernetes中有3种类型的object:API object、Object和Kubernetes object。其中前两种object已经介绍过了,第三种object(Kubernetes object)后面会有说明。由于Kubernetes在描述这些object的时候并不严谨,因此一定要结合上下文来理解object的含义。

4.API group

由于Kubernetes不断迭代快速向前发展,resource的种类、功能、特性和访问方式也是不断变化的,这就涉及resource的分类问题。但是,Kubernetes并没有以resource为对象来划分版本;也没有以resource的某个成员(FIELD)为对象来划分版本,这样的划分粒度太细,管理难度大;也没有以Kubernetes软件本身为对象来划分版本,这样的划分粒度又太大,不够灵活。总之,这几种划分既不利于Kubernetes自身的开发,也不利于Kubernetes的使用。

由于所有的resource都是和REST API关联的,resource的变化不光体现在自身,还体现在访问resource的接口、即REST API上。因此,Kubernetes以API为对象来划分版本,即API分类不同,其支持的resource就可能不同。以API进行划分,可以使得resource及其行为保持一个完整、清晰而又一致的视图。用户通过REST API同Kunernetes打交道,基于API分类,对于用户而言是十分自然的事情,使用起来也非常方便。

Kubernetes首先使用API group对resource分类,API group是一个字符串,它会写入resource的REST路径。一个典型的resource的REST路径如下所示,其中,apis是所有API的固有信息;extensions则是API group;v1beta1是API版本;ingresses是resource的名字。

5.API version

API group可以对API进行分类,但是光这样还不够,同一类的API还会有不同的版本,如果不进一步划分,会给开发、管理和使用带来很多问题。因此,Kubernetes在API group的基础上,对API version(API版本)进一步分类。Kubernetes将API版本划分为Alpha、Beta、Stable三个级别,具体说明如下。

Alpha:该版本的名字会包含字符串alpha,如v1alpha1,它会写入resource的REST路径中。这是一个不稳定的版本,可能包含错误,而且功能和API接口随时会被删除或修改。因此,如果想尝试某项新特性,可以使用该版本做短期的测试,但不要将其应用到生产环境中。

Beta:该版本的名字会包含字符串beta,如v1beta1,它会写入resource的REST路径中。这是一个相对稳定的版本,各项功能都经过了充分的测试。该版本所支持的功能特性会一直保留,但会做一些细节上的修改,这样可能会导致该版本的接口同后续版本的接口不一致。Beta版本最大的意义在于,如果需要的某项新特性在Beta版本中,则可以充分使用和验证该特性,并积极反馈,这样就有可能使得开发者按照用户的意见进行修改,否则一旦Beta版本升级成稳定版本,就很难再修改该特性了。

Stable:该版本的名字以v开头,后面跟数字,例如v1,它会写入resource的REST路径中。这是一个稳定的版本,每项功能都经过很好的测试,并且接口也不会随意修改,以保证兼容性。因此在实际生产中最好使用该版本。

6.apiVersion

API group(GROUP)和API version(VERSION)都是resource的REST路径的重要信息,它们两者的组合:GROUP/VERSION,称之为apiVersion。可以使用以下命令来查看Kubernetes所支持的apiVersion。

上述命令执行结果如下,都是GROUP/VERSION形式的apiVersion,例如第一行admissionregistration.k8s.io/v1,其中admissionregistration.k8s.io就是GROUP(API group),v1则是VERSION(API version)。

Kubernetes使用apiVersion有很多好处,具体说明如下。

逻辑清晰:REST API按照GROUP划分后,再分不同的VERSION,例如apiextensions.k8s.io VERSION下就有v1和v1beta1两个VERSION,既逻辑清晰又便于管理和协作开发。

解除了耦合:resource的apiVersion和Kubernetes软件的release版本解除了耦合,resource的apiVersion如图1-3所示,而本书Kubernetes的release版本则是v1.20.1,这样release不需要等待apiVersion全部升级后才能发布新版本,既不影响开发又可以快速迭代发布版本。

非常灵活:同一个GROUP可以有不同开发状态的版本,例如GROUP apiextensions.k8s.io就有v1和v1beta1两个版本。开发者可以根据需要,来选择不同的apiVersion组合构成Kubernetes release,非常灵活。

7.查看resource信息

Kubernetes有多种类型的resource,可以使用下面的命令查看当前Kubernetes所支持的resource。

上述命令的输出如图1-3所示。

图1-3 resource信息图

图1-3中共有5列内容,说明如下。

1)第一列是resource的名字。

2)第二列是resource的缩写,例如pods的缩写就是po。

3)第三列是该resource所属的API group。

4)第四列表示该resource是否位于Namespace之中,Namespace用来划分Kubernetes,不同的Namespace之间,resource是互相隔离的,因此可以认为Namespace是一个虚拟Kubernetes集群。但并不是所有的resource都在Namespace内,只有NAMESPACED为true的resource才可以划分到一个Namespace中,NAMESPACED为false的resource不属于任何Namespace。

5)第五列表示resource类型,其取值可以是resource的名字或缩写。

命令“kubectl api-resources-o wide”可以查看resource的更多信息,例如resource支持的操作,例如Pod所支持的操作就包括[create delete delete collection get list patch update watch]。

图1-3所示的这些resource就是整个Kubernetes的API,和SDK文档中一页又一页的API函数接口相比,实在是简洁太多,这就是Kubernetes基于REST风格来设计架构所带来的好处。

每个resource都可以查看它的结构信息,即它的Object定义,示例命令如下。

该命令会打印Pod的Object定义。其中kubectl是命令,explain是选项,pod是参数,pod表示要查看的resource类型,可以用resource的Name或缩写来替代,而且不区分大小写。

可以使用“kubectl explain pod--recursive=true”来打印Pod各成员的详细信息,包括metadata、spec和status等嵌套Object的详细信息,以及在它们内部嵌套的Object的详细信息。

可以访问https://git.k8s.io获取resource各成员的详细信息,但是https://git.k8s.io的速度很慢,很多时候无法访问。https://k8s.mybatis.io/提供了镜像内容,因此可以访问该网站,获取FILELDS的详细信息。

可以使用“kubectl describe deployment XXX”来打印deployment resource XXX信息,其中kubectl.kubernetes.io/last-applied-configuration后面的内容就是该XXX Object的信息。

总之,上述方法可以获取Kubernetes所支持的各类resource,以及每个resource的Object信息,这将为后续创建或操作resource打下良好基础。

1.2.2 Kubernetes object——定义Kubernetes运行状态

Kubernetes object是一类特殊的resource,它是Kubernetes集群状态的抽象。可以通过创建Kubernetes object来告诉Kubernetes,用户希望它以什么样的状态运行。例如,在Kubernetes object中指定了某个Pod的副本个数为2,那么Kubernetes首先会运行两个Pod副本,然后监控这些Pod副本的状态,如果有Pod副本不可用,在条件允许的情况下,Kubernetes会运行新的Pod副本,使得当前运行的Pod副本数始终等于2,从而努力使得Kubernets按照用户所描述的状态运行。此外,还可以查询object信息来获取Kubernetes的运行情况,例如当前运行的容器化应用有哪些,它们所在的节点是哪几个,当前可用的节点有哪些,当前容器化应用的行为策略是什么,诸如重启策略、更新和容错等。因此,Kubernetes集群中所有的object的集合,就构成了该集群的运行状态。

“容器化应用”指将应用程序制作成镜像,通过镜像运行容器来运行该应用程序。

1.Kubernetes object的特征

Kubernetes object最显著的特征是:Kubernetes object是有生命周期的,分为创建、运行和删除这三个阶段,具体操作可以参考https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/。

此外,Kubernetes object是持久化存储的实体,一旦创建就会一直存在,即使集群重启后,Kubernetes依然会创建该Kubernetes object,并努力使得集群达到Kubernetes object配置所描述的状态。只有当Kubernetes object被删除后,集群重启才不会重新创建该Kubernetes object。

2.Kubernetes object同其他Kubernetes概念的区别

在理解Kubernetes object时,还要特别注意同Kubernetes中其他概念的区别,具体说明如下。

首先要特别注意区分resource和object。在Kubernetes的官方文档和学习资料中,会经常遇到resource和object混用的情况,初学者往往会迷惑而分不清楚。根据前面的定义,resource是REST中的概念,而Kubernetes是按照REST设计的,因此Kubernetes一切皆为resource。而Kubernetes object则是resource的一种,它是集群状态的抽象,并且在resource中的比重很大。resource中除了Kubernetes object,还有少部分是virtual类型,这部分resource通常用来表示操作,而不是Kubernetes object。

其次还要特别注意区分API object、Object和Kubernetes object。Kubernetes的官方文档中对这3种object的描述并不严谨,不严格区分大小写,有的地方甚至直接统称为object,因此一定要结合上下文去理解。

3.Kubernetes object的结构

Kubernetes object的公共成员(字段)如表1-1所示,这5个成员是每个Kubernetes object都具有的,其中前4个成员用于创建Kubernetes object时填写,第5个成员status,Kubernetes object创建后由Kubernetes填写和更新,供用户查询。

表1-1 Kubernetes object描述字段表

可以使用前面描述的“kubectl explain”命令查看每个Kubernetes object的具体结构。

1.2.3 Pod——实现Kubernetes中容器的逻辑组合

Pod是Kubernetes中最基础和最重要的Kubernetes object,它是Kubernetes中最小的执行单位,也是用户能够在Kuberntes中创建和部署的最小Kuberntes object。Pod由一组容器组成,这组容器在集群中的同一个节点上运行,共享相同的内部网络和存储资源,互相协作对外提供某种特定的服务,即“微服务”。

要注意的是,Pod中容器的运行和管理是由一个叫作“容器运行时”(container runtime)的组件实现的,“容器运行时”不是Kubernetes的内置模块,而是一个外部组件,Kubernetes常用的“容器运行时”有containerd、CRI-O和Docker。

Pod中文翻译为豆荚,Pod内部的容器则可以理解为豆荚中的豆子。

1.Pod的生命周期

Pod生命周期的各个阶段如表1-2所示。

表1-2 Pod状态表

Pod的running阶段,只是说明Pod中有1个容器正在运行,或者是处在启动或重启过程中,并不是说Pod所有的容器都处在运行的状态。

2.Pod中容器的状态

一旦Pod同Kubernetes集群的某个节点绑定后,就会在该节点创建容器,因此容器也有状态,其说明如表1-3所示。

表1-3 Pod容器状态表

3.Pod的网络

以Pod的常用网络Calico为例,Kubernetes默认会为每个Pod分配一个IP地址,例如192.168.2.140,所有Pod的IP会在同一个网段,这是由kubeadm初始化(kubeadm init)时,指定参数“--pod-network-cidr=192.168.2.0/24”所决定的。

Pod中的所有容器会共享该Pod的IP地址,这是因为,这些容器共享的是同一块网卡,该网卡上的IP地址就是192.168.2.140。因此,Pod内的容器间通信,直接用localhost+端口即可。此外,当Pod所在节点开启IP转发(iptables-P FORWARD ACCEPT)后,Kubernetes的节点和该IP可以互相ping通;Kubernetes其他Pod的容器,也可以和该IP互相ping通(Proxy iptables情况下)。

1.2.4 RC/RS——控制Pod副本个数

RC/RS是典型的Kubernetes object,它们用来确保Pod副本按照用户指定的数量运行,具体说明如下。

1.RC(Replication Controller)

RC是Kubernetes object,它是Pod副本(Replication)数量的抽象,所谓Pod副本是指按照同一个Pod的Object定义所创建的Pod,例如设置某个Pod副本数为2,那么Kubernetes就会按照该Pod的Object的定义创建两个Pod,这两个Pod内启动的容器来源于同一个镜像,容器运行参数也一样,只是Pod运行的节点不同。在Kubernetes生产环境中,为了确保应用的性能和可用性,通常会设置Pod的副本数大于1。

RC可以实现Pod数量的重新规划(Rescheduling),例如在RC中指定某个Pod的副本数量为2,那么该RC创建后,就会使得集群中该Pod的数量始终维持在2。如果之前该Pod的副本数是3,RC会删除掉其中的一个Pod;如果运行的Pod数量为1,那么RC则会启动一个新的Pod。至于如何监控Pod数量的变化,如何对Pod进行增加/删除操作,新增的Pod在哪个节点上运行,等等,这些都由RC自动完成;同时RC还可以很方便地实现应用规模的缩放(Scaling),应用的规模取决于Pod的副本数,通过修改RC定义中的副本数,重新创建该RC,就可以很方便地改变Pod的副本数,从而实现应用规模的缩放。

2.RS(ReplicaSet)

RS是RC的升级版,它和RC主要的区别在于Selector(选择器)。Selector用于RC/RS来选择Pod作为其管理对象,每个Pod创建时会设置(Label)标签(标签可以有多个),Selector根据标签来选择符合条件的Pod,然后维护这些Pod的副本数。

其中RC中的Selector是equality-based(基于相等)的,即根据Selector中的表达式,对Pod的标签进行相等关系(等于/不等于)运算,以此决定该Pod是否为其管理对象;RS中的Selector是set-based(基于集合)的,根据Selector中的表达式,对Pod的标签进行集合运算,以此决定该Pod是否为其管理对象。RS中的Selector相对RC的Selector更为灵活,功能更强大。

总之,RS可以实现RC的所有功能,同时还有更为强大的Selector,此外RS还可以用于Pod的水平自动伸缩(HPA,Horizontal Pod Autoscalers),实现Pod规模随负载而自动调整。

Kubernetes的官方文档(https://www.kubernetes.org.cn/replicasets)中推荐使用RS。

1.2.5 Deployment——在Kubernetes中部署应用

Deployment是Kubernetes object,它是用户部署Pod的行为的抽象。因此,在Deployment中可以创建Pod,也可以创建RS来管理Pod副本和实现集群的伸缩,还可以很方便地对Deployment行为进行回滚,暂停和恢复等操作。

根据Kubernetes官方文档的建议,用户应尽量避免直接创建Pod和RC/RS,而是使用Deployment来完成Pod和RS的创建和使用。

1.2.6 Service——以统一的方式对外提供服务

Service是Kubernetes object,它提供了一种固定的Pod服务访问方式(通过固定的IP或者字符串标识加上端口来访问Pod所提供的服务),而不用关心Pod副本具体在集群的哪个节点上运行。下面举例说明Service出现的背景以及它在Kubernetes的作用。

假设部署了一个提供Web服务(LAMP)的Pod,并设置该Pod的副本数为3,那么Kubernetes会在集群中启动3个Pod副本,每个Pod会有单独的IP。在没有Service的情况下,用户需要通过这3个Pod中任意一个的IP和端口去访问Web服务,如果Pod所在节点不可用了,Kubernetes会在其他的节点上启动一个新的Pod,此时该Pod的IP就改变了。因此,用户需要关注Pod IP的变化,并用新的IP去访问Web服务,这样既不能很好地保证服务的可用性,也无法实现规模化应用。

Kubernetes提供了Service来解决上述问题,Service创建后会提供一个固定的IP(以ClusterIP类型的Service为例),这个IP地址是不会变化的,它不会随Pod副本IP的改变而改变,因此,用户可以始终根据该IP,加上对应的端口去访问Web服务,完全不用关心提供Web服务的Pod在哪个节点。

Kubernetes提供了多种类型的Service,供集群节点上的应用,或集群外的节点的应用来访问Pod服务。

一个Service对应一组Pod服务(Pod副本IP不同、端口相同),其中每个Pod的服务,由该Pod的IP+端口来标识,Kubernetes把这个标识(Pod的IP和端口)称为一个Endpoint(端点),它是Pod服务的具体提供者。一个Service对应的所有Endpoint的集合称为Endpoints,Endpoints是kubernetes中的一个resource,用户访问Service,最终会由该Service所对应的Endpoints中的某个Endpoint来提供,Kubernetes创建Service时,会根据其配置文件中的selector描述来自动创建Endpoints。

1.2.7 其他核心概念

本节介绍Kubernetes的其他核心概念,包括Controller、StatefulSet、Configmap/Secret和Namespace,具体说明如下。

1.Controller——实现Kubernetes状态控制

Kubernetes的Controller是一个控制回路,它用来监控集群的状态,然后在必要的时候,直接更改集群的状态或者发起请求,使得当前集群的状态向期望的状态靠拢。可以把Controller理解成是一个无限循环,它会持续不断地监控一种或多种Kubernetes object,一旦发现该Kubernetes object的状态同Kubernetes object所配置的期望状态(由spec字段指定)不一致时,则会采取相应的措施来调整该Kubernetes object,使得其状态同配置一致。Kubernetes有很多内置的Controller,典型的如RS和Deployment等,除了内置Controller,Kubernetes还支持用户编写的自定义Controller。

有关Controller的更多详细信息,参考https://kubernetes.io/zh/docs/concepts/architecture/controller/

2.StatefulSet——管理Kubernetes上有状态的应用

StatefulSet是一个Kubernetes Workload object,用于管理Kubernetes上有状态的应用。它可以实现Pod的有序部署、删除和伸缩,并且为每个Pod赋予稳定且唯一的ID,即便Pod被重新调度,新Pod仍旧会使用被替换Pod的名字、主机名和存储。

有关StatefulSet的更多详细信息,参考https://kubernetes.io/zh/docs/tutorials/stateful-application/basic-stateful-set/;

https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset;

https://kubernetes.io/blog/2016/12/statefulset-run-scale-stateful-applications-in-kubernetes/。

3.Configmap/Secret——实现Kubernetes配置存储

Configmap是一种Kubernetes object,它以键值对(KV)的方式来存储明文数据,Pod可以将它用作环境变量、命令行参数或存储卷的配置文件。这样就解除了容器镜像同配置文件之间的耦合,既实现了镜像的标准化,又方便配置的修改。Configmap使用方便,但它不提供保密或加密功能,如果要存储机密数据,则可以使用Secret,Secret也是一种Kubernetes object,它和Configmap在功能上类似,但是,Secret支持以加密的方式来存储数据。

更多详细内容参考https://kubernetes.io/zh/docs/concepts/configuration/secret/。

4.Namespace——实现Kubernetes中的虚拟集群

Namespace实现了单个Kubernetes集群内resource的隔离,不同Namespace内的resource互不可见,同一个Namespace内的resource名字必须唯一。基于Namespace,可以在单个物理Kubernetes集群上实现多个虚拟集群,每个虚拟集群有自己的Namespace名称,有自己的Deployment和Pod等Kubernetes object,就好像是一个单独的Kubernetes集群一样。

更多详细信息参考https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/。