作者:风敬
本文篇幅超过 7 千字,通读全文大略须要 20 分钟。文章内容源自大量实在场景的积淀和剖析,倡议珍藏,以供查阅。
在 K8s 中,Pod 作为工作负载的运行载体,是最为外围的一个资源对象。Pod 具备简单的生命周期,在其生命周期的每一个阶段,可能产生多种不同的异常情况。K8s 作为一个简单零碎,异样诊断往往要求弱小的常识和教训储备。联合实战经验以及 EDAS 用户实在场景的演绎,咱们总结了 K8s Pod 的 13 种常见异样场景,给出各个场景的常见谬误状态,剖析其起因和排查思路。
Pod 生命周期
在整个生命周期中,Pod 会呈现 5 种阶段(Phase)。
- Pending:Pod 被 K8s 创立进去后,起始于 Pending 阶段。在 Pending 阶段,Pod 将通过调度,被调配至指标节点开始拉取镜像、加载依赖项、创立容器。
- Running:当 Pod 所有容器都已被创立,且至多一个容器曾经在运行中,Pod 将进入 Running 阶段。
- Succeeded:当 Pod 中的所有容器都执行实现后终止,并且不会再重启,Pod 将进入 Succeeded 阶段。
- Failed:若 Pod 中的所有容器都已终止,并且至多有一个容器是因为失败终止,也就是说容器以非 0 状态异样退出或被零碎终止,Pod 将进入 Failed 阶段。
- Unkonwn:因为某些起因无奈获得 Pod 状态,这种状况 Pod 将被置为 Unkonwn 状态。
一般来说,对于 Job 类型的负载,Pod 在胜利执行完工作之后将会以 Succeeded 状态为终态。而对于 Deployment 等负载,个别冀望 Pod 可能继续提供服务,直到 Pod 因删除隐没,或者因异样退出 / 被零碎终止而进入 Failed 阶段。
Pod 的 5 个阶段是 Pod 在其生命周期中所处地位的简略宏观概述,并不是对容器或 Pod 状态的综合汇总。Pod 有一些细分状态(PodConditions),例如 Ready/NotReady、Initialized、PodScheduled/Unschedulable 等。这些细分状态形容造成 Pod 所处阶段的具体成因是什么。比方,Pod 以后阶段是 Pending,对应的细分状态是 Unschedulable,这就意味着 Pod 调度呈现了问题。
容器也有其生命周期状态(State):Waiting、Running 和 Terminated。并且也有其对应的状态起因(Reason),例如 ContainerCreating、Error、OOMKilled、CrashLoopBackOff、Completed 等。而对于产生过重启或终止的容器,上一个状态(LastState)字段不仅蕴含状态起因,还蕴含上一次退出的状态码(Exit Code)。例如容器上一次退出状态码是 137,状态起因是 OOMKilled,阐明容器是因为 OOM 被零碎强行终止。在异样诊断过程中,容器的退出状态是至关重要的信息。
除了必要的集群和利用监控,个别还须要通过 kubectl 命令收集异样状态信息。
// 获取 Pod 以后对象形容文件
kubectl get po <podName> -n <namespace> -o yaml
// 获取 Pod 信息和事件(Events)kubectl describe pod <podName> -n <namespace>
// 获取 Pod 容器日志
kubectl logs <podName> <containerName> -n <namespace>
// 在容器中执行命令
kubectl exec <podName> -n <namespace> -c <containerName> -- <CMD> <ARGS>
Pod 异样场景
Pod 在其生命周期的许多工夫点可能产生不同的异样,依照 Pod 容器是否运行为标志点,咱们将异样场景大抵分为两类:
- 在 Pod 进行调度并创立容器过程中产生异样,此时 Pod 将卡在 Pending 阶段。
- Pod 容器运行中产生异样,此时 Pod 依照具体场景处在不同阶段。
下文将对这具体的 13 种场景进行形容和剖析。
调度失败
常见谬误状态:Unschedulable
Pod 被创立后进入调度阶段,K8s 调度器根据 Pod 申明的资源申请量和调度规定,为 Pod 筛选一个适宜运行的节点。当集群节点均不满足 Pod 调度需要时,Pod 将会处于 Pending 状态。造成调度失败的典型起因如下:
- 节点资源有余
K8s 将节点资源(CPU、内存、磁盘等)进行数值量化,定义出节点资源容量(Capacity)和节点资源可调配额(Allocatable)。资源容量是指 Kubelet 获取的计算节点以后的资源信息,而资源可调配额是 Pod 可用的资源。Pod 容器有两种资源额度概念:申请值 Request 和限度值 Limit,容器至多能获取申请值大小、至少能获取限度值的资源量。Pod 的资源申请量是 Pod 中所有容器的资源申请之和,Pod 的资源限度量是 Pod 中所有容器的资源限度之和。K8s 默认调度器依照较小的申请值作为调度根据,保障可调度节点的资源可调配额肯定不小于 Pod 资源申请值。当集群没有一个节点满足 Pod 的资源申请量,则 Pod 将卡在 Pending 状态。
Pod 因为无奈满足资源需要而被 Pending,可能是因为集群资源有余,须要进行扩容,也有可能是集群碎片导致。以一个典型场景为例,用户集群有 10 几个 4c8g 的节点,整个集群资源使用率在 60% 左右,每个节点都有碎片,但因为碎片太小导致扩不进去一个 2c4g 的 Pod。一般来说,小节点集群会更容易产生资源碎片,而碎片资源无奈供 Pod 调度应用。如果想最大限度地缩小资源节约,应用更大的节点可能会带来更好的后果。
- 超过 Namespace 资源配额
K8s 用户能够通过资源配额(Resource Quota)对 Namespace 进行资源使用量限度,包含两个维度:
- 限定某个对象类型(如 Pod)可创建对象的总数。
- 限定某个对象类型可耗费的资源总数。
如果在创立或更新 Pod 时申请的资源超过了资源配额,则 Pod 将调度失败。此时须要查看 Namespace 资源配额状态,做出适当调整。
- 不满足 NodeSelector 节点选择器
Pod 通过 NodeSelector 节点选择器指定调度到带有特定 Label 的节点,若不存在满足 NodeSelector 的可用节点,Pod 将无奈被调度,须要对 NodeSelector 或节点 Label 进行正当调整。
- 不满足亲和性
节点亲和性(Affinity)和反亲和性(Anti-Affinity)用于束缚 Pod 调度到哪些节点,而亲和性又细分为软亲和(Preferred)和硬亲和(Required)。对于软亲和规定,K8s 调度器会尝试寻找满足对应规定的节点,如果找不到匹配的节点,调度器依然会调度该 Pod。而当硬亲和规定不被满足时,Pod 将无奈被调度,须要查看 Pod 调度规定和指标节点状态,对调度规定或节点进行正当调整。
- 节点存在污点
K8s 提供污点(Taints)和容忍(Tolerations)机制,用于防止 Pod 被调配到不适合的节点上。如果节点上存在污点,而 Pod 没有设置相应的容忍,Pod 将不会调度到该 节点。此时须要确认节点是否有携带污点的必要,如果不必要的话能够移除污点;若 Pod 能够调配到带有污点的节点,则能够给 Pod 减少污点容忍。
- 没有可用节点
节点可能会因为资源有余、网络不通、Kubelet 未就绪等起因导致不可用(NotReady)。当集群中没有可调度的节点,也会导致 Pod 卡在 Pending 状态。此时须要查看节点状态,排查不可用节点问题并修复,或进行集群扩容。
镜像拉取失败
常见谬误状态:ImagePullBackOff
Pod 通过调度后调配到指标节点,节点须要拉取 Pod 所需的镜像为创立容器做筹备。拉取镜像阶段可能存在以下几种起因导致失败:
- 镜像名字拼写错误或配置了谬误的镜像
呈现镜像拉取失败后首先要确认镜像地址是否配置谬误。
- 公有仓库的免密配置谬误
集群须要进行免密配置能力拉取公有镜像。自建镜像仓库时须要在集群创立免密凭证 Secret,在 Pod 指定 ImagePullSecrets,或者将 Secret 嵌入 ServicAccount,让 Pod 应用对应的 ServiceAccount。而对于 acr 等镜像服务云产品个别会提供免密插件,须要在集群中正确装置免密插件能力拉取仓库内的镜像。免密插件的异样包含:集群免密插件未装置、免密插件 Pod 异样、免密插件配置谬误,须要查看相干信息进行进一步排查。
- 网络不通
网络不通的常见场景有三个:
- 集群通过公网拜访镜像仓库,而镜像仓库未配置公网的拜访策略。对于自建仓库,可能是端口未凋谢,或是镜像服务未监听公网 IP;对于 acr 等镜像服务云产品,须要确认开启公网的拜访入口,配置白名单等拜访控制策略。
- 集群位于专有网络,须要为镜像服务配置专有网络的访问控制,能力建设集群节点与镜像服务之间的连贯。
- 拉取海内镜像例如 gcr.io 仓库镜像,需配置镜像减速服务。
- 镜像拉取超时
常见于带宽有余或镜像体积太大,导致拉取超时。能够尝试在节点上手动拉取镜像,察看传输速率和传输工夫,必要时能够对集群带宽进行升配,或者适当调整 Kubelet 的 –image-pull-progress-deadline 和 –runtime-request-timeout 选项。
- 同时拉取多个镜像,触发并行度管制
常见于用户弹性扩容出一个节点,大量待调度 Pod 被同时调度下来,导致一个节点同时有大量 Pod 启动,同时从镜像仓库拉取多个镜像。而受限于集群带宽、镜像仓库服务稳定性、容器运行时镜像拉取并行度管制等因素,镜像拉取并不反对大量并行。这种状况能够手动打断一些镜像的拉取,依照优先级让镜像分批拉取。
依赖项谬误
常见谬误状态:Error
在 Pod 启动之前,Kubelet 将尝试查看与其余 K8s 元素的所有依赖关系。次要存在的依赖项有三种:PersistentVolume、ConfigMap 和 Secret。当这些依赖项不存在或者无奈读取时,Pod 容器将无奈失常创立,Pod 会处于 Pending 状态直到满足依赖性。当这些依赖项能被正确读取,但呈现配置谬误时,也会呈现无奈创立容器的状况。比方将一个只读的长久化存储卷 PersistentVolume 以可读写的模式挂载到容器,或者将存储卷挂载到 /proc 等非法门路,也会导致容器创立失败。
容器创立失败
常见谬误状态:Error
Pod 容器创立过程中呈现了谬误。常见起因包含:
- 违反集群的安全策略,比方违反了 PodSecurityPolicy 等。
- 容器无权操作集群内的资源,比方开启 RBAC 后,须要为 ServiceAccount 配置角色绑定。
- 短少启动命令,Pod 形容文件和镜像 Dockerfile 中均未指定启动命令。
- 启动命令配置谬误。Pod 配置文件能够通过 command 字段定义命令行,通过 args 字段给命令行定义参数。启动命令配置谬误的状况十分多见,要分外留神命令及参数的格局。正确的填写形式可参考:
初始化失败
常见谬误状态:CrashLoopBackOff
K8s 提供 Init Container 个性,用于在启动利用容器之前启动一个或多个初始化容器,实现应用程序所需的预置条件。Init container 与利用容器实质上是一样的,但它们是仅运行一次就完结的工作,并且必须在执行实现后,零碎能力继续执行下一个容器。如果 Pod 的 Init Container 执行失败,将会 block 业务容器的启动。通过查看 Pod 状态和事件定位到 Init Container 故障后,须要查看 Init Container 日志进一步排查故障点。
回调失败
常见谬误状态:FailedPostStartHook 或 FailedPreStopHook 事件
K8s 提供 PostStart 和 PreStop 两种容器生命周期回调,别离在容器中的过程启动前或者容器中的过程终止之前运行。PostStart 在容器创立之后立刻执行,但因为是异步执行,无奈保障和容器启动命令的执行程序相关联。如果 PostStart 或者 PreStop 回调程序执行失败,罕用于在容器完结前优雅地开释资源。如果 PostStart 或者 PreStop 回调程序执行失败失败,容器将被终止,依照重启策略决定是否重启。当呈现回调失败,会呈现 FailedPostStartHook 或 FailedPreStopHook 事件,进一步联合容器打出的日志进行故障排查。
就绪探针失败
常见谬误状态:容器曾经全副启动,然而 Pod 处于 NotReady 状态,服务流量无奈从 Service 达到 Pod
K8s 应用 Readiness Probe(就绪探针)来确定容器是否曾经就绪能够承受流量。只有当 Pod 中的容器都处于就绪状态时,K8s 才认定该 Pod 处于就绪状态,才会将服务流量转发到该容器。个别就绪探针失败分为几种状况:
- 容器内利用起因:健康检查所配置规定对应的端口或者脚本,无奈胜利探测,如容器内利用没失常启动等。
- 探针配置不当:写错查看端口导致探测失败;检测距离和失败阈值设置不合理,例如每次查看距离 1s,一次不通过即失败;启动提早设置太短,例如利用失常启动须要 15s,而设置容器启动 10s 后启用探针。
- 零碎层问题:节点负载高,导致容器过程 hang 住。
- CPU 资源有余:CPU 资源限度值过低,导致容器过程响应慢。
须要特地阐明的是,对于微服务利用,服务的注册和发现由注册核心治理,流量不会通过 Service,间接从上游 Pod 流到上游 Pod。然而注册核心并没有如 K8s 就绪探针的查看机制,对于启动较慢的 JAVA 利用来说,服务注册胜利后所需资源依然可能在初始化中,导致呈现上线后流量有损的状况。对于这一类场景,EDAS 提供提早注册和服务预热等解决方案,解决 K8s 微服务利用上线有损的问题。
存活探针失败
常见谬误状态:CrashLoopBackOff
K8s 应用 Liveness Probe(存活探针)来确定容器是否正在运行。如果存活态探测失败,则容器会被杀死,随之依照重启策略决定是否重启。存活探针失败的起因与就绪探针相似,然而存活探针失败后容器会被 kill 隐没,所以排障过程要辣手得多。一个典型的用户场景是,用户在压测期间通过 HPA 弹性扩容出多个新 Pod,然而新 Pod 一启动就被大流量阻塞,无奈响应存活探针,导致 Pod 被 kill。kill 后又重启,重启完又挂掉,始终在 Running 和 CrashLoopBackOff 状态中振荡。微服务场景下能够应用提早注册和服务预热等伎俩,防止刹时流量打挂容器。如果是程序自身问题导致运行阻塞,倡议先将 Liveness 探针移除,通过 Pod 启动后的监控和过程堆栈信息,找出流量涌入后过程阻塞的根因。
容器退出
常见谬误状态:CrashLoopBackOff
容器退出分为两种场景:
- 启动后立刻退出,可能起因是:
- 启动命令的门路未蕴含在环境变量 PATH 中。
- 启动命令援用了不存在的文件或目录。
- 启动命令执行失败,可能因为运行环境短少依赖,也可能是程序自身起因。
- 启动命令没有执行权限。
- 容器中没有前台过程。容器应该至多蕴含一个 long-running 的前台过程,不能后盾运行,比方通过 nohup 这种形式去启动过程,或是用 tomcat 的 startup.sh 脚本。
对于容器启动后立刻退出的状况,通常因为容器间接隐没,无奈获取其输入流日志,很难间接通过现场定位问题。一个繁难的排查形式是,通过设置非凡的启动命令卡住容器(比方应用 tail -f /dev/null),而后进到容器中手动执行命令看看后果,确认问题起因。
- 运行一段时间后退出,这种状况个别是容器内 1 过程 Crash 或者被零碎终止导致退出。此时首先查看容器退出状态码,而后进一步查看上下文信息进行谬误定位。这种状况产生时容器曾经删除隐没,无奈进入容器中查看日志和堆栈等现场信息,所以个别举荐用户对日志、谬误记录等文件配置长久化存储,留存更多现场信息。几种常见的状态码如下:
OOMKilled
常见谬误状态:OOMKilled
K8s 中有两种资源概念:可压缩资源(CPU)和不可压缩资源(内存,磁盘)。当 CPU 这种可压缩资源有余时,Pod 只会“饥饿”,但不会退出;而当内存和磁盘 IO 这种不可压缩资源有余时,Pod 会被 kill 或者驱赶。因为内存资源有余 / 超限所导致的 Pod 异样退出的景象被称为 Pod OOMKilled。K8s 存在两种导致 Pod OOMKilled 的场景:
- Container Limit Reached,容器内存用量超限
Pod 内的每一个容器都能够配置其内存资源限额,当容器理论占用的内存超额,该容器将被 OOMKilled 并以状态码 137 退出。OOMKilled 往往产生在 Pod 曾经失常运行一段时间后,可能是因为流量减少或是长期运行累积的内存逐步减少。这种状况须要查看程序日志以理解为什么 Pod 应用的内存超出了预期,是否出现异常行为。如果发现程序只是依照预期运行就产生了 OOM,就须要适当进步 Pod 内存限度值。一个很常见的谬误场景是,JAVA 容器设置了内存资源限度值 Limit,然而 JVM 堆大小限度值比内存 Limit 更大,导致过程在运行期间堆空间越开越大,最终因为 OOM 被终止。对于 JAVA 容器来说,个别倡议容器内存限度值 Limit 须要比 JVM 最大堆内存稍大一些。
- Limit Overcommit,节点内存耗尽
K8s 有两种资源额度概念:申请值 Request 和限度值 Limit,默认调度器依照较小的申请值作为调度根据,保障节点的所有 Pod 资源申请值总和不超过节点容量,而限度值总和容许超过节点容量,这就是 K8s 资源设计中的 Overcommit(超卖)景象。超卖设计在肯定水平上能进步吞吐量和资源利用率,但会呈现节点资源被耗尽的状况。当节点上的 Pod 理论应用的内存总和超过某个阈值,K8s 将会终止其中的一个或多个 Pod。为了尽量避免这种状况,倡议在创立 Pod 时抉择大小相等或相近的内存申请值和限度值,也能够利用调度规定将内存敏感型 Pod 打散到不同节点。
Pod 驱赶
常见谬误状态:Pod Evicted
当节点内存、磁盘这种不可压缩资源有余时,K8s 会依照 QoS 等级对节点上的某些 Pod 进行驱赶,开释资源保障节点可用性。当 Pod 产生驱赶后,下层控制器例如 Deployment 会新建 Pod 以维持正本数,新 Pod 会通过调度调配到其余节点创立运行。对于内存资源,前文曾经剖析过能够通过设置正当的申请值和限度值,防止节点内存耗尽。而对于磁盘资源,Pod 在运行期间会产生临时文件、日志,所以必须对 Pod 磁盘容量进行限度,否则某些 Pod 可能很快将磁盘写满。相似限度内存、CPU 用量的形式,在创立 Pod 时能够对本地长期存储用量(ephemeral-storage)进行限度。同时,Kubelet 驱赶条件默认磁盘可用空间在 10% 以下,能够调整云监控磁盘告警阈值以提前告警。
Pod 失联
常见谬误状态:Unkonwn
Pod 处于 Unkonwn 状态,无奈获取其详细信息,个别是因为所在节点 Kubelet 异样,无奈向 APIServer 上报 Pod 信息。首先查看节点状态,通过 Kubelet 和容器运行时的日志信息定位谬误,进行修复。如果无奈及时修复节点,能够先将该节点从集群中删除。
无奈被删除
常见谬误状态:卡在 Terminating
当一个 Pod 被执行删除操作后,却长时间处于 Terminating 状态,这种状况的起因有几种:
- Pod 关联的 finalizer 未实现。首先查看 Pod 的 metadata 字段是否蕴含 finalizer,通过一些特定上下文信息确认 finalizer 工作具体是什么,通常 finalizer 的工作未实现可能是因为与 Volume 相干。如果 finalizer 曾经无奈被实现,能够通过 patch 操作移除对应的 Pod 上的 finalizer 实现删除操作。
- Pod 对中断信号没有响应。Pod 没有被终止可能是过程对信号没有响应,能够尝试强制删除 Pod。
- 节点故障。通过查看雷同节点上的其余 Pod 状态确认是否节点故障,尝试重启 Kubelet 和容器运行时。如果无奈修复,先将该节点从集群中删除。
EDAS 排障工具链
EDAS 对利用全生命周期的大部分异样都有积淀和剖析,升高用户学习老本,缩短排障工夫。EDAS 提供一系列解决方案和工具帮忙用户解决利用生命周期中的异样问题,包含利用变更前的变更预检、利用变更和运行的事件追踪可观测、利用异样时的诊断工具。
利用变更预检
EDAS 在利用变更工作下发前将通过预检环节,利用变更预检能够在利用部署前查看集群状态及变更参数是否无效,可能无效防止利用变更过程出错,升高变更危险。以后利用变更预检提供集群可用资源查看、集群健康检查、各项依赖配置查看等我的项目,对于非预期的预检后果给出剖析和处理倡议。例如对于集群资源余量不满足 Pod 调度需要的异样场景,变更预检后果将显示资源查看不通过,用户可能第一工夫做出针对性调整。
利用事件观测
EDAS 对利用生命周期中的事件进行追踪提供可观测能力。对于利用变更过程提供残缺的事项展现,让用户可能白屏观测到变更中的每一个步骤和相干上下文信息。当出现异常变更状况时,将具体的事件和相干资源信息在白屏透出,并对异样事件进行剖析解读并给出操作倡议。例如给 Pod 配置了容器服务仓库镜像,但并未正确配置集群免密插件,EDAS 将镜像拉取失败事件抛出,并疏导用户查看镜像拉取权限。
诊断工具箱
对于异样 Pod,通常须要连贯到 Pod 容器,对业务过程进行诊断,必要时候还须要对异样进行复现。EDAS 提供云原生工具箱,让用户在网页上连贯 Pod 容器 Shell,并且提供 Arthas、Tcpdump 等工具,补救镜像软件工具包的缺失。对于 Pod 曾经隐没、不适宜在业务 Pod 进行诊断等场景,云原生工具箱提供 Pod 复制能力,依据诊断场景不同,用户能够按需抉择开启诊断 Pod。
对于上文中提到的容器过程被大流量阻塞,导致 Pod 被 Liveness 打挂的场景,用户能够通过云原生工具箱,开启一个移除 Liveness 的诊断 Pod,设置全链路流量管制规定,打入一些测试流量,应用 Arthas 提供的 trace、stack、watch 等工具精准定位问题
参考文档
- https://kubernetes.io/docs/co…
- Managing Compute Resources for Containers
- Configure Quality of Service for Pods – Kubernetes
- https://docs.docker.com/engin…
- https://developer.aliyun.com/…
- https://alibaba.github.io/arthas