Kubernetes实战(第二版)--第六章 管理Pod容器的生命周期
本章涵盖了
-
检查pod状态
-
使用存活探针(liveness probes)保持容器健康
-
使用生命周期钩子在容器启动和关闭时执行操作
-
了解pod及其容器的完整生命周期
在阅读了前一章之后,您应该能够部署、检查包含一个或多个容器的POD并与之通信。在这一章,将深入了解POD及其容器操作。
请注意 可以在https://github.com/luksa/kubernetes-in-action-2nd-edition/tree/master/Chapter06上找到本章的代码。 |
6.1 理解Pod的状态
当创建一个pod对象并且运行后,可以通过API读取pod对象,来查看pod发生了什么。正如您在第4章学到的,pod对象清单,以及大多数其他类型的对象清单,包含一个提供对象状态的部分。pod的状态部分包含以下信息:
- pod的IP地址和所在工作节点
- pod启动时间
- pod的服务质量(QoS)
- pod处于什么阶段,
- pod的条件
- pod中容器的状态
IP地址和起始时间不需要任何进一步的解释,而且QoS类现在也不相关——您将在第19章学习它。然而,pod的阶段和条件以及容器的状态对于理解pod的生命周期非常重要。
6.1.1 理解pod阶段
在pod生命周期的任何时刻,它都处于如下图所示的五个阶段之一。
图6.1 Kubernetes pod的阶段
下表解释了每个阶段的含义。
pod阶段 |
描述 |
Pending |
创建Pod对象之后,这是它的初始阶段。在将Pod调度到一个节点并提取并启动其容器的镜像之前,一直处于这个阶段。 |
Running |
Pod中至少有一个容器在运行。 |
Succeeded |
当Pod的所有容器成功完成,不打算无限期运行的pod被标记为Succeeded
|
Failed |
如果没有将pod配置为无限期运行,且至少有一个容器终止失败,则pod将被标记为Failed。 |
Unknown |
pod状态是未知的,因为Kubelet已经停止与API服务器的通信。可能工作节点已失败或已与网络断开连接。 |
表6.1 Pod可以处于的阶段列表
Pod的阶段提供了一个关于Pod正在发生的事情的快速总结。让我们再次展开kiada pod,检查它的阶段。通过将manifest文件再次应用到你的集群来创建pod,就像在上一章(你会在Chapter06/pod.kiada.yaml中找到它):
$ kubectl apply -f pod.kiada.yaml
显示Pod的阶段
pod的阶段是pod对象的状态部分中的一个字段。你可以通过显示它的清单和选择性地搜索字段来查看它:
$ kubectl get po kiada -o yaml | grep phase
phase: Running
提示 还记得jq工具吗?可以使用它来打印phase字段的值,如下所示 kubectl get po kiada -o json | jq .status.phas |
可以使用kubectl describe命令查看Pod的阶段。Pod的状态显示在靠近输出顶部的地方。
$ kubectl describe po kiada
Name: kiada
Namespace: default
...
Status: Running
...
虽然kubectl get pods显示的STATUS列也显示了阶段,但这只对没问题的pods是正确的:
$ kubectl get po kiada
NAME READY STATUS RESTARTS AGE
kiada 1/1 Running 0 40m
对于有问题的Pod,STATUS列显示Pod有什么问题。将在本章后面看到这一点。
6.1.2 理解Pod条件
Pod的阶段并不能说明Pod的状况。可以通过查看pod的条件列表了解更多信息,就像在第4章中对节点对象所做的那样。Pod的状态表明Pod是否达到了某种状态,以及为什么会这样。
与阶段相反,Pod同时有几个条件。在撰写本文时,已知四种条件类型。下表对它们进行了解释。
Pod Condition |
描述 |
PodScheduled |
指示pod是否已被调度到某个节点。 |
Initialized |
Pod的初始容器已经全部成功完成。 |
ContainersReady |
Pod里的所有容器都表明已经准备好了。这是整个Pod准备就绪的必要条件,但不是充分条件。 |
Ready |
pod已经准备好为其客户端提供服务。Pod内的容器和Pod内的readiness gates都已就绪。注意:这在第10章中有解释。 |
表6.2 Pod条件一览表
每个条件要么满足要么不满足。在下面的图中可以看到,PodScheduled和Initialized条件开始时是未满足的,但很快就会满足,并且在Pod的整个生命周期中都是如此。相比之下,在Pod的生命周期中,Ready和ContainersReady条件可以改变很多次。
图6.2Pod在其生命周期内条件的转换
还记得在节点对象中可以找到的条件吗?它们是MemoryPressure, DiskPressure, PIDPressure和Ready。如您所见,每个对象都有自己的条件类型集,但许多都包含通用的Ready条件,它通常指示对象是否一切正常。
检查Pod的条件
要查看Pod的条件,你可以使用kubectl describe,如下:
$ kubectl describe po kiada
...
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
...
kubectl describe命令只显示每个条件是否为真。要找出为什么一个条件是假的,您必须查找状态。pod清单中的条件字段如下:
$ kubectl get po kiada -o json | jq .status.conditions
[
{
"lastProbeTime": null,
"lastTransitionTime": "2020-02-02T11:42:59Z",
"status": "True",
"type": "Initialized"
},
...
每个条件都有一个status字段,指示条件是True、False还是Unknown。在kiada pod的情况下,所有条件的状态都是True,这意味着它们都被满足了。条件还可以包含一个reason字段,该字段为条件状态的最后一次更改指定面向机器的原因,以及一个message字段,该字段详细解释该更改。lastTransitionTime字段显示更改发生的时间,而lastProbeTime表示最后一次检查此条件的时间。6.1.3 了解容器状态Pod的状态中还包含每个容器的状态。检查状态可以更好地了解每个容器的操作。该状态包含几个字段。state字段指示容器的当前状态,而lastState字段显示前一个容器终止后的状态。容器状态还指示了容器的内部ID (containerID)、容器正在运行的镜像和imageID、容器是否就绪以及重新启动的频率(restartCount)。了解容器状态容器状态中最重要的部分是state。容器可以处于如下图所示的states之一。
图6.3 容器的可能states下表对各个状态进行了解释。
Container State |
描述 |
Waiting |
容器正在等待启动。reason和 message字段表明容器为什么处于这种状态。 |
Running |
容器已经创建,进程正在其中运行。startat字段表示该容器启动的时间。 |
Terminated |
在容器中运行的进程已经终止。startat和finhedat字段指示容器何时启动,何时终止。主进程终止时使用的退出码在exitCode字段中。 |
Unknown |
无法确定容器的状态。 |
表6.3可能的容器states显示Pod容器的状态kubectl get pods显示的pod列表只显示了每个pod中的容器数量以及其中有多少已经就绪。要查看单个容器的状态,可以使用kubectl describe:
$ kubectl describe po kiada
...
Containers:
kiada:
Container ID: docker://c64944a684d57faacfced0be1af44686...
Image: luksa/kiada:0.1
Image ID: docker-pullable://luksa/kiada@sha256:3f28...
Port: 8080/TCP
Host Port: 0/TCP
State: Running//容器的当前状态和启动时间
Started: Sun, 02 Feb 2020 12:43:03 +0100
Ready: True//容器是否准备好提供其服务
Restart Count: 0//容器重新启动了多少次
Environment: <none>
...
请关注清单中带注释的行,因为它们表明容器是否健康。kiada容器正在Running并Ready。它从未重新启动过。
提示你也可以像这样使用jq来显示容器的状态:kubectl get po kiada -o json | jq .status.containerStatuse |
检查init容器状态在前一章中,了解了除了常规容器之外,pod还可以拥有在启动时运行的init容器。与常规容器一样,这些容器的状态在pod对象清单的状态部分中可用,但在initContainerStatuses字段中。
检查kiada-init pod的状态 作为一个额外的练习,可以自己尝试,创建上一章中的kiada-init pod,并检查它的两个常规容器和两个初始化容器的阶段、条件和状态。使用kubectl describe命令和kubectl get po kiada-init -o json | jq .status命令查找对象定义中的信息 |
6.2 保持容器健康运行
在前一章中创建的pod运行时没有任何问题。但如果其中一个容器挂掉了呢?如果pod里的所有容器都挂掉了怎么办?如何保持这些pod的健康和其容器正常运行?这是本节的重点。6.2.1 理解容器自动重启当一个pod被调度到一个节点时,该节点上的Kubelet启动它的容器,并从那时起,只要pod对象存在,它们就会一直运行。如果容器中的主进程因任何原因终止,Kubelet将重启容器。如果应用程序中出现错误导致崩溃,Kubernetes会自动重启应用程序,因此,即使应用程序本身不做任何特殊操作,在Kubernetes中运行它也会自动赋予它自我修复的能力。让我们看看它的实际应用。观察容器故障在前一章中,您创建了kiada-ssl pod,它包含Node.js和Envoy容器。再次创建pod,并通过运行以下两个命令启用与pod的通信:
$ kubectl apply -f pod.kiada-ssl.yaml
$ kubectl port-forward kiada-ssl 8080 8443 9901
现在终止Envoy容器,看看Kubernetes如何处理这个情况。在一个单独的终端中运行以下命令,这样你就可以看到当一个容器终止时pod的状态是如何变化的:
$ kubectl get pods -w
你也可以使用下面的命令在另一个终端中观察事件:
$ kubectl get events -w
你可以通过发送KILL信号,模仿一个主进程崩溃的容器,但你不能在容器内部这样做,因为Linux内核不让你杀root进程(进程PID为1)。你将不得不SSH pod的主机节点,进而杀死进程。幸运的是,Envoy的管理界面允许您通过它的HTTP API停止进程。
要终止envoy容器,在浏览器中打开URL http://localhost:9901,单击quitquitquit按钮或在另一个终端中运行curl命令:
$ curl -X POST http://localhost:9901/quitquitquit
OK
要查看容器及其所属的pod发生了什么,请检查前面运行的kubectl get pods -w命令的输出。这是它的输出:
$ kubectl get po -w
NAME READY STATUS RESTARTS AGE
kiada-ssl 2/2 Running 0 1s
kiada-ssl 1/2 NotReady 0 9m33s
kiada-ssl 2/2 Running 1 9m34s
该清单显示pod的STATUS从Running变为NotReady,而READY列表明两个容器中只有一个准备好了。紧接着,Kubernetes重启了容器,pod的状态返回到Running。“restart”列表示一个容器已经被重启。
请注意
如果其中一个容器失效,其他容器会继续运行。
现在检查前面运行的kubectl get events -w命令的输出。下面是该命令及其输出:
$ kubectl get ev -w
LAST SEEN TYPE REASON OBJECT MESSAGE
0s Normal Pulled pod/kiada-ssl Container image already
present on machine
0s Normal Created pod/kiada-ssl Created container envoy
0s Normal Started pod/kiada-ssl Started container envoy
这些事件表明,新的envoy容器已经启动。您应该能够再次通过HTTPS访问应用程序。请确认您的浏览器或curl。清单中的事件还暴露了Kubernetes如何重启容器的一个重要细节。第二个事件表明已经重新创建了整个envoy容器。Kubernetes从不重新启动容器,而是丢弃它并创建一个新的容器。不管怎样,我们称之为重新启动容器。请注意当容器被重新创建时,进程写入容器文件系统的任何数据都会丢失。这种行为有时是不可取的。如下一章所述,要持久化数据,必须向pod添加一个存储卷。请注意如果在pod中定义了init容器,并且重新启动了其中一个pod的常规容器,则不会再次执行init容器。配置pod的重启策略默认情况下,Kubernetes重启容器,不管容器中的进程是以0退出还是非0退出——换句话说,不管容器成功完成还是失败。可以通过在pod的规范中设置restartPolicy字段来更改此行为。存在三种重启策略。如下图所示。
图6.4 pod的restartPolicy决定是否重启容器
三种重启策略说明如下表所示。
重启策略 |
说明 |
Always |
无论容器中的进程终止时的退出码是什么,都将重新启动容器。这是默认的重启策略。 |
OnFailure |
只有当进程以非零退出码终止时,容器才会重新启动,按照约定,这表示失败。 |
Never |
容器永远不会重新启动——即使它失败了。 |
表6.4 Pod 重启策略
请注意
令人惊讶的是,重新启动策略是在pod级别配置的,并应用于它的所有容器。不能为每个容器单独配置它。
理解容器重新启动之前插入的时间延迟
如果你多次调用Envoy的/quitquitquit endpoint,你会注意到每次在容器终止后重启它需要更长的时间。pod的状态显示为NotReady或CrashLoopBackOff。这是它的意思。
如下图所示,当容器第一次终止时,它将立即重新启动。然而,下一次Kubernetes会等待10秒再重新启动它。每次终止后,这个延迟会加倍到20,40,80秒,然后是160秒。从那时起,延迟时间保持在五分钟。这种两次尝试之间加倍的延迟称为指数回退。
图6.5 容器重启之间的指数回退
在最坏的情况下,可以阻止一个容器启动长达5分钟时间。
请注意
当容器成功运行10分钟时,延迟将重置为零。如果容器需要稍后重新启动,则立即重新启动。
在Pod清单中检查容器状态如下:
$ kubectl get po kiada-ssl -o json | jq .status.containerStatuses
...
"state": {
"waiting": {
"message": "back-off 40s restarting failed container=envoy ...",
"reason": "CrashLoopBackOff"
正如您在输出中看到的,当容器等待重新启动时,它的状态是waiting,原因是CrashLoopBackOff。message字段告诉您重新启动容器所需的时间。请注意当你让Envoy终止时,它会以零退出码终止,也就是说它还没有崩溃。因此,CrashLoopBackOff状态可能会产生误导。6.2.2使用活性探针检查容器的运行状况在前一节中,您了解到Kubernetes通过在应用程序的进程终止时重新启动应用程序来保持应用程序的健康。但是应用程序也可能在没有终止的情况下变得无响应。例如,一个有内存泄漏的Java应用程序最终开始发生OutOfMemoryErrors,但它的JVM进程继续运行。理想情况下,Kubernetes应该检测这种错误并重新启动容器。应用程序可以自己捕获这些错误并立即终止,但是如果应用程序因为陷入无限循环或死锁而停止响应,该怎么办呢?如果应用程序无法检测到这一点怎么办?为了确保在这种情况下重新启动应用程序,可能需要从外部检查其状态。活性探针简介Kubernetes可以通过定义活性探针来检查应用程序是否仍然活动。可以为pod中的每个容器指定一个活性探针。Kubernetes定期运行探针,以询问应用程序是否仍然运行良好。如果应用程序没有响应、出现错误或响应为阴性,则认为容器不健康并终止。然后,如果重启策略允许,则重新启动容器。请注意活性探针只能在pod的常规容器中使用。它们不能在init容器中定义。活性探针的类型Kubernetes可以使用以下三种机制之一探针容器:
-
HTTP GET探针----将一个GET请求发送到容器的IP地址(位于您指定的网口和路径上)。如果探针接收到响应,而响应代码不表示错误(换句话说,如果HTTP响应代码是2xx或3xx),则认为探针成功。如果服务器返回错误响应代码,或者没有及时响应,则认为探针失败。
-
TCP Socket探针----尝试打开到容器指定端口的TCP连接。如果连接成功建立,则认为探针成功。如果连接不能及时建立,则认为探针失败。
-
Exec探针---在容器内执行命令,并检查终止时使用的退出码。如果退出码为零,则探针成功。非零退出码被认为是失败。如果命令未能及时终止,也认为探针失败。
请注意除了活性探针,容器还可以有启动探针(在第6.2.6节中讨论)和就绪探针(在第10章中解释)。6.2.3 创建HTTP GET活性探针