Deployment

Horizontal Pod Autoscaler

在Kubernetes 1.2中HPA被升级为稳定版本( apiVersion: autoscaling/v1),但仍然保留了旧版本( apiVersion: extensions/v1beta1)。 Kubernetes从1.6版本开始,增强了根据应用自定义的指标进行自动扩容和缩容的功能, API版本为autoscaling/v2alpha1,并不断演进。
HPA与之前的RC、 Deployment一样,也属于一种Kubernetes资源对象。通过追踪分析指定RC控制的所有目标Pod的负载变化情况,来确定是否需要有针对性地调整目标Pod的副本数量,这是HPA的实现原理。当前, HPA有以下两种方式作为Pod负载的度量指标。

  • CPUUtilizationPercentage。
  • 应用程序自定义的度量指标,比如服务在每秒内的相应请求数( TPS或QPS)。

CPUUtilizationPercentage是一个算术平均值,即目标Pod所有副本自身的CPU利用率的平均值。创建方式,可以通过yaml文件,也可以通过命令:kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10

StatefulSet

在Kubernetes系统中, Pod的管理对象RC、 Deployment、 DaemonSet和Job都面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MySQL集群、 MongoDB集群、 Akka集群、 ZooKeeper集群等,这些应用集群有4个共同点。
( 1)每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。
( 2)集群的规模是比较固定的,集群规模不能随意变动。
( 3)集群中的每个节点都是有状态的,通常会持久化数据到永久存储中。
( 4)如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损
如果通过RC或Deployment控制Pod副本数量来实现上述有状态的集群,就会发现第1点是无法满足的,因为Pod的名称是随机产生的, Pod的IP地址也是在运行期才确定且可能有变动的,我们事先无法为每个Pod都确定唯一不变的ID。另外,为了能够在其他节点上恢复某个失败的节点,这种集群中的Pod需要挂接某种共享存储,为了解决这个问题, Kubernetes从1.4版本开始引入了PetSet这个新的资源对象,并且在1.5版本时更名为StatefulSet, StatefulSet从本质上来说,可以看作Deployment/RC的一个特殊变种,它有如下特性。

  • StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其
    他成员。假设StatefulSet的名称为kafka,那么第1个Pod叫kafka-0,第2个叫kafka-1,
    以此类推。
  • StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod
    已经是运行且准备好的状态。
  • StatefulSet里的Pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除Pod时
    默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。

StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于哪个Headless Service。 Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。 StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例都创建了一个DNS域名,这个域名的格式为:$(podname).$(headless service name)
比如一个3节点的Kafka的StatefulSet集群对应的Headless Service的名称为kafka,StatefulSet的名称为kafka,则StatefulSet里的3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、 kafka-3.kafka,这些DNS名称可以直接在集群的配置文件中固定下来。

Service

概述

Service服务也是Kubernetes里的核心资源对象之一, Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个微服务。
在这里插入图片描述
从上图中可以看到, Kubernetes的Service定义了一个服务的访问入口地址,前端的应用( Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例, Service与其后端Pod副本集群之间则是通过Label Selector来实现无缝对接的。 RC的作用实际上是保证Service的服务能力和服务质量始终符合预期标准。
通过分析、识别并建模系统中的所有服务为微服务—Kubernetes Service,我们的系统最终由多个提供不同业务能力而又彼此独立的微服务单元组成的,服务之间通过TCP/IP进行通信,从而形成了强大而又灵活的弹性网格,拥有强大的分布式能力、弹性扩展能力、容错能力,程序架构也变得简单和直观许多。
既然每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint( Pod IP+ContainerPort)以被客户端访问,现在多个Pod副本组成了一个集群来提供服务,那么客户端如何来访问它们呢?一般的做法是部署一个负载均衡器(软件或硬件),为这组Pod开启一个对外的服务端口如8000端口,并且将这些Pod的Endpoint列表加入8000端口的转发列表,客户端就可以通过负载均衡器的对外IP地址+服务端口来访问此服务。客户端的请求最后会被转发到哪个Pod,由负载均衡器的算法所决定。
Kubernetes也遵循上述常规做法,运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。但Kubernetes发明了一种很巧妙又影响深远的设计:Service没有共用一个负载均衡器的IP地址,每个Service都被分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP。这样一来,每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。
我们知道, Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同。而Service一旦被创建, Kubernetes就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。于是,服务发现这个棘手的问题在Kubernetes的架构里也得以轻松解决:只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可完美解决问题。

Kubernetes的服务发现机制

Kubernetes通过Add-On增值包引入了DNS系统,把服务名作为DNS域名,这样程序就可以直接使用服务名来建立通信连接了。目前, Kubernetes上的大部分应用都已经采用了DNS这种新兴的服务发现机制。

外部系统访问Service的问题

为了更深入地理解和掌握Kubernetes,我们需要弄明白Kubernetes里的3种IP,这3种
IP分别如下。

  • Node IP: Node的IP地址。
  • Pod IP: Pod的IP地址。
  • Cluster IP: Service的IP地址。

首先, Node IP是Kubernetes集群中每个节点的物理网卡的IP地址,是一个真实存在的物理网络,所有属于这个网络的服务器都能通过这个网络直接通信,不管其中是否有部分节点不属于这个Kubernetes集群。这也表明在Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务时,都必须通过Node IP通信。
其次, Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,前面说过, Kubernetes要求位于不同Node上的Pod都能够彼此直接通信,所以Kubernetes里一个Pod里的容器访问另外一个Pod里的容器时,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量是通过Node IP所在的物理网卡流出的。
最后说说Service的Cluster IP,它也是一种虚拟的IP,但更像一个“ 伪造” 的IP网络,原因有以下几点。

  • Cluster IP仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)。
  • Cluster IP无法被Ping,因为没有一个“ 实体网络对象” 来响应。
  • Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信的基础,并且它们属于Kubernetes集群这样一个封闭的空间,集群外的节点如果要访问这个通信端口, 则需要做一些额外的工作。
  • 在Kubernetes集群内, Node IP网、 Pod IP网与Cluster IP网之间的通信,采用的是Kubernetes自己设计的一种编程方式的特殊路由规则,与我们熟知的IP路由有很大的不同。

根据上面的分析和总结,我们基本明白了: Service的Cluster IP属于Kubernetes集群内部的地址,无法在集群外部直接使用这个地址。那么矛盾来了:实际上在我们开发的业务系统中肯定多少有一部分服务是要提供给Kubernetes集群外部的应用或者用户来使用的,典型的例子就是Web端的服务模块,比如上面的tomcat-service,那么用户怎么访问它?采用NodePort是解决上述问题的最直接、有效的常见做法。以tomcat-service为例,在Service的定义里做如下扩展即可(见代码中的粗体部分):
在这里插入图片描述
其中, nodePort:31002这个属性表明手动指定tomcat-service的NodePort为31002,否则Kubernetes会自动分配一个可用的端口。接下来在浏览器里访问http://:31002/,就可以看到Tomcat的欢迎界面了,如图所示。
在这里插入图片描述
但NodePort还没有完全解决外部访问Service的所有问题,比如负载均衡问题。假如在我们的集群中有10个Node,则此时最好有一个负载均衡器,外部的请求只需访问此负载均衡器的IP地址,由负载均衡器负责转发流量到后面某个Node的NodePort上,如图所示:
在这里插入图片描述

图中的Load balancer组件独立于Kubernetes集群之外,通常是一个硬件的负载均衡器,或者是以软件方式实现的,例如HAProxy或者Nginx。对于每个Service,我们通常需要配置一个对应的Load balancer实例来转发流量到后端的Node上,这的确增加了工作量及出错的概率。于是Kubernetes提供了自动化的解决方案,如果我们的集群运行在谷歌的公有云GCE上,那么只要把Service的type=NodePort改为type=LoadBalancer, Kubernetes就会自动创建一个对应的Load balancer实例并返回它的IP地址供外部客户端使用。其他公有云提供商只要实现了支持此特性的驱动,则也可以达到上述目的。此外,裸机上的类似机制( Bare Metal Service Load Balancers)也在被开发。

Job

批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项( work item),在处理完成后,整个批处理任务结束。从1.2版本开始, Kubernetes支持批处理类型的应用,我们可以通过Kubernetes Job这种新的资源对象定义并启动一个批处理任务Job。与RC、 Deployment、 ReplicaSet、 DaemonSet类似, Job也控制一组Pod容器。从这个角度来看, Job也是一种特殊的Pod副本自动控制器,同时Job控制Pod副本与RC等控制器的工作机制有以下重要差别。

( 1) Job所控制的Pod副本是短暂运行的,可以将其视为一组Docker容器,其中的每个Docker容器都仅仅运行一次。当Job控制的所有Pod副本都运行结束时,对应的Job也就结束了。 Job在实现方式上与RC等副本控制器不同, Job生成的Pod副本是不能自动重启的,对应Pod副本的RestartPoliy都被设置为Never。因此,当对应的Pod副本都执行完成时,相应的Job也就完成了控制使命,即Job生成的Pod在Kubernetes中是短暂存在的。 Kubernetes在1.5版本之后又提供了类似crontab的定时任务——CronJob,解决了某些批处理任务需要定时反复执行的问题。
( 2) Job所控制的Pod副本的工作模式能够多实例并行计算,以TensorFlow框架为例,可以将一个机器学习的计算任务分布到10台机器上,在每台机器上都运行一个worker执行计算任务,这很适合通过Job生成10个Pod副本同时启动运算。

Volume

Volume(存储卷)是Pod中能够被多个容器访问的共享目录。 Kubernetes的Volume概念、用途和目的与Docker的Volume比较类似,但两者不能等价。首先, Kubernetes中的Volume被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下;其次, Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器终止或者重启时, Volume中的数据也不会丢失。最后, Kubernetes支持多种类型的Volume,例如GlusterFS、 Ceph等先进的分布式文件系统。
Volume的使用也比较简单,在大多数情况下,我们先在Pod上声明一个Volume,然后在容器里引用该Volume并挂载( Mount)到容器里的某个目录上。举例来说,我们要给之前的Tomcat Pod增加一个名为datavol的Volume,并且挂载到容器的/mydata-data目录上,则只要对Pod的定义文件做如下修正即可(注意代码中的粗体部分):
在这里插入图片描述
除了可以让一个Pod里的多个容器共享文件、让容器的数据写到宿主机的磁盘上或者写文件到网络存储中, Kubernetes的Volume还扩展出了一种非常有实用价值的功能,即容器配置文件集中化定义与管理,这是通过ConfigMap这种新的资源对象来实现的,后面会详细说明。

configmap

在大多数情况下,我们都希望能集中管理系统的配置参数,而不是管理一堆配置文件。针对上述问题, Kubernetes给出了一个很巧妙的设计实现,如下所述。首先,把所有的配置项都当作key-value字符串,当然value可以来自某个文本文件,比如配置项password=123456、user=root、 host=192.168.8.4用于表示连接FTP服务器的配置参数。这些配置项可以作为Map表中的一个项,整个Map的数据可以被持久化存储在Kubernetes的Etcd数据库中,然后提供API以方便Kubernetes相关组件或客户应用CRUD操作这些数据,上述专门用来保存配置参数的Map就是Kubernetes ConfigMap资源对象。
接下来, Kubernetes提供了一种内建机制,将存储在etcd中的ConfigMap通过Volume映射的方式变成目标Pod内的配置文件,不管目标Pod被调度到哪台服务器上,都会完成自动映射。进一步地,如果ConfigMap中的key-value数据被修改,则映射到Pod中的“ 配置文件” 也会随之自动更新。于是, Kubernetes ConfigMap就成了分布式系统中最为简单(使用方法简单,但背后实现比较复杂)且对应用无侵入的配置中心。

小结

上述这些组件是Kubernetes系统的核心组件,它们共同构成了Kubernetes系统的框架和计算模型。通过对它们进行灵活组合,用户就可以快速、方便地对容器集群进行配置、创建和管理。除了本章所介绍的核心组件,在Kubernetes系统中还有许多辅助配置的资源对象,例如LimitRange、 ResourceQuota。另外,一些系统内部使用的对象Binding、 Event等请参考Kubernetes的API文档。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐