05K8Pod:最小调度单元的使用进阶及实践
文章目录
通过上一节课的学习,相信你已经知道了 Pod 是 Kubernetes 中原子化的部署单元,它可以包含一个或多个容器,而且容器之间可以共享网络、存储资源。在日常使用过程中,也应该尽量避免在一个 Pod 内运行多个不相关的容器,具体原因在上一节课中也已经详细阐述。
在实际生产使用的过程中,通过 kubectl 可以很方便地部署一个 Pod。但是 Pod 运行过程中还会出现一些意想不到的问题,比如:
Pod 里的某一个容器异常退出了怎么办?
有没有“健康检查”方便你知道业务的真实运行情况,比如容器运行正常,但是业务不工作了?
容器在启动或删除前后,如果需要做一些特殊处理怎么办?比如做一些清理工作。
如果容器所在节点宕机,重启后会对你的容器产生影响吗?
……
在这一课时中,我将通过一些示例一一解答你的这些问题,并告诉你 Pod 最佳的使用方法。
在了解 Pod 的高阶用法之前,我们先聊聊 Pod 的运行状态。
Pod 的运行状态
我们先回到上一节 04 课时中的例子:
|
|
我们通过 kubectl 创建 Pod 成功后,可以通过如下命令看到 Pod 的状态:
|
|
注:我们这里使用了 kubectl 命令行 JSONPATH 模板能力,你可以将这条命令当作一个 tip,在日常工作中使用。在本专栏的最后,我们也会单独讲一些 kubectl 的使用秘笈,在此不展开讲。
我们看到,这个时候 Pod 处于 Pending 状态,具体的值来自 Pod 对象的 status.phase 字段。
你也可以使用 kubectl get 命令来查看容器的状态:
|
|
看到这里,你会发现这个地方显示的是 ContainerCreating ,这和上面的 Pending 不一致啊!先别急,我们来 describe 一下(这里我只截取跟 Pod 状态最相关的片段):
|
|
可以看到,这边 Status 依然是 Pending 。其实这是 kubectl 在显示时做的转换,它会遍历容器的 State,如果容器的状态为 Waiting 的话,就读取 State.Reason 字段作为 Pod 的 Status。这个时候由于镜像在本地不存在,需要去镜像中心拉取。
一般来说,处于 Pending 状态的 Pod,不外乎以下 2 个原因:
Pod 还未被调度;
Pod 内的容器镜像在待运行的节点上不存在,需要从镜像中心拉取。
等待镜像拉取结束,再来查看 Pod 的状态,已经变为 Running 状态。
|
|
这个时候,就标志着 Pod 内的所有容器均被创建出来了,且至少有一个容器为在运行状态中。那么如果想知道 Pod 内所有的容器是否都在运行中呢?我们可以通过 kubectl get 来看到:
|
|
在这里,我们看到 2/2 。前一个 2 表示目前正在运行的容器数量,后一个 2 表示定义的容器数量。当这两个数值相等的时候,就可以标识着 Pod 内所有容器均正常运行。
Pod 的 Status 除了上述的 Pending 、 Running 以外,官方还定义了下面这些状态:
Succeeded 来表示 Pod 内的所有容器均成功运行结束,即正常退出,退出码为 0;
Failed 来表示 Pod 内的所有容器均运行终止,且至少有一个容器终止失败了,一般这种情况,都是由于容器运行异常退出,或者被系统终止掉了;
Unknown 一般是由于 Node 失联导致的 Pod 状态无法获取到。
既然 Pod 内的容器会出现异常退出状态,那么有没有一些重启策略可以让 Kubelet 对容器进行重启呢?
Pod 的重启策略
Kubernetes 中定义了如下三种重启策略,可以通过 spec.restartPolicy 字段在 Pod 定义中进行设置。
Always 表示一直重启,这也是默认的重启策略。Kubelet 会定期查询容器的状态,一旦某个容器处于退出状态,就对其执行重启操作;
OnFailure 表示只有在容器异常退出,即退出码不为 0 时,才会对其进行重启操作;
Never 表示从不重启;
注:在 Pod 中设置的重启策略适用于 Pod 内的所有容器。
虽然我们可以设置一些重启策略,确保容器异常退出时可以重启。但是对于运行中的容器,是不是就意味着容器内的服务正常了呢?
比如某些 Java 进程启动速度非常慢,在容器启动阶段其实是无法提供服务的,虽然这个时候该容器是处于运行状态。
再比如,有些服务的进程发生阻塞,导致无法对外提供服务,这个时候容器对外还是显示为运行态。
那么我们该如何解决此类问题呢?有没有一些方法,比如可以通过一些周期性的检查,来确保容器中运行的业务没有任何问题。
Pod 中的健康检查
为此,Kubernetes 中提供了一系列的健康检查,可以定制调用,来帮助解决类似的问题,我们称之为 Probe(探针)。
目前有如下三种 Probe:
livenessProbe可以用来探测容器是否真的在“运行”,即“探活”。如果检测失败的话,这个时候 kubelet 就会停掉该容器,容器的后续操作会受到其重启策略的影响。
readinessProbe常常用于指示容器是否可以对外提供正常的服务请求,即“就绪”,比如 nginx 容器在 reload 配置的时候无法对外提供 HTTP 服务。
startupProbe则可以用于判断容器是否已经启动好,就比如上面提到的容器启动慢的例子。我们可以通过参数,保证有足够长的时间来应对“超长”的启动时间。 如果检测失败的话,同livenessProbe的操作。这个 Probe 是在 1.16 版本才加入进来的,到 1.18 版本变为 beta。也就是说如果你的 Kubernetes 版本小于 1.18 的话,你需要在 kube-apiserver 的启动参数中,显式地在 feature gate 中开启这个功能。可以参考这个文档查看如何配置该参数。
如果某个 Probe 没有设置的话,我们默认其是成功的。
为了简化一些通用的处理逻辑,Kubernetes 也为这些 Probe 内置了如下三个 Handler:
ExecAction 可以在容器内执行 shell 脚本;
HTTPGetAction 方便对指定的端口和 IP 地址执行 HTTP Get 请求;
TCPSocketAction 可以对指定端口进行 TCP 检查;
在这里 Probe 还提供了其他配置字段,比如 failureThreshold (失败阈值)等,你可以到这个官方文档中查看更详细的解释。
注:对于每一种 Probe,Kubelet 只会执行其中一种 Handler。如果你定义了多个 Handler,则会按照 Exec、HTTPGet、TCPSocket 的优先级顺序,选择第一个定义的 Handler。
下面我们通过一个例子,来了解这三个 Probe 的工作流程。
|
|
在这个例子中,我们在命名空间 demo 下面创建了一个名为 probe-demo 的 Pod。在这个 Pod 里,我们配置了三种 Probe。在 Kubelet 创建好对应的容器以后,会先运行 startupProbe 中的配置,这里我们用 HTTP handler 每隔 2 秒钟通过 http://localhost:9876/health 来判断服务是不是启动好了。这里我们会尝试 3 次检测,如果 6 秒以后还未成功,那么这个容器就会被干掉。而是否重启,这就要看 Pod 定义的重启策略。
一旦容器通过了 startupProbe 后,Kubelet 会每隔 5 秒钟进行一次探活检测 (livenessProbe),每隔 10 秒进行一次就绪检测(readinessProbe)。
在平常使用中,建议你对全部服务同时设置 readiness 和 liveness 的健康检查。
有一点需要注意的是,通过 TCP 对端口进行检查,仅适用于端口已关闭或者进程停止的情况。因为即使服务异常,只要端口是打开状态,健康检查依然是通过的。
除了健康检查以外,我们有时候在容器退出前要做一些清理工作,比如利用 Nginx 自带的停止功能停掉进程,而不是强制杀掉该进程,这可以避免一些正在处理的请求中断。此时我们就需要一个 hook(钩子程序)来帮助我们达到这个目的了。
容器生命周期内的 hook
目前在 Kubernetes 中,有如下两种 hook。
PostStart 可以在容器启动之后就执行。但需要注意的是,此 hook 和容器里的 ENTRYPOINT 命令的执行顺序是不确定的。
PreStop 则在容器被终止之前被执行,是一种阻塞式的方式。执行完成后,Kubelet 才真正开始销毁容器。
同上面的 Probe 一样,hook 也有类似的 Handler:
Exec 用来执行 Shell 命令;
HTTPGet 可以执行 HTTP 请求。
我们来看个例子:
|
|
可以看出来,我们可以借助 preStop 以优雅的方式停掉 Nginx 服务,从而避免强制停止容器,造成正在处理的请求无法响应。
init 容器
在 Kubernetes 中还有一种特殊的容器,即 init 容器。看名字就知道,这个容器工作在正常容器(为了方便区分,我们这里称为应用容器)启动之前,通常用来做一些初始化工作,比如环境检测、OSS 文件下载、工具安装,等等。
应用容器专注于业务处理,其他一些无关的初始化任务就可以放到 init 容器中。这种解耦有利于各自升级,也降低相互依赖。
一个 Pod 中允许有一个或多个 init 容器。init 容器和其他一般的容器非常像,其与众不同的特点主要有:
总是运行到完成,可以理解为一次性的任务,不可以运行常驻型任务,因为会 block 应用容器的启动运行;
顺序启动执行,下一个的 init 容器都必须在上一个运行成功后才可以启动;
禁止使用 readiness/liveness 探针,可以使用 Pod 定义的activeDeadlineSeconds,这其中包含了 Init Container 的启动时间;
禁止使用 lifecycle hook。
我们来看一个 Init 容器的例子:
|
|
在 myapp-container 启动之前,它会依次启动 init-myservice、init-mydb,分别来检查依赖的服务是否可用。
写在最后
其实作为 Kubernetes 内部最核心的对象之一,Pod 承载了太多的功能。 为了增加可扩展、可配置性,Kubernetes 增加了各种 Probe、Hook 等,以此方便使用者进行接入配置。所以在一开始使用的时候,会觉得 Pod 中配置项太多。
但是不要害怕,这些配置项都是有一定目的的 。通过上面合理地归类和示例,可以很好地帮助你理解 Pod Spec 中的一些定义。
下一节课开始,我们就要学习如何部署高可用业务。如果你对本节课有什么想法或者疑问,欢迎你在留言区留言,我们一起讨论。
-– ### 精选评论 ##### *阳: > 这里的 init container 有很多用处,比如优化系统内核参数,像ingress-nginx这种镜像,我们可以通过init container 进行tcp连接的优化,以及系统打开最大的文件句柄数的优化等。 ##### **锋: > 请问老师,Pod从ContainerCreating状态变为Running状态的过程大概经历了10分钟,这种场景如何排查呢? ###### 讲师回复: > 先排查调度器是否调度,再看调度节点的情况。可以通过kubectl descibe pod-name -n some-ns 来看看相关的event信息。 ##### *东: > 这章节的脚本是在matser上运行还是在node上运行? ##### xxx: > PreStop 则在容器被终止之前被执行执行这个命令后,容器终止前,这个容器的服务会被访问到吗还是会访问到其他的pod上 ##### **伟: > 老师好,在样例中的这个path: /health这个配置中的/health这个路径是容器中的服务提供的吗? ##### **锋: > 请问探针是在容器running以后才会发挥作用吗? ###### 讲师回复: > 是的 ##### TUTU: > get到新知识点了,多种探针,可以按照优先级,666 ##### *豆: > 不错,又学习了 ##### **3756: > 原来readinessProbe是判断容器业务是否正常livenessProbe失败会导致容器重启
文章作者 anonymous
上次更新 2024-06-11