likes
comments
collection
share

用preStop回调函数删除job运行产生的临时文件

作者站长头像
站长
· 阅读数 12

需求描述

我需要运行一个job来跑一个一次性的任务,job运行结束后,需要删除job。此外在job运行的过程中,会在节点上生成许多的临时文件,为了避免临时文件过多占用有限的磁盘空间,因此我还需要在job结束前清理掉这些临时文件。

需求分析

通过上述的描述,需求难点在于删除job之前通过何种方式把在节点上生成的临时文件给清理掉。

我最开始的想法是在job运行的脚本里添加删除文件的代码,类似这样:

apiVersion: batch/v1
kind: Job
metadata:
  name: job-worker
spec:
  template:
    spec:
      containers:
        - name: busybox
          image: busybox:stable
          imagePullPolicy: Always
          command:
            - bash
            - "-c"
            - |
              set -x
              mv test /test-package/
              echo 'Hello job-worker' > /test-package/test.txt
              sleep 360000000
              rm -rf /test/*
          volumeMounts:
            - name: package
              mountPath: /test-package
      volumes:
        - name: package
          hostPath:
            path: /test-package/143319e8-5061-4bd7-b926-59efe1d85350-all/package
            type: DirectoryOrCreate
      restartPolicy: Never
  backoffLimit: 1
  activeDeadlineSeconds: 2400

但是这样会存在一个问题,如果job异常,或者job被人为地删掉了,那临时文件就不会被清理掉了,还是会残留在节点的磁盘上。

于是我又想了另外一个方案,就是新起一个协程在job被删除后去到节点上把这些临时文件给删掉,但这个方案存在更多的问题,比如说job拉起的pod每次调度到的节点都是不一样的,该如何找到这个节点呢?过于麻烦了,于是这个方案又被我否决了。

经过一番调研之后,我发现preStop回调函数很适合这个场景,最终决定使用preStop回调函数来做,那什么是preStop函数呢?

preStop介绍

Kubernetes supports the postStart and preStop events. Kubernetes sends the postStart event immediately after a Container is started, and it sends the preStop event immediately before the Container is terminated. A Container may specify one handler per event.

Kubernetes 支持 postStart 和 preStop 事件。 当一个容器启动后,Kubernetes 将立即发送 postStart 事件;在容器被终结之前, Kubernetes 将发送一个 preStop 事件。容器可以为每个事件指定一个处理程序。

postStart和preStop的区别

k8s支持postStart和preStop两种类型的回调函数,那这俩货有啥区别呢?

Kubernetes sends the postStart event immediately after the Container is created. There is no guarantee, however, that the postStart handler is called before the Container's entrypoint is called. The postStart handler runs asynchronously relative to the Container's code, but Kubernetes' management of the container blocks until the postStart handler completes. The Container's status is not set to RUNNING until the postStart handler completes.

Kubernetes sends the preStop event immediately before the Container is terminated. Kubernetes' management of the Container blocks until the preStop handler completes, unless the Pod's grace period expires.

PostStart

这个回调在容器被创建之后立即被执行。 但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。 没有参数传递给处理程序。

PreStop

在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等) 而被终止之前,此回调会被调用。 如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。 在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。 Pod 的终止宽限周期在 PreStop 回调被执行之前即开始计数, 所以无论回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。 没有参数会被传递给处理程序。

简单来说就是,postStart就是容器创建之后就立马执行的,只有postStart函数执行完了,容器的状态才会变成Running。preStop则刚好相反,是在容器结束前执行的,只有preStop函数执行完了,容器才会被Termineted。

preStop的使用方式

postStart和preStop的使用方式一致,所以就以preStop为例。根据需求分析,我们来改造先前的job,通过增加preStop函数,在容器结束之前,把产生的临时文件给删掉。

回调函数的使用方式有两种,一种是exec,用于在容器内执行特定命令;另一种是http,用于请求容器的某个端口。因为我们是通过挂载的方式把文件挂载到节点上,是一个本地的操作,所以使用exec即可。

apiVersion: batch/v1
kind: Job
metadata:
  name: job-worker
spec:
  template:
    spec:
      containers:
        - name: busybox
          image: busybox:stable
          imagePullPolicy: Always
          lifecycle:
            preStop:
              exec:
                command:
                  - bash
                  - "-c"
                  - rm -rf /cadm-package/*
          command:
            - bash
            - "-c"
            - |
              set -x
              mv test /test-package/
              echo 'Hello job-worker' > /test-package/test.txt
              sleep 360000000
          volumeMounts:
            - name: package
              mountPath: /test-package
      volumes:
        - name: package
          hostPath:
            path: /test-package/143319e8-5061-4bd7-b926-59efe1d85350-all/package
            type: DirectoryOrCreate
      restartPolicy: Never
  backoffLimit: 1
  activeDeadlineSeconds: 2400

需要注意的是,PreStop 回调并不会与停止容器的信号处理程序异步执行;回调必须在可以发送信号之前完成执行。 如果 PreStop 回调在执行期间停滞不前,Pod 的阶段会变成 Terminating并且一直处于该状态, 直到其 terminationGracePeriodSeconds 耗尽为止,这时 Pod 会被杀死。 这一宽限期是针对 PreStop 回调的执行时间及容器正常停止时间的总和而言的。

client-go 删除job时的一个坑

踩坑场景

这里记录一下我踩的一个坑。

在我创建完job,并且job能够成功运行之后,我想测试preStop是否生效。于是我就调用client-go去delete job,代码是这么写的:

// job-worker是一个job对象
kubeclient.Delete(ctx, job-worker)

但是调用接口去删除job,发现job虽然能够顺利删除,但是在节点上生成的临时文件却被没有清理。于是我就怀疑是preStop没有生效。我就尝试通过命令行的方式来删除job:

kubectl delete job-worker

此时我发现job被成功删除了,临时文件也被清理掉了。这时我就有点百思不得其解了,按照道理来说,通过client-go去删除job和kubectl delete去删除job本质上是一样的,都是通过去调k8s的api去删除资源,没有道理结果不一致。

一顿排查下来发现,是因为我用client-go去删的时候,只是删了job,但 pod 没有删,所以就没有触发preStop函数,自然就没有清理临时文件,但是使用kubectl delete去删除job的时候,却是把job和pod一同删去的

解决方式

遇事不决,就问Google。谷歌上一搜,果然就发现GitHub上有一位老哥就遇到相同的问题:

用preStop回调函数删除job运行产生的临时文件

大概翻译过来,就是这老哥在问,为啥我用client-go去删除job的时候把pod给漏掉,是啥问题呢?下面就有大兄弟说,啊你这可以在代码里加一个propagationPolicy,指定级联删除策略。然后这老哥也很热心地把代码里的注释贴了出来:

用preStop回调函数删除job运行产生的临时文件

PropagationPolicy分为三类,分别是Orphan(孤儿),Background(后台级联删除)和Foreground(前台级联删除),三者的区别是:

  • Orphan(孤儿):只删除资源本身,不删除依赖的资源对象
  • Background(后台级联删除):立即删除资源本身,后台清理所有的依赖对象,kubectl默认指定的级联删除策略就是Background
  • Foreground(前台级联删除):先清理所有的依赖对象,最后再删除资源本身。

最后,代码改动如下:

deleteOption := client.PropagationPolicy(metav1.DeletePropagationBackground)
// job-worker是一个job对象
kubeclient.Delete(ctx, job-worker,deleteOption...)

参考文档

容器生命周期回调

为容器的生命周期事件设置处理函数

Delete K8S Job leaks the pods · Issue #495 · kubernetes/client-go

垃圾收集