kubernetes in action 第二版----第六章翻译
6
管理pod中容器的生命周期
本章涵盖
• 检查pod的状态
• 用活性探针保持容器健康
• 使用生命周期钩子在容器启动和关闭时执行操作
• 了解pod及其容器的完整生命周期
在阅读了前一章之后,你应该能够部署、检查包含一个或多个容器的pod并与之通信。在本章中,你将更深入地了解pod及其容器的操作方式。
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的阶段
下表解释了每个阶段的含义。
表6.1 pod可能在的阶段列表
Pod 阶段 描述
Pending 创建Pod对象之后,这是它的初始阶段。在将pod调度到一个节点并启动其容器的镜像之前,它一直处于这个阶段。
Running 至少有一个pod还在运转
Succeeded 不打算无限期运行的pod在其所有容器成功完成时被标记为成功。
Failed 如果没有将pod配置为无限期运行,并且它的至少一个容器失败终止,则将该pod标记为Failed。
Unknown pod的状态是未知的,因为Kubelet已经停止报告与API服务器的通信。可能工作节点已失败或已与网络断开连接。
pod的阶段提供了对pod发生的事情的快速总结。让我们再次部署kubia pod并检查它的阶段。如前一章所述,在你的集群应用kubia.yaml清单创建pod:
![]()
显示pod的阶段
pod的阶段是pod对象状态段中的字段之一。你可以通过显示它的清单来查看它,并可以选择对输出进行grepping以搜索该字段:

建议 还记得jq工具吗?你可以用它来打印出phase字段的值,像这样:
![]()
你也可以使用kubectl describe查看pod的阶段:
![]()
虽然kubectl get pods显示的STATUS列似乎也显示了阶段,但这只适用于运行正常的pod:

对于不健康的pod, STATUS列指示pod发生的错误。你将在本章后面看到这一点。
6.1.2 理解pod状态
pod的阶段对pod的状况说明得很少。你可以通过查看pod的状态列表了解更多信息,就像在第4章中对节点对象所做的那样。pod的状态表明pod是否达到了某种状态,以及为什么会这样。
与阶段相反,pod同时具有几种状态。在撰写本文时,已知四种状态类型。下表对它们进行了解释。
表6.2 pod状态列表
Pod状态 描述
PodScheduled 指示pod是否已被调度到某个节点。
Initialized pod的初始化容器都已成功完成。
ContainersReady pod里的所有容器都准备好了。这是整个pod准备就绪的必要条件,但不是充分条件。
Ready pod已准备好为其客户端提供服务。舱内的集装箱和舱内的准备门都报告已经准备好了。注意:这在第10章中有解释。
每个状态要么满足,要么不满足。如下图所示,PodScheduled和Initialized状态开始时为未满足,但很快就会满足,并在pod的整个生命周期中保持这种状态。相反,Ready和ContainersReady状态在pod的生命周期中可以改变很多次。

你还记得在节点对象中可以找到的状态吗?它们是MemoryPressure,DiskPressure, PIDPressure和Ready。如你所见,每个对象都有自己的一组状态类型,但许多对象都包含通用的Ready状态,该状态通常指示对象是否一切正常。
检查pod的状态
要查看pod的状态,可以使用kubectl描述,如下面的清单所示:

#A 已经初始化完成的pod
#B pod与它的容器在ready状态
#C 已经调度到节点的pod
kubectl describe命令只显示每个状态是否为真。要找出状态为假的原因,必须检查pod清单,如下一个清单所示。

每个状态都有一个指示字段,指示条件是True、False还是未知的。就kubia pod来说,所有状态的条件都是True,这意味着它们都满足了。状态还可以包含一个原因字段,该字段解释条件状态最后一次更改的面向机器的原因,以及一个详细解释状态变更细节的message字段。lastTransitionTime字段显示变更发生的时间,而lastProbeTime表示最后一次检查此状态的时间。
6.1.3 理解pod中容器的状态
pod的状态中还包含它的每个容器的状态。检查状态可以更好地了解每个容器的操作情况。
状态包含几个字段。state字段指示容器的当前状态,而lastState字段显示容器终止前的状态。容器状态也表示容器的内部ID(containerID),容器正在运行的镜像和镜像ID,容器是否准备好了,以及它重启的频率(restartCount)。
理解容器状态
容器状态中最重要的部分是它的状态。容器可以处于如下图所示的状态之一。

图6.3 容器的可能状态
下表解释了各个状态.
表6.3 容器可能的状态
容器状态 描述
Waiting 容器正在等待启动。原因和消息字段表示容器处于此状态的原因
Running 容器已经创建,并且正在其中运行进程。startAt字段表示容器启动的时间。
Terminated 在容器中运行的进程已经终止。startAt和finishedAt字段指示容器何时启动和何时终止。终止主进程的退出代码位于exitCode字段中。
Unknown 无法确定容器的状态。
显示pod容器的状态
kubectl get pods显示的pod列表仅显示每个pod中的容器数量以及其中准备好的容器数量。要查看各个容器的状态,必须使用kubectl description,如下面的清单所示。

#A 容器的当前状态及其启动时间
#B 容器是否准备好提供服务
#C 容器重启了多少次
注意清单中带注释的行,因为它们指示容器是否正常运行。kubia容器的状态为“Running”,“Ready”。它从未重启过。
建议 你还可以使用jq显示容器状态,如下所示:kubectl get po kubia -o jsonjq .status. containerstatus . |
检查init容器状态
在前一章中,你了解到除了常规容器之外,pod还可以拥有在pod启动时运行的init容器。与常规容器一样,这些容器的状态在pod对象清单的status段中,但在initcontainerstatus字段中。
检查kubia-init pod的状态
作为额外的练习,你可以自己尝试创建kubia-init中定义的pod。使用kubectl描述和使用kubectl get po kubia-init -o json | jq .status命令检索pod清单来检查它的阶段、状态和它的两个常规容器以及两个init容器的状态。
6.2 保持容器健康
在前一章中创建的pod运行起来没有任何问题。但如果其中一个容器死了怎么办?如果一个pod里所有的容器都死了怎么办?你如何保持pod的健康和pod中容器的运行?这是本节的重点。
6.2.1 理解容器自动重启
当一个pod被调度到一个节点时,该节点上的Kubelet启动它的容器,从那时起,只要pod对象存在,容器就一直运行。如果容器中的主进程因任何原因终止,Kubelet将重新启动容器。如果应用程序中的错误导致它崩溃,Kubernetes会自动重启它,所以即使不需要在应用程序本身做任何特殊的操作,在Kubernetes中运行它也会自动赋予它自我修复的能力。让我们看看实际情况。
观察一个容器的失败
在前一章中,你创建了kubia-ssl pod,其中包含Node.js和Envoy容器。重新创建pod,并运行以下两个命令启用与pod的通信:

现在,你将引起Envoy容器终止,以查看Kubernetes如何处理这种情况。在一个单独的终端上运行以下命令,这样你就可以看到当其中一个容器终止时pod的状态是如何变化的:
![]()
你还需要使用以下命令在另一个终端中观察事件:
![]()
你可以通过向容器的主进程发送KILL信号来模拟它的崩溃,但是你不能在容器内部这样做,因为Linux内核不允许你杀死根进程(PID为1的进程)。你必须SSH到pod的主机节点,并从那里终止该进程。幸运的是,Envoy的管理接口允许你通过它的HTTP API停止进程。
要终止envoy容器,在浏览器中打开URL http://localhost:9901并单击quitquit按钮或在另一个终端中运行以下curl命令:

要查看容器和它所属的pod发生了什么,请检查前面运行的kubectl get pods -w命令的输出。下一个清单显示了它。

清单显示pod的STATUS从Running变为NotReady,而READY列表明两个容器中只有一个准备好了。此后,立即Kubernetes重新启动容器,pod的状态返回到Running。RESTARTS列表示一个容器已经重启。
注意 如果pod的一个容器失败,其他容器将继续运行。
现在检查前面运行的kubectl get events -w命令的输出。如下列表显示。

事件显示新的envoy容器已经启动。你应该能够再次通过HTTPS访问应用程序。请用浏览器或curl确认。
列表中的事件还揭示了Kubernetes如何重启容器的重要细节。第二个事件表明整个特使容器已被重新创建。Kubernetes从不重新启动容器,而是丢弃它并创建一个新的容器。无论如何,我们将此称为重新启动容器。
注意 在重新创建容器时,进程写入容器文件系统的任何数据都将丢失。
这种行为有时是不可取的。要持久化数据,必须向pod添加存储卷,这将在下一章中解释。
注意 如果在pod中定义了init容器,并且重新启动了pod的常规容器之一,则不会再次执行init容器。
配置pod的重启策略
默认情况下,Kubernetes重启容器,而不管容器中的进程是否以零或非零退出码退出——换句话说,容器是否成功完成或失败。这种行为可以通过在pod的spec段中设置restartPolicy字段来改变。
存在三种重启策略。如下图所示。

图6.4 pod的restartPolicy决定其容器是否重新启动
三种重启策略说明如下表所示。
表6.4 pod的重启策略
重启策略 描述
Always 无论容器中的进程以什么退出码结束,都将重新启动容器。这是默认的重启策略。
OnFailure 只有当进程以非零退出码结束时,容器才会重新启动,按照惯例,退出码表示失败。
Never 容器永远不会重新启动——即使它失败了也不会。
注意 令人惊讶的是,重启策略是在pod级别配置的,并应用于它的所有容器。它不能为每个容器单独配置。
了解容器重新启动之前插入的时间延迟
如果多次调用Envoy的/quitquit端点,你会注意到每次在容器终止后重新启动它都需要更长的时间。pod的状态显示为NotReady或CrashLoopBackOff。这就是它的意思。
如下图所示,容器第一次终止时,将立即重新启动。但是,下一次,Kubernetes在重新启动它之前等待10秒钟。在后续每次终止后,这个延迟会加倍到20、40、80,然后是160秒。从那时起,延误时间保持在5分钟。这种两次尝试之间的延迟翻倍被称为指数后退。

图6.5容器重启之间的指数回退
在最坏的情况下,一个容器可以因此被阻止启动长达五分钟。
注意 当容器成功运行10分钟后,延迟被重置为零。如果稍后必须重新启动容器,则立即重新启动。
在下面的列表中可以看到,容器在等待重新启动时处于Waiting状态,原因显示为CrashLoopBackOff。message字段指示容器重新启动需要多长时间。

注意 当你告诉Envoy终止时,它以退出代码0终止,这意味着它还没有崩溃。因此,CrashLoopBackOff状态可能具有误导性。
6.2.2 使用活动探针检查容器的运行状况
在上一节中,你了解到Kubernetes通过在进程终止时重新启动应用程序来保持应用程序的健康。但是应用程序也可能在未终止的情况下变得无响应。例如,一个有内存泄漏的Java应用程序最终开始抛出OutOfMemoryErrors,但它的JVM进程继续运行。理想情况下,Kubernetes应该检测到这种错误并重新启动容器。
应用程序可以自己捕获这些错误并立即终止,但是如果应用程序由于进入无限循环或死锁而停止响应,该怎么办呢?如果应用程序无法检测到这一点怎么办?为了确保在这种情况下重新启动应用程序,可能需要从外部检查其状态。
介绍活性探针
Kubernetes可以配置为通过定义一个活性探针来检查应用程序是否仍然处于活动状态。你可以为pod中的每个容器指定一个活性探针。Kubernetes定期运行探测,询问应用程序是否仍然正常运行。如果应用程序不响应、发生错误或响应为负数,则认为容器不健康并终止。然后,如果重启策略允许,将重新启动容器。
注意 活性探针只能在pod的常规容器中使用。它们不能定义在init容器中.
活性探针的类型
Kubernetes可以通过以下三种机制之一探测容器:
- HTTP GET探针在指定的网络端口和路径上向容器的IP地址发送GET请求。如果探测接收到响应,并且响应代码不表示错误(换句话说,如果HTTP响应代码是2xx或3xx),则认为探测成功。如果服务器返回错误响应代码,或者没有及时响应,则认为探测失败。
- TCP套接字探测尝试打开到容器的指定端口的TCP连接。如果连接成功建立,则认为探测成功。如果不能及时建立连接,则认为探测失败。
- Exec探测器在容器内执行命令,并检查终止命令的退出代码。如果退出码为0,则探测成功。非零退出码被认为是失败。如果命令未能及时终止,则认为探测失败。
注意 除了活动探针外,容器还可以有启动探针(将在6.2.6节讨论)和就绪探针(将在第10章解释)。
6.2.3 创建HTTP GET活性探针
让我们看看如何向kubia-ssl pod中的每个容器添加活动探针。因为它们都运行理解HTTP的应用程序,所以使用HTTP对pod中的每个荣天气进行GET探测是有意义的。Node.js应用程序不提供任何端点显式检查应用程序的运行状况,但Envoy代理可以。在实际应用程序中,两种情况都会遇到。
在pod清单中定义活性探针
下面的清单显示了pod的更新清单,它为两个容器中的每个容器定义了一个具有不同配置级别的活动探测。

#A 运行Node.js的容器的活动探测定义
#B Envoy代理的活动探针
这些活动探针将在接下来的两节中解释。
使用所需的最小配置定义活性探针
用于kubia容器的活动探测是用于基于http的应用程序的探测的最简单版本。探针只是将端口8080上的路径/的HTTP GET请求发送到容器,确定容器是否还能服务请求。如果应用程序响应的HTTP状态在200到399之间,则认为该应用程序运行正常。
探测没有指定任何其他字段,因此使用默认设置。第一个请求在容器启动后10秒发送,并且每10秒重复一次。如果应用程序在一秒钟内没有响应,则认为探测尝试失败。如果连续失败三次,则认为容器不健康并终止。
了解活动探测配置选项
Envoy代理的管理接口提供了一个特殊的端点/ready,通过它可以公开其运行状况。Envoy容器的活性探针不是针对端口8443(Envoy通过该端口向Node.js转发HTTPS请求),而是针对管理端口上的这个特殊端点(端口号9901)。
注意 正如你在Envoy容器的活性探针中看到的那样,你可以通过名称而不是编号来指定探测的目标端口。
Envoy容器的活性探针还包含其他字段。下面的图最好地解释了这些字段。

图6.6 活性探针的配置和操作
参数initialDelaySeconds决定了在启动容器后Kubernetes应该延迟执行第一个探测的时间。periodSeconds字段指定执行两个连续探测之间的时间,而timeoutSeconds字段指定在探测尝试计数失败之前等待响应的时间。failureThreshold字段指定探测必须失败多少次,容器才会被认为不健康并可能重新启动。
6.2.4 观察活性探针
要查看Kubernetes在活动探测失败时重新启动容器,可以从kubia-liveness创建pod。使用kubectl apply,并运行kubectl port-forward启用与pod的通信。你需要停止kubectl端口转发命令,该命令仍然在前面的练习中运行。确认pod正在运行并且正在响应HTTP请求。
观察一个成功的活体探测
在每个单独的容器启动后不久,pod容器的活性探针就会开始发射。由于两个容器中的进程都是健康的,因此探针不断报告成功。由于这是正常状态,探测成功的事实在pod状态及其事件的任何地方都没有明确指示。
Kubernetes正在执行探测的唯一指示是在容器日志中。kubia容器中的Node.js应用程序在每次处理HTTP请求时都会向标准输出输出一行。这包括活性探针请求,所以你可以使用下面的命令显示它们:
![]()
envoy容器的活性探针被配置为向envoy的管理接口发送HTTP请求,该接口不会将HTTP请求记录到标准输出,而是记录到容器文件系统中的/var/log/envoy.admin.log文件。显示日志文件的命令如下:![]()
观察活性探针失败
一个成功的活性探针并不有趣,所以让我们导致Envoy的活性探针失败。要查看幕后会发生了什么,在一个单独的终端中执行以下命令开始观察事件:
![]()
使用Envoy的管理接口,你可以将其运行状况检查端点配置为成功或失败。要使其失败,请在浏览器中打开URL http://localhost:9901并单击healthcheck/fail按钮,或使用以下curl命令:
![]()
执行完命令后,请立即观察另一个终端上显示的事件。当探测失败时,记录一个Warning事件,指出错误并返回HTTP状态码:
![]()
由于探针的failureThreshold设置为3,因此单个故障不足以认为容器不健康,因此它会继续运行。你可以通过单击Envoy的管理界面中的healthcheck/ok按钮,或者使用curl,使活动探测再次成功:
![]()
如果你的速度足够快,则不会重新启动容器。
观察活性探针达到故障阈值
如果让活性探测多次失败,你应该看到类似于下一个清单中的事件(注意,由于页面宽度的限制,有些列被省略了)。

请记住,探测失败阈值设置为3,因此当探测连续失败三次时,容器将停止并重新启动。清单中的事件表明了这一点。
kubectl get pods命令显示容器已经重启:

RESTART列显示在pod中进行了一次容器重启。
了解活性探针失败的容器是如何重新启动的

#A 这是新容器的状态。
#B 前一个容器以退出码0终止。
列表中显示的退出代码0表示应用程序进程自行优雅退出。如果它被杀死了,退出码应该是137。
注意 退出码128+n表示进程因外部信号n退出。退出码137为128+9,其中9表示KILL信号。当容器被终止时,你将看到此退出代码。退出码143是128+15,其中15是TERM信号。当容器运行一个正常终止的shell时,通常会看到此退出代码
让我们检查Envoy的日志,以确认它捕获了TERM信号并自行终止。你必须使用kubectl logs命令和--container或更短的-c选项来指定你感兴趣的容器。
另外,由于重新启动后容器已被替换为新容器,因此必须使用--previous或-p标志请求前一个容器的日志。如下列表显示了完整的命令及其输出的最后四行。

日志确认Kubernetes向进程发送了TERM信号,允许它正常关闭。如果它不是自己终止的,Kubernetes就会强行杀死它。
容器重新启动后,其运行状况检查端点使用HTTP状态200进行响应
还是OK,表明容器是健康的。
6.2.5 使用exec和tcpSocket活性探针类型
对于不公开HTTP健康检查端点的应用程序,应该使用tcpSocket或exec执行活性探测。
添加tcpsocket活性探针
对于接受非http的TCP连接的应用程序,可以配置tcpSocket活动探测。Kubernetes尝试打开TCP端口的套接字,如果连接建立,则认为探测成功,否则认为失败。
下面的清单显示了一个tcpSocket活动探测的示例。
#A 这个tcpSocket探测使用TCP端口1234
#B 探测器每隔2s运行一次
#C 单个探测器故障就足以重启容器。
列表中的探针配置为检查容器的网络端口1234是否打开。每两秒钟尝试一次建立连接,一次失败的尝试就足以认为容器不健康。
增加一个EXEC活性探针
不接受TCP连接的应用程序可以提供一个命令来检查它们的状态。对于这些应用程序,使用exec探针。如下图所示,该命令在容器内执行,因此必须在容器的文件系统上可用。

图6.7 exec探测器在容器内运行命令
下面的列表显示了一个探测示例,它每两秒钟运行一次/usr/bin/healthcheck,以确定在容器中运行的应用程序是否仍然活跃。

#A 要运行的命令及其参数
#B 探测器每秒运行一次
#C 命令必须在1秒内返回
#D 单次探测失败就足以重新启动容器
如果命令返回退出码0,则认为容器是健康的。如果它返回非零退出码或未能在timeoutSeconds字段中指定的一秒钟内完成,则容器将立即终止,如failureThreshold字段中配置的那样,这表明单次探测失败足以将容器视为不健康。
6.2.6 当应用程序启动缓慢时使用启动探针
默认的活性探针设置为应用程序提供了20到30秒的时间来开始响应活性探针请求。如果应用程序启动所需时间较长,则需要重新启动,并且必须重新启动。如果第二次启动也需要同样长的时间,则重新启动。如果这种情况持续下去,容器将永远无法到达活动探测成功的状态,并陷入无休止的重启循环。
为了防止这种情况,你可以增加initialDelaySeconds、periodSeconds或failureThreshold的设置,以说明较长的启动时间,但这将对应用程序的正常操作产生负面影响.如果应用程序变得不健康,perioseconds * failureThreshold的结果越高,重新启动应用程序所需的时间就越长。对于需要几分钟才能启动的应用程序,增加这些参数以防止应用程序过早重启可能不是一个可行的选择。
启动探针介绍
为了处理应用程序的启动和稳态操作之间的差异,Kubernetes还提供了启动探针。
如果为容器定义了启动探针,则仅在容器启动时执行探针。启动探针可以配置为考虑应用程序的缓慢启动。当启动探针成功时,Kubernetes切换到使用活动探测,它被配置为快速检测应用程序何时变得不健康。
将启动探针添加到pod的清单中
想象一下,kubia Node.js应用程序需要一分钟多的时间来预热,但你希望它在正常操作期间变得不健康后,在10秒内重新启动。下面的列表显示了如何配置启动和活动探测.


#A 启动探针和活性探针通常使用相同的端点
#B 应用程序有120秒的时间启动
#C 启动后,应用程序的健康状况每5秒检查一次,并且在活性探针失败两次时重新启动
当清单中定义的容器启动时,应用程序有120秒的时间开始响应请求。Kubernetes每10秒执行一次启动探测,最多尝试12次。
如下图所示,与活性探针不同,启动探针失败是完全正常的。失败仅表明应用程序尚未完全启动。启动探测成功表示应用程序已成功启动Kubernetes应该切换到活动探测。然后,通常使用较短的时间执行活性探测,这允许更快地检测无响应的应用程序。

图 6.8 结合启动和活性探针快速检测应用程序运行状况问题
注意 如果启动探针失败的次数频繁到足以达到failureThreshold,容器就会因为活性探针失败而终止。
通常,启动和活性探针被配置为使用相同的HTTP端点,但也可以使用不同的端点。你还可以将启动探针配置为exec或tcpSocket探测,而不是httpGet探测。
6.2.7 创建有效的活动探测处理程序
你应该为所有pod定义一个活性探针。如果没有一个,Kubernetes无法知道你的应用程序是否还活着,除了检查应用程序进程是否已经终止。
使用实现糟糕的活性探针处理程序导致不必要的重启
在实现活性探针的处理程序时,无论是作为应用程序中的HTTP端点还是作为附加的可执行命令,都要非常小心地正确实现它。如果实现不良的探针返回否定响应,即使应用程序运行正常,应用程序也将不必要地重新启动。许多Kubernetes用户都经历了惨痛的教训。如果可以确保应用程序进程在变得不健康时自行终止,那么不定义活动探测可能更安全。
活性探针应该检查什么
kubia容器的活动探测没有配置为调用实际的健康检查端点,而只是检查Node.js服务器是否响应对根URI的简单HTTP请求。这可能看起来过于简单,但即使是这样的活性探针也会产生奇妙的效果,因为如果服务器不再响应HTTP请求(这是它的主要任务),它会导致容器重新启动。如果没有定义活性探针,则pod将保持在不健康状态,在这种状态下,它不会响应任何请求,并且必须手动重新启动。有一个简单的活性探针比没有强。
为了提供更好的活动性检查,web应用程序通常会公开一个特定的健康检查端点,比如/healthz。当调用此端点时,应用程序对在应用程序中运行的所有主要组件执行内部状态检查,以确保它们都没有死亡或不再做它们应该做的事情。
建议 确保/healthz HTTP端点不需要身份验证;否则,探测将总是失败,导致容器无限期地重新启动。
确保应用程序只检查其内部组件的操作,而不检查任何受外部因素影响的操作。例如,当前端服务的健康检查端点无法连接到后端服务时,它不应该响应失败。如果后端服务失败,重启前端并不能解决问题。这样的活性探针将在重启后再次失败,因此容器将反复重启,直到后端修复。如果许多服务以这种方式相互依赖,则单个服务的故障可能导致整个系统级联失败。
保持探测器轻便
活性探针调用的处理程序不应该使用太多的计算资源,也不应该花费太长的时间来完成。默认情况下,探测相对频繁地执行,并且只给它一秒钟的时间来完成。
使用消耗大量CPU或内存的处理程序可能会严重影响容器的主进程。在本书的后面,你将了解如何限制容器可用的CPU时间和总内存。探测处理程序消耗的CPU和内存要计数到容器的资源配额,因此使用资源密集型处理程序将减少应用程序主进程可用的CPU时间。
建议 在容器中运行Java应用程序时,你可能希望使用HTTP GET探测,而不是启动整个JVM的exec活性探针。这同样适用于需要大量计算资源的命令。
避免探针处理程序中的重试循环
你已经了解到探针的故障阈值是可配置的。不要在探测处理程序中实现重试循环,而是保持简单,将failureThreshold字段设置为更高的值,以便探测必须失败几次才能认为应用程序不健康。在处理程序中实现自己的重试机制是浪费精力,并且代表了另一个潜在的故障点。
6.3 在容器启动和关闭时执行操作
在前一章中,你了解了可以在pod生命周期开始时使用init容器来运行容器。你可能还希望在每次容器启动和停止之前运行其他进程。你可以通过向容器添加生命周期钩子来实现这一点。目前支持两种类型的钩子:
- 启动后钩子(Post-start hooks),在容器启动时执行
- 预停止挂钩,在容器停止之前执行。
这些生命周期钩子是为每个容器指定的,而init容器是在pod级别指定的。下一个图将帮助你可视化生命周期钩子如何适应容器的生命周期。

图6.9启动后和停止前钩子如何适应容器的生命周期
与活性探针一样,生命周期钩子可以用于任何一种情况
- 在容器内执行命令,或者
- 发送一个HTTP GET请求到容器中的应用程序。
注意 与活性探针一样,生命周期钩子只能应用于常规容器,而不能应用于init容器。与探测不同,生命周期钩子不支持tcpSocket处理程序。
让我们分别来看看这两种类型的钩子,看看它们可以用来做什么。
6.3.1使用启动后钩子在容器启动时执行动作
启动后生命周期钩子在容器创建后立即被调用。你可以使用exec类型的钩子在主进程启动时执行一个额外的进程,或者你可以使用httpGet钩子向在容器中运行的应用程序发送一个HTTP请求,以执行某种类型的初始化或预热过程。
如果你是应用程序的作者,则可以在应用程序代码本身中执行相同的操作,但是如果需要将其添加到不是你自己创建的现有应用程序中,则可能无法这样做。启动后钩子提供了一种简单的替代方法,它不需要更改应用程序或其容器镜像。
让我们看一个例子,看看如何在你将要创建的新服务中使用post-start钩子。
使用post-start容器生命周期钩子在容器中运行命令
上世纪90年代,在我第一次接触Unix时,我发现一件有趣的事情,那就是每次我登录我们高中的服务器时,fortune命令都会显示随机的,有时甚至是有趣的消息,当时我的服务器正在运行Solaris操作系统。现在,你很少在Unix/Linux系统上看到安装fortune命令,但是你仍然可以在无聊的时候安装并运行它。下面是它可能显示的一个例子:
![]()
在下面的练习中,你将结合fortune程序和Nginx web服务器来创建基于web的fortune服务。
对于第一个版本的服务,fortune命令会将消息写入一个文件,然后由Nginx提供服务。尽管这意味着在每个请求返回相同的消息,但这是一个非常好的开始。稍后将迭代地改进服务。
Nginx web服务器作为一个容器镜像是可用的,所以让我们使用它。因为fortune命令在镜像中不可用,所以你通常会基于Nginx镜像构建一个新镜像,并在容器构建过程中安装fortune包。
现在我们让事情变得非常简单,在容器启动时安装并运行fortune命令。虽然这不是你通常应该做的事情,但poststart钩子可以用于此。下面的列表显示了pod清单,其中定义了一个执行此操作的启动后钩子。

#A我们把这个pod命名为fortune-poststart
#B nginx:alpine容器镜像在这个单容器pod中
#C 使用启动后生命周期钩子用于在容器启动时运行命令
#D执行的shell命令
#E Nginx服务器运行在80端口
这个pod被命名为fortune-poststart,它包含一个基于nginx:alpine镜像的容器。为容器定义了一个postStart生命周期钩子。Nginx服务器启动时执行如下命令:
![]()
让我给你解释一下命令。它执行sh shell,运行以下两个命令:
1. apk add fortune命令安装fortune包。
2. fortune命令被执行,它的输出被重定向到/usr/share/nginx/html/quote文件。
该命令与主进程并行运行。postStart的名称有点误导人,因为钩子不是在主进程完全启动之后调用的,而是在容器创建之后调用的,与主进程启动的时间大致相同。当postStart钩子完成后,fortune命令产生的引用就存储在文件中,准备由Nginx提供服务。
使用kubectl apply命令从fortune-poststart.yaml文件创建pod。然后,你应该能够使用curl或你的浏览器在fortune-poststart pod的端口80上的URI /quote处获取引用。你已经学习了如何做到这一点,但是你可能需要参考侧栏,因为有一个警告。
访问fortune-poststart pod
要从fortune-poststart pod中检索引用,必须首先运行kubectl port-forward命令,可能会失败,如下所示:
$ kubectl port-forward fortune-poststart 80
Unable to listen on port 80: Listeners failed to create with the following errors: [unable
to create listener: Error listen tcp4 127.0.0.1:80: bind: permission denied unable to
create listener: Error listen tcp6 [::1]:80: bind: permission denied] error: unable to listen on any of the requested ports: [{80 80}]
如果操作系统不允许运行绑定到端口号0-1023的进程,则该命令将失败。要解决这个问题,你必须使用更高的本地端口号,如下所示:
$ kubectl port-forward fortune-poststart 1080:80
最后一个参数告诉kubectl在本地使用端口1080,并将其转发到pod的端口80。你现在可以在http://localhost:1080/quote访问fortune服务。
理解启动后钩子如何影响容器
虽然启动后钩子与主容器进程异步运行,但它以两种方式影响容器。
首先,容器保持在Waiting状态,原因是ContainerCreating,直到钩子调用完成。Pod的阶段正在Pending中。如果此时运行kubectl logs命令,它将拒绝显示日志,即使容器正在运行。kubectl port-forward命令也拒绝将端口转发到pod。
如果你希望自己看到这一点,请部署fortune-poststart-slow。你可以在本书的代码存档中找到Yaml pod清单文件。它定义了一个启动后钩子,需要60秒才能完成。创建pod后,立即检查其状态,并使用以下命令显示日志:

返回的错误消息暗示容器尚未启动,但事实并非如此。为了证明这一点,使用以下清单中的命令列出容器中的进程:

#A Nginx正在运行
#B 启动后的钩子进程
启动后钩子影响容器的另一种方式是,钩子中使用的命令无法执行或返回非零退出代码。如果发生这种情况,整个容器将会重启。要查看启动后钩子失败的示例,请部署pod manifest fortune-poststart-fail.yaml。
如果你使用kubectl get pods -w查看pod的状态,你会看到以下状态:

它显示已执行的命令和终止命令的代码。当你查看以下清单中显示的pod事件时,你将看到一个FailedPostStartHook警告事件,该事件指示退出代码以及命令打印到标准输出或错误输出的内容。

同样的信息也包含在pod的status段中的containerstatus字段中,但只是在很短的时间内,因为容器状态很快就会更改为CrashLoopBackOff。
建议 由于pod的状态可以快速变化,因此仅检查其状态可能无法告诉你需要知道的一切。与其在某个特定时刻检查状态,回顾鲸群发生的事件通常是了解全貌的更好方法。
捕获通过POST-START钩子调用的进程产生的输出
正如你刚刚了解到的,如果post-start钩子中定义的命令的输出失败,可以检查它。在命令成功完成的情况下,命令的输出不会记录在任何地方。要查看输出,命令必须记录到一个文件中,而不是标准输出或错误输出。然后,你可以使用如下命令查看文件的内容:
![]()
使用HTTP get POST-START钩子
在前面的示例中,你配置了启动后钩子来调用容器中的命令。或者,你可以让Kubernetes在启动容器时发送一个HTTP GET请求,方法是使用httpGet post-start钩子。
注意 你不能同时为一个容器指定exec和httpGet启动后钩子。只能指定一个。
例如,当在容器中启动web应用程序时,你可能希望使用post-start钩子向应用程序发送初始请求,以便初始化其缓存或预热其其他内部组件,这样可以更快地处理实际客户发送的第一个请求。
如下列表显示了一个启动后钩子定义的示例:

#A 这是一个执行HTTP GET请求的启动后生命周期钩子
#B 请求被发送到pod的80端口
#C HTTP GET请求中请求的URI
清单中的示例显示了一个httpGet post-start钩子,它调用pod端口80上的/warm URI。你可以在poststart-httpget.yaml中找到这个例子。本书代码归档中的Yaml文件。
除了清单中显示的端口和路径外,还可以指定方案(HTTP或HTTPS)和host字段,以及要在请求中发送的httpHeaders。host字段默认为pod IP。注意不要将其设置为localhost,因为localhost指的是承载pod的节点,而不是pod本身。
与基于命令的post-start钩子一样,当容器的主进程启动时,立即执行HTTP GET启动后钩子。如果进程没有立即启动,这可能会有问题。正如你已经了解到的,如果启动后钩子失败,容器将重新启动。要想自己看到这一点,请尝试创建poststartttpget-slow.yaml中定义的pod。
警告 对没有立即开始接受连接的应用程序使用HTTP GET启动后钩子可能会导致容器进入一个无限的重启循环。
有趣的是,Kubernetes不会将钩子视为失败,如果HTTP服务器响应一个HTTP错误代码,如404未找到。确保在HTTP中指定了正确的URI GET钩子,否则你甚至可能不会注意到启动后钩子什么也不做。
6.3.2 在容器终止之前运行进程
除了在容器启动时执行命令或发送HTTP请求外,Kubernetes还允许在容器中定义pre-stop钩子。
Pre-stop钩子在容器终止之前立即执行。要终止一个进程,通常会向它发送TERM信号。这告诉应用程序完成它正在做的事情并关闭。容器也是如此。每当容器需要停止或重新启动时,TERM信号被发送到容器中的主进程。但是,在此之前,Kubernetes首先执行预停止钩子(如果为容器配置了一个)。除非进程已经由于调用而终止,否则在pre-stop钩子完成之前不会发送TERM信号
注意 当容器终止被启动时,将不再调用活动探测和其他探测。
Pre-stop钩子可用于启动容器的优雅关闭或执行额外的操作,而不必在应用程序本身中实现它们。与启动后钩子一样,你可以在容器内执行命令,也可以向其中运行的应用程序发送HTTP请求。
使用pre-stop生命周期钩子优雅地关闭容器
在fortune pod中使用的Nginx web服务器通过立即关闭所有打开的连接并终止进程来响应TERM信号。这并不理想,因为此时正在处理的客户端请求不允许完成。
幸运的是,你可以通过运行命令Nginx -s quit来指示Nginx优雅地关闭。当你运行此命令时,服务器将停止接受新的连接,等待所有正在运行的请求都被处理完,然后退出。
当你在Kubernetes pod中运行Nginx时,你可以使用pre-stop生命周期钩子来运行这个命令,并确保pod优雅地关闭。下面的清单显示了这个pre-stop钩子的定义。你可以在fortune-prestop.yaml中找到它pod清单。

#A 这是一个pre-stop生命周期钩子
#B 它执行一个命令
#C 这是要执行的命令
每当使用此预停止钩子的容器被终止时,在容器的主进程接收到TERM信号之前,在容器中执行命令nginx -s quit。
与启动后钩子不同的是,无论pre-stop钩子的结果如何,容器都会被终止——命令执行失败或退出码非零都不会阻止容器被终止。如果pre-stop钩子失败,你将在pod事件中看到一个FailedPreStopHook警告事件,但是如果你只监视pod的状态,则可能看不到任何失败的指示。
建议 如果成功完成pre-stop钩子对系统的正常运行至关重要,请确保它成功运行。我经历过pre-stop钩子根本不运行的情况,但工程师甚至没有意识到这一点。
与pos-start钩子一样,你也可以配置pre-stop钩子,将HTTP GET请求发送到应用程序,而不是执行命令。HTTP GET pre-stop钩子的配置与启动后钩子的配置相同。有关更多信息,请参见6.3.1节
___________________________________________________________________
应用程序没有接收到TERM信号
许多开发人员错误地定义了一个pre-stop钩子,只是为了在pre-stop钩子中向他们的应用程序发送一个TERM信号。当他们发现他们的应用程序从未接收到TERM信号时,他们就会这样做。根本原因通常不是信号从未发送,而是它被容器内的东西吞噬了。当你在Dockerfile中使用ENTRYPOINT或CMD指令的shell形式时,通常会发生这种情况。这些指令有两种形式。
exec形式是:ENTRYPOINT ["/myexecutable", "1st-arg", " second -arg"]
shell形式是:ENTRYPOINT /myexecutable 1 -arg 2 -arg
当你使用exec形式时,将直接调用可执行文件。它启动的进程成为容器的根进程。当你使用shell形式时,shell作为根进程运行,而shell将可执行文件作为子进程运行。在这种情况下,shell进程是接收TERM信号的进程。不幸的是,它不会将此信号传递给子进程。
在这种情况下,不是添加一个pre-stop钩子来发送TERM信号到你的应用程序,正确的解决方案是使用ENTRYPOINT或CMD的exec形式。
注意,如果在容器中使用shell脚本运行应用程序,也会出现同样的问题。在这种情况下,你必须拦截并向应用程序传递信号,或者使用exec shell命令在脚本中运行应用程序。
Pre-stop钩子只有在容器被请求终止时才会被调用,要么是因为它的活性探测失败,要么是因为pod必须关闭。当在容器中运行的进程自行终止时,不会调用它们。
理解生命周期钩子针对的是容器,而不是pod
关于post-start和pre-stop钩子的最后一个考虑,我想强调这些生命周期钩子适用于容器而不是pod。你不应该使用pre-stop钩子来执行需要在整个pod关闭时执行的操作,因为每次容器需要终止时都要运行pre-stop钩子。这种情况在pod的一生中会发生好几次,而不仅仅是在pod关闭的时候。
6.4 理解pod生命周期
到目前为止,在本章中,你已经了解了很多关于pod中的容器如何运行的知识。现在让我们仔细看看pod及其容器的整个生命周期。
当你创建一个pod对象时,Kubernetes将它调度到一个工作节点,然后运行它的容器。pod的生命周期分为三个阶段,如下图所示:
图6.10 pod生命周期的三个阶段
pod生命周期的三个阶段是:
1. 初始化阶段,在此期间pod的初始化容器运行。
2. 运行阶段,pod的常规容器在其中运行。
3. 终止阶段,在此阶段,pod的容器被终止。
让我们看看在每一个阶段会发生什么。
6.4.1 理解初始化阶段
正如你已经了解到的,首先运行pod的init容器。它们按照pod规范中的initContainers字段中指定的顺序运行。让我来解释一下展开的一切。
拉取容器镜像
在启动每个init容器之前,它的容器镜像被拉取到工作节点。pod规范中容器定义中的imagepulpolicy字段决定是每次都拉取镜像,还是只拉取第一次,还是从不拉取镜像。
镜像拉取策略 描述
Not specified 如果没有显式指定imagePullpolicy,则默认为Always如果镜像使用了:latest标签。对于其他图像标记,默认为IfNotPresent。
Always 每次容器(重新)启动时都会拉取镜像。如果本地缓存的镜像与注册中心的镜像匹配,则不会再次下载该镜像,但仍然需要联系注册表。
Never 容器映像永远不会从注册中心拉取。它必须事先存在于工作节点上。当部署具有相同镜像的另一个容器时,它要么存储在本地,要么构建在节点本身上,或者直接由其他人下载。
IfNotPresent 如果工作节点上不存在镜像,则拉取该镜像。这确保了
镜像只在第一次需要时才被拉。
表6.5图片抓取策略列表
每次重新启动容器时也会应用镜像拉取策略,因此需要仔细查看。检查下图来理解这三个策略的行为。

图6.11三种不同的图片抓取策略概述
警告 如果imagePullPolicy设置为Always并且镜像注册中心处于脱机状态,则容器将不会运行,即使已经在本地存储了相同的映像。因此,不可用的注册中心可能会阻止应用程序(重新)启动。
运行容器
当第一个容器镜像下载到节点时,容器就启动了。当第一个init容器完成后,将拉取下一个init容器的镜像并启动容器。这个过程一直重复,直到所有的init容器都成功完成。失败的容器可能会重新启动,如下图所示。

图6.12所有init容器必须运行到完成后,才能启动常规容器
重新启动失败的init容器
如果init容器因错误而终止,并且pod的重启策略设置为Always或OnFailure,则失败的init容器会被重启。如果策略设置为Never,则后续的init容器和pod的常规容器永远不会启动。pod的状态显示为Init:Error无限期。然后必须删除并重新创建pod对象以重新启动应用程序。有关此类pod的示例,请参见fortune-init-failnorestart。本书代码归档中的Yaml文件。
注意 如果需要重新启动容器,并且imagePullPolicy设置为Always,则会再次拉取容器镜像。如果容器由于错误而终止,并且你推送了一个具有修复错误的相同标记的新镜像,则不需要重新创建pod,因为更新的容器映像将在容器重新启动之前被拉出。
重新执行pod的init容器
Init容器通常只执行一次。即使稍后终止了pod的一个主容器,也不会重新执行pod的init容器。但是,在特殊情况下,例如当Kubernetes必须重新启动整个pod时,pod的init容器可能会再次执行。这意味着init容器执行的操作必须是幂等的。
6.4.2 了解运行阶段
当所有init容器都成功完成后,pod的常规容器都将并行创建。理论上,每个容器的生命周期应该独立于pod中的其他容器,但事实并非如此。有关更多信息,请参见侧栏。
容器的post-start钩子会阻塞下一个容器的创建
Kubelet不会同时启动pod的所有容器。它按照pod规范中定义的顺序同步创建和启动容器。如果为容器定义了post-start钩子,它就会与主容器进程异步运行,但是post-start钩子处理程序的执行会阻塞后续容器的创建和启动。
这是将来可能会更改的实现细节。
相反,容器的终止是并行执行的。长时间运行的pre-stop钩子会阻止定义它的容器的关闭,但不会阻止其他容器的关闭。容器的pre-stop钩子都是同时调用的。
下面的序列对每个容器独立运行。首先,提取容器镜像,并启动容器。当容器终止时,如果pod的重启策略中提供了这一点,它将被重新启动。容器将继续运行,直到启动pod终止。下面将更详细地解释这个顺序。
拉取容器镜像
在创建容器之前,按照pod的imagePullpolicy从镜像注册中心提取它的镜像。一旦提取了镜像,就创建了容器。
注意 如果其中一个容器镜像不能被拉出,其他容器无论如何都会运行。
警告 容器不一定在同一时刻启动。如果拉取镜像需要很长时间,那么在所有其他容器都已经启动之后,容器可能会正常启动。如果一个容器依赖于另一个容器,请记住这一点。
运行容器
当主容器进程启动时,容器启动。如果在容器中定义了post-start钩子,它将与主容器进程并行调用。Post-start钩子异步运行,并且必须成功,容器才能继续运行。
与主容器和潜在的post-start钩子进程一起,启动探针(如果为容器定义)被启动。当启动探针成功或未配置启动探针时,启动活动探针。
在失败时终止并重新启动容器
如果启动或活性探针失败的次数太多,以至于达到了配置的失败阈值,那么容器将被终止。与init容器一样,pod的restartPolicy决定是否重新启动容器。
也许令人惊讶的是,如果将重启策略设置为Never并且启动钩子失败,则pod的状态显示为Completed,即使post-start钩子失败。你可以看到这一点通过在fortune-poststart-fail-norestart.yaml文件中创建pod,
引入终止宽限期
如果必须终止容器,则调用容器的pre-stop钩子,以便应用程序可以正常关闭。当pre-stop钩子完成时,或者如果没有定义pre-stop钩子,TERM信号被发送到主容器进程。这是应用程序应该关闭的另一个提示。
应用程序有一定的终止时间。这个时间可以使用pod规范中的terminationGracePeriodSeconds字段进行配置,默认为30秒。定时器在pre-stop钩子被调用时启动,或者在没有定义钩子的情况下发送TERM信号时启动。如果进程在终止宽限期结束后仍在运行,则通过KILL信号强制终止进程。这将终止容器。

图6.13容器的终止顺序
在容器终止之后,如果pod的重启策略允许的话,它将被重新启动。如果没有,容器将保持在Terminated状态,但其他容器将继续运行,直到整个pod关闭或它们也失败。
6.4.3 理解终止阶段
pod的容器将继续运行,直到最终删除pod对象。当发生这种情况时,将启动终止pod中的所有容器,并将其状态更改为终止。
介绍删除宽限期
每个容器在pod关闭时的终止顺序与由于活性探测失败而终止容器时的顺序相同,只是pod的删除宽限期决定了容器有多少时间可以自行关闭,而不是终止宽限期。
这个宽限期是在pod的metadata.deletionGracePeriodSeconds字段中定义的,该字段在删除pod时初始化。默认情况下,它从spec.terminationGracePeriodSeconds字段获取其值,但是你可以在kubectl delete命令中指定不同的值。稍后你将看到如何做到这一点。
了解pod的容器是如何被终止的
如下图所示,pod的容器是并行终止的。对于每个pod的容器,将调用容器的pre-stop钩子,然后将TERM信号发送给主容器进程,最后,如果删除宽限期在进程自己停止之前到期,则使用KILL信号终止进程。在pod中的所有容器停止运行后,将删除pod对象。

图6.14 pod内部的终止序列
检查缓慢关闭的pod
让我们在你之前创建的一个pod上看看pod生命的最后阶段。如果kubia-ssl pod没有在集群中运行,请重新创建它。现在通过运行kubectl delete kubia-ssl删除这个pod。
删除pod要花很长时间,不是吗?我至少数了30秒。这既不正常,也不可接受,所以让我们来解决它。
考虑到你在本节中学到的内容,你可能已经知道是什么导致pod需要这么长时间才能完成。如果不是,让我来帮你分析一下情况。
kubia-ssl pod有两个容器。在删除pod对象之前,两者都必须停止。两个容器都没有定义pre-stop钩子,当你删除pod的时候,两个容器应该马上收到TERM信号。我前面提到的30秒与默认的终止宽限期值相匹配,因此看起来其中一个容器(如果不是两个容器)在接收到TERM信号时不会停止,并且在宽限期到期后被终止。
修改终止宽限期
你可以将pod的terminationGracePeriodSeconds字段设置为一个较低的值,如下面的清单所示,并查看pod是否关闭得更快。

#A 这个pod的容器在收到TERM信号后有5秒的时间终止,否则它们将被杀死
建议 很少需要缩短终止宽限期。但是,如果应用程序通常需要更多时间才能正常关闭,则建议扩展它。
指定删除pod时的删除宽限期
每当你删除一个pod时,pod的terminationGracePeriodSeconds决定了该pod的关闭时间,但是当你使用—grace-period命令行选项执行kubectl delete命令时,你可以覆盖这个时间。
例如,要关闭pod,可以运行以下命令:
![]()
注意 如果将此宽限期设置为零,则不会执行pod的pre-stop钩子。
修复kubia应用程序的关闭行为
考虑到宽限期的缩短导致pod关闭的速度更快,很明显,两个容器中至少有一个在收到TERM信号后不会自行终止。要查看是哪一个,请重新创建pod,然后在再次删除pod之前运行以下命令以流式传输每个容器的日志:

日志显示Envoy代理捕获了信号并立即终止,而Node.js应用程序似乎没有响应该信号。要解决这个问题,需要将下面清单中所示的代码添加到app.js文件的末尾。你可以在本书的代码存档中的Chapter06/kubia-v2-image目录中找到修改后的文件。

在对代码进行更改之后,创建一个标记为:1.1的新容器镜像,将其推送到镜像注册中心,并部署一个使用新镜像的新pod。如果你不想创建镜像,你也可以使用Docker Hub上发布的luksa/kubia:1.1镜像。要创建pod,请应用kubia-ssl-v1-l.yaml文件,你可以在本书的代码存档中找到它。
如果你删除这个新的pod,你会发现它关闭得快得多。从kubia容器的日志中可以看到,它一接收到TERM信号就开始关闭。
建议 你应该确保你的init容器也处理TERM信号,以便在初始化时删除pod对象时它们立即关闭。
6.4.4可视化pod容器的整个生命周期
为了结束这一章关于pod中发生的事情,我对pod生命中发生的一切进行了最后的概述。下面两个图概括了本章所解释的一切。pod的初始化如下图所示。

图6.15 pod初始化阶段的完整概述
初始化完成后,pod容器的正常操作开始。如下图所示。

图6.16 pod运行全景图
6.5总结
在本章中,你学到了:
- pod的状态包含有关pod的阶段信息,其条件和每个容器的状态的信息。你可以通过运行kubectl describe命令或使用kubectl get -o yaml命令检索完整的pod清单来查看状态。
- 根据pod的重启策略,它的容器可以在终止后重新启动。实际上,容器永远不会真正重新启动。相反,旧容器将被销毁,并在其位置创建一个新容器。
- 如果容器反复终止,则在每次重新启动之前插入一个指数增长的延迟。第一次重启没有延迟,然后延迟为10秒,然后在每次重启之前延迟加倍。最大延迟为5分钟,当容器正常运行至少两次时,延迟被重置为零。
- 每次尝试下载容器映像失败后,还会出现指数级增长的延迟。
- 向容器添加活性探针可确保在容器停止响应时重新启动它。活性探针通过HTTP的GET请求检查应用程序的状态,通过在容器中执行命令或打开到容器的一个网络端口TCP连接。
- 如果应用程序需要很长时间才能启动,那么可以使用比活性探针中的设置更宽松的设置来定义启动探针,以防止容器过早重启。
- 你可以为每个pod的主容器定义生命周期钩子。Post-start钩子在容器启动时调用,而pre-stop钩子在容器必须关闭时调用。生命周期钩子被配置为发送HTTP GET请求或在容器内执行命令。
- 如果在容器中定义了pre-stop钩子,并且容器必须终止,则首先调用该钩子。然后将TERM信号发送到容器中的主进程。如果进程在终止序列开始后的terminationGracePeriodSeconds时间内没有停止,则进程将被杀死。
- 当你删除一个pod对象时,它的所有容器将被并行终止。pod的deletionGracePeriodSeconds是给容器关闭的时间。默认情况下,它被设置为终止宽限期,但是可以使用kubectl delete命令覆盖它。
- 如果关闭一个pod需要很长时间,很可能是其中运行的一个进程没有处理TERM信号。添加一个TERM信号处理程序是比缩短终止或删除宽限期更好的解决方案。
现在你了解了关于在pod中操作容器的所有内容。在下一章中,你将了解pod的另一个重要组成部分——存储卷。
更多推荐



所有评论(0)