27.2 核心概念

Kubernetes的核心概念见图27-1。

要想深入理解Kubernetes的特性和工作机制,首先要掌握Kubernete模型中的核心概念。这些核心概念反映了Kubernetes设计过程中对应用容器集群的认知模型。

首先是集群组件,从架构上看,Kubernetes集群(Cluster)也采用了典型的“主-从”架构。一个集群主要由管理组件(Master)和工作节点(Node)组件构成。

另外,Kubernetes集群的主要任务始终围绕着应用的生命周期。通过将不同资源进行不同层次的抽象,Kubernetes提供了灵活可靠的生命周期管理。资源的核心抽象主要包括:

·容器组(Pod):由位于同一节点上若干容器组成,彼此共享网络命名空间和存储卷(Volume)。Pod是Kubernetes中进行管理的最小资源单位,是最为基础的概念。跟容器类似,Pod是短暂的,随时可变的;

·服务(Service):若干(往往是同类型的)Pod形成的对外提供某个功能的抽象,不随Pod改变而变化,带有唯一固定的访问路径,如IP地址或者域名。

·复制控制器(Replication Controller):负责启动Pod,并维护其健康运行的状态。是用户管理Pod的句柄。

·部署(Deployment):创建Pod,并可根据参数自动创建管理Pod的复制控制器,并且支持升级。1.2.0版本引入提供比复制控制器更方便的操作;

·横向Pod扩展器(Horizontal Pod Autoscaler,HPA):类似云里面的自动扩展组,根据Pod的使用率(典型如CPU)自动调整一个部署里面Pod的个数,保障服务可用性;

27.2 核心概念 - 图1

图27-1 Kubernetes的核心概念

此外,还有一些管理资源相关的辅助概念,主要包括:

·注解(Annotation):键值对,可以存放大量任意数据,一般用来添加对资源对象的详细说明,可供其他工具处理。

·标签(Label):键值对,可以标记到资源对象上,用来对资源进行分类和筛选;

·名字(Name):用户提供给资源的别名,同类资源不能重名;

·命名空间(Namespace):这里是指资源的空间,避免不同租户的资源发生命名冲突,另外可以进行资源限额;

·持久卷(PersistentVolume):类似于Docker中数据卷的概念,就是一个数据目录,Pod对其有访问权限。

·秘密数据(Secret):存放敏感数据,例如用户认证的口令等;

·选择器(Selector):基于标签概念的一个正则表达式,可通过标签来筛选出一组资源;

·Daemon集(DaemonSet):确保节点上肯定运行某个Pod,一般用来采集日志和监控节点;

·任务(Job):确保给定数目的Pod正常退出(完成了任务);

·入口资源(Ingress Resource):用来提供七层代理服务;

·资源限额(Resource Quotas):用来限制某个命名空间下对资源的使用,开始逐渐提供多租户支持;

·安全上下文(Security Context):应用到容器上的系统安全配置,包括uid、gid、capabilities、SELinux角色等;

·服务账号(Service Accounts):操作资源的用户账号。

下面分别解释这些核心概念的含义和用法。

27.2.1 集群组件

集群由使用Kubernetes组件管理的一组节点组成,这些节点提供了容器资源池供用户使用。主要包括管理(Master)组件和节点(Node)组件。

1.Master组件

顾名思义,Master组件提供所有与管理相关的操作,例如调度、监控、支持对资源的操作等。

Master会通过Node Controller来定期检查所管理的Node资源的健康状况,完成Node的生命周期管理。

2.Node组件

在Kubernetes中,Node是实际工作的计算实例(在1.1之前版本中名字叫做Minion)。节点可以是虚拟机或者物理机器,在创建Kubernetes集群过程中,都要预装一些必要的软件来响应Master的管理。目前,Node的配置还只能通过人工手动进行,未来可以支持通过Master来自动配置。

Node节点有几个重要的属性:地址信息、阶段状态、资源容量、节点信息。

地址信息包括:

·主机名(HostName):节点所在系统的主机别名,基本不会用到;

·外部地址(ExternalIP):集群外部客户端可以通过该地址访问到的节点;

·内部地址(InternalIP):集群内可访问的地址,外部往往无法通过该地址访问节点。

阶段状态包括:

·待定(Pending):新创建节点,还未就绪状态,需要进一步的配置;

·运行中(Running):正常运行中的节点,可被分配Pod,会定期汇报运行状态消息;

·终止(Terminated):节点已经停止,处于不可用状态,判断条件为5分钟内未收到运行状态消息。

资源容量包括常见操作系统资源,如CPU、内存、最多存放的Pod个数等。

节点信息包括操作系统内核信息、Kubernetes版本信息、Docker引擎版本信息等,会由kubelet定期汇报。

27.2.2 资源抽象

Kubernetes对集群中的资源进行了不同级别的抽象,每个资源都是一个REST对象,通过API进行操作,通过JSON/YAML格式的模板文件进行定义。

在使用Kubernetes过程中,要注意积累这些模板文件。在Kubernetes代码包的example目录下自带了十分翔实的示例模板文件,推荐读者参考使用。

1.容器组(Pod)

在Kubernetes中,并不直接操作容器,最小的管理单位是容器组(Pod)。容器组由一个或多个容器组成,Kubernetes围绕容器组进行创建、调度、停止等生命周期管理。

同一个容器组中,各个容器共享命名空间(包括网络、IPC、文件系统等容器支持的命名空间)、cgroups限制和存储卷。这意味着同一容器组中,各个应用可以很方便相互进行访问,比如通过localhost地址进行网络访问,通过信号量和共享内存进行进程间通信等,类似于经典场景中运行在同一个操作系统中的一组进程。可以简单将一个Pod当作是一个抽象的“虚拟机”,里面运行若干个不同的进程(每个进程实际上就是一个容器)。

实现上,是先创建一个gcr.io/google_containers/pause容器,创建相关命名空间,然后创建Pod中的其他容器,共享pause容器的命名空间。

容器组内的若干容器往往是存在共同的应用目的,彼此关联十分紧密,例如一个Web应用和对应的日志采集应用或状态监控应用。如果单纯把这些相关的应用放一个容器里面,又会造成过度耦合,管理升级都不方便。

容器组的经典应用场景包括:

·内容管理,文件和数据加载,缓存管理等;

·日志处理,状态快照等;

·监控代理,消息发布等;

·代理机制,网桥、网卡等;

·控制器,管理器,配置以及更新等。

跟其他资源类似,容器组是一个REST对象,用户可以通过YAML或者JSON模板来定义一个容器组资源,例如:


  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: nginx-test
  5. spec:
  6. containers:
  7. - name: nginx
  8. image: nginx
  9. ports:
  10. - containerPort: 80

可以说,容器组既保持了容器轻量解耦的特性,又提供了调度操作的便利性,在实践中提供了比单个容器更为灵活和更有意义的抽象。

容器组生命周期包括五种状态值:待定(Pending)、运行(Running)、成功(Succeeded)、失败(Failed)、未知(Unknown):

·待定(Pending):已经被系统接受,但容器镜像还未就绪;

·运行(Running):分配到节点,所有容器都被创建,至少一个容器在运行中;

·成功(Succeeded):所有容器都正常退出,不需要重启,任务完成;

·失败(Failed):所有容器都退出,至少一个容器是非正常退出;

·未知(Unknown):未知状态,例如所在节点无法汇报状态。

2.复制控制器(Replication Controller)和部署(Deployment)

在Kubernetes看来,Pod资源是可以随时故障的,并不非要保证Pod的运行,而是在发生失败后重新生成。Kubernetes通过复制控制器来实现这一功能。

用户申请容器组后,复制控制器将负责调度容器组到某个节点上,并保证它的给定份数(replica)正常运行。当实际运行Pod份数超过数目,则终止某些Pod;当不足,则创建新的Pod。一般建议,即使Pod份数是1,也要使用复制控制器来创建,而不是直接创建Pod。

可以将复制控制器类比为进程的监管者(supervisor)的角色,只不过它不光能保持Pod的持续运行,还能保持集群中给定类型Pod同时运行的个数为指定值。

Pod是临时性的,可以随时被复制控制器创建或者销毁,这意味着要通过Pod自身的地址访问应用是不能保证一致性的。Kubernetes通过服务的概念来解决这个问题。

从1.2.0版本开始,Kubernetes将正式引入部署(Deployment)机制来支持更灵活的Pod管理,从而用户无需直接跟复制控制器打交道了。

3.横向Pod扩展器(Horizontal Pod Autoscaler)

横向扩展器(HPA)在复制控制器和部署的基础上增加了自动反馈机制,通过定期查询Pod的资源利用率来调整Pod的数目,确保服务的可用性。

典型的一个场景是,定期(由—horizontal-pod-autoscaler-sync-period参数指定)通过Heapster组件来检查目标部署内Pod的平均CPU使用情况,当CPU使用率高出目标值,自动增加Pod数;反之降低。内部机制是通过scale接口来操作资源。

27.2 核心概念 - 图2提示

Heapster是一套监控工具,支持对从kubelet中采集的系统数据进行整合处理和展示,可参考https://github.com/kubernetes/heapster

用户可以显式定义一个横向扩展器资源,绑定到部署对象上,也可以通过autoscale命令来自动创建,例如:


  1. $ kubectl autoscale rc test_rc --min=2 --max=5 --cpu-percent=80

会自动创建一个复制控制器,复制个数范围为2~5,CPU利用率的目标值为80%。

注意,当使用复制控制器进行滚动升级时,横向扩展器并不会自动绑定到新创建的复制控制器上。

4.服务(Service)

Kubernetes主要面向的对象是持续运行,并且无状态(stateless)。服务的提出主要是要解决Pod地址可变的问题。由于Pod随时可能故障,并可能在其他节点上被重启起来,它的地址是不能保持固定的。因此,用一个服务来代表提供某一类功能(可以通过标签来筛选)的一些Pod,并分配不随Pod位置变化而改变的虚拟访问地址(Cluster IP)。这也符合微服务的理念。

比如网站的后端服务可能有多个Pod都运行了后端处理程序,它们可以组成一个服务。前端只需要通过服务的唯一虚拟地址来访问即可,而无需关心具体是访问到了哪个Pod。可见,服务跟负载均衡器实现的功能很相似。

组成一个服务的Pod可能属于不同复制控制器,但服务自身是不知道复制控制器的存在的。

同样,服务也是一个REST对象,用户可以通过模板来定义一个服务资源:


  1. {
  2. "kind": "Service",
  3. "apiVersion": "v1",
  4. "metadata": {
  5. "name": "web-service"
  6. },
  7. "spec": {
  8. "selector": {
  9. "app": "webApp"
  10. },
  11. "ports": [
  12. {
  13. "protocol": "TCP",
  14. "port": 80,
  15. "targetPort": 80
  16. }
  17. ]
  18. }
  19. }

这个模板会筛选所有带有标签app:webApp的Pod,作为web-service,对外呈现的访问端口为80,映射到Pod的80端口上。

服务在创建后,会被自动分配一个集群地址(Cluster IP),这个地址并不绑定到任何接口,将作为访问服务的抽象地址。访问该地址会被映射到Pod的实际地址。实现上是通过kube-proxy进程。每个节点上都会运行一个kube-proxy进程,负责将到某个Service的访问给代理或者均衡到具体的Pod上去。同时,会为每一个服务都创建环境变量,指向集群地址;或者在DNS中注册该服务的集群地址。

也许会有用户考虑使用DNS方式来替代服务的集群IP机制,这是完全可以的,Kubernetes也提供了基于skydns的插件支持。但是要处理好DNS查找的缓存过期时间问题。当某个Pod发生变化时,要让客户端本地的DNS缓存过期。

另外,服务支持进行不同类型的健康检查(通过容器spec中的livenessProbe或ReadinessProbe字段定义),目前包括三种类型:

·通过HTTP获取资源是否成功;

·在容器中执行指定命令,返回值是否为0;

·打开给定socket端口是否成功。

探测的结果可能为成功、失败或未知。其中LivenessProbe反映的是容器自身状态,如果配置了重启策略,则失败状态会触发自动重启;而ReadinessProbe字段用来反映容器内的服务是否可用。

27.2.3 辅助概念

1.标签(Label)

标签是一组键值对,用来标记所绑定对象(典型的就是Pod)的识别属性,进而可以分类。比如name=apache|nginx、type=web|db、release=alpha|beta|stable、tier=frontend|backend等。另外,Label键支持通过/来添加前缀,可以用来标注资源的组织名称等。一般前缀不能超过253个字符,键名不能超过63个字符。

标签所定义的属性是不唯一的,这意味着不同资源可能带有相同的标签键值对。这些属性可以将业务的相关信息绑定到对象上,用来对资源对象进行分类和选择过滤。

2.注解(Annotation)

注解跟标签很相似,也是一组键值对。不同的是,注解并不是为了分类资源对象,而是为了给对象增加更丰富的描述信息。这些信息是任意的,数据量可以很大,可以包括各种结构化、非结构化的数据。

常见的注解包括时间戳、发行信息、开发者信息等。

3.选择器(Selector)

基于资源对象上绑定的标签信息,选择器可以通过指定标签键值对来过滤出一组特定的资源对象。选择器支持的语法包括:基于等式(Equality-based)的和基于集合(Set-based)的。

基于等式的选择,即通过指定某个标签是否等于某个值,例如env=production或者tier!=frontend等。多个等式可以通过AND逻辑组合在一起。

基于集合的选择,即通过指定某个标签的值是否在某个集合内,例如env in(staging,production)。

4.持久卷(PersistentVolume)

PersistentVolume即容器挂载的数据卷,有跟Pod一致的生命周期,Pod生存过程(包括重启)中,数据卷跟着存在;Pod退出,则数据卷跟着退出。

几个比较常见的数据卷类型包括:emptyDir、hostPath、gcePersistentDisk、awsElasticBlockStore、nfs、gitRepo、secret。

·emptyDir:当Pod创建的时候,在节点上创建一个空的挂载目录,挂载到容器内。当Pod从节点离开(例如删除掉)的时候,挂载目录内数据被自动删除掉。节点上的挂载位置可以为物理硬盘或内存。这一类的挂载适用于非持久化的存储,例如跟Pod任务相关的临时数据等。除此之外,其他存储格式大都是持久化的;

·hostPath:将节点上某已经存在的目录挂载到Pod中,Pod退出后,节点上的数据将保留;

·gcePersistentDisk:使用GCE的Persistent Disk服务,Pod退出后,数据会被保留;

·awsElasticBlockStore:使用AWS的EBS Volume服务,数据也会被持久化保留;

·nfs:使用nfs协议的网络存储,也是持久化数据存储;

·gitRepo:挂载一个空目录到Pod,然后clone指定的git仓库代码到里面,适用于直接从仓库中给定版本的代码来部署应用;

·secret:用来传递敏感信息(如密码等),基于内存的tmpfs,挂载临时秘密文件;

其他类型还包括iscsi、flocker、glusterfs、rbd、downwardAPI、FlexVolume、AzureFileVolume等。

持久化的存储以插件的形式提供为PersistentVolume资源,用户通过请求某个类型的PersistentVolumeClaim资源,来从匹配的持久化存储卷上获取绑定的存储。

资源定义仍然通过yml或json格式的模板文件,例如定义一个持久化存储卷:


  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: pv01
  5. spec:
  6. capacity:
  7. storage: 100Gi
  8. accessModes:
  9. - ReadWriteOnce
  10. persistentVolumeReclaimPolicy: Recycle
  11. nfs:
  12. path: /tmp
  13. server: 192.168.0.2

5.秘密数据(Secret)

秘密数据资源用来保存一些敏感的数据,这些数据(例如密码)往往不希望别的用户看到,但是在启动某个资源(例如Pod)的时候需要提供。通过把敏感数据放到Secret里面,用户只需要提供Secret的别名即可使用。

例如,我们希望通过环境变量传递用户名和密码信息到一个Pod中。

首先,将用户名和密码使用base64进行编码:


  1. $ echo "admin" | base64
  2. YWRtaW4K
  3. $ echo "admin_pass" | base64
  4. YWRtaW5fcGFzcwo=

编写一个Secret的对象模板,添加前面的base64编码数据:


  1. apiVersion: v1
  2. kind: Secret
  3. metadata:
  4. name: test_secret
  5. type: Opaque
  6. data:
  7. password: YWRtaW5fcGFzcwo=
  8. username: YWRtaW4K

通过kubectl创建秘密数据对象test_secret:


  1. $ kubectl create -f ./secret.yaml
  2. secret "test_secret" created

通过环境变量在Pod中使用秘密数据:


  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: secret-test-pod
  5. spec:
  6. containers:
  7. - name: test-container
  8. image: nginx
  9. env:
  10. - name: SECRET_USERNAME
  11. valueFrom:
  12. secretKeyRef:
  13. name: test_secret
  14. key: username
  15. - name: SECRET_PASSWORD
  16. valueFrom:
  17. secretKeyRef:
  18. name: test_secret
  19. key: password
  20. restartPolicy: Never

在对应容器(secret-test-pod.test-container)内,通过环境变量$SECRET_USERNAME和$SECRET_PASSWORD即可获取到原始的用户名和密码信息。

此外,还可以采用数据卷的方式把秘密数据的值以文件形式放到容器内。通常,秘密数据不要超过1MB。

在整个过程中,只有秘密数据的所有人和最终运行的容器(准确的说,需要是同一个服务账号下面的)能获取原始敏感数据,只接触到Pod定义模板的人是无法获取到的。

6.UID和名字(Name)

Kubernetes用UID和名字来标识对象。其中,UID是全局唯一的,并且不能复用;而名字则仅仅要求对某种类型的资源(在同一个命名空间内)内是唯一的,并且当某个资源移除后,其名字可以被新的资源复用。

这意味着,可以创建一个Pod对象,命名为test,同样可以创建一个复制控制器,命名也为test。一般名字字符串的长度不要超过253个字符。

7.命名空间(Namespace)

命名空间用来隔离不同用户的资源,类似租户或项目的概念。同一个命名空间内资源不允许重名,但不同命名空间之间,允许存在重名。

用户在创建资源的时候可以通过—namespace=<some_namespace>来指定所属的命名空间。

Kubernetes集群启动后,会保留两个特殊的命名空间:

·default:资源未指定命名空间情况下,默认属于该空间;

·kube-system:由Kubernetes系统自身创建的资源。

另外,大部分资源都属于某个命名空间,但部分特殊资源,如节点、持久存储等不属于任何命名空间。