本章节将继续分享关于kubernetes中的一些重要概念。
一、Pod
Pod 是 Kubernetes 的最小工作单元。每个 Pod 包含一个或多个容器。Pod 中的容器会作为一个整体被 Master 调度到某个 Node 上运行。(可以把pod想象成豌豆荚,里面的豌豆就是容器,可以有一个或多个。)
说到Pod,简单介绍其概念。首先,Pod运行在一个我们称之为节点(Node)的环境中,这个节点可能是物理机或虚拟机,通常一个节点上上面运行几百个Pod;其次每个Pod运行着一个特殊的被称之为根容器(Pause),和一组用户容器组成。一方面,用Pause 容器的状态代表整个容器组的状态;另一方面,这些业务容器共享根容器(Pause)的网络栈和Volume挂载卷,因此它们之间的通信和数据交换效率更为高效。在设计时我们可以充分利用这一特性将一组密切相关的服务进程放到同一个Pod中。最后需要注意的是并不是每一个Pod和它里面运行的容器都能映射到一个Service上,只有那些提供服务的一组Pod才会被映射成一个服务。
Kubernetes 引入 Pod 主要基于下面两个目的:
- 可管理性:
在一组容器作为一个单元的情况下,我们难以对“整体”简单地进行判断及有效地进行行动。比如,一个容器死亡了,此时算是整体死亡么?引入业务无关并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整体容器组的状态,就简单、巧妙地解决了这个难题。 - 通信和资源共享:
Pod里的多个业务容器共享Pause容器的IP,即相同的 IP 地址和 Port 空间。它们可以直接用 localhost 通信。同样的,这些容器可以共享存储,当 Kubernetes 挂载 volume 到 Pod,本质上是将 volume 挂载到 Pod 中的每一个容器。这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。
静态Pod & 普通Pod
-
普通的Pod:
普通Pod一旦被创建,就会被放入到etcd中存储,随后会被Kubernetes Master调度到某个具体的Node上并进行绑定(Binding),随后该Pod 被对应的Node上的kubelet进程实例化成一组相关的docker容器运行起来。
当Pod里的某个容器停止时,Kubernetes会自动检测到这个问题并且重新启动这个Pod (重启Pod里的所有容器),如果Pod所在的Node宕机,则会将这个Node上所有的Pod从新调度到其他节点上。 -
静态Pod (Static Pod):
静态Pod不存放在Kubernetes的etcd存储里,而是存放在某个具体的Node上的文件夹中(如果是k8s集群是通过kubeadm部署的,该文件夹默认是/etc/kubernetes/manifests/),并且只在此Node上启动运行。
静态Pod是由kubelet进行管理的仅存在于特定Node上的Pod。他们不能通过API Server进行管理,无法与ReplicationController(RC)、Deployment、或者DaemonSet进行关联,并且kubelet也无法对它们进行健康检查。静态Pod总是由kubelet进行创建的,并且总是在kubelet所在的Node上运行的
三、副本控制器类型(Pod叫副本)
ReplicationController (简称为RC)
ReplicaSet (简称为RS)
Deployment
StatefulSet
DaemonSet
Job,Cronjob
Pods 有两种使用方式:
运行单一容器:
one-container-per-Pod 是 Kubernetes 最常见的模型,这种情况下,只是将单个业务容器简单封装成 Pod。即便是只有一个容器,Kubernetes 管理的也是 Pod 而不是直接管理容器。
运行多个容器:
但问题在于:哪些容器应该放到一个 Pod 中?
答案是:这些容器联系必须 非常紧密,而且需要 直接共享资源。
示例:
下面这个 Pod 包含两个容器:一个 File Puller,一个是 Web Server。他们两个container的net namespace、uts namespace、ipc namespace是属于共享的。 mnt namespace、user namespace、pid namespace是互相隔离的。
Kubernetes里的所有资源对象都可以采用yaml或者JSON格式的文件来定义或描述,下面是我们在之前Hello World例子里用到的myweb这个Pod的资源定义文件:
apiVersion: v1
kind: Pod
metadata:
name: myweb
spec:
containers:
- name: myweb
image: tomcat:jre8
ports:
- containerPort: 8080
env:
- name: MYSQL_SERVICE_HOST
value: 'mysql'
- name: MYSQL_SERVICE_PORTT
value: '3306'
说明:kind为Pod表明这是一个Pod的定义,metadata里的name属性为Pod的名字,metadata里还能定义资源对象的标签(Label),这里表明myweb拥有一个name=myweb的标签(Label)。Pod里所包含的容器组的定义是在spec中,这里定义了一个名字为myweb、对应镜像为tomcat:jre8的容器,该容器注入了名为MYSQL_SERVICE_HOST='mysql’和MYSQL_SERVICE_PORT='3306’的环境变量(env关键字),并且在8080端口(containerPort)上启动容器进程。
Pod的IP加上这里的容器端口(containerPort),就组成了一个新的概念 --Endpoint,它代表着此Pod里的一个服务进程的对外通信地址。
我们所熟悉的Docker Volume在Kubernetes里也有对应的概念 — Pod Volume,后者有一些扩展,比如可以用分布式文件系统ceph实现后端存储功能;Pod Volume是定义在Pod之上,然后被各个容器挂载到自己的文件系统中。
顺便提一下Kubernetes的Event概念,Event是一个事件的记录,记录了事件的最早产生时间、最后重现时间、重复次数、发起者、类型,以及导致此事件的原因等众多信息。Event通常会关联到某个具体的资源对象上,是排查故障的重要参考信息,之前我们看到Node的描述信息包括了Event,而Pod同样有Event记录,用来发现某个Pod迟迟无法创建时,可以用kubectl describe pod xxxx来查看它的描述信息,用来定位问题的原因,比如下面这个Event记录信息表明Pod里的一个容器被探针检测失败一次:
[root@k8s-m1 ~]# kubectl describe po -n metallb-system speaker-t44hq
......(省略了一部分)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning BackOff 59s (x26996 over 4d1h) kubelet Back-off restarting failed container
每个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里,一个计算资源进行配额限定需要设定以下两个参数。
Request:该资源的最小申请量,系统必须满足要求。
Limits:该资源最大允许使用量,不能被突破,当容器试图使用超过这个量的资源时,可能会被Kubernetes Kill并重启。
通常我们会把Request设置为一个比较小的数值,符合容器平时的工作负载情况下的资源需求,而把Limit设置为负载均衡情况下资源占用的最大量。比如下面这些定义,表明MySQL容器申请最少0.3个CPU及128MiB内存,在运行过程中MySQL容器所能使用的资源配额最大为0.6个CPU及256MiB内存:
spec:
containers:
- name: mydb
image: mysql
resources:
requests:
memory: "128Mi"
cpu: "300m"
limits:
memory: "256Mi"
cpu: "600m"
最后给出Pod及Pod周边对象的示意图作为总结,如图所示,后面部分涉及这张图里的对象和概念,以进一步加强理解。
二、Lable
Label是Kubernetes系统中另外一个核心概念。一个Label是一个key=value的键值对,其中key与vaue由用户自己指定。Label可以附加到各种资源对象上,例如Node、Pod、Service、RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上去,Label通常在资源对象定义时确定,也可以在对象创建后动态添加或者删除。
我们可以通过指定的资源对象捆绑一个或多个不同的Label来实现多维度的资源分组管理功能,以便于灵活、方便地进行资源分配、调度、配置、部署等管理工作。例如:部署不同版本的应用到不同的环境中;或者监控和分析应用(日志记录、监控、告警)等。一些常用等label示例如下
版本标签:“release”:“stable”,“release”:“canary”…
环境标签:“environment”:“dev”,“environment”:“qa”,“environment”:“production”
架构标签:“tier”:“fronted”,“tier”:“backend”,“tier”:"middleware”
分区标签:“partition”:“customerA”,“partition”:"customerB”…
质量管控标签:“track”:“daily”:“rack”:“weekly”
Label相当于我们熟悉的“标签”,給某个资源对象定义一个Label,就相当于給它打了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes通过这种方式实现了类似SQL的简单又通用的对象查询机制。
Label Selector可以被类比为SQL语句中的where查询条件,例如,name=redis-slave这个label Selector作用于Pod时,可以被类比为select * from pod where pod’s name = 'redis-slave’这样的语句。当前有两种Label Selector的表达式:基于等式的(Equality-based)和基于集合的(Set-based),前者采用“等式类”的表达式匹配标签,下面是一些具体的例子。
-
基于等式的表达式匹配标签实例:
name=redis-slave:匹配所有具有标签name=redis-slave的资源对象。
env != production:匹配所有不具有标签env=production的资源对象。 -
基于集合方式的表达式匹配标签实例:
name in (redis-master,redis-slave):匹配所有具有标签name=redis-master或者name=redis-slave的资源对象。
name notin (php-frontend):匹配所有不具有标签name=php-frontend的资源对象。
可以通过多个Label Selector表达式的组合实现复杂的条件,多个表达式之间用“,”进行分隔即可,几个条件之间是“AND”的关系,即同时满足多个条件,比如下面的例子:
name=redis-slave,env!=production
name notin (php-fronted),env!=production
Label Selector在Kubernetes中重要使用场景有以下几种:
- kube-controller进程通过资源对象RC上定义都Label Selector来筛选要监控的Pod副本的数量,从而实现Pod副本的数量始终符合预期设定的全自动控制流程。
- kube-proxy进程通过Service的Label Selector来选择对应的Pod,自动建立起每个Service到对应Pod的请求转发路由表,从而实现Service的智能负载均衡机制。
通过对某些Node定义特定的Label,并且在Pod定义文件中使用NodeSelector这种标签调度策略,kube-scheduler进程可以实现Pod“定向调度”的特性。 - 前面我们只是介绍了一个name=XXX的Label Selector。让我们看一个更复杂的例子。假设为Pod定义了Label: release、env和role,不同的Pod定义了不同的Label值,如图下图所示,如果我们设置了“role=frontend”的Label Selector,则会选取到Node 1和Node 2上到Pod。
而设置“release=beta”的Label Selector,则会选取到Node 2和Node 3上的Pod,如下图所示。
总结:使用Label可以給对象创建多组标签,Label和Label Selector共同构成了Kubernetes系统中最核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。
三、Annotation
可以使用 Kubernetes Annotation(注解)为对象附加任意的非标识的元数据自定义信息。
Annotation与Label类似,也使用key/value键值对的形式进行定义。不同的是Label具有严格的命名规则,Label定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector(标签选择器)。而Annotation(注解)则是用户任意定义的“附加”信息,便于用户自行的一些查找行为。
注解和标签一样,是键/值对的:
“metadata”: {
“annotations”: {
“key1” : “value1”,
“key2” : “value2”
}
}
说明:
Map 中的键和值必须是字符串。也就是说,你不能使用数字、布尔值、列表或其他类型的键或值。
以下是一些例子,用来说明哪些信息可以使用注解来记录:
build信息、release信息、Docker镜像信息等,例如时间戳、release id号、镜像hash值、docker registry地址等。
日志库、监控库、分析库等资源库的地址信息。
程序调试工具信息,例如工具、版本号等。
团队等联系信息,例如各种联系方式、负责人名称、网址等。
例如,下面是一个 Pod 的配置文件,其注解中包含 imagebuilder: “margu_168”
[root@k8s-m1 tmp]# cat anno.yaml
apiVersion: v1
kind: Pod
metadata:
name: annotations-test
annotations:
imagebuilder: "margu_168"
spec:
containers:
- name: nginx
image: nginx:1.20.1
ports:
- containerPort: 80
其实一般的pod我们不会添加annotation,在使用ingress-nginx,我们常常通过annotation添加一些关于nginx额外的配置,如rewrite-target、use-regex等等。
更多关于kubernetes的知识分享,请前往博客主页。