Kubernetes
中的基本概念和术语大多是围绕 资源对象(Resource Object
)来说 的,而资源对象在总体上可分为以下两类:
Node
、Pod
、服务Service
、存储卷 Volume
。Label
、注解Annotation
、命名空间Namespace
、部署Deployment
、HPA
、PVC
。资源对象一般包括几个通用属性:版本、类别(Kind
)、名称、标签、注解,如下所述。
Name
)、标签、注解这三个属性属于资源对象的元数据(metadata
),资源对象的名称要唯一。资源对象的标签是很重要的数据,也是 Kubernetes
的一大设计特性,比如通过标签来表明资源对象的特征、类别,以及通过标签筛选不同的资源对象并实现对象之间的关联、控制或协作功能。我们可以采用 YAML
或 JSON
格式声明(定义或创建)一个 Kubernetes
资源对象,每个资源对象都有自己的特定结构定义(可以理解为数据库中一个特定的表),并且统一保存在 etcd
这种非关系型数据库中,以实现最快的读写速度。此外,所有资源对象都可以通过 Kubernetes
提供的kubectl
工具(或者 API 编程调用)执行增、删、改、查等操作。
这里按照功能或用途对众多资源对象其进行分类,将其分为集群类、应用类、存储类及安全类这四大类,下面来讲解。
集群(Cluster
)表示一个由 Master
和 Node
组成的 Kubernetes
集群。
Master
指的是集群的控制节点。在每个 Kubernetes
集群中都需要有一个或一组被称为 Master
的节点,来负责整个集群的管理和控制。Master
通常占据一个独立的服务器(在高可用部署中建议至少使用 3 台服务器),是整个集群的“大脑”,如果它发生宕机或者不可用,那么对集群内容器应用的管理都将无法实施。
在 Master
上运行着以下关键进程:
Master
上通常还需要部署etcd
服务。Kubernetes
集群中除 Master
外的其他服务器被称为 Node
。Node
可以是一台物理主机,也可以是一台虚拟机。Node
是Kubernetes
集群中的工作负载节点,每个 Node
都会被 Master
分配一些工作负载(Docker
容器),当某个Node
宕机时,其上的工作负载会被 Master
自动转移到其他 Node
上。
在每个 Node
上都运行着以下关键进程:
Kubernetes Service
的通信与负载均衡机制的服务。Docker
) : 负责本机的容器创建和管理。我们可以运行以下命令查看在集群中有多少个 Node:
查询node详情:
在集群类里还有一个重要的基础概念一一命名空间,它在很多情况下用于实现多租户的资源隔离,典型的一种思路就是给每个租户都分配一个命名空间。每个命名空间都是相互独立的存在,属于不同命名空间的资源对象从逻辑上相互隔离。
在每个 Kubernetes
集群安装完成且正常运行之后,Master
会自动创建两个命名空间。
default
):用户创建的资源对象如果没有指定命名空间,则被默认存放在default
命名空间中;kube-system
):系统相关的资源对象如网络组件、DNS
组件、监控类组件等,都被安装在 kube-system
命名空间中。我们可以通过命名空间将集群内部的资源对象“分配”到不同的命名空间中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时能被分别管理。当给每个租户都创建一个命名空间来实现多租户的资源隔离时,还能结合 Kubernetes
的资源配额管理,限定不同租户能占用的资源,例如 CPU
使用量、内存使用量等。
命名空间的定义很简单,如下所示的 YAML
文件定义了名为development
的命名空间:
查看(注意加上-namespace
或-n
):
应用类相关的资源对象主要是围绕 Service(服务)和 Pod 这两个核心对象展开的。
给某个资源对象定义一个 label
,就相当于给它打了一个标签,随后可以通过 Label Selector
(标签选择器)查询和筛选拥有某些 Label 的资源对象,Kubernetes 通过这种方式实现了类似 SQL 的简单又通用的对象查询机制。
Pod、Deployment和Service的关系:
前面提到,大部分 Service
都是无状态的服务,可以由多个Pod
副本实例提供服务。通常情况下,每个 Service
对应的Pod
服务实例数量都是固定的,如果一个一个地手工创建 Pod
实例,就太麻烦了,最好是用模板的思路,即提供一个 Pod
模板(Template
),然后由程序根据我们指定的模板自动创建指定数量的 Pod
实例。这就是Deployment
这个资源对象所要完成的事情了,举例:
Kubernetes
内部在每个 Node
上都运行了一套全局的虚拟负载均衡器,自动注入并自动实时更新集群中所有 Service
的路由表,通过 iptables
或者 IPVS
机制,把对 Service
的请求转发到其后端对应的某个 Pod
实例上,并在内部实现服务的负载均衡与会话保持机制。
不仅如此,Kubernetes
还采用了一种很巧妙又影响深远的设计一 ClusterIP
地址。
Service
一旦被创建,Kubernetes
就会自动为它分配一个全局唯一的虚拟 IP
地址,即ClusterIP
地址,而且在Service
的整个生命周期内,其 ClusterIP
地址不会发生改变,这样一来,每个服务就变成了具备唯一 IP
地址的通信节点,远程服务之间的通信问题就变成了基础的 TCP
网络通信问题。
我们需要先弄明白 Kubernetes 的三种 IP,分别如下:
对于每个 Service,我们通常需要配置一个对应的负载均衡器实例来转发流量到后端的 Node 上,这的确增加了工作量及出错的概率,如下图:
NodePort 的确功能强大且通用性强,但也存在一个问题,即每个 Service 都需要在 Node 上独占一个端口,而端口又是有限的物理资源,那能不能让多个 Service 共用一个对外端口呢,这就是后来增加的 Ingress资源对象所要解决的问题。
Ingress其实只能将多个 HTTP (HTTPS)的 Service“聚合”,通过虚拟域名或者 URL Path 的特征进行路由转发功能,考虑到常见的微服务都采用了 HTTP REST 协议,所以 Ingress 这种聚合多个 Service 并将其暴露到外网的做法还是很有效的。
在一定程度上,我们可以把 Ingress 的实现机制理解为基于 Nginx 的支持虚拟主机的 HTTP 代理。下面是一个 Ingress 的实例:
我们知道,Deployment
对象是用来实现无状态服务的多副本自动控制功能的,那么有状态的服务,比如: ZooKeeper:集群、MySQL 高可用集群(3 节点集群)、Kafka 集群等是怎么实现自动部署和管理的呢?
这个问题就复杂多了,这些一开始是依赖 StatefulSet
解决的,但后来发现对于一些复杂的有状态的集群应用来说,StatefulSet 还是不够通用和强大,所以后面又出现了 Kubernetes Operator
。
平台开发者借助 Operator
框架提供的 API
,可以更方便地开发一个类似 StatefulSet
的控制器。在这个控制器里,开发者通过编码方式实现对目标集群的自定义操控,包括集群部署、故障发现及集群调整等方面都可以实现有针对性的操控,从而实现更好的自动部署和智能运维功能。
从发展趋势来看,未来主流的有状态集群基本都会以 Operator 方式部署到 Kubernetes集群中。
除了无状态服务、有状态集群、常见的第三种应用,还有批处理应用。批处理应用的特点是一个或多个进程处理一组数据(图像、文件、视频等),在这组数据都处理完成后,批处理任务自动结束。为了支持这类应用,Kubernetes 引入了新的资源对象一一 Job,下面是一个计算圆周率的经典例子:
Jobs
控制器提供了两个控制并发数的参数:completions
和 parallelism
, completions 表示需要运行任务数的总数,parallelism 表示并发运行的个数。后来kubernetes增加了cronjob可以周期性的执行某个任务。
通过前面的学习,我们初步理解了三种应用建模的资源对象,总结如下。
在进行应用建模时,应该如何解决应用需要在不同的环境中修改配置的问题呢?这就涉及 ConfigMap
和 Secret
两个对象。
用户将配置文件的内容保存到 ConfigMap 中,文件名可作为 key, value 就是整个文件的内容,多个配置文件都可被放入同一个 ConfigMap。
在建模用户应用时,在 Pod 里将 ConfigMap。定义为特殊的 Volume 进行挂载。在 Pod 被调度到某个具体 Node 上时,ConfigMap 里的配置文件会被自动还原到本地目录下,然后映射到 Pod 里指定的配置目录下,这样用户的程序就可以无感知地读取配置了。
在 ConfigMap 的内容发生修改后,Kubernetes 会自动重新获取 ConfigMap 的内容,并在目标节点上更新对应的文件。
Secrett也用于解决应用配置的问题,不过它解决的是对敏感信息的配置问题,比如数据库的用户名和密码、应用的数字证书、Tokn、SSH 密钥及其他需要保密的敏感配置。对于这类敏感信息,我们可以创建一个 Secret 对象,然后被 Pod 引用。Secret 中的数据要求以 BASE64 编码格式存放。注意,BASE64 编码并不是加密的,在 Kubernetes1.7 版本以后,Secret 中的数据才可以以加密的形式进行保存,更加安全。
首先就是 HPA (Horizontal Pod Autoscaler)
,我们可以将 HPA
理解为 Pod
横向自动扩容,即自动控制 Pod
数量的增加或减少。通过追踪分析指定 Deployment
控制的所有目标Pod
的负载变化情况,来确定是否需要有针对性地调整目标 Pod
的副本数量,这是 HPA
的实现原理。
例如:Kubernetes
内置了基于Pod
的 CPU
利用率进行自动扩缩容的机制,应用开发者也可以自定义度量指标如每秒请求数,来实现自定义的 HPA
功能。下面是一个 HPA
定义的例子:
根据上面的定义,我们可以知道这个 HPA
控制的目标对象是一个名为 php-apache
的 Deployment
里的 Pod
副本,当这些 Pod
副本的 CPU
利用率的值超过 90% 时,会触发自动动态扩容。
VPA (Vertical Pod Autoscaler)
即垂直 Pod
自动扩缩容,它根据容器资源使用率自动推测并设置 Pod
合理的CPU
和内存的需求指标,从而更加精确地调度 Pod
,实现整体上节省集群资源的目标,因为无须人为操作,因此也进一步提升了运维自动化的水平。
存储类的资源对象主要包括: Volume
、Persistent Volume
、PVC
和 StorageClass
。
Volume
是 Pod
中能够被多个容器访问的共享目录。
首先Kubernetes
中的 Volume
被定义在 Pod
上,被一个 Pod
里的多个容器挂载到具体的文件目录下;其次,Kubernetes
中的 Volume
与 Pod
的生命周期相同,但与容器的生命周期不相关,当容器终止或者重启时,Volume
中的数据也不会丢失;最后,Kubernetes
支持多种类型的 Volume
,例如 GlusterFS、Ceph
等分布式文件系统。
Volume
的使用:在大多数情况下,我们先在 Pod
上声明一个 Volume
,然后在容器里引用该 Volume
并将其挂载(Mount
)到容器里的某个目录下。
举例来说,若我们要给之前的 Tomcat Pod
增加一个名为 datavol
的Volume
,并将其挂载到容器的/mydata-data
目录下,则只对Pod
的定义文件做如下修正即可(代码中的粗体部分):
Kubernetes
提供了非常丰富的Volume
类型供容器使用,例如:临时目录、宿主机目录、共享存储等,下面对其中一些常见的类型进行说明。
一个 emptyDir
是在 Pod
分配到 Node
时创建的。从它的名称就可以看出,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为这是 Kubernetes
自动分配的一个目录,当 Pod
从 Node
上移除时,emptyDir
中的数据也被永久移除。
emptyDir
的一些用途如下。
emptyDir
使用的是节点的存储介质,例如磁盘或者网络存储。还可以使用 emptyDir. Nedium
属性,把这个属性设置为“Memory
”,就可以使用更快的基于内存的后端存储了。需要注意的是,这种情况下的 emptyDir
使用的内存会被计入容器的内存消耗,将受到资源限制和配额机制的管理。
HostPath
为在 Pod
上挂载宿主机上的文件或目录,通常可以用于以下几方面:
Docker
引擎内部数据结构的容器应用时,可以通过定hostPath
为宿主机/var/Iib/docker
目录,使容器内部的应用可以直接访问 Docker
的文件系统。在使用这种类型的 Volume
时,需要注意以下几点。
Node
上具有相同配置的 Pod
,可能会因为宿主机上的目录和文件不同,而导致对 Volume
上目录和文件的访问结果不一致。Kubernetes
无法将 hostPath
在宿主机上使用的资源纳入管理。在下面的例子中使用了宿主机的/data
目录定义了一个hostPath
类型的 Volume
:
公有云提供的 Volume
类型包括谷歌公有云提供的 GCEPersistentDisk
、亚马逊公有云提供的 AWS Elastic Block Store (EBS Volume)
等。当我们的Kubernetes
集群运行在公有云上或者使用公有云厂家提供的 Kubernetes
集群时,就可以使用这类 Volume
。
SCSI
存储设备上的目录挂载到 Pod
中。NFS Server
上的目录挂载到 Pod
中。GlusterFS
网络文件系统的目录挂载到 Pod
中。 Ceph
块设备共享存储(Rados Block Device
)挂载到Pod
中。Git
库克隆(clone
)一个 git repository
以供 Pod
使用。Secret
数据挂载为容器内的文件。Volume
属于静态管理的存储,即我们需要事先定义每个 Volume
,然后将其挂载到 Pod
中去用,这种方式存在很多弊端,典型的弊端如下。
Kubernetes
自动化的追求目标。Volume
可能不符合目标应用的需求,比如容量问题、性能问题。所以 Kubernetes 后面就发展了存储动态化的新机制,来实现存储的自动化管理。相关的核心对象(概念)有三个:Persistent Volume(简称 PV)、StorageClass、PVC。
dynamically provisioned
)的一个存储卷,可以被理解成 Kubernetes
集群中某个网络存储对应的一块存储,它与 Volume
类似,但 PV
并不是被定义在 Pod
上的,而是独立于 Pod
之外定义的。PV
目前支持的类型主要有 gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC (Fibre Channel)、NFS、iSCSI、RBD (Rados Block Device)、CephFS、Cinder、GlusterFS、VsphereVolume、Quobyte Volumes、Mware Photon、Portworx Volumes、ScaleIO Volumes、ostPath、Local 等。PVC
正如其名,表示应用希望申请的 PV
规格,其中重要的属性包括 accessModes
(存储访问模式)、storageClassName
(用哪种 StorageClass
来实现动态创建)及 resources
(存储的具体规格)有了以 StorageClass
与 PVC
为基础的动态 PV
管理机制,我们就很容易管理和使用 Volume
了,只要在 Pod
里引用 PVC
即可达到目的,如下面的例子所示:
安全始终是 Kubernetes 发展过程中的一个关键领域。
从本质上来说 ,Kubernetes
可被看作一个多用户共享资源的资源管理系统,这里的资源主要是各种Kubernetes
里的各类资源对象,比如 Pod、Service、Deployment
等。只有通过认证的用户才能通过 Kubernetes
的 API Server
查询、创建及维护相应的资源对象,理解这一点很关键。
在默认情况下,Kubernetes
在每个命名空间中都会创建一个默认的名称为 default
的 Service Account
,因此 Service Account
是不能全局使用的,只能被它所在命名空间中的 Pod
使用。通过以下命令可以查看集群中的所有 Service Account
:
Service Account
是通过 Secret
来保存对应的用户(应用)身份凭证的,这些凭证信息有 CA
根证书数据(ca.crt
)和签名后的Token
信息(Token
)。
在 Token
信息中就包括了对应的Service Account
的名称,因此 API Server
通过接收到的Token
信息就能确定 Service Account
的身份。在默认情况下,用户创建一个 Pod
时,Pod
会绑定对应命名空间中的 default
这个 Service Account
作为其“公民身份证”。
当 Pod
里的容器被创建时,Kubernetes
会把对应的Secret
对象中的身份信息(ca.crt、Token
等)持久化保存到容器里固定位置的本地文件中,因此当容器里的用户进程通过 Kubernetes
提供的客户端 API
去访问 API Server
时,这些API
会自动读取这些身份信息文件,并将其附加到 HTTPS
请求中传递给API Server
以完成身份认证逻辑。在身份认证通过以后,就涉及“访问授权”的问题,这就是 RBAC
要解决的问题了。
首先我们要学习的是 Role
这个资源对象,包括 Role
与 ClusterRole
两种类型的角色。角色定义了一组特定权限的规则,比如可以操作某类资源对象。局限于某个命名空间的角色由Role
对象定义,作用于整个 Kubernetes
集群范围内的角色则通过 Cluster Role
对象定义。
下面是 Role
的一个例子,表示在命名空间 default
中定义一个Role
对象,用于授予对 Pod
资源的读访问权限,绑定到该 Role
的用户则具有对 Pod
资源的 get
、watch
和list
权限:
接下来就是如何将 Role
与具体用户绑定(用户授权)的问题了。我们可以通过 RoleBinding
与 ClusterRoleBinding
来解决这个问题。下面是一个具体的例子,在命名空间 default
中将“pod-reader”
角色授予用户“Caden
”,结合对应的 Role
的定义,表明这一授权将允许用户“Caden
”从命名空间 default
中读取 pod
:
在 RoleBinding
中使用 subjects
(目标主体)来表示要授权的对象,这是因为我们可以授权三类目标账号:Group
(用户组)、User
(某个具体用户)和 Service Account
(Pod
应用所使用的账号)。
在安全领域,除了以上针对 API Server
访问安全相关的资源对象,还有一种特殊的资源对象一一 NetworkPolicy
(网络策略),它是网络安全相关的资源对象,用于解决用户应用之间的网络隔离和授权问题。
NetworkPolicy
是一种关于 Pod
间相互通信,以及 Pod
与其他网络端点间相互通信的安全规则设定。
NetworkPolicy
资源使用标签选择 Pod
,并定义选定 Pod
所允许的通信规则。在默认情况下,Pod
间及Pod
与其他网络端点间的访问是没有限制的,这假设了 Kubernetes
集群被一个厂商(公司/租户)独占,其中部署的应用都是相互可信的,无须相互防范。但是,如果存在多个厂商共同使用一个Kubernetes
集群的情况,则特别是在公有云环境中,不同厂商的应用要相互隔离以增加安全性,这就可以通过 NetworkPolicy
来实现了。