前言

在Kubernetes 日常维护中,都会出现各种业务需求,比如当一个Deployment Pod 资源类型启动之后,我还需要让它在业务容器生命周期中,执行一些自定义任务内容,那么该如何满足这一需求呢?这时候就引入一个概念Pod Hook(钩子)。本篇将会结合实际生产案例,带大家了解一下Pod Hook独特魅力

Pod Hook类型

Init Containers Hook

对于初始化容器钩子,它们在Pod中所有容器Running之前运行,这意味着初始化容器钩子将在Pod中的任何其它容器的ENTRYONINT之前执行,允许执行在正式容器启动之前执行一些额外的逻辑,例如初始化数据,创建配置文件等,初始化容器钩子可以用来确保Pod的各个容器都具备运行所需要的环境和资源

Contrainer Lifecycle Hook

Pod Hook是由Kubelet发起的,当一个容器进程启动后或者终止前运行,这是存在于容器生命周期中,我们可以根据业务需求来为Pod配置Hook

Kubernetes为我们提供了两种钩子函数:

PostStart(容器启动后): 这个钩子在容器创建后开始执行设设定的逻辑,例如注册服务,健康检测,或者执行脚本任务等等

PreStop(容器停止前):在容器停止之前执行所设定的逻辑,例如保存数据,清理资源等等。容器生命周期钩子可以用于实现一些容器级别的操作,例如与外部服务交互、监控、日志记录等等,

无论是初始化容器钩子,还是容器生命周期钩子,它们都是可以通过定义在Pod的Spec字段中的initContainersContainers 属性中的lifecycle字段来配置,使用这些钩子可以在Pod的不同阶段执行自定义逻辑,以满足特定的需求和业务场景

 背景需求

某个业务场景需要在Pod生命周期中,添加一个自定义Shell脚本任务,该脚本是一个循环执行的内容,为了复现问题,先给出一个任务脚本,如下:

该脚本会每间隔2s就会创建一个以数字为前缀的txt文件,且是不断递增创建新文件

#!/bin/bash
count=1
while true
do
    filename="$count.txt"
    touch "/usr/share/nginx/html/$filename"
    echo "Created $filename"
    count=$((count + 1))
    sleep 2
done

我们以ConfigMap的形式创建并挂载

kind: ConfigMap
apiVersion: v1
metadata:
  name: filebash
  namespace: middleware
  annotations:
    kubesphere.io/creator: admin
data:
  file.sh: |-
    #!/bin/bash
    count=1
    while true
    do
        filename="b$count.txt"
        touch "/usr/share/nginx/html/$filename"
        echo "Created $filename"
        count=$((count + 1))
        sleep 2
    done

记一次因Pod钩子引起Pod 持续Containercreating事件_执行上下文

此时你会发现一个问题,最新Pod状态一直处于ContainerCreating,一直持续了半个小时仍然没有变成Running状态

记一次因Pod钩子引起Pod 持续Containercreating事件_Kubernetes_02

常见原因

那么综上所述,通常这个情况,导致Pod一直处于ContainerCreating状态,其中肯定是有一些问题阻止了容器的成功创建,常见的原因包括:

  • 镜像拉取问题

这是最常见的问题之一,可能指定的镜像不存在,镜像名称拼写有误或者网络问题导致镜像无法响应等,可使用kubectl describe pod <pod name>查看镜像拉取的相关问题

  • 资源不足

如果pod所调度的节点内存或者CPU资源不足,也会导致容器无法正常创建,那么Pod就会一直处于ContainerCreating,需检查集群的资源(cpu、内存)是否足以启动Pod中的容器,可以使用kubectl describe node命令查看所调度的节点使用情况

#kubectl describe node
  • 网络问题

kubernetes集群网络一般都依赖于CNI(容器网络接口)插件出现了问题,可能会导致容器重建,确保Pod能够与Kubernetes集群网络正常通信,

#kubectl exec -it nginx-669d46686d-scfmk -n middleware bash
  • Docker运行出现问题

如果Docker守护进程或者容器运行的问题,可能会阻止容器的创建

  • 持久卷问题

Pod依赖于一个持久卷声明(PVC)并且该PVC不可用或因为某些原因无法挂载,那么Pod将保持在“ControllerCreating”状态

  • pod上下文问题

如果一个pod或者容器的安全上下文没有正确配置(例如,Pod试图以一个不存在的用户身份运行),它会阻止启动

问题排查

尝试用kubectl describe pod命令来检查pod的事件,看是否出现异常关键信息

# kubectl describe  pod nginx-669d46686d-vk6k2 -n middleware

记一次因Pod钩子引起Pod 持续Containercreating事件_Kubernetes_03

很显然没有任何异常信息,随后我检查了pod网络是否异常,发现网络也正常

记一次因Pod钩子引起Pod 持续Containercreating事件_执行上下文_04

经过一系列排查,看来都不是因为简单的异常导致的,冷静下来进行思索...羊毛出在羊身上。

问题定位

为什么之前引用shell脚本作为Pod hook,Pod都正常创建呢,而这次不行呢?这时候大概率可以是任务脚本内容本身的问题,随后我们把目光锁定在脚本内容上。我发现Pod hook脚本仍在执行,于是乎便开始思考:PostStart钩子它是在Pod状态Running之前运行还是Running之后在执行Pod hook呢?

记一次因Pod钩子引起Pod 持续Containercreating事件_Kubernetes_05

由上图可以发现,Pod hook任务正在执行,但是Pod状态仍然是ContainerCreateing,, 由此可见,postStart钩子是在容器状态就绪之前就已执行,因为脚本是无限循环创建文件的状态,不会停止,假设状态不停止,那么Pod 一直就会处于ContainerCreateing 。由此可得出结论 正常需要在钩子完成之后才能继续进行容器的创建和启动,而无限循环的脚本会一直占用钩子的执行上下文,导致钩子无法完成

拓展知识点

上面说到Pod执行上下文,那么什么是Pod执行上下文呢?

在Kubernetes中,Pod的执行上下文(Excution Context )指的是容器运行时所处的环境和条件,这个上下文包括了容器运行所需的各种资源、配置和运行状态等信息。Pod的执行上下文包括以下方面

资源限制和需求:

指定了Pod中每个容器的CPU、内存等资源的限制和需求。这些限制和需求会影响容器在节点上的调度和运行。

网络配置:

指定了Pod的网络配置,包括网络插件、IP地址、端口映射等。这些配置决定了容器之间以及容器与外部网络的通信方式。

存储卷:

指定了Pod中容器的存储卷配置,包括持久卷、临时卷等。这些存储卷可以用于容器之间的数据共享或持久化存储。

环境变量和配置:

指定了容器的环境变量和配置文件等信息。这些信息可以影响容器内部的应用程序的行为和配置。

生命周期钩子:

指定了容器的生命周期钩子,包括postStartpreStop等。这些钩子会在容器启动前和停止后执行特定的操作。

总结

Pod的执行上下文在Pod创建和调度过程中起着重要的作用。它会影响容器的启动、运行和终止过程,确保容器能够在正确的环境下运行,并满足其所需的资源和配置要求。

问题解决

定位了问题所在,那么想要解决该问题也就好办了,于是乎,我这里给罗列出很多解决办法,可供大家参考

修改钩子脚本

将无限循环的脚本改为执行完所需操作后自动退出,以释放钩子的执行上下文。例如,在脚本中添加适当的条件判断,当满足条件时跳出循环或执行退出命令。

使用其他机制

如果需要在容器启动后执行长时间运行的任务,可以考虑使用Kubernetes中的其他机制,如Init Containers或Sidecar Containers。这些机制可以在容器启动后运行额外的容器来执行需要长时间运行的任务,而不会阻塞主容器的启动。

调整重启策略

如果无法修改钩子脚本或使用其他机制,可以考虑调整Pod的重启策略。将重启策略设置为Never,这样当钩子脚本无法完成时,Pod将不会被终止和重启,而是保持在ContainerCreating状态。但请注意,这可能会导致其他问题,如容器无法正常启动或无法自动恢复。 无论哪种方法,都建议在修改Pod的配置之前,先进行测试和验证,确保修改后的配置能够正常运行,并且不会引入其他问题。

脚本后台运行

将脚本设定为后台执行,可以通过nohup将脚本放入后台执行,此时就不会占用前台的上下文环境。

在这里,我们选择将脚本通过nohup置于后台运行

lifecycle:
   postStart:
     exec:
       command:
         - /bin/sh
         - '-c'
         - ' nohup sh /opt/file.sh >/tmp/file.log &'

记一次因Pod钩子引起Pod 持续Containercreating事件_Pod_06

将脚本置于后台运行之后,可以发现,脚本仍然正常执行,并且Pod running正常

记一次因Pod钩子引起Pod 持续Containercreating事件_生命周期_07

记一次因Pod钩子引起Pod 持续Containercreating事件_Kubernetes_08

END!