Kubernetes对象
了解Kubernetes对象
Kubernetes对象
Kubernetes对象是Kubernetes系统中的持久实体。Kubernetes使用这些实体来表示集群的状态。具体来说,他们可以描述:
- 容器化应用正在运行(以及在哪些节点上)
- 这些应用可用的资源
- 关于这些应用如何运行的策略,如重新策略,升级和容错
- Kubernetes对象是“record of intent”,一旦创建了对象,Kubernetes系统会确保对象存在。通过创建对象,可以有效地告诉Kubernetes系统你希望集群的工作负载是什么样的。
- 要使用Kubernetes对象(无论是创建,修改还是删除),都需要使用Kubernetes API。例如,当使用kubectl命令管理工具时,CLI会为提供Kubernetes API调用。你也可以直接在自己的程序中使用Kubernetes API,Kubernetes提供一个golang客户端库 (其他语言库正在开发中-如Python)。
对象(Object)规范和状态
- 每个Kubernetes对象都包含两个嵌套对象字段,用于管理Object的配置:Object Spec和Object Status。Spec描述了对象所需的状态 - 希望Object具有的特性,Status描述了对象的实际状态,并由Kubernetes系统提供和更新。
- 通过Kubernetes Deployment 来表示在集群上运行的应用的对象。创建Deployment时,可以设置Deployment Spec,来指定要运行应用的三个副本。Kubernetes系统将读取Deployment Spec,并启动你想要的三个应用实例 - 来更新状态以符合之前设置的Spec。如果这些实例中有任何一个失败(状态更改),Kuberentes系统将响应Spec和当前状态之间差异来调整,这种情况下,将会开始替代实例。
描述Kubernetes对象
在Kubernetes中创建对象时,必须提供描述其所需Status的对象Spec,以及关于对象(如name)的一些基本信息。当使用Kubernetes API创建对象(直接或通过kubectl)时,该API请求必须将该信息作为JSON包含在请求body中。通常,可以将信息提供给kubectl .yaml文件,在进行API请求时,kubectl将信息转换为JSON。
实例
以下示例是一个.yaml文件,显示Kubernetes Deployment所需的字段和对象Spec:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
使用对象
使用上述.yaml文件创建Deployment,是通过在kubectl中使用kubectl create命令来实现。将该.yaml文件作为参数传递。如下例子:
$ kubectl create -f docs/user-guide/nginx-deployment.yaml --record
其输出与此类似:
deployment "nginx-deployment" created
必填字段
对于要创建的Kubernetes对象的yaml文件,需要为以下字段设置值:
- apiVersion - 创建对象的Kubernetes API 版本
- kind - 要创建什么样的对象?
- metadata- 具有唯一标示对象的数据,包括 name(字符串)、UID和Namespace(可选项)
还需要提供对象Spec字段,对象Spec的精确格式(对于每个Kubernetes 对象都是不同的),以及容器内嵌套的特定于该对象的字段。Kubernetes API reference可以查找所有可创建Kubernetes对象的Spec格式。
Kubernetes专业术语
- 更加细的详解关于Kubernetes的组件,包括一些专业术语,Kubernetes中的大部分概念如Node、Pod、Replication Controller、Service等都可以被看作一种资源对象,几乎所有资源对象都可以通过Kubernetes提供的kubectl工具(或者API编程调用)执行增、删、改、查等操作并将其保存在etcd中持久化存储。从这个角度来看,Kubernetes其实是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的“资源期望状态”与当前环境中的“实际资源状态”的差异来实现自动控制和自动纠错的高级功能。
- 在声明一个Kubernetes资源对象的时候,需要注意一个关键属性:apiVersion。以下面的Pod声明为例,可以看到Pod这种资源对象归属于v1这个核心API。
Master
Kubernetes里的Master指的是集群控制节点,在每个Kubernetes集群里都需要有一个Master来负责整个集群的管理和控制,基本上Kubernetes的所有控制命令都发给它,它负责具体的执行过程,我们后面执行的所有命令基本都是在Master上运行的。Master通常会占据一个独立的服务器(高可用部署建议用3台服务器),主要原因是它太重要了,是整个集群的“首脑”,如果它宕机或者不可用,那么对集群内容器应用的管理都将失效。
在Master上运行着以下关键进程。
🎃 Kubernetes API Server(kube-apiserver):提供了HTTP Rest接口的关键服务进程,是Kubernetes里所有资源的增、删、改、查等操作的唯一入口,也是集群控制的入口进程。
🎃 Kubernetes Controller Manager(kube-controller-manager):Kubernetes里所有资源对象的自动化控制中心,可以将其理解为资源对象的“大总管”。
🎃 Kubernetes Scheduler(kube-scheduler):负责资源调度(Pod调度)的进程,相当于公交公司的“调度室”。
另外,在Master上通常还需要部署etcd服务,因为Kubernetes里的所有资源对象的数据都被保存在etcd中。
Node
除了Master,Kubernetes集群中的其他机器被称为Node,在较早的版本中也被称为Minion。与Master一样,Node可以是一台物理主机,也可以是一台虚拟机。Node是Kubernetes集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker容器),当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上。
在每个Node上都运行着以下关键进程。
👙 kubelet:负责Pod对应的容器的创建、启停等任务,同时与Master密切协作,实现集群管理的基本功能。
👙 kube-proxy:实现Kubernetes Service的通信与负载均衡机制的重要组件。
👙 Docker Engine(docker):Docker引擎,负责本机的容器创建和管理工作。
Node可以在运行期间动态增加到Kubernetes集群中,前提是在这个节点上已经正确安装、配置和启动了上述关键进程,在默认情况下kubelet会向Master注册自己,这也是Kubernetes推荐的Node管理方式。一旦Node被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的情报,例如操作系统、Docker版本、机器的CPU和内存情况,以及当前有哪些Pod在运行等,这样Master就可以获知每个Node的资源使用情况,并实现高效均衡的资源调度策略。而某个Node在超过指定时间不上报信息时,会被Master判定为“失联”,Node的状态被标记为不可用(Not Ready),随后Master会触发“工作负载大转移”的自动流程。
我们可以执行下述命令查看在集群中有多少个Node:
[root@localhost test]# kubectl get nodes
NAME STATUS ROLES AGE VERSION
localhost.localdomain Ready master 32h v1.18.1
[root@localhost test]# kubectl describe node localhost.localdomain
Name: localhost.localdomain
Roles: master
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/arch=amd64
kubernetes.io/hostname=localhost.localdomain
kubernetes.io/os=linux
node-role.kubernetes.io/master=
Annotations: flannel.alpha.coreos.com/backend-data: {"VtepMAC":"62:92:ec:17:49:6a"}
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: true
flannel.alpha.coreos.com/public-ip: 10.100.35.106
kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
node.alpha.kubernetes.io/ttl: 0
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sun, 25 Jul 2021 10:17:40 +0800
Taints: node-role.kubernetes.io/master:NoSchedule
Unschedulable: false
Lease:
HolderIdentity: localhost.localdomain
AcquireTime: <unset>
RenewTime: Mon, 26 Jul 2021 18:44:09 +0800
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
NetworkUnavailable False Sun, 25 Jul 2021 10:18:46 +0800 Sun, 25 Jul 2021 10:18:46 +0800 FlannelIsUp Flannel is running on this node
MemoryPressure False Mon, 26 Jul 2021 18:44:11 +0800 Sun, 25 Jul 2021 10:17:40 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Mon, 26 Jul 2021 18:44:11 +0800 Sun, 25 Jul 2021 10:17:40 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Mon, 26 Jul 2021 18:44:11 +0800 Sun, 25 Jul 2021 10:17:40 +0800 KubeletHasSufficientPID kubelet has sufficient PID available
Ready True Mon, 26 Jul 2021 18:44:11 +0800 Mon, 26 Jul 2021 09:55:53 +0800 KubeletReady kubelet is posting ready status
Addresses:
InternalIP: 10.100.35.106
Hostname: localhost.localdomain
Capacity:
cpu: 2
ephemeral-storage: 51175Mi
hugepages-2Mi: 0
memory: 8009696Ki
pods: 110
Allocatable:
cpu: 2
ephemeral-storage: 48294789041
hugepages-2Mi: 0
memory: 7907296Ki
pods: 110
System Info:
Machine ID: 5a38dc7ca92645909f5fa30bc277bbee
System UUID: 015BAF92-EED9-440C-83F1-3965831DC4D1
Boot ID: 0bf0ee42-9daa-4494-adf2-c56578f3eedf
Kernel Version: 3.10.0-862.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://19.3.13
Kubelet Version: v1.18.1
Kube-Proxy Version: v1.18.1
PodCIDR: 10.244.0.0/24
PodCIDRs: 10.244.0.0/24
Non-terminated Pods: (12 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits AGE
--------- ---- ------------ ---------- --------------- ------------- ---
default volume-pod-localhost.localdomain 0 (0%) 0 (0%) 0 (0%) 0 (0%) 22m
default web-localhost.localdomain 0 (0%) 0 (0%) 0 (0%) 0 (0%) 86m
kube-system coredns-5df79dbff-2ztlg 100m (5%) 0 (0%) 70Mi (0%) 170Mi (2%) 32h
kube-system coredns-5df79dbff-fh4tb 100m (5%) 0 (0%) 70Mi (0%) 170Mi (2%) 32h
kube-system etcd-localhost.localdomain 0 (0%) 0 (0%) 0 (0%) 0 (0%) 32h
kube-system kube-apiserver-localhost.localdomain 250m (12%) 0 (0%) 0 (0%) 0 (0%) 32h
kube-system kube-controller-manager-localhost.localdomain 200m (10%) 0 (0%) 0 (0%) 0 (0%) 32h
kube-system kube-flannel-ds-cltrg 100m (5%) 100m (5%) 50Mi (0%) 50Mi (0%) 32h
kube-system kube-proxy-pzqq4 0 (0%) 0 (0%) 0 (0%) 0 (0%) 32h
kube-system kube-scheduler-localhost.localdomain 100m (5%) 0 (0%) 0 (0%) 0 (0%) 32h
kubernetes-dashboard dashboard-metrics-scraper-d4c994986-qvkhz 0 (0%) 0 (0%) 0 (0%) 0 (0%) 32h
kubernetes-dashboard kubernetes-dashboard-6d6c4668f8-4jsz8 0 (0%) 0 (0%) 0 (0%) 0 (0%) 32h
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 850m (42%) 100m (5%)
memory 190Mi (2%) 390Mi (5%)
ephemeral-storage 0 (0%) 0 (0%)
上述命令展示了Node的如下关键信息。
😷 Node的基本信息:名称、标签、创建时间等。
😷 Node当前的运行状态:Node启动后会做一系列的自检工作,比如磁盘空间是否不足(DiskPressure)、内存是否不足(MemoryPressure)、网络是否正常(NetworkUnavailable)、PID资源是否充足(PIDPressure)。在一切正常时设置Node为Ready状态(Ready=True),该状态表示Node处于健康状态,Master将可以在其上调度新的任务了(如启动Pod)。
😷 Node的主机地址与主机名。
😷 Node上的资源数量:描述Node可用的系统资源,包括CPU、内存数量、最大可调度Pod数量等。
😷 Node可分配的资源量:描述Node当前可用于分配的资源量。
😷 主机系统信息:包括主机ID、系统UUID、Linux kernel版本号、操作系统类型与版本、Docker版本号、kubelet与kube-proxy的版本号等。
😷 当前运行的Pod列表概要信息。
😷 已分配的资源使用概要信息,例如资源申请的最低、最大允许使用量占系统总量的百分比。
😷 Node相关的Event信息。
Pod
Pod是Kubernetes最重要的基本概念,如图1.4所示是Pod的组成示意图,我们看到每个Pod都有一个特殊的被称为“根容器”的Pause容器。Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或多个紧密相关的用户业务容器。
Kubernetes为每个Pod都分配了唯一的IP地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP地址。
Pod其实有两种类型:普通的Pod及静态Pod(Static Pod)。后者比较特殊,它并没被存放在Kubernetes的etcd存储里,而是被存放在某个具体的Node上的一个具体文件中,并且只在此Node上启动、运行。
Pod的定义
- Kind为Pod表明这是一个Pod的定义,metadata里的name属性为Pod的名称,在metadata里还能定义资源对象的标签,这里声明myweb拥有一个name=myweb的标签。
- 在Pod里所包含的容器组的定义则在spec一节中声明,这里定义了一个名为myweb、对应镜像为kubeguide/tomcat-app:v1的容器,该容器注入了名为MYSQL_SERVICE_HOST='mysql'和MYSQL_SERVICE_PORT='3306'的环境变量(env关键字),并且在8080端口(containerPort)启动容器进程。
- 每个Pod都可以对其能使用的服务器上的计算资源设置限额,当前可以设置限额的计算资源有CPU与Memory两种,其中CPU的资源单位为CPU(Core)的数量,是一个绝对值而非相对值。
- 对于绝大多数容器来说,一个CPU的资源配额相当大,所以在Kubernetes里通常以千分之一的CPU配额为最小单位,用m来表示。通常一个容器的CPU配额被定义为100~300m,即占用0.1~0.3个CPU。由于CPU配额是一个绝对值,所以无论在拥有一个Core的机器上,还是在拥有48个Core的机器上,100m这个配额所代表的CPU的使用量都是一样的。与CPU配额类似,Memory配额也是一个绝对值,它的单位是内存字节数。
在Kubernetes里,一个计算资源进行配额限定时需要设定以下两个参数。
◎ Requests:该资源的最小申请量,系统必须满足要求。
◎ Limits:该资源最大允许使用的量,不能被突破,当容器试图使用超过这个量的资源时,可能会被Kubernetes“杀掉”并重启。
通常,我们会把Requests设置为一个较小的数值,符合容器平时的工作负载情况下的资源需求,而把Limit设置为峰值负载情况下资源占用的最大量。下面这段定义表明MySQL容器申请最少0.25个CPU及64MiB内存,在运行过程中MySQL容器所能使用的资源配额为0.5个CPU及128MiB内存:
Label
Label(标签)是Kubernetes系统中另外一个核心概念。一个Label是一个key=value的键值对,其中key与value由用户自己指定。Label可以被附加到各种资源对象上,例如Node、Pod、Service、RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上。Label通常在资源对象定义时确定,也可以在对象创建后动态添加或者删除。
通过给指定的资源对象捆绑一个或多个不同的Label来实现多维度的资源分组管理功能,以便灵活、方便地进行资源分配、调度、配置、部署等管理工作。监控和分析应用(日志记录、监控、告警)等。
常用的Label示例如下。
🔔 版本标签:"release":"stable"、"release":"canary"。
🔔 环境标签:"environment":"dev"、"environment":"qa"、"environment":"production"。
🔔 架构标签:"tier":"frontend"、"tier":"backend"、"tier":"middleware"。
🔔 分区标签:"partition":"customerA"、"partition":"customerB"。
🔔 质量管控标签:"track":"daily"、"track":"weekly"。
Label相当于我们熟悉的“标签”。给某个资源对象定义一个Label,就相当于给它打了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes通过这种方式实现了类似SQL的简单又通用的对象查询机制。
Replication Controller
RC是Kubernetes系统中的核心概念之一,简单来说,它其实定义了一个期望的场景,即声明某种Pod的副本数量在任意时刻都符合某个预期值,所以RC的定义包括如下几个部分。
🎧 Pod期待的副本数量。
🎧 用于筛选目标Pod的Label Selector。
🎧 当Pod的副本数量小于预期数量时,用于创建新Pod的Pod模板(template)。
下面是一个完整的RC定义的例子,即确保拥有tier=frontend标签的这个Pod(运行Tomcat容器)在整个Kubernetes集群中始终只有3个副本:
[root@master ~]# vi replicaset.yaml
kind: ReplicationController
apiVersion: v1
metadata:
name: nginx
namespace: default
spec:
replicas: 3
selector:
app: nginx
template:
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: 192.168.200.10/library/nginx
ports:
- containerPort: 80
- 在我们定义了一个RC并将其提交到Kubernetes集群中后,Master上的Controller Manager组件就得到通知,定期巡检系统中当前存活的目标Pod,并确保目标Pod实例的数量刚好等于此RC的期望值,如果有过多的Pod副本在运行,系统就会停掉一些Pod,否则系统会再自动创建一些Pod。
- 可以说,通过RC,Kubernetes实现了用户应用集群的高可用性,并且大大减少了系统管理员在传统IT环境中需要完成的许多手工运维工作(如主机监控脚本、应用监控脚本、故障恢复脚本等)。
Replication Controller由于与Kubernetes代码中的模块Replication Controller同名,同时“Replication Controller”无法准确表达它的本意,所以在Kubernetes 1.2中,升级为另外一个新概念—Replica Set,官方解释其为“下一代的RC”。Replica Set与RC当前的唯一区别是,Replica Sets支持基于集合的Label selector(Set-based selector),而RC只支持基于等式的Label Selector(equality-based selector),这使得Replica Set的功能更强。
Replica Set与Deployment这两个重要的资源对象逐步替代了之前RC的作用,是Kubernetes 1.3里Pod自动扩容(伸缩)这个告警功能实现的基础,也将继续在Kubernetes未来的版本中发挥重要的作用。
最后总结一下RC(Replica Set)的一些特性与作用。
- 在大多数情况下,我们通过定义一个RC实现Pod的创建及副本数量的自动控制。
- 在RC里包括完整的Pod定义模板。
- RC通过Label Selector机制实现对Pod副本的自动控制。
- 通过改变RC里的Pod副本数量,可以实现Pod的扩容或缩容。
- 通过改变RC里Pod模板中的镜像版本,可以实现Pod的滚动升级。
Deployment
Deployment是Kubernetes在1.2版本中引入的新概念,用于更好地解决Pod的编排问题。为此,Deployment在内部使用了Replica Set来实现目的,无论从Deployment的作用与目的、YAML定义,还是从它的具体命令行操作来看,我们都可以把它看作RC的一次升级,两者的相似度超过90%。
Deployment相对于RC的一个最大升级是我们可以随时知道当前Pod“部署”的进度。实际上由于一个Pod的创建、调度、绑定节点及在目标Node上启动对应的容器这一完整过程需要一定的时间,所以我们期待系统启动N个Pod副本的目标状态,实际上是一个连续变化的“部署过程”导致的最终状态。
Deployment的典型使用场景有以下几个。
- 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建。
- 检查Deployment的状态来看部署动作是否完成(Pod副本数量是否达到预期的值)。
- 更新Deployment以创建新的Pod(比如镜像升级)。
- 如果当前Deployment不稳定,则回滚到一个早先的Deployment版本。
- 暂停Deployment以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment,进行新的发布。
- 扩展Deployment以应对高负载。
- 查看Deployment的状态,以此作为发布是否成功的指标。
- 清理不再需要的旧版本ReplicaSets。
运行kubectl describe deployments,可以清楚地看到Deployment控制的Pod的水平扩展过程.
Pod的管理对象,除了RC和Deployment,还包括ReplicaSet、DaemonSet、StatefulSet、Job等,分别用于不同的应用场景中。
Horizontal Pod Autoscaler
在Kubernetes 1.1中首次发布重量级新特性—Horizontal Pod Autoscaling(Pod横向自动扩容,HPA)。在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)。
StatefulSet
在Kubernetes系统中,Pod的管理对象RC、Deployment、DaemonSet和Job都面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MySQL集群、MongoDB集群、Akka集群、ZooKeeper集群等,这些应用集群有4个共同点。
(1)每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。
(2)集群的规模是比较固定的,集群规模不能随意变动。
(3)集群中的每个节点都是有状态的,通常会持久化数据到永久存储中。
(4)如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。
特性
◎ StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设StatefulSet的名称为kafka,那么第1个Pod叫kafka-0,第2个叫kafka-1,以此类推。
◎ StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态。
◎ StatefulSet里的Pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。
Service
Service服务也是Kubernetes里的核心资源对象之一,Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个微服务,之前讲解Pod、RC等资源对象其实都是为讲解Kubernetes Service做铺垫的。图1.12显示了Pod、RC与Service的逻辑关系。
Kubernetes的Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现无缝对接的。RC的作用实际上是保证Service的服务能力和服务质量始终符合预期标准。
通过分析、识别并建模系统中的所有服务为微服务—Kubernetes Service,我们的系统最终由多个提供不同业务能力而又彼此独立的微服务单元组成的,服务之间通过TCP/IP进行通信,从而形成了强大而又灵活的弹性网格,拥有强大的分布式能力、弹性扩展能力、容错能力。
既然每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint(Pod IP+ContainerPort)以被客户端访问,现在多个Pod副本组成了一个集群来提供服务,通过负载均衡器的对外IP地址+服务端口来访问此服务。客户端的请求最后会被转发到哪个Pod,由负载均衡器的算法所决定。
Kubernetes也遵循上述常规做法,运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。但Kubernetes发明了一种很巧妙又影响深远的设计:Service没有共用一个负载均衡器的IP地址,每个Service都被分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP。这样一来,每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。
实例
创建一个名为service.yaml的定义文件,内容如下:
[root@master ~]# vi service-clusterip.yaml
apiVersion: v1
kind: Service
metadata:
labels:
run: nginx
name: service-clusterip
spec:
ports:
- port: 80
protocol: TCP
targetPort: 81
selector:
app: nginx
type: ClusterIP
[root@master ~]# kubectl create -f service-clusterip.yaml
service/service-clusterip created
上面定义了一个service-clusterip的Service,它的服务端口为81。
外部系统访问Service的问题
◎ Node IP:Node的IP地址。
◎ Pod IP:Pod的IP地址。
◎ Cluster IP:Service的IP地址。
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这种新的资源对象来实现的。
Kubernetes提供了非常丰富的Volume类型
1.emptyDir
一个emptyDir Volume是在Pod分配到Node时创建的。从它的名称就可以看出,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为这是Kubernetes自动分配的一个目录,当Pod从Node上移除时,emptyDir中的数据也会被永久删除。emptyDir的一些用途如下。
- 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留。
- 长时间任务的中间过程CheckPoint的临时保存目录。
- 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。
目前,用户无法控制emptyDir使用的介质种类。如果kubelet的配置是使用硬盘,那么所有emptyDir都将被创建在该硬盘上。Pod在将来可以设置emptyDir是位于硬盘、固态硬盘上还是基于内存的tmpfs上,上面的例子便采用了emptyDir类的Volume。
2.hostPath
hostPath为在Pod上挂载宿主机上的文件或目录,它通常可以用于以下几方面。
- 容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储。
- 需要访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问Docker的文件系统。
在使用这种类型的Volume时,需要注意以下几点。
- 在不同的Node上具有相同配置的Pod,可能会因为宿主机上的目录和文件不同而导致对Volume上目录和文件的访问结果不一致。
- 如果使用了资源配额管理,则Kubernetes无法将hostPath在宿主机上使用的资源纳入管理。
在下面的例子中使用宿主机的/data目录定义了一个hostPath类型的Volume:
3.gcePersistentDisk
使用这种类型的Volume表示使用谷歌公有云提供的永久磁盘(Persistent Disk,PD)存放Volume的数据,它与emptyDir不同,PD上的内容会被永久保存,当Pod被删除时,PD只是被卸载(Unmount),但不会被删除。需要注意的是,你需要先创建一个PD,才能使用gcePersistentDisk。
使用gcePersistentDisk时有以下一些限制条件。
- Node(运行kubelet的节点)需要是GCE虚拟机。
- 这些虚拟机需要与PD存在于相同的GCE项目和Zone中。
通过gcloud命令即可创建一个PD:
定义gcePersistentDisk类型的Volume的示例如下:
4.awsElasticBlockStore
与GCE类似,该类型的Volume使用亚马逊公有云提供的EBS Volume存储数据,需要先创建一个EBS Volume才能使用awsElasticBlockStore。
使用awsElasticBlockStore的一些限制条件如下。
- Node(运行kubelet的节点)需要是AWS EC2实例。
- 这些AWS EC2实例需要与EBS Volume存在于相同的region和availability-zone中。
- EBS只支持单个EC2实例挂载一个Volume。
通过aws ec2 create-volume命令可以创建一个EBS Volume:
定义awsElasticBlockStore类型的Volume的示例如下:
5.NFS
使用NFS网络文件系统提供的共享目录存储数据时,我们需要在系统中部署一个NFS Server。定义NFS类型的Volume的示例如下:
6.其他类型的Volume
- iscsi:使用iSCSI存储设备上的目录挂载到Pod中。
- flocker:使用Flocker管理存储卷。
- glusterfs:使用开源GlusterFS网络文件系统的目录挂载到Pod中。
- rbd:使用Ceph块设备共享存储(Rados Block Device)挂载到Pod中。
- gitRepo:通过挂载一个空目录,并从Git库clone一个git repository以供Pod使用。
- secret:一个Secret Volume用于为Pod提供加密的信息,你可以将定义在Kubernetes中的Secret直接挂载为文件让Pod访问。Secret Volume是通过TMFS(内存文件系统)实现的,这种类型的Volume总是不会被持久化的。
Persistent Volume
前提到的Volume是被定义在Pod上的,属于计算资源的一部分,而实际上,网络存储是相对独立于计算资源而存在的一种实体资源。比如在使用虚拟机的情况下,我们通常会先定义一个网络存储,然后从中划出一个“网盘”并挂接到虚拟机上。Persistent Volume(PV)和与之相关联的Persistent Volume Claim(PVC)也起到了类似的作用。
- PV可以被理解成Kubernetes集群中的某个网络存储对应的一块存储,它与Volume类似,但有以下区别。
- PV只能是网络存储,不属于任何Node,但可以在每个Node上访问。
- PV并不是被定义在Pod上的,而是独立于Pod之外定义的。
- PV目前支持的类型包括:gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibre Channel)、Flocker、NFS、iSCSI、RBD(Rados Block Device)、CephFS、Cinder、GlusterFS、VsphereVolume、Quobyte Volumes、VMware Photon、Portworx Volumes、ScaleIO Volumes和HostPath(仅供单机测试)。
下面给出了NFS类型的PV的一个YAML定义文件,声明了需要5Gi的存储空间:
比较重要的是PV的accessModes属性,目前有以下类型。
- ReadWriteOnce:读写权限,并且只能被单个Node挂载。
- ReadOnlyMany:只读权限,允许被多个Node挂载。
- ReadWriteMany:读写权限,允许被多个Node挂载。
如果某个Pod想申请某种类型的PV,则首先需要定义一个PersistentVolumeClaim对象:
然后,在Pod的Volume定义中引用上述PVC即可:
PV是有状态的对象,它的状态有以下几种。
- Available:空闲状态。
- Bound:已经绑定到某个PVC上。
- Released:对应的PVC已经被删除,但资源还没有被集群收回。
- Failed:PV自动回收失败。
Namespace
Namespace(命名空间)是Kubernetes系统中的另一个非常重要的概念,Namespace在很多情况下用于实现多租户的资源隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
Kubernetes集群在启动后会创建一个名为default的Namespace,通过kubectl可以查看:
接下来,如果不特别指明Namespace,则用户创建的Pod、RC、Service都将被系统创建到这个默认的名为default的Namespace中。
Namespace的定义很简单。如下所示的YAML定义了名为development的Namespace。
一旦创建了Namespace,我们在创建资源对象时就可以指定这个资源对象属于哪个Namespace。比如在下面的例子中定义了一个名为busybox的Pod,并将其放入development这个Namespace里:
此时使用kubectl get命令查看,将无法显示:
这是因为如果不加参数,则kubectl get命令将仅显示属于default命名空间的资源对象。
可以在kubectl命令中加入--namespace参数来查看某个命名空间中的对象:
Annotation
Annotation(注解)与Label类似,也使用key/value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector。Annotation则是用户任意定义的附加信息,以便于外部工具查找。在很多时候,Kubernetes的模块自身会通过Annotation标记资源对象的一些特殊信息。
通常来说,用Annotation来记录的信息如下。
- build信息、release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像Hash值、Docker Registry地址等。
- 日志库、监控库、分析库等资源库的地址信息。
- 程序调试工具信息,例如工具名称、版本号等。
- 团队的联系信息,例如电话号码、负责人名称、网址等。
ConfigMap
为了能够准确和深刻理解Kubernetes ConfigMap的功能和价值,我们需要从Docker说起。我们知道,Docker通过将程序、依赖库、数据及配置文件“打包固化”到一个不变的镜像文件中的做法,解决了应用的部署的难题,但这同时带来了棘手的问题,即配置文件中的参数在运行期如何修改的问题。我们不可能在启动Docker容器后再修改容器里的配置文件,然后用新的配置文件重启容器里的用户主进程。为了解决这个问题,Docker提供了两种方式:
- 在运行时通过容器的环境变量来传递参数;
- 通过Docker Volume将容器外的配置文件映射到容器内。
这两种方式都有其优势和缺点,在大多数情况下,后一种方式更合适我们的系统,因为大多数应用通常从一个或多个配置文件中读取参数。但这种方式也有明显的缺陷:我们必须在目标主机上先创建好对应的配置文件,然后才能映射到容器里。
首先,把所有的配置项都当作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就成了分布式系统中最为简单(使用方法简单,但背后实现比较复杂)且对应用无侵入的配置中心。