k8s系列文章6: 资源对象介绍
前面的文章已经介绍了用kubeadm搭建一套简单的k8s集群,并在上面运行了一个简单的服务示例。下面介绍一下k8s中比较重要的资源对象:
1. Master
Master是k8s的控制节点,在每个k8s中,都必须有一个Master负责整个集群的管理和控制,基本上k8s所有的控制命令都发给它。如果Master节点宕机,整个集群都将不能使用。Master节点是k8s的“大脑”。Master节点上运行着以下关键进程:
- Kubernetes API Server(kube-apiserver):提供了http REST接口的关键服务进程,是k8s里所有资源的增删改查等操作的唯一入口,也是集群控制入口进程。
- Kubernetes Controller Manager(Kube-controller-manager):k8s里所有对象的自动化控制中心,可以把它理解成资源对象的“大总管”。
- Kubernetes Scheduler(kube-scheduler):负责资源调度(pod调度)的进程,相当于公交公司的调度室。
同时,在Master上通常还需要部署etcd服务,因为Kubernetes里的所有资源对象的数据都被保存在etcd中。
2. Node
除了Master节点,k8s集群中的其他机器,无论是虚拟机还是物理机,都是Node节点。每个Node都会被Master分配一些工作负载。Node上运行着以下关键进程:
- kubelet:负责Pod对应的容器的创建、启停等任务,同时于Master密切协作,实现集群管理的基本功能。
- kube-proxy:实现Kubernetes Service的通信和负载均衡的重要组件。
- Docker Engine(docker):Docker引擎,负责本机的容器创建和管理工作。
Node可以在运行期间动态增加到kubernetes集群中,前提是在这个节点上正确安装、配置和启动了上述关键进程,默认情况下,kubelet会向Master注册自己,这也是Kubernetes推荐的ode管理方式。一旦Node被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的情报,例如操作系统、Docker版本,机器的CPU和内存等情况;以及哪些Pod在运行等。这样Master就可以获知每个Node的资源使用情况。若某个Node在超过指定时间范围内不上报信息时,会被Master判定为“失联”,Node的状态被标记为不可用(Not Ready),随后Master会触发“工作负载大转移”的自动流程。
执行下列命令查看在集群中有多少个Node:
kubectl get nodes
获取Nodes.png
然后,通过以下命令查看某个Node的详细信息:
kubectl describe node host-10-5-3-206
Node详细信息
上述命令展示Node的基本关键信息,如下:
- Node的基本信息:名称、标签、创建时间等。
- Node当前的运行状态:Node启动后会做一系列的自检工作。
- Node的主机地址和主机名。
- Node上的资源数量。
- Node的可分配的资源量。
- 主机系统信息。
- 当前运行的Pod列表的该要信息。
- 已分配的资源使用该要信息,例如资源申请的最低、最大允许使用量等。
- Node相关的Event信息。
3. pod
pod是k8s最基础重要的基本概念,每个pod都有一个特殊的被称为“跟容器”pause容器。pause容器对应的镜像属于k8s平台的一部分,除了Pause容器,每个Pod还包含一个或多个紧密相关的业务容器。 pod的组成示意图k8s设计pod的原因如下:
- 在一组容器作为一个单元的情况下,很难简单地对“整体”进行有效的判断和行动。引入业务无关并且不易死亡的Pause容器作为pod的跟容器,以它的状态代表整个容器的状态,就很巧妙的解决了问题。
-
Pod里的多个业务容器共享Pause容器的IP,共享Pause容挂接的Volume。这样不仅简化了业务容器之间的通信问题,也很好的解决了这一组容器的文件共享问题。
在k8s中,一个pod容器能直接和另一个主机上的容器进行通信。Pod有两种类型,分别是普通额Pod和静态的 Pod。后者是一种特殊的Pod,它并不存放在k8s的etcd上,而是被存在某个具体的Node的一个主机文件中,并且只在此Node上启动、运行。普通的pod一旦被创建,就会被放在etcd中存储,随后会被Kubernetes Master调度到某个Node上并进行绑定(binding),随后该pod被对应的Node上的kubelet进程示例化成一组相关的Docker容器并启动。在默认情况下,当Pod里面的某个容器停止时,k8s会自动检测这个问题摒弃重新启动这个pod-->实际上就是重启pod中的所有容器,如果pod所在的Node宕机,就回将这个Node上的所有Pod重新调度到其他节点上。pod、node和Master的关系如下图所示:
pod,node和容器的关系
每个Pod都可以对其能使用的资源设置限额,当前可以设置限额的计算资源有CPU和Memory两种,其中CPU的资源单位为CPU(code)的数量,是一个绝对值而非相对值。
对于绝大多数的容器而言,一个CPU是相当大的,k8s中以千分之一的CPU为最小资源单位,用m表示。通常一个容器的CPU的配额是100m~300m。在k8s中,一个计算资源进行配额限定时需要设以下两个参数: - Request:该资源的最小申请量,系统必须满足要求;
- Limits:该资源允许使用的最大量,不能被突破。下面的示例定义mysql容器能使用的资源配额为0.5个CPU及128MiB内存:
spec:
containers:
- name: db
image: mysql
resources:
requests: #一般情况下
memory: "64Mi"
cpu: "250m"
limits: #最大值
memory: "128Mi"
cpu: "500m"
4. Labels
Lables是k8s系统中另一个核心的概念,一个Label是一个key=value形式的键值对,其中key和value由用户自己指定。Label相当于我们熟悉的标签,通过Label Selector进行选择。Label Selector可以理解成是SQL语句中的where语句。目前有两种Label Selector表达式,分别是分别是:
- Equality-based:eg:name=redis-slave匹配所有具有redis-slave标签的资源
- Set-based:eg:name in (redis-slave, redis-master) 匹配具有redis-slave和redis-master的对象
可以通过多个label Selector语句的组合实现复杂的条件筛选,多个表达式之间用“,”连接。matchLabels用于定义一组Label,和直接在Selector中的作用相同。Label Selector在k8s中的重要使用场景如下: - kube-controller进程通过在资源对象RC上定义的Label Selector来筛选要监控的pod副本的数量。
- kube-proxy进程通过Service的Label Selector来选择对应的pod,自动建立每个service到对应pod的请求转发路由表,从而实现service智能负载均衡机制。
- 通过对某些Node定义特定的Label,并且在Pod定义中使用NodeSelector这种标签,kube-scheduler进程可以实现pod定向调度的特性。
总之,使用Label可以给对象创建多组标签,Label和LabelSelector共同构成来k8s系统中核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现来整个集群的高可用性。
5. Replication Controller
RC是k8s系统中的核心概念之一,简单来说,它其实定义了一个期望的场景,即声明某种Pod的副本的数量在任意时刻都符合某个预期值,所以RC定义包括如下几个部分。
- Pod期待的副本的数量
- 用于筛选目标Pod的Label Selector
- 当Pod的副本数量小于预期数量时,用于创建新Pod的Pod模版(template)
下面是一个完整的RC定义的例子,作用是确保拥有tier=frontend标签的这个Pod(运行Tomcat容器)在整个k8s集群中始终只有一个副本:
apiVersion: v1
kind: ReplocationController
metadata: frontend
spec:
replicas: 1
selector:
tier: frontend
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
container:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
env:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
在定义了一个RC并将其提交到k8s集群中后,Master上的Controller Manager组件就得到通知,定期巡检系统中当前存活的目标Pod,并确保Pod示例的数量刚好等于此RC的期望值,如果有过多的Pod副本的存在运行,系统就会停掉一些Pod,否则系统会自动创建一些Pod。可以说,通过RC,k8s实现了用户应用集群的高可用性,并且大大减少了系统管理员在传统IT环境中需要完成的许多手工运维工作。在运行时,可以通过修改RC的副本数量,来实现Pod的动态缩放(Scaling),这可以通过执行kubectl scale命令来一键完成。
kubectl scale rc redis-slave --replicas=3
需要注意的是,删除RC并不会影戏那个通过该RC已经创建好的Pod。为了删除所有Pod,可以设置replicas的值为0,然后更新该RC。同时,kubectl提供了stop和delete命令来一次性删除RC和RC控制的全部Pod。RC支持滚动升级。Replication Controller由于与k8s中的Replication Controller同名,k8s在1.2之后,升级为另一个新的概念,Replica Set。官方解释是“新一代的RC”,当前的唯一区别是,Replica Sets支持基于Label selector(Set-based selector),而RC只支持基于等式的Label Selector(equality-based selector),这使得Replica Set的功能更强。下面等价于之前RC例子的Replica Set的定义(省去来Pod模版部分的内容):
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: frontend
spec:
selector:
matchlabels:
tier: frontend
matchExpressions:
- {key: tier, operator: in, values: [frontend]}
template:
.....
当前,我们很少使用Replica Set,它主要被Deployment这个更高级的资源对象所使用,从而形成一整套Pod创建建设、删除、更新的编排机制。Replica Set和Deplotment这两个重要的资源对象逐渐替代了之前的RC,是k8s 1.3里Pod自动弹缩这个告警功能实现的基础。
综上:
- 在大多数情况下,我们定义一个RC实现Pod的创建及副本数量的自动控制
- 在RC里包括完整的Pod的定义模版
- RC通过label Selector机制实现对Pod副本的自动控制
- 通过改变RC里的Pod副本数量,可以实现Pod的扩容和缩容
- 通过改变RC里Pod模版中的镜像版本,可以实现Pod的滚动升级
6. Deployment
Deployment是k8s在1.2版本中引入的概念,用于更好地解决Pod的编排问题。从各种角度来看,Deployment和RC的相似度超过90%。Deployment相对于RC的一个最大升级就是我们可以随时知道Pod“部署”的进度。实际上由于一个Pod的创建、调度、绑定节点及在目标Node上启动对应的容器这一完整过程需要一定的时间,所以 我们期待系统启动N个Pod副本的目标状态,实际上是一个连续变化的“部署过程”导致的最终状态。
7. Horizontal Pod Autoscaler
有的场景下,需要频繁的触发水平扩容和缩容。1.1版本发布了Horizontal Pod Autoscale(HPA),HPA有以下两种方式作为Pod负载的度量指标。
- CPUUtilozationPercentage
- 应用程序自定义的度量指标,比如服务在每秒内的相应请求数量(TPS或QPS)
前者是一个算数平均值,即目标副本自身的CPU利用率的平均值。一个HPA定义的具体例子如下:
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
namespace: default
spec:
maxReplicas: 10
minReplicas:1
scaleTargetRef:
kind: Deplotment
name: php-apache
targetCPUUtilizationPercentage: 90
和上面等价的命令行行为:
kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10
8. StatefulSet
k8s系统中,Pod的管理对象RC、Deployment、DeamonSet和Job都面向无状态的服务。但是有时候是很多的服务是有状态的,特别是一些有着以下特点的集群:
- 每个节点都有都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并进行通信。
- 集群的规模是比较固定的,集群规模不能随意变动。
- 集群中的每个环节都是有状态的,通常会持久化到永久存储中。
- 如果磁盘损坏,则集群中的某个节点无法正常运行,集群功能受损。
为了解决服务有状态的问题,k8s引入了StatefulSet,可以将这个对象看成是RC/Deployment的一个变种,它有以下的特性: - StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他的成员。假设StatefulSet的名称为kafka,那么第一个Pod叫kafka-0,第2个叫做kafka-1,以此类推。
- Stateful控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态。
- StatefulSet里的pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除 Pod时默认不会删除于StatefulSet相关的存储卷。
StatefulSet除了要于PV卷捆绑使用以存储Pod的状态数据,还要于Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于那个Headless Service。
9. Service
9.1 概述
Service服务是k8s的核心资源对象之一,k8s里的每个Service其实就是我们经常提起的微服务架构中的一个微服务,之前讲解Pod、RC等资源对象其实都是为讲解k8s Service做铺垫的。K8s的Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service和其后端Pod副本集群之间则是通过Label Selector来实现无缝对接的。RC的作用实际上是保证Service的服务能力和服务数量始终符合预期标准。K8s的服务之间通过TCP/IP进行通信。k8s发明了一种很巧妙的服务发现和负载均衡机制。Service不是公用一个负载均衡的IP地址,每个Service都被分配了一个全局唯一的的虚拟IP地址,这个虚拟IP被称为Cluster IP地址。服务调用就变成了最基础的TCP网络通信问题。下面是一个tomcat-service的yaml文件:
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
-port: 8000
selector:
tier: frontend
运行下面的内容进行创建:
kubectl create -f tomcat-server.yaml
运行下面的命令分查看端口和Cluster IP:
kubectl get endpoints
kubectl get svc tomcat-service -o yaml
在很多服务中,都存在多端口问题,通常一个端口提供业务服务,另一个端口提供管理功能。k8s支持多个Endpoint,但是要求每一个Endpoint都定义一个名称来区分。下面的 例子是Tomcat多端口service的定义示例:
apVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
name: service-port
- port: 8005
name: shutdown-port
selector:
tier: frontend
9.2 k8s的服务发现机制
&emps;任何分布式系统都会涉及“服务发现”这个基础问题,大部分分布式系统都通过提供特定的API接口来实现服务发现功能,但是这样会导致平台的侵入性比较强,也增加了开发、测试的难度。k8s则采用了直观朴素的思路去解决这个棘手的问题。
首先,每个k8s中的Service都有唯一的Cluster IP及唯一的名称,而名称是由开发者自己定义的,部署时也没有必要改变,所以完全可以固定到配置中,接下来的问题就是如何通过Service的名称找到对应的Cluster IP。
k8s通过Add-on增值包引入了DNS系统,将服务名作为域名,这样程序就可以直接使用服务名来建立通信了。后续将会介绍如何部署DNS系统。
9.3 外部系统访问Service的问题
k8s中有三种IP,弄清他们的区别对于 理解k8s很有必要。
- Node IP:Node的IP地址
- Pod IP:Pod的IP地址
- Cluster IP:Service的IP地址
Node IP是真实在的IP地址。集群外的节点访问k8s集群中的某个节点或者是服务时,都必须通过Node IP通信。Pod IP是每个Pod的IP地址,它是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,前面说过,k8s要求位于不同Node上的Pod能够彼此直接通信你,所以Pod都能够彼此直接通信,所以k8s里一个Pod容器访问另一个Pod里的容器时,就是通过Pod IP所在 的虚拟二层网络进行通信的,而真实的流量是通过Node IP所在的物理网卡流出的。cluster IP更像是一个“伪造”的IP网络,因为: - Cluster IP仅仅作用于k8s Service这个对象,并由k8s管理和分配的IP地址,来源于Cluster IP池。
- Cluster IP无法被ping,因为没有一个实体网络来相应。
- Cluster IP只能结合Service Port组成一个具体的通信端口,单独的IP不具有TCP/IP通信的基础,并且他们属于k8s集群中这样一个封闭的空间,集群外的节点如果要访问这个通信地址,则需要一些额外的工作。
- 在k8s集群内,Node IP网络、Pod IP网络和Cluster IP网之间的通信,采用的是k8s自己设计的一种编程方式的特殊的路由规则,和我们熟悉的IP路由有很大的不同。
10. Job
批处理任务通常并行启动多个计算机进程去处理一批工作项,在处理完成后,整个批处理任务结束。从1.2开始,k8s支持批处理类型的应用,可以通过Job这种资源对象定义并启动一个批处理任务的Job。Job控制一组Pod容器,可以将Job看作是一个特殊的Pod副本控制器,同时Job控制Pod副本和RC等控制器的工作机制有以下的区别:
- Job所控制的Pod副本是短暂运行的,可以将其视为一组Docker容器,起哄的每个Docker容器都仅仅运行一次,当Job控制的所有Pod的副本都运行结束时,对应的Job也就结来。Job生成的副本是不能自动重启的,对应的Pod副本的RestartPolicy都被设置为Never。
- Job所控制的Pod副本的工作模式能够多实例并行计算。
11. Volume
Volume是Pod中能够被多个容器访问的共享目录。k8s的Volume概念、用途和目的和Docker的Volume比较类似,但是两者不能等价。首先,k8s的volume被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下;其次,k8s的Volume和Pod的生命周期相同,但是与容器的生命周期不相关,当容器终止或者重启时,Volume中的数据并不会丢失,最后,k8s支持多种类型的Volume。使用Volume,必须要现在Pod声明一个Volume,然后在容器中引用该Volume并挂载(Mount)到容器里的某个目录上。定义一个Volume如下:
# Pod的部分内容
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
volumes:
- name: detavol
emptydir: {}
containers:
- name: tomcat-demo
image: tomcat
volumeMounts:
- mountPath: /mydata-data
name: datavol
imagePullpolicy: IfNotPresent
除了可以让Pod里的多个容器共享文件、让容器的数据可以写到宿主机的磁盘上或者写文件到网络存储中,k8s的Volume还扩展了一种非常有使用价值的功能,就是容器配置文件的集中化定义与 管理,这是通过ConfigMap这种新的资源对象来实现的。k8s实现了丰富的Volume类型的,如下:
- emptydir:一个emptydir Volume实在Pod分配到Node时创建的。它的初始内容为空,并且无需指定宿主机上对应的目录文件,因为这是k8s自动分配的一个目录,当pod从Node上移除时,它里面的数据会被永久删除,emptydir有下面的一些用途。
1.临时空间,例如对于某些应用程序运行时所需的临时目录,且无需永久保留
2.长时间任务中的中间过程CheckPoint的临时保存目录
3.一个容器需要从另一个容器中获取数据的目录(多容器数据共享) - hostPath:hostPath是在Pod容器上挂载宿主机上的文件或者目录,它通常可以用于以下几个方面
1.容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储。
2.需要访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义hostsPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问Docker的文件系统。
在使用这种类型的Volume时,需要注意以下几点:
1.在不同的Node上具有相同配置的Pod,可能会因为宿主机上的目录和文件不同而导致对Volume上目录和文件的访问结果不一致,
2.如果使用了资源配额管理,则k8s无法将hostPath在宿主机上使用的资源纳入管理。
定义一个hostPath类型的Volume的文件如下:
volumes:
-name: "Persistent-storage"
hostPath:
path: "/data"
- gcePersistentDisk:使用这种Volume表示使用谷歌公有云提供的永久磁盘。此处不做过多解释。
- awsElasticBlockStore:使用亚马逊公有云提供的EBS Volume存储数据,此处不做过多解释。
- iscsi:使用iSCSI存储设备上的目录挂载到Pod中
-gitRepo:通过挂载一个空目录,并从Git库中clone一个git repository以供Pod使用。
12. Persistent Volume
网络存储是相对独立于计算资源而存在的一种实体资源。比如在使用虚拟机的情况下,我们通常会定义一个网络存储,然后从中划出一个“网盘”并挂载到虚拟机上。Persistent Volume(PV)和与之相关联的Persistent Volume chaim(PVC)也起到了类似的作用。PV可以理解成是k8s集群中的某个网络存储对应的一块存储,它与Volume类似,但是有以下区别:
1.PV只能是网络存储,不属于任何Node,但是可以被任何Node访问
2.PV并不是被定义 在Pod上的,而是独立于Pod之外定义的
下面给出一个NFS类型的PV的yaml定义问价,声明需要 5G的空间:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
spec:
capacity:
storage: 5Gi
accessModes:
nfs:
path: /somepath
server: 172.17.0.2
  如果某个Pod想要申请某种类型的PV,则首先需要定义一个Persistent VolumeClaim对象:
```yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
stoorage: 8Gi
然后在Pod的Volume定义中引用上述PVC即可
volume:
- name: mypod
persistent:VolumeVlaim:
claimName: myclaim
最后说下PV的状态。PV是有状态的对象,它的状态有以下集中
1.Available:空闲状态
2.Bound:已经绑定到某个PVC上
3.Released:对应的PVC已经被删除,但是资源还没有被集群回收
4.Failed:PV自动回收失败
13. Namespace
Namespace是k8s系统中另一个非常重要的概念,Namespace在很多情况下用于实现用户的资源隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享整个集群资源的同时还能被分别管理。Namespace的定义很简单:
apiVersion: v1
kind: Namespace
metadata:
name: development
一旦创建了Namespace,在创建资源的时候,就可以指定资源对象属于哪个Namespace
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: development
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
14. ConfigMap
为了解决Docker容器运行时修改配置文件的问题,k8s提供了如下巧妙的实现:
首先,把所有的配置项当作是key-value字符串。value可以来自某个文本文件。配置参数可以作为Map表中的一个项,整个Map的数据可以被持久化存储在etcd数据库中,然后提供API以方便k8s相关组件或者客户应用CRUD操作这些数据,上述专门用来保存配置参数的Map就是K8s的ConfigMap对象。然后k8s提供一种内建机制,将存储在etcd中的ConfigMap通过Volume映射的方式变成目标Pod内的配置文件,不管目标Pod被调度到哪个服务器上,都会自动映射。
以上是k8s中资源的一个概述,后续将会继续根据资源,逐渐进行深入研究。你 ,学废了吗?