Cron in a Pod

On a recent project, I came across a problem where I needed to run a cron job inside a pod.

The cron job had to connect to a database to do its thing and all the information required for the script was already available in the pod’s environment variables.

Now I know there is a Kubernetes CronJob resource that can be used, but this quick blog post is based on the use case of just a normal Linux cron job, inside a Kubernetes pod, that requires access to environment variables.

All the code is available here if required:

https://github.com/vishbhalla/cron-in-a-pod

Let’s run through a simple example. Below is noddy script that echoes the contents of an environment variable imaginatively called $MY_ENV_VARIABLE:

if [[ -z $MY_ENV_VARIABLE ]];
then
  echo "MY_ENV_VARIABLE is empty!"
else
  echo $MY_ENV_VARIABLE
fi

Below is a cron line that can be installed in Linux in order to run that script and output the results to a log file:

* * * * * bash -c '/tmp/echo_env.sh' >> /tmp/cron.log 2>&1
 

Now here is a Dockerfile that runs Ubuntu, copies the above files over, installs the cron job to run the script, and then tails the log file created by the cron job:

# Use a full fat linux image:
FROM ubuntu:latest

# Upload our local files to /tmp in the container:
COPY ./echo_env.* /tmp/

# Install cron as it's not installed by the above image as default:
RUN apt-get update && apt upgrade -y && apt-get install -y cron

# Make the cron script executable, and touch the log file it will be writing to:
RUN chmod +x /tmp/echo_env.sh && touch /tmp/cron.log

# Install our cron job in root user's crontab, using the file we copied over above:
RUN crontab -u root /tmp/echo_env.cron

# Start the container by starting the cron service, and spit out the logfile the cron should be writing to:
ENTRYPOINT service cron start && tail -f /tmp/cron.log

I’m working locally with minikube, so I can build via:

Lastly, here is a Kubernetes manifest that simply starts a pod with the above docker image, setting the value of our wonderfully named environment variable:

apiVersion: v1
kind: Pod
metadata:
  name: echo-env
spec:
  containers:
  - name: echo-env
    image: echo_env:latest
    imagePullPolicy: Never
    env:
    - name: MY_ENV_VARIABLE
      value: "Hello from the environment"

So we can now apply the manifest via:

And see the the output of the cron job every minute via:

As you can see:

Why? Because Cron is designed to run in its own fresh shell, with limited environment variables. So we need to find a way of grabbing them from the pod’s environment, and making them available to the cron job.

One way to achieve this is to have the pod spit out all the environment variables into a file and make them available upon start up. Take a look at the ENTRYPOINT in the below Dockerfile:

# Use a full fat linux image:
FROM ubuntu:latest

# Upload our local files to /tmp in the container:
COPY ./echo_env.* /tmp/

# Install cron as it's not installed by the above image as default:
RUN apt-get update && apt upgrade -y && apt-get install -y cron

# Make the cron script executable, and touch the log file it will be writing to:
RUN chmod +x /tmp/echo_env.sh && touch /tmp/cron.log

# Install our cron job in root user's crontab, using the file we copied over above:
RUN crontab -u root /tmp/echo_env.cron

# Start the container by starting the cron service, and spit out the logfile the cron should be writing to:
ENTRYPOINT export -p | grep -v LS_COLORS > /tmp/env.sh && service cron start && tail -f /tmp/cron.log

I’ve excluded the environment variable LS_COLORS above simply because there are some special characters in it, and I didn’t need access it, so safer to just remove it.

Now we can edit the cron job to execute this file before running the script:

* * * * * bash -c '. /tmp/env.sh && /tmp/echo_env.sh' >> /tmp/cron.log 2>&1

So after building our new docker image, and spinning up a new, updated pod, you should now see the variable being correctly reported in the logs:

With this being a Ubuntu image, the better way to obtain the environment variables would be to stick them into /etc/environment via something like:

But I chose the approach I did as it should work with any Linux variant.