Kubernetes 核心概念POD
Pod概述
Pod 是 k8s 系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最
小资源对象模型,也是在 k8s 上运行容器化应用的资源对象
Pod基本概念
- 最小部署的单元
- Pod里面是由一个或多个容器组成【一组容器的集合】
- 一个pod中的容器是共享网络命名空间
- Pod是短暂的
- 每个Pod包含一个或多个紧密相关的用户业务容器
Pod存在的意义
创建容器使用docker,一个docker对应一个容器,一个容器运行一个应用进程
Pod是多进程设计,运用多个应用程序,也就是一个Pod里面有多个容器,而一个容器里面运行一个应用程序
Pod 特点
Pod有两个必须知道的特点
网络
每一个Pod都会被指派一个唯一的Ip地址,在Pod中的每一个容器共享网络命名空间,包括Ip地址和网络端口,在同一个Pod中的容器可以同locahost进行互相通信,当Pod中的容器需要与Pod外的实体进行通信时,则需要通过端口等共享的网络资源。
存储
Pod能够配置共享存储卷,在Pod中所有的容器能够访问共享存储卷,允许这些容器共享数据,存储卷也允许在一个Pod持久化数据,以防止其中的容器需要被重启。
五种 Pod 共享资源
Pod 是 k8s 最基本的操作单元,包含一个或多个紧密相关的容器。
一个 Pod 可以被一个容器化的环境看作应用层的“逻辑宿主机”;
一个 Pod 中的多个容器应用通常是紧密耦合的,Pod 在 Node 上被创建、启动或者销毁;
每个 Pod 里运行着一个特殊的被称之为 Volume 挂载卷,因此他们之间通信和数据交换更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放入同一个 Pod 中。
同一个 Pod 里的容器之间仅需通过 localhost 就能互相通信,一个 Pod 中的应用容器共享五种资源
- PID 命名空间:Pod 中的不同应用程序可以看到其他应用程序的进程 ID。
- 网络命名空间:Pod 中的多个容器能够访问同一个 IP 和端口范围。
- IPC 命名空间:Pod 中的多个容器能够使用 SystemV IPC 或 POSIX 消息队列进行通信。
- UTS 命名空间:Pod 中的多个容器共享一个主机名。
- Volumes(共享存储卷):Pod 中的各个容器可以访问在 Pod 级别定义的 Volumes。
Pod定义
下面是Pod的资源清单
1 | apiVersion: v1 #必选,版本号,例如v1 |
Pod 使用
使用方式
自主式Pod
这种Pod本身是不能自我修复的,当Pod被创建后(不论是由你直接创建还是被其他Controller),都会被Kuberentes调度到集群的Node上,直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上,Pod不会自愈。
如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除,同样的,如果Pod所在Node缺少资源或者Pod处于维护状态,Pod也会被驱逐。
创建资源清单
通过yaml文件或者json描述Pod和其内容器的运行环境和期望状态,例如一个最简单的运行nginx应用的pod,定义如下
1 | vi nginx-pod.yml |
1 | apiVersion: v1 |
创建POD
使用
kubectl
命令创建pod,并查看pod的状态
1 | kubectl apply -f nginx-pod.yml |
访问pod
可以通过
k8s
创建的虚拟IP进行访问,可以在k8s的任何一个节点访问
1 | curl 10.244.1.10 |
删除Pod
可以使用
delete
删除Pod,删除后不能进行恢复
1 | kubectl delete pod nginx |
我们发现Pod节点故障后将不会被自动恢复,这种情况下是不安全的,我们需要使用控制器来运行Pod
控制器管理的Pod
Kubernetes使用更高级的称为Controller的抽象层,来管理Pod实例,Controller可以创建和管理多个Pod,提供副本管理、滚动升级和集群级别的自愈能力。
例如,如果一个Node故障,Controller就能自动将该节点上的Pod调度到其他健康的Node上。虽然可以直接使用Pod,但是在Kubernetes中通常是使用Controller来管理Pod的
创建资源清单
通过yaml文件或者json描述Pod和其内容器的运行环境和期望状态,例如一个最简单的运行nginx应用的pod,定义如下
1 | vi nginx-pod.yml |
1 | apiVersion: apps/v1 |
创建Pod
1 | kubectl apply -f nginx-pod.yml |
创建后发现,有两个nginx的pod在运行,符合我们的预期
删除Pod
这里可以尝试删除Pod
1 | kubectl delete pod nginx-deployment-f77774fc5-cgs82 |
删除Pod后发现重新新建了一个Pod,这是因为有控制器发现少了一个Pod就会进行重新拉起来一个
Pod策略管理
镜像拉取策略
pod的镜像拉取策略分为三种:
- always(总是从官方下载镜像)
- never(从不下载镜像)
- ifnotpresent(如果本地没有镜像就从官方下载镜像)。
Kubernetes集群默认使用IfNotPresent策略
配置示例
我们以下面的
Always
来举例,看下Pod的镜像拉取策略
1 | apiVersion: apps/v1 |
生效配置
使用如下命令生效配置,并查看K8s在本地拥有镜像的情况下是否重新拉取镜像
1 | kubectl apply -f nginx-pod.yml |
我们可以通过如下命令来查看k8s创建Pod的过程
1 | kubectl describe pod nginx-deployment |
我们发现在Node1节点已经存在镜像的情况下重新进行拉取镜像了,其他的两种方式大家可以试一试
重启策略
在Pod中的容器可能会由于异常等原因导致其终止退出,Kubernetes提供了重启策略以重启容器。
Pod通过restartPolicy
字段指定重启策略,重启策略类型为:Always、OnFailure 和 Never,默认为 Always,重启策略对同一个Pod的所有容器起作用,容器的重启由Node上的kubelet执行。Pod支持三种重启策略,在配置文件中通过restartPolicy
字段设置重启策略:
重启策略 | 说明 |
---|---|
Always | 当容器失效时,由kubelet自动重启该容器 |
OnFailure | 当容器终止运行且退出码不为0时,由kubelet自动重启该容器 |
Never | 不论容器运行状态如何,kubelet都不会重启该容器 |
注意:注意:K8S 中不支持重启Pod资源,只有删除重建,这里的重启是指在Pod的宿主Node上进行本地重启,而不是调度到其它Node上
容器重启演示
当Container退出后,Container可能会被重启,那么Container是如何被重启的呢?是kubelet调用类似于docker start的API拉起?还是重新创建一个docker容器?
答案是,kubelet会重新创建一个docker容器,我们给一个例子,创建一个如下的Pod,然后每隔10S返回0
配置资源清单
1 | vi nginx-pod.yml |
1 | apiVersion: v1 |
我们运行并查看POD的状态变化
1 | kubectl apply -f nginx-pod.yml |
我们发现POD在不断的重启
我们登录node1节点查看下节点的容器的情况
1 | docker ps|grep nginx |
我们发现间隔一段时间执行上面的命令,我们发现容器的ID已经不一样了,说明我们的容器已经被重建了
OnFailure重启测试
上面我们验证了K8s判断一旦容器出现问题就会重启,但是我们使用的是
Always
策略,下面我们改为OnFailure
策略
1 | apiVersion: v1 |
启动并进行验证
1 | kubectl apply -f nginx-pod.yml |
我们发现如果使用
OnFailure
策略,如果推出了将不会重启
Pod 原理
为什么需要POD
假设 Kubernetes 中调度的基本单元就是容器,对于一个非常简单的应用可以直接被调度直接使用,没有什么问题,但是往往还有很多应用程序是由多个进程组成的,有的同学可能会说把这些进程都打包到一个容器中去不就可以了吗?理论上是可以实现的,但是不要忘记了容器运行时管理的进程是 pid=1 的主进程,其他进程死掉了就会成为僵尸进程,没办法进行管理了,这种方式本身也不是容器推荐的运行方式,一个容器最好只干一件事情,所以在真实的环境中不会使用这种方式。
那么我们就把这个应用的进程进行拆分,拆分成一个一个的容器总可以了吧?但是不要忘记一个问题,拆分成一个一个的容器后,是不是就有可能出现一个应用下面的某个进程容器被调度到了不同的节点上呀?往往我们应用内部的进程与进程间通信(通过 IPC 或者共享本地文件之类)都是要求在本地进行的,也就是需要在同一个节点上运行。
所以我们需要一个更高级别的结构来将这些容器绑定在一起,并将他们作为一个基本的调度单元进行管理,这样就可以保证这些容器始终在同一个节点上面,这也就是 Pod 设计的初衷。
POD实现原理
在一个 Pod 下面运行几个关系非常密切的容器进程,这样一来这些进程本身又可以收到容器的管控,又具有几乎一致的运行环境,也就完美解决了上面提到的问题
其实 Pod 也只是一个逻辑概念,真正起作用的还是 Linux 容器的 Namespace 和 Cgroup 这两个最基本的概念,Pod 被创建出来其实是一组共享了一些资源的容器而已,首先 Pod 里面的所有容器,都是共享的同一个 Network Namespace,但是涉及到文件系统的时候,默认情况下 Pod 里面的容器之间的文件系统是完全隔离的,但是我们可以通过声明来共享同一个 Volume。
我们可以指定新创建的容器和一个已经存在的容器共享一个 Network Namespace,在运行容器(docker 容器)的时候只需要指定 –net=container:目标容器名 这个参数就可以了,但是这种模式有一个明显的问题那就是容器的启动有先后顺序问题,那么 Pod 是怎么来处理这个问题的呢?那就是加入一个中间容器(没有什么架构是加一个中间件解决不了的?),这个容器叫做 Infra 容器,而且这个容器在 Pod 中永远都是第一个被创建的容器,这样是不是其他容器都加入到这个 Infra 容器就可以了,这样就完全实现了 Pod 中的所有容器都和 Infra 容器共享同一个 Network Namespace 了,如下图所示:
所以当我们部署完成 Kubernetes 集群的时候,首先需要保证在所有节点上可以拉取到默认的 Infra 镜像,默认情况下 Infra 镜像地址为 k8s.gcr.io/pause:3.5,这个容器占用的资源非常少,但是这个镜像默认是需要科学上网的,所以很多时候我们在部署应用的时候一直处于 Pending 状态或者报 sandbox image 相关的错误信息,大部分是因为所有 Pod 最先启动的容器镜像都拉不下来,肯定启动不了,启动不了其他容器肯定也就不能启动了。
网络
普通容器不会创建自己的网卡,配置自己的 IP,而是和 Infra 容器共享 IP、端口范围等,而且容器之间的进程可以通过 lo 网卡设备进行通信:
- 也就是容器之间是可以直接使用
localhost
进行通信的; - 看到的网络设备信息都是和 Infra 容器完全一样的;
- 也就意味着同一个 Pod 下面的容器运行的多个进程不能绑定相同的端口;
- 而且 Pod 的生命周期只跟 Infra 容器一致,而与容器 A 和 B 无关。
存储
对于文件系统 Kubernetes 是怎么实现让一个 Pod 中的容器共享的呢?
默认情况下容器的文件系统是互相隔离的,要实现共享只需要在 Pod 的顶层声明一个 Volume,然后在需要共享这个 Volume 的容器中声明挂载即可
如何划分 Pod
面我们介绍了 Pod 的实现原理,了解到了应该把关系紧密的容器划分到同一个 Pod 中运行,那么怎么来区分“关系紧密”呢?举一个简单的示例,比如我们的 Wordpress 应用,是一个典型的前端服务器和后端数据服务的应用,那么你认为应该使用一个 Pod 还是两个 Pod 呢?
如果在同一个 Pod 中同时运行服务器程序和后端的数据库服务这两个容器,理论上肯定是可行的,但是不推荐这样使用,我们知道一个 Pod 中的所有容器都是同一个整体进行调度的,但是对于我们这个应用 Wordpress 和 MySQL 数据库一定需要运行在一起吗?当然不需要,我们甚至可以将 MySQL 部署到集群之外对吧?所以 Wordpress 和 MySQL 即使不运行在同一个节点上也是可行的,只要能够访问到即可。
但是如果你非要强行部署到同一个 Pod 中呢?从某个角度来说是错误的,比如现在我们的应用访问量非常大,一个 Pod 已经满足不了我们的需求了,怎么办呢?扩容对吧,但是扩容的目标也是 Pod,并不是容器,比如我们再添加一个 Pod,这个时候我们就有两个 Wordpress 的应用和两个 MySQL 数据库了,而且这两个 Pod 之间的数据是互相独立的,因为 MySQL 数据库并不是简单的增加副本就可以共享数据了,所以这个时候就得分开部署了,采用第二种方案,这个时候我们只需要单独扩容 Wordpress 的这个 Pod,后端的 MySQL 数据库并不会受到扩容的影响。
将多个容器部署到同一个 Pod 中的最主要参考就是应用可能由一个主进程和一个或多个的辅助进程组成,比如上面我们的日志收集的 Pod,需要其他的 sidecar 容器来支持日志的采集。所以当我们判断是否需要在 Pod 中使用多个容器的时候,我们可以按照如下的几个方式来判断:
- 这些容器是否一定需要一起运行,是否可以运行在不同的节点上
- 这些容器是一个整体还是独立的组件
- 这些容器一起进行扩缩容会影响应用吗
Pod的生命周期
前面我们已经了解了 Pod 的设计原理,接下来我们来了解下 Pod 的生命周期。
下图展示了一个 Pod 的完整生命周期过程,其中包含 Init Container
、Pod Hook
、健康检查
三个主要部分,接下来我们就来分别介绍影响 Pod 生命周期的部分:
POD的五种状态
我们先了解下 Pod 的状态,因为 Pod 状态可以反应出当前我们的 Pod 的具体状态信息,也是我们分析排错的一个必备的方式
首先先了解下 Pod 的状态值,我们可以通过 kubectl explain pod.status
命令来了解关于 Pod 状态的一些信息,Pod 的状态定义在 PodStatus
对象中,其中有一个 phase
字段,下面是 phase
的可能取值:
1 | kubectl explain pod.status |
1 | phase <string> |
下面是五种状态的描述
状态值 | 说明 |
---|---|
挂起(Pending) |
Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建,等待时间包括调度 Pod 的时间和通过网络下载镜像的时间。 |
运行中(Running) |
该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态。 |
成功(Succeeded) |
Pod 中的所有容器都被成功终止,并且不会再重启。 |
失败(Failed) |
Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容器以非0状态退出或者被系统终止。 |
未知(Unknown) |
因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败。 |
初始化容器
了解了 Pod 状态后,首先来了解下 Pod 中最新启动的
Init Container
,也就是我们平时常说的初始化容器
使用场景
Init Container就是用来做初始化工作的容器,可以是一个或者多个,如果有多个的话,这些容器会按定义的顺序依次执行。
我们知道一个 Pod 里面的所有容器是共享数据卷和 Network Namespace的,所以 Init Container 里面产生的数据可以被主容器使用到。从上面的 Pod 生命周期的图中可以看出初始化容器是独立与主容器之外的,只有所有的初始化容器执行完之后,主容器才会被启动,那么初始化容器有哪些应用场景呢:
等待其他模块 Ready
这个可以用来解决服务之间的依赖问题,比如我们有一个 Web 服务,该服务又依赖于另外一个数据库服务,但是在我们启动这个 Web 服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个 InitContainer,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们主容器的 Web 服务才被启动起来,这个时候去连接数据库就不会有问题了
做初始化配置
比如集群里检测所有已经存在的成员节点,为主容器准备好集群的配置信息,这样主容器起来后就能用这个配置信息加入集群
其它场景
如将 Pod 注册到一个中央数据库、配置中心等
演示示例
比如现在我们来实现一个功能,在 Nginx Pod 启动之前去重新初始化首页内容,如下所示的资源清单:(init-pod.yaml)
1 | vi init-demo.yaml |
1 | apiVersion: v1 |
配置描述
上面的资源清单中我们首先在 Pod 顶层声明了一个名为 workdir 的 Volume
,前面我们用了 hostPath 的模式,这里我们使用的是 emptyDir{}
,这个是一个临时的目录,数据会保存在 kubelet 的工作目录下面,生命周期等同于 Pod 的生命周期。
然后我们定义了一个初始化容器,该容器会将刘备
写入到 /work-dir 目录下面,但是由于我们又将该目录声明挂载到了全局的 Volume,同样的主容器 nginx 也将目录 /usr/share/nginx/html 声明挂载到了全局的 Volume,所以在主容器的该目录下面会同步初始化容器中创建的 index.html 文件
运行测试
启动这个POD并且创建完成后可以查看该 Pod 的状态
1 | kubectl apply -f init-demo.yaml |
可以发现 Pod 现在的状态处于 Init:0/1
状态,意思就是现在第一个初始化容器还在执行过程中
此时我们可以查看 Pod 的详细信息:
1 | kubectl describe pod init-demo |
从上面的描述信息里面可以看到初始化容器已经启动了,现在处于 Running 状态,所以还需要稍等,到初始化容器执行完成后退出初始化容器会变成 Completed 状态,然后才会启动主容器。
访问测试
待到主容器也启动完成后,Pod 就会变成Running 状态,然后我们去访问下 Pod 主页,验证下是否有我们初始化容器中下载的页面信息:
1 | curl 10.244.1.13 |
Pod 钩子
我们知道 Pod 是 Kubernetes 集群中的最小单元,而 Pod 是由容器组成的,所以在讨论 Pod 的生命周期的时候我们可以先来讨论下容器的生命周期。
实际上 Kubernetes 为我们的容器提供了生命周期的钩子,就是我们说的 Pod Hook,Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中,我们可以同时为 Pod 中的所有容器都配置 hook。
钩子类型
Kubernetes 为我们提供了两种钩子函数
PostStart
这个钩子在容器创建后立即执行
但是,并不能保证钩子将在容器 ENTRYPOINT
之前运行,因为没有参数传递给处理程序,主要用于资源部署、环境准备等,不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起,容器将不能达到 running 状态
PreStop
这个钩子在容器终止之前立即被调用
它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用发出之前完成,主要用于优雅关闭应用程序、通知其他系统等,如果钩子在执行期间挂起,Pod 阶段将停留在 running 状态并且永不会达到 failed 状态
使用注意
注意:PostStart用的不是很多,而PreStop用的相对很多;
如果 PostStart
或者 PreStop
钩子失败, 它会杀死容器,所以我们应该让钩子函数尽可能的轻量,当然有些情况下,长时间运行命令是合理的, 比如在停止容器之前预先保存状态。
实现方式
我们有两种方式来实现上面的钩子函数:
Exec
- 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器。HTTP
- 对容器上的特定的端点执行 HTTP 请求。
通知类型
当用户请求删除含有 Pod 的资源对象时(如 Deployment 等),K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S 提供两种信息通知:
- 默认:K8S 通知 node 执行容器 stop 命令,容器运行时会先向容器中 PID 为 1 的进程发送系统信号 SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送 SIGKILL 的系统信号强行 kill 掉进程
- 使用 Pod 生命周期(利用 PreStop 回调函数),它在发送终止信号之前执行
PostStart演示示例
以下示例中,定义了一个 Nginx Pod,其中设置了 PostStart 钩子函数,即在容器创建成功后,写入一句话到
/usr/share/message
文件中:
1 | vi pod-postStart.yaml |
1 | apiVersion: v1 |
启动POD
直接创建上面的 Pod:
1 | kubectl apply -f pod-postStart.yaml |
查看POD的详情
1 | kubectl describe pod hook-demo1 |
查看内容
创建成功后可以查看容器中
/usr/share/message
文件是否内容正确
1 | kubectl exec -it hook-demo1 -- cat /usr/share/message |
PreStop演示示例
以下示例中,定义了一个 Nginx Pod,其中设置了
PreStop
钩子函数,即在容器退出之前,优雅的关闭 Nginx:
1 | vi pod-prestop.yaml |
1 | apiVersion: v1 |
创建POD
上面定义的两个 Pod,一个是利用
preStop
来进行优雅删除,另外一个是利用preStop
来做一些信息记录的事情,同样直接创建上面的 Pod
1 | kubectl apply -f pod-prestop.yaml |
优雅停机
创建完成后,我们可以直接删除 hook-demo2 这个 Pod,在容器删除之前会执行 preStop 里面的优雅关闭命令,这个用法在后面我们的滚动更新的时候用来保证我们的应用零宕机非常有用。
1 | kubectl delete pod hook-demo2 |
持久化数据
第二个 Pod 我们声明了一个 hostPath 类型的 Volume,在容器里面声明挂载到了这个 Volume,所以当我们删除 Pod,退出容器之前,在容器里面输出的信息也会同样的保存到宿主机(一定要是 Pod 被调度到的目标节点)的 /tmp/k8s
目录下面,我们可以查看 hook-demo3 这个 Pod 被调度的节点:
1 | kubectl get po -o wide |
可以看到这个 Pod 被调度到了
node01
这个节点上,我们可以先到该节点上查看/tmp/k8s
目录下面目前没有任何内容:
1 | ls /tmp/k8s/ |
现在我们来删除 hook-demo3 这个 Pod,安装我们的设定在容器退出之前会执行
preStop
里面的命令,也就是会往 message 文件中输出一些信息:
1 | kubectl delete pod hook-demo3 |
Pod 健康检查
强大的自愈能力是Kubernetes这类容器编排引擎的一个重要特性,自愈的默认实现方式是自动重启发生故障的容器。
为什么需要健康检查
用户还可以利用Liveness和Readiness探测机制设置更精细的健康检查,进而实现如下需求:
- 零停机部署。
- 避免部署无效的镜像。
- 更加安全的滚动升级。
检查策略
在Pod部署到Kubernetes集群中以后,为了确保Pod处于健康正常的运行状态,Kubernetes提供了两种探针,用于检测容器的状态:
存活探测
Liveness是检查容器是否处于运行状态,如果检测失败,kubelet将会杀掉掉容器,并根据重启策略进行下一步的操作,如果容器没有提供Liveness Probe,则默认状态为Success;
Liveness探测器是让Kubernetes知道你的应用是否活着,如果你的应用还活着,那么Kubernetes就让它继续存在,如果你的应用程序已经死了,Kubernetes将移除Pod并重新启动一个来替换它。
让我们想象另一种情况,当我们的应用在成功启动以后因为一些原因“宕机”,或者遇到死锁情况,导致它无法响应用户请求。
在默认情况下,Kubernetes会继续向Pod发送请求,通过使用存活探针来检测,当发现服务不能在限定时间内处理请求(请求错误或者超时),就会重新启动有问题的pod。
就绪探测
Readiness 是检查容器是否已经处于可接受服务请求的状态,如果Readiness Probe失败,端点控制器将会从服务端点(与Pod匹配的)中移除容器的IP地址,Readiness的默认值为Failure,如果一个容器未提供Readiness,则默认是Success。
就绪探针旨在让Kubernetes知道你的应用是否准备好为请求提供服务,Kubernetes只有在就绪探针通过才会把流量转发到Pod,如果就绪探针检测失败,Kubernetes将停止向该容器发送流量,直到它通过。
一个应用往往需要一段时间来预热和启动,比如一个后端项目的启动需要连接数据库执行数据库迁移等等,一个Spring项目的启动也需要依赖Java虚拟机。即使该过程已启动,您的服务在启动并运行之前也无法运行。应用在完全就绪之前不应接收流量,但默认情况下,Kubernetes会在容器内的进程启动后立即开始发送流量。通过就绪探针探测,直到应用程序完全启动,然后才允许将流量发送到新副本。
两者对比
- Liveness探测和Readiness探测是两种Health Check机制,如果不特意配置,Kubernetes将对两种探测采取相同的默认行为,即通过判断容器启动进程的返回值是否为零来判断探测是否成功。
- 两种探测的配置方法完全一样,支持的配置参数也一样,不同之处在于探测失败后的行为:Liveness探测是重启容器;Readiness探测则是将容器设置为不可用,不接收Service转发的请求。
- Liveness探测和Readiness探测是独立执行的,二者之间没有依赖,所以可以单独使用,也可以同时使用,用Liveness探测判断容器是否需要重启以实现自愈;用Readiness探测判断容器是否已经准备好对外提供服务
如何配置
对于LivenessProbe和ReadinessProbe用法都一样,拥有相同的参数和相同的监测方式。
- initialDelaySeconds:用来表示初始化延迟的时间,也就是告诉监测从多久之后开始运行,单位是秒
- timeoutSeconds: 用来表示监测的超时时间,如果超过这个时长后,则认为监测失败
- periodSeconds:指定每多少秒执行一次探测,Kubernetes如果连续执行3次Liveness探测均失败,则会杀掉并重启容器
使用场景
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针; kubelet 将根据 Pod 的
restartPolicy
自动执行正确的操作。如果希望容器在探测失败时被杀死并重新启动,那么请指定一个存活探针,并指定
restartPolicy
为 Always 或 OnFailure。如果要仅在探测成功时才开始向 Pod 发送流量,请指定就绪探针,在这种情况下,就绪探针可能与存活探针相同,但是 spec 中的就绪探针的存在意味着 Pod 将在没有接收到任何流量的情况下启动,并且只有在探针探测成功后才开始接收流量。
如果您希望容器能够自行维护,您可以指定一个就绪探针,该探针检查与存活探针不同的端点。
如果您只想在 Pod 被删除时能够排除请求,则不一定需要使用就绪探针;在删除 Pod 时,Pod 会自动将自身置于未完成状态,无论就绪探针是否存在,当等待 Pod 中的容器停止时,Pod 仍处于未完成状态。
默认的健康检查
我们首先学习Kubernetes默认的健康检查机制:每个容器启动时都会执行一个进程,此进程由Dockerfile的CMD或ENTRYPOINT指定。
如果进程退出时返回码非零,则认为容器发生故障,Kubernetes就会根据restartPolicy重启容器
创建资源清单
Pod的restartPolicy设置为OnFailure,默认为Always,sleep 10; exit 1模拟容器启动10秒后发生故障
1 | vi pod-default-health.yml |
1 | apiVersion: v1 |
创建容器
1 | kubectl apply -f pod-default-health.yml |
监控Pod变化
1 | kubectl get pods -o wide -w |
该命令可以不断显示容器的因为失败不断重启
在上面的例子中,容器进程返回值非零,Kubernetes则认为容器发生故障,需要重启。
有不少情况是发生了故障,但进程并不会退出,比如访问Web服务器时显示500内部错误,可能是系统超载,也可能是资源死锁,此时httpd进程并没有异常退出,在这种情况下重启容器可能是最直接、最有效的解决方案,那我们如何利用HealthCheck机制来处理这类场景呢?
探针类型
探针类型是指通过何种方式来进行健康检查,K8S有三种类型的探测:HTTP,Command和TCP。
exec存活探针
对于命令探测,是指Kubernetes在容器内运行命令,如果命令以退出代码0返回,则容器将标记为正常。否则,它被标记为不健康
下面的资源会在先创建一个nginx的任务,生存测试探针livenessProbe
会执行test -e /tmp/healthy
命令检查文件是否存在, 若文件存在则返回状态码 0,表示成功通过测试。
1 | apiVersion: v1 |
创建pod
创建pod
1 | kubectl create -f pod-demo.yaml |
启动后不断检测
/tmp/healthy
是否存在,不存在重启容器
创建文件
新开一个窗口写入登录pod容器,写入
healthy
文件
1 | 登录pod容器 |
查看原来的pod状态
1 | kubectl get pods -o wide -w |
我们发现pod不在不断地重启了
HTTP就绪探针
HTTP探测可能是最常见的探针类型,即使应用不是HTTP服务,也可以创建一个轻量级HTTP服务器来响应探测,比如让Kubernetes通过HTTP访问一个URL,如果返回码在200到300范围内,就将应用程序标记为健康状态,否则它被标记为不健康。
上面 清单 文件 中 定义 的 httpGet 测试 中, 请求的资源路径 为/healthy
, 地址 默认 为 Pod IP, 端口使用了容器中定义的端口名称 HTTP, 这也是明确为容器指明要暴露的端口的用途之一。
1 | apiVersion: v1 |
创建pod
1 | kubectl create -f pod-demo.yaml |
我们发现nginx 一致处于未未就绪状态
查看pod详情
1 | kubectl describe pod pod-nginx-demo |
创建文件
新开一个窗口写入登录pod容器,写入
healthy
文件
1 | 登录pod容器 |
查看原来的pod状态
1 | kubectl get pods -o wide -w |
访问
1 | curl 10.244.1.30/healthy |
再次删除
healthy
文件
1 | rm -f /usr/share/nginx/html/healthy |
再次查看pod状态,进入未就绪状态
1 | kubectl get pods -o wide -w |