How To Create Kubernetes Jobs/Cron Jobs – Getting Started Guide

Kubernetes Jobs/Cron Jobs – Getting Started Guide

Kubernetes jobs are primarily meant for short-lived and batch workloads. It runs for completion as opposed to other objects like replicasets, replication controllers and DaemonSets which runs continuously. This tutorial explains the process of creating kubernetes jobs and cron jobs along with a few tips and tricks.

Jobs run until the tasks specified in the job is completed. This means, if the pods give exit code 0, the job will exit. Whereas in normal Kubernetes deployments, irrespective of the exit codes, the deployment object will create new pods when it terminates or throws an error to keep the desired stated of the deployment.

Also, during a Job run, if the node hosting the pod fails, the job pod will get automatically rescheduled to another node.

Kubernetes Jobs Use Case

The best use case for Kubernetes jobs are,

  1. Batch processing: Let’s say you want to run a batch task one time a day or during a specific schedule. It could be something like reading files from storage or a database and feed it to a service to process the files.
  2. Operations/ad-hoc tasks: Let’s say you want to run a script/code which runs a database cleanup activity.

Creating a Kubernetes Job

In this example, we will use an Ubuntu container that runs a for loop and echoes a message based on the argument you pass to the container. An argument is a number that decides how many times the loop should run.

You Might Like: Get 16% Off + $100 Off On Kubernetes Certification & Course Bundle

For example, if you pass 100 as an argument, it will echo the message 100 times and the container will exit.

You can view the Dockerfile and script from here -> kube-job-example Docker configs

Let’s get started with a job with a simple setup.

  1. Create a job.yaml file with our custom Docker image with 100 as a command argument. The value 100 will be passed to the docker ENTRYPOINT script as an argument.
    apiVersion: batch/v1 
    kind: Job metadata:   
    name: kubernetes-job-example   
    labels:     
      jobgroup: jobexample 
    spec:   
      template:     
        metadata:       
          name: kubejob       
          labels:         
            jobgroup: jobexample     
        spec:       
          containers:       
          - name: c         
            image: devopscube/kubernetes-job-demo:latest         
            args: ["100"]       
         restartPolicy: OnFailure
    
  2. Let’s create a job using kubectl with the job.yaml file.
    kubectl apply -f job.yaml
  3. Check the status on the job using kubectl.
    kubectl get jobs
  4. You can get the list of pods using kubectl.
    kubectl get po
  5. You can get the job pod logs using kubectl.
    kubectl logs kubernetes-job-example-bc7s9 -f

Multiple Job Pods and Parallelism

When a job is deployed you can make it run on multiple pods with parallelism.

For example, if you want to run 6 pods and run 2 pods in parallel, you need to add the following two parameters to your job manifest.

completions: 5
parallelism: 2

Here is the manifest file with those parameters.

apiVersion: batch/v1
kind: Job
metadata:
  name: kubernetes-parallel-job
  labels:
    jobgroup: jobexample
spec:
  completions: 5
  parallelism: 2
  template:
    metadata:
      name: kubernetes-parallel-job
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: devopscube/kubernetes-job-demo:latest
        args: ["100"]
      restartPolicy: OnFailure

Generate Random Job Name

You cannot have a single job manifest file and create multiple jobs from it. Kubernetes will throw an error stating that a job with the same name exists.

To circumvent this problem, you can add the generateName name parameter to the metadata.

For example,

apiVersion: batch/v1
kind: Job
metadata:
  generateName: kube-job-
  labels:
    jobgroup: jobexample

In the above example, every time you run the manifest, a job will get created with kube-job- as a prefix followed by a random string.

Creating a Kubernetes CronJob

What if you want to run a batch job on specific schedules, for example, every 2 hours. You can create a Kubernetes cronjob with a cron expression. The job will automatically kick in as per the schedule you mention in the job.

Here is how we specify a cron schedule. You can use the crontab generator to generate your own schedule.

schedule: "0,15,30,45 * * * *"

If we were to run our previous job as a cron job every 15 minutes, here is how the manifest looks. Create a file named cron-job.yaml and copy the following manifest.

apiVersion: batch/v1beta1
kind: CronJob
metadata:
    name: kubernetes-cron-job
spec:
  schedule: "0,15,30,45 * * * *"
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: cron-batch-job
        spec:
          restartPolicy: OnFailure
          containers:
          - name: kube-cron-job
            image: devopscube/kubernetes-job-demo:latest
            args: ["100"]

Let’s deploy the cronjob using kubectl.

kubectl create -f cron-job.yaml

List the cronjobs

kubectl get cronjobs

Trigger a CronJob Manually

There are situations where you might want to execute the cronjob in an ad-hoc manner. You can do this by creating a job from an existing cron job.

For example, if you want a cronjob to be triggered manually, here is what we should do.

kubectl create job --from=cronjob/kubernetes-cron-job manual-cron-job

--from=cronjob/kubernetes-cron-job will copy the cronjob template and creates a job named manual-cron-job

Few Key Parameters

There are a few more key parameters you can use with kubernetes jobs/cronjobs based on your needs. Let’s have a look at each.

  1. failedJobHistoryLimit & successfulJobsHistoryLimit: Deletes the failed and successful job history based on the retention number you provide. This is super useful to trim down all failed entries when you try to list the jobs. For example,
    failedJobHistoryLimit: 5 
    successfulJobsHistoryLimit: 10
  2. backoffLimit: Total number of retries if your pod fails.

Kubernetes Jobs/Cron Jobs – Getting Started Guide

Running Custom Scripts In Docker With Arguments – ENTRYPOINT Vs CMD

Custom Scripts In Docker With Arguments

Use Case:  You need to run a custom shell script in your Docker container with arguments passed to the script. These arguments decide how the script should be run inside the container.

In this guide, we will look int to running custom shell scripts inside a Docker container with command line arguments.

The key Dockerfile instructions used for this use case are

  1. ENTRYPOINT: Here you will specify the command that has to be executed when the container starts. The default ENTRYPOINT command is /bin/sh -c
  2. CMD:  It acts as an argument for ENTRYPOINT.

Executing Commands Using CMD Vs ENTRYPOINT

  1. Let’s take the following Dockerfile example. It installs http-tools an starts the ab (apache benchmark) utility using CMD and Entrypoint. Both does the same job.
    Using CMD:

     

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    CMD ["ab"]

    Using ENTRYPOINT: 

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    ENTRYPOINT ["ab"]
    
  2. If you build this image and run the container, it will throw the following error. The reason is, ab command requires a http endpoint as an argument to start the service.
    ➜  docker run demo
    ab: wrong number of arguments
    Usage: ab [options] [http[s]://]hostname[:port]/path
    Options are:
        -n requests     Number of requests to perform
  3. We have two ways to get around this problem. Hardcode the http endpoint argument as shown below.
    Using CMD:

     

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    CMD ["ab"] ["http://google.com/"]

    Using ENTRYPOINT:

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    ENTRYPOINT ["ab" , "http://google.com/" ]
  4. Or, you can pass the ab command with the http endpoint at the end of the docker run command. Here is the key difference between CMD and ENTRYPOINT
    Using CMD:
    Just add the full ab command at the end of the docker run command. It will override the whole CMD specified in the Dockerfile.
    Dockerfile:

     

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    CMD ["ab"]
    

    Docker Command:

    docker run ab-demo ab http://google.com/

    Using ENTRYPOINT:
    You cannot override the whole ENTRYPOINT. So if you want to pass URL argument for ab using ENTRYPOINT, you just need to pass the URL as the ab command is part of ENTRYPOINT and the URL you pass in the run command will be overridden by CMD and gets appended to the ENTRYPOINT script. In this case, CMD instruction is not required in the Dockerfile
    Dockerfile:

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    ENTRYPOINT ["ab"]
    

    Docker Command:

    docker run ab-demo http://google.com/

    You can also use both CMD and ENTRYPOINT instruction to achieve this. Here is how the Dockerfile looks.

    FROM centos:7
    MAINTAINER Devopscube
    RUN yum -y update && \
        yum -y install httpd-tools && \
        yum clean all
    ENTRYPOINT ["ab"]
    CMD ["http://dummy-url.com/"]

    When ENTRYPOINT and CMD used in the same Dockerfile, everything in the CMD instruction will be appended to the ENTRYPOINT as argument. If you run a container using the above Dockerfile, at container start, ab script will get executed with the dummy-url as an argument.

How To Run Custom Script Inside Docker

In this example, we have a custom shell script which accepts three command line arguments ($1, $2 & $3). If you pass true as the the first argument, the script will run in a infinite loop. Other two arguments are just to print the values.

Step 1: Create a script.sh file and copy the following contents.

#!/bin/bash
set -x
while $1
do
	echo "Press [CTRL+C] to stop.."
	sleep 5
    echo "My second and third argument is $2 & $3"
done

Step 2: You should have the script.sh is the same folder where you have the Dockerfile. Create the Dockerfile with the following contents which copies the script to container and runs it it ENTRYPOINT using the arguments from CMD. We are passing true as the first argument, so the script will run in an infinite loop echoing batman and superman arguments as outputs.

FROM centos:7
MAINTAINER Devopscube
RUN yum -y update && \
    yum -y install httpd && \
    yum clean all
COPY ./script.sh /
RUN chmod +x /script.sh
ENTRYPOINT ["/script.sh"]
CMD ["true", "batman", "superman"]

Step 3: Lets build this Dockerfile with image name script-demo.

docker build -t script-demo .

Step 4: Now lets create a container named demo using script-demo image.

docker run --name demo -d script-demo

You can check the container logs using the following command.

docker logs demo -f

Step 4: You can also pass the CMD arguments at the end of docker run command. It will override the arguments passed in the Dockerfile. For example,

docker run --name demo -d script-demo false spiderman hulk

Here "false spiderman hulk" will override "true", "batman", "superman" present in the docker image

Custom Scripts In Docker With Arguments

How to Set Up Ingress On Kubernetes Using Nginx Controller

kubernetes-nginx-ingress-controller

In this tutorial, you will learn how to setup Kubernetes ingress using Nginx ingress controller and to route traffic to deployments using wildcard DNS.

If you want to understand how Kubernetes ingress works, please read this blog post on Kubernetes Ingress Tutorial.

Here is an example architecture of Kubernetes ingress using Nginx ingress controller

kubernetes ingress architecture - Nginx ingress controller

Prerequisites:

  1. A Kuberntes cluster
  2. kubectl utility installed and authenticated to kubernetes cluster.
  3. Admin access to kubernetes cluster.
  4. A valid domain to point to ingress controller Load Balancer.

If you are on google cloud, assign admin permissions to your account to enable cluster roles.

ACCOUNT=$(gcloud info --format='value(config.account)')
kubectl create clusterrolebinding owner-cluster-admin-binding \
    --clusterrole cluster-admin \
    --user $ACCOUNT

Note: This tutorial was tried on google cloud GKE cluster. Logically it should work on all cloud environments. If at all you face any error, you might need to do some tweaks in the setup.

Setup Nginx Ingress Controller

There are two nginx ingress controllers.

  1. Nginx ingress controller by kubernetes community
  2. Nginx ingress controller by Nginx Inc

We will be using the Nginx controller from the kubernetes community.

Ingress controller needs a specific namespace, service account, cluster role bindings, configmaps etc. You can create all the kubernetes objects mentioned using the yaml file from official ingress repo.

Let’s deploy the ingress controller using mandatory.yaml file from the official repo. It has the consolidated list of kubernetes objects required for the Nginx controller.

You Might Like: Kubernetes Certification Tips

Lets create the Nginx controller deployment using kubectl.

kubectl apply -f https://github.com/kubernetes/ingress-nginx/blob/master/deploy/static/provider/cloud/deploy.yaml

Check the ingress controller pods to make sure if it is setup correctly.

kubectl get pods -n ingress-nginx

Setup LoadBalancer Service For Ingress Controller

Next step is to create a service of Type Loadbalancer to expose the nginx controller deployment outside the cluster.

Step 1: Create a project directory locally and switch to that directory.

mkdir ingress-deployment && cd ingress-deployment

Step 2: Create a file named nginx-ingress.yaml

vi nginx-ingress.yaml

Step 3: Copy the following contents to the file.

Note: The annotations under the labels are very important for integrating with the nginx controller deployment.

kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  externalTrafficPolicy: Local
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  ports:
    - name: http
      port: 80
      targetPort: http
    - name: https
      port: 443
      targetPort: https

Step 4: Create the ingress service.

kubectl apply -f nginx-ingress.yaml

Step 5: Check the created service if it is attached to the external load balancer.

kubectl get svc -n ingress-nginx

Map a Domain Name To Loadbalancer IP

To make our ingress settings work, we need to map a domain name to the load balancer IP. You can do it in two ways.

Single DNS Mapping:

You can map single domain directly as a A record to the load balancer IP. Using this you can have only one domain for the ingress controller and multiple path based traffic routing.

For example,

www.example.com --> Loadbalancer IP

You can have path based routing using this model.

Few examples,

http://www.example.com/app1
http://www.example.com/app2
http://www.example.com/app1/api
http://www.example.com/app2/api

Wildcard DNS Mapping:

If you map a wildcard DNS to the load balancer, you can have dynamic DNS end points through ingress.

For example,

*.apps.example.com

This way you can have multiple dynamic subdomains through single ingress controller and each DNS can have its own path based routing.

For example,

#URL one

http://demo1.apps.example.com/api
http://demo1.apps.example.com/api/v1
http://demo1.apps.example.com/api/v2

#URL two

http://demo2.apps.example.com/api
http://demo2.apps.example.com/api/v1
http://demo2.apps.example.com/api/v2

For demo purposes, we have mapped a wildcard DNS to the LoadBalancer IP. Based on your DNS provider, you can do this setting.

Setup A Demo Application

For testing purposes, we will deploy a demo application and add a ClusterIp service to it.

Also Read: Kubernetes deployment tutorial

Step 1: create a namespace named dev

kubectl create namespace dev

Step 2: Create a file named hello-app.yaml

Step 3: Copy the following contents and save the file.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app
  namespace: dev
spec:
  selector:
    matchLabels:
      app: hello
  replicas: 3
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
      - name: hello
        image: "gcr.io/google-samples/hello-app:2.0"

Step 4: Create the deployment using kubectl

kubectl create -f hello-app.yaml

Check the deployment status.

kubectl get deployments -n dev

Step 5: Create a file named hello-app-service.yaml

Step 6: Copy the following contents and save the file.

apiVersion: v1
kind: Service
metadata:
  name: hello-service
  namespace: dev
  labels:
    app: hello
spec:
  type: ClusterIP
  selector:
    app: hello
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Step 7: Create the service using kubectl.

kubectl create -f hello-app-service.yaml

Check the service status

kubectl get svc -n dev

Create Kubernetes Ingress Object

Now let’s create an ingress object to access our hello app using a DNS. An ingress object is nothing but a setup of routing rules.

If you are wondering how ingress object is connected to Nginx controller, the ingress controller pod connects to the Ingress API to check for rules and it updates its nginx.conf accordingly.

Step 1: Create a file named ingress.yaml

Step 2: Copy the following contents and save the file.

Replace test.apps.example.info with your domain name. Here the assumption is that you have a wildcard DNS in the format *.apps.example.info

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  namespace: dev
spec:
  rules:
  - host: test.apps.example.info
    http:
      paths:
      - backend:
          serviceName: hello-service
          servicePort: 80

Step 3: Describe created ingress object created to check the configurations.

kubectl describe ingress  -n dev

Now if you try to access test.apps.example.info domain (replace it with your domain name), you should be able to access our sample app deployed.

kubernetes-nginx-ingress-controller