Waiting for K8S Job to finish

9/10/2019

I'm looking for a way to wait for Job to finish execution Successfully once deployed.

Job is being deployed from Azure DevOps though CD on K8S on AWS. It is running one time incremental database migrations using Fluent migrations each time it's deployed. I need to read pod.status.phase field.

If field is "Succeeded", then CD will continue. If it's "Failed", CD stops.

Anyone have an idea how to achieve this?

-- Andrija
amazon-web-services
azure-devops
continuous-deployment
kubernetes
kubernetes-pod

2 Answers

9/11/2019

I think the best approach is to use the kubectl wait command:

Wait for a specific condition on one or many resources.

The command takes multiple resources and waits until the specified condition is seen in the Status field of every given resource.

It will only return when the Job is completed (or the timeout is reached):

kubectl wait --for=condition=complete job/myjob --timeout=60s

If you don't set a --timeout, the default wait is 30 seconds.


Note: kubectl wait was introduced on Kubernetes v1.11.0. If you are using older versions, you can create some logic using kubectl get with --field-selector:

kubectl get pod --field-selector=status.phase=Succeeded

-- Eduardo Baitello
Source: StackOverflow

9/11/2019

We can check Pod status using K8S Rest API.

In order to connect to API, we need to get a token: https://kubernetes.io/docs/tasks/administer-cluster/access-cluster-api/#without-kubectl-proxy

# Check all possible clusters, as you .KUBECONFIG may have multiple contexts:
kubectl config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'

# Select name of cluster you want to interact with from above output:
export CLUSTER_NAME="some_server_name"

# Point to the API server refering the cluster name
APISERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}")

# Gets the token value
TOKEN=$(kubectl get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='default')].data.token}"|base64 -d)

From above code we have acquired TOKEN and APISERVER address.

On Azure DevOps, on your target Release, on Agent Job, we can add Bash task:

#name of K8S Job object we are waiting to finish
JOB_NAME=name-of-db-job
APISERVER=set-api-server-from-previous-code
TOKEN=set-token-from-previous-code

#log APISERVER and JOB_NAME for troubleshooting
echo API Server: $APISERVER
echo JOB NAME: $JOB_NAME

#keep calling API until you get status Succeeded or Failed.
while true; do

  #read all pods and query for pod containing JOB_NAME using jq.
  #note that you should not have similar pod names with job name otherwise you will get mutiple results. This script is not expecting multiple results.
  res=$(curl -X GET $APISERVER/api/v1/namespaces/default/pods/ --header "Authorization: Bearer $TOKEN" --insecure | jq --arg JOB_NAME "$JOB_NAME" '.items[] | select(.metadata.name | contains($JOB_NAME))' | jq '.status.phase')

  if (res=="Succeeded"); then
   echo Succeeded
   exit 0
  elif (res=="Failed"); then
    echo Failed
    exit 1
  else    
    echo $res
  fi
  sleep 2
done

If Failed, script will exit with code 1 and CD will stop (if configured that way).
If Succeeded, exist with code 0 and CD will continue.

In final setup: - Script is part of artifact and I'm using it inside Bash task in Agent Job. - I have placed JOB_NAME into Task Env. Vars so it can be used for multiple DB migrations. - Token and API Server address are in Variable group on global level.

TODO:

  • curl is not existing with code 0 if URL is invalid. It needs --fail flag, but still above line exists 0.
  • "Unknown" Pod status should be handled as well
-- Andrija
Source: StackOverflow