使用命令行等待kubernetes任务完成,无论是失败还是成功

47

最佳的等待 Kubernetes 作业完成的方法是什么?我注意到很多建议使用:

kubectl wait --for=condition=complete job/myjob

但我认为这只有在工作成功时才有效。如果失败了,我就必须做类似这样的事情:

kubectl wait --for=condition=failed job/myjob

有没有办法使用wait同时等待两个条件?如果没有,那么等待作业成功或失败的最佳方法是什么?

5个回答

43

运行第一个等待条件作为子进程并捕获其PID。如果条件满足,此进程将以退出码0退出。

kubectl wait --for=condition=complete job/myjob &
completion_pid=$!

对于失败等待条件,按照相同的方式处理。这里的技巧是添加&& exit 1,以便在作业失败时子进程返回非零退出代码。

kubectl wait --for=condition=failed job/myjob && exit 1 &
failure_pid=$!

然后使用Bash内置的wait -n $PID1 $PID2等待其中一个条件成功。该命令将捕获首个退出的进程的退出代码:

Mac用户请注意,wait -n [...PID]需要Bash 4.3或更高版本。由于许可问题,MacOS永远停留在3.2版本。请参阅这个Stackoverflow帖子了解如何安装最新版本。

wait -n $completion_pid $failure_pid

最后,您可以检查wait -n的实际退出代码以查看作业是否失败:

exit_code=$?

if (( $exit_code == 0 )); then
  echo "Job completed"
else
  echo "Job failed with exit code ${exit_code}, exiting..."
fi

exit $exit_code

完整示例:

# wait for completion as background process - capture PID
kubectl wait --for=condition=complete job/myjob &
completion_pid=$!

# wait for failure as background process - capture PID
kubectl wait --for=condition=failed job/myjob && exit 1 &
failure_pid=$! 

# capture exit code of the first subprocess to exit
wait -n $completion_pid $failure_pid

# store exit code in variable
exit_code=$?

if (( $exit_code == 0 )); then
  echo "Job completed"
else
  echo "Job failed with exit code ${exit_code}, exiting..."
fi

exit $exit_code

2
你可以使用 if wait ... 代替将退出代码存储在变量中。 - Exagone313
1
我认为 --for=condition=failure 应该改为 --for=condition=failed - James M
@JamesMcLaughlin 你说得对,可以在API参考文档kubectl explain job.status.conditions.type中看到。我已经更新了代码 :) - Sebastian N
@Exagone313 你说得对 - 在原脚本中我使用了 trap ,所以我在别处使用了退出代码。 - Sebastian N
1
在 MacOS 上不支持 wait -n :( - Martin Melka
2
请确保您没有开启"set -e"!否则,如果failure_pid获胜,wait -n命令将立即退出,您将无法得到漂亮的if语句日志记录。除此之外,这种方法完美地运作了。 - David Lavender

11

如果使用--timeout=0,您可以利用这种行为。

在此情况下,命令行会立即返回结果代码0或1。以下是一个示例:

retval_complete=1
retval_failed=1
while [[ $retval_complete -ne 0 ]] && [[ $retval_failed -ne 0 ]]; do
  sleep 5
  output=$(kubectl wait --for=condition=failed job/job-name --timeout=0 2>&1)
  retval_failed=$?
  output=$(kubectl wait --for=condition=complete job/job-name --timeout=0 2>&1)
  retval_complete=$?
done

if [ $retval_failed -eq 0 ]; then
    echo "Job failed. Please check logs."
    exit 1
fi

condition=failedcondition=complete任意一个为真时,执行将退出while循环(retval_completeretval_failed将为0)。

接下来,您只需要检查并处理您想要的条件。在我的情况下,我希望在作业失败时快速失败并停止执行。


9

wait -n的方法对我不起作用,因为我需要它在Linux和Mac上都能正常工作。

我对Clayton提供的答案进行了一些改进,因为他的脚本在启用set -e -E时无法正常工作。以下代码可以即使在这种情况下也能正常工作。

while true; do
  if kubectl wait --for=condition=complete --timeout=0 job/name 2>/dev/null; then
    job_result=0
    break
  fi

  if kubectl wait --for=condition=failed --timeout=0 job/name 2>/dev/null; then
    job_result=1
    break
  fi

  sleep 3
done

if [[ $job_result -eq 1 ]]; then
    echo "Job failed!"
    exit 1
fi

echo "Job succeeded"

根据您的情况,您可能需要添加超时以避免无限循环。


我想问一下,为什么仅使用 set -e 就不足以识别错误命令呢?那样的话,我就不需要检查失败条件了吗?@Martin Melka - vgdub
当您调用 kubectl wait --for=condition=failed --timeout=0 job/name 并且 pod 的状态 不是 failed 时,该命令将以非零退出代码退出。启用 set -e 后,这将导致整个脚本终止。这里的逻辑是“当 kubectl wait 以非零代码退出时,继续轮询它”。我们只希望在 kubectl wait 以零退出代码退出时退出脚本,因为这意味着 pod 已经完成或失败。 - Martin Melka
但是Pod状态通常会出现“错误”,例如0/1(错误),所以在可能的情况下,在first失败的任务集中,-e将会正确地退出脚本,对吗? - vgdub
抱歉,我不明白你的意思。kubectl wait 不会以 pod 状态的退出代码退出。如果 pod 目前处于已完成(成功)状态,则 kubectl wait --for=condition=complete --timeout=0 job/name 将以 0(成功)退出。否则将以 1(错误)退出(即,如果 pod 目前仍在运行/挂起/失败/其他)。同样,如果 pod 目前处于失败状态,则 kubectl wait --for=condition=failed --timeout=0 job/name 将以 0(成功)退出。这样做是因为没有 kubectl 命令可以执行“当 pod 成功或出错时退出”。 - Martin Melka

4

kubectl wait --for=condition=<条件名命令可以等待特定条件,目前似乎无法指定多个条件。

我的解决方法是使用oc get --wait命令,--wait选项会在目标资源更新后关闭命令。我将使用oc get --wait监视作业的status部分,直到status被更新。当status部分被更新时,表示作业已经按照一些状态条件完成了。

如果作业成功完成,则status.conditions.type立即更新为Complete。但如果作业失败,则作业的Pod将自动重启,而不论restartPolicy是否为OnFailureNever。但如果第一个更新后未更新为Complete,则我们可以认为该作业处于Failed状态。

以下是我的测试证据。

  • 测试成功完成的作业yaml文件
    # vim job.yml
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: pi
    spec:
      parallelism: 1
      completions: 1
      template:
        metadata:
          name: pi
        spec:
          containers:
          - name: pi
            image: perl
            command: ["perl",  "-wle", "exit 0"]
          restartPolicy: Never
  • 如果成功完成作业,则会显示Complete
    # oc create -f job.yml &&
      oc get job/pi -o=jsonpath='{.status}' -w &&
      oc get job/pi -o=jsonpath='{.status.conditions[*].type}' | grep -i -E 'failed|complete' || echo "Failed"
job.batch/pi created map[startTime:2019-03-09T12:30:16Z active:1]Complete
  • 测试失败完成的作业yaml文件
    # vim job.yml
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: pi
    spec:
      parallelism: 1
      completions: 1
      template:
        metadata:
          name: pi
        spec:
          containers:
          - name: pi
            image: perl
            command: ["perl",  "-wle", "exit 1"]
          restartPolicy: Never
  • 如果第一个更新后未更新为Complete,则会显示Failed。测试完成后,请删除现有作业资源。
# 删除名为 pi 的任务
job.batch "pi" 已删除
# 创建 job.yml 文件中定义的任务,并检查任务状态,直到任务状态为失败或完成,否则输出“Failed” job.batch/pi 已创建 map[active:1 startTime:2019-03-09T12:31:05Z] Failed

5
我制作了一个简单的脚本来检查状态,就像你展示的那样:until [[ $SECONDS -gt $end ]] || [[ $(kubectl get jobs $job_name -o jsonpath='{.status.conditions[?(@.type=="Failed")].status}') == "True" ]] || [[ $(kubectl get jobs $job_name -o jsonpath='{.status.conditions[?(@.type=="Complete")].status}') == "True" ]]; do - ruazn2
太好了,我很抱歉展示了 openshift cli 的例子。但你可以使用 kubernetes cli 来赶上进度,它非常棒! - Daein Park
实际上没有--wait选项,-w代表--watch。 - Dominik

3
您可以使用以下解决方法,使用kubectl logs --follow命令:
kubectl wait --for=condition=ready pod --selector=job-name=YOUR_JOB_NAME --timeout=-1s
kubectl logs --follow job/YOUR_JOB_NAME

当你的工作结束时,它将终止,并返回任何状态。


它无法处理具有多个容器的作业。 - Tura
1
找到了“单个作业中多个容器”的解决方案 - 您应该在 kubectl logs 命令中添加标志 --all-containers=true - Tura
1
已测试,它在第一个容器完成后退出,不适用于initContainer + container;--pod-running-timeout=40s --ignore-errors=true无效。 - Denis Trofimov
1
您的提示也适用于作业之外的独立Pod。 - david.perez
kubectl logs有一个选项--pod-running-timeout,它似乎取代了第一个命令,并且如果Pod永远不会启动,则不会挂起。 - Raphael
好像也不起作用。算了吧。:/ 不幸的是,“logs”似乎在工作将其状态切换为“成功”或“失败”之前终止。所以我们必须再次等待才能访问结果。又回到原点! - Raphael

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接