可以把容器想像成豆荚里的豆子,把一个或多个关系紧密的豆子包在一起就是豆荚(一个Pod)。在k8s中我们不会直接操作容器,而是把容器包装成Pod再进行管理.
为什么需要Pod?
我们先来谈一个问题:那就是我们为什么需要Pod?在Linux容器中,Namespace做隔离,Cgroups做限制,rootfs(unionFS)做文件系统就可以了,那为什么需要Pod呢?
容器的本质就是进程,Kubernetes的本质,就是操作系统.
容器与虚拟机不同,它是单进程,因为它的一号进程不具备管理多进程多线程的能力,具体请参考:https://blog.csdn.net/M2l0ZgSsVc7r69eFdTj/article/details/102028724
在一个操作系统中,我们知道会有进程和进程组的关系.或者说,因为某些需要,某些应用必须部署在同一台机器上面.
举个例子:rsyslogd由三个进程组成:一个imklog模块,一个imuxsock模块,一个rsyslogd自己的main函数主进程,这三个进程一定要运行在同一台机器上面才行,否则它们之间的通信就会出现问题
Pod是Kubernetes中的原子调度单位,也就是说,当Kubernetes调度时,是根据Pod而不是容器来调度的.
此时,我们将imklog,imuxsock和main三个容器(多个集成),放在一个Pod中,Kubernetes在调用时,会直接部署在node1上面,node2根本就不会考虑.
这是我们为什么需要Pod的原因之一.
为什么我们需要Pod,还有一个更重要的层面:容器设计模式.
在了解之前,我们需要了解一下,关于Pod最重要的一个事实:它只是一个逻辑概念.也就是说,Kubernetes真正处理的,还是宿主机操作系统上的Linux容器的Namespace和Cgroups,而并不存在一个所谓的Pod的边界或者隔离环境.那么,Pod又是怎么被"创建"出来的呢?Pod,其实就是一组共享了某些资源的容器.更详细的说就是,Pod里所有的容器,共享的是同一个Network Namespace,并且可以声明共享同一个Volume.
基于此,尝试讲解一下容器设计模式.
假设,现在我们有应用的war包,它需要被放在tomcat的相关目录下运行起来.
如果,你只能用Docker来做这件事情,那么你会如何处理war包与tomcat之间的关系呢?
1,把war包直接放在tomcat镜像的相关目录下,做成一个新的镜像运行起来.但是当我的war包进行了更新时,或者需要升级tomcat镜像,那我就要重新制作一个新的镜像,一次两次三次,你确定要去做这样的步骤很多次?
2,我才不管war包,我只发布一个tomcat容器.但是这个容器的目录,就必须声明一个hostPath类型的Volume,从而能将宿主机上的war包挂载进tomcat容器当中运行起来.这样的话,你需要解决另外一个问题:我怎么让每一台宿主机都预先准备好这个存储war包的目录呢?这样的话,就只能维护一套分布式存储系统了.
是不是挺烦人的?那么我们有了Pod之后,这个问题就很容易解决了.我们可以把war包和tomcat分别做成镜像,然后把它们作为一个Pod里的两个容器"合"在一起.而这个操作,正是容器设计模式里最常用的一种模式:sidecar.
Pod创建流程
step.1
kubectl 向 k8s api server 发起一个create pod 请求(即我们使用Kubectl敲一个create pod命令) 。
step.2
k8s api server接收到pod创建请求后,不会去直接创建pod;而是生成一个包含创建信息的yaml。
step.3
apiserver 将刚才的yaml信息写入etcd数据库。到此为止仅仅是在etcd中添加了一条记录, 还没有任何的实质性进展。
step.4
scheduler 查看 k8s api ,类似于通知机制(scheduler的主要作用就是调度pod到相应的node上)。
首先判断:pod.spec.Node == null?
若为null,表示这个Pod请求是新来的,需要创建;因此先进行调度计算,找到最“闲”的node。
然后将信息在etcd数据库中更新分配结果:pod.spec.Node = nodeA (设置一个具体的节点)
ps:同样上述操作的各种信息也要写到etcd数据库中中。
step.5
kubelet 通过监测etcd数据库(即不停地看etcd中的记录),发现 k8s api server 中有了个新的Node;
如果这条记录中的Node与自己的编号相同(即这个Pod由scheduler分配给自己了);
则调用node中的docker api,创建container。
凡是调度、网络、存储,以及安全相关的属性,基本上是 Pod 级别的。
这些属性的共同特征是,它们描述的是“机器”这个整体,而不是里面运行的“程序”。比如,配置这个“机器”的网卡(即:Pod 的网络定义),配置这个“机器”的磁盘(即:Pod 的存储定义),配置这个“机器”的防火墙(即:Pod 的安全定义)。更不用说,这台“机器”运行在哪个服务器之上(即:Pod 的调度)。
Pod生命周期(Status 状态),pod.status.phase:
pending , running, succeeded, failed, unknown
挂起(Pending):Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度 Pod 的时间和通过网络下载镜像的时间,这可能需要花点时间。
运行中(Running):该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态。
成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启。
失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。
未知(Unknown):因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。
Pod重启策略:
当某个容器异常退出或者健康检查失败, kubelet将根据RestartPolicy的设置来进行相应的操作, 重启策略有Always , OnFailure, Never
Always: 当容器失效时, 由kubelet自动重启该容器
OnFailure: 当容器终止运行且退出码不为0时, 由kubelet自动重启该容器
Never: 不论容器运行状态如何, kubelet都不会重启该容器
但一定要强调的是,Pod 的恢复过程,永远都是发生在当前节点上,而不会跑到别的节点上去。事实上,一旦一个 Pod 与一个节点(Node)绑定,除非这个绑定发生了变化(pod.spec.node 字段被修改),否则它永远都不会离开这个节点。这也就意味着,如果这个宿主机宕机了,这个 Pod 也不会主动迁移到其他节点上去。而如果你想让 Pod 出现在其他的可用节点上,就必须使用 Deployment 这样的“控制器”来管理 Pod,哪怕你只需要一个 Pod 副本,这就是控制器的作用的中重要性。
假如一个 Pod 里只有一个容器,然后这个容器异常退出了。那么,只有当 restartPolicy=Never 时,这个 Pod 才会进入 Failed 状态。而其他情况下,由于 Kubernetes 都可以重启这个容器,所以 Pod 的状态保持 Running 不变。
而如果这个 Pod 有多个容器,仅有一个容器异常退出,它就始终保持 Running 状态,哪怕即使 restartPolicy=Never。只有当所有容器也异常退出之后,这个 Pod 才会进入 Failed 状态。
其他情况,都可以以此类推出来。因此,必须要定义健康检查,可自己定义标准,否则很容易把异常的pod当作正常的使用。
kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算, 例如1丶2丶4丶8倍等, 最长延时5min, 并且在重启后的10min后重置该时间
pod的重启策略与控制方式息息相关
RC和DeamonSet必须设置为Always,需要保证该容器持续运行
Job: OnFailure或Never, 确保容器执行完成后不再重启
Pod通过spec指定的主要参数说明:
NodeSelector:是一个供用户将 Pod 与 Node 进行绑定的字段
这样的一个配置,意味着这个 Pod 永远只能运行在携带了“disktype: ssd”标签(Label)的节点上;否则,它将调度失败。
NodeName:一旦 Pod 的这个字段被赋值,Kubernetes 项目就会被认为这个 Pod 已经经过了调度,调度的结果就是赋值的节点名字。所以,这个字段一般由调度器负责设置,但用户也可以设置它来“骗过”调度器,当然这个做法一般是在测试或者调试的时候才会用到。
HostAliases:定义了 Pod 的 hosts 文件(比如 /etc/hosts)里的内容
需要指出的是,在 Kubernetes 项目中,如果要设置 hosts 文件里的内容,一定要通过这种方法。否则,如果直接修改了 hosts 文件的话,在 Pod 被删除重建之后,kubelet 会自动覆盖掉被修改的内容。
除了上述跟“机器”相关的配置外,你可能也会发现,凡是跟容器的 Linux Namespace 相关的属性,也一定是 Pod 级别的。这个原因也很容易理解:Pod 的设计,就是要让它里面的容器尽可能多地共享 Linux Namespace,仅保留必要的隔离和限制能力。这样,Pod 模拟出的效果,就跟虚拟机里程序间的关系非常类似了。
在这个 Pod 中,我定义了共享宿主机的 Network、IPC 和 PID Namespace。这就意味着,这个 Pod 里的所有容器,会直接使用宿主机的网络、直接与宿主机进行 IPC 通信、看到宿主机里正在运行的所有进程。
ImagePullPolicy 镜像拉取策略 :
Always: 表示每次都尝试重新拉取镜像
IfNotPresent: 表示如果本地有镜像, 则使用本地的镜像, 本地不存在时拉取镜像
Never: 表示仅使用本地镜像
Lifecycle:
它定义的是 Container Lifecycle Hooks。顾名思义,Container Lifecycle Hooks 的作用,是在容器状态发生变化时触发一系列“钩子”。
容器探测:
1、存活性探针:livenessProbe
可自定义健康检查行为,作为服务是否正常的标准。一旦不满足条件,将被定义为不健康并暴露出来,尤其在web项目中很实用。因为仅凭pod是否running无法判断服务是否正常。
2、就绪性探针:readinessProbe
readinessProbe 检查结果的成功与否,决定的这个 Pod 是不是能被通过 Service 的方式访问到,而并不影响 Pod 的生命周期。
PodPreset(Pod 预设置):
运维人员就可以定义一个 PodPreset 对象。在这个对象中,凡是他想在开发人员编写的 Pod 里追加的字段,都可以预先定义好。比如这个 preset.yaml:
apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
name: allow-database
spec:
selector:
matchLabels:
role: frontend
env:
- name: DB_PORT
value: "6379"
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
在这个 PodPreset 的定义中,首先是一个 selector。这就意味着后面这些追加的定义,只会作用于 selector 所定义的、带有“role: frontend”标签的 Pod 对象,这就可以防止“误伤”。label是一个分组机制,通过它可以多维度的给资源对象加标签,相同资源lable的key必须唯一。而想要使用或者改变他们的控制器们,可通过selector.matchLabels来将相应的资源选出并作出动作。与之对应的,annotation就不具备分组和被选中的功能,它只是单纯的为资源添加注解,方便识别或记忆。使用Label可以给对象创建多组标签,Label 和 Label Selector共同构成了Kubernetes系统中最核心的应用模型。
使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性.
PodPreset 里定义的内容,只会在 Pod API 对象被创建之前追加在这个对象本身上,而不会影响任何 Pod 的控制器的定义。PodPreset 改变pod而不是控制器。
Kubernetes 项目会帮你合并(Merge)这两个 PodPreset 要做的修改。而如果它们要做的修改有冲突的话,这些冲突字段就不会被修改。
查看API对象的yaml文件:
$ kubectl get pod website -o yaml