Laravel in Kubernetes Part 9 - Deploying the Laravel Scheduler
In this post, we'll cover deploying the Laravel Scheduler in Kubernetes.
The Laravel Scheduler takes care of running tasks / jobs on a set schedule or at specific times.
Table of contents
Kubernetes Cronjob or Cron in a Container ?
There are some differences we need to be aware of before willy-nilly jumping into a specific implementation.
Kubernetes Cronjobs
Kubernetes comes with a built-in Cron mechanism which can be used to run tasks or jobs on a schedule.
Whilst it is a great mechanism to run jobs on a schedule, we have built most of our scheduling into our Laravel app, to make it more declarative and testable with our codebase.
We could run our scheduler (Which needs to be run every minute with actual cron), using a Kubernetes CronJob, but there are a few things to be aware of.
Kubernetes CronJobs have some limitations to be aware of. They will also create a new pod every minute to run the Laravel Scheduler, and once completed kill it off.
This means that there will be a new pod scheduled every minute, which might cause some churn and duplicated runs, and also have the issue where if there is not enough resources to schedule the pods, our cron will stop running and not be scheduled until we fix the issue.
Creating new pods every minute also creates a lot of scheduling overhead.
Cron in a container
Cron in a container is a bit more lightweight in terms of scheduling, but does have some invisibility when it comes to how many schedule runs have passed, and when they start failing.
Cron will not fail if one of its jobs fails, and it will just continue trudging silently on.
This might be more performant than the Kubernetes CronJobs, but we might not spot unexpected failures in our Laravel Scheduler.
For this reason, we are going to use the Kubernetes CronJobs, but we will also cover running inside a cron container, for cases where Kubernetes performance is an issue
Laravel Scheduler on one server at a time
Laravel has a built in feature for running a task on only one server at a time.
I strongly recommend using this feature if your jobs are not idempotent, meaning re-runnable with the same end result. If you are sending mails or notifications, you want to make sure you don't run them twice if a Cron accidentally runs multiple times.
Kubernetes CronJob
We want to create a new Kubernetes CronJob object, in which we can specify how to run the scheduler.
Cronjob folder
We'll start by creating a new folder in our deployment repo called cron
$ mkdir -p cron
CronJob resource
Within the new cron
folder, we can create our new CronJob object, passing in the environment variables in the same way we did before.
This will instruct Kubernetes to run a pod every minute with our command.
Create a new file called cronjob.yml
in the cron
directory.
apiVersion: batch/v1
kind: CronJob
metadata:
name: laravel-in-kubernetes-scheduler
spec:
schedule: "* * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: scheduler
image: [your_registry_url]/cli:v0.0.1
command:
- php
args:
- artisan
- schedule:run
envFrom:
- configMapRef:
name: laravel-in-kubernetes
- secretRef:
name: laravel-in-kubernetes
restartPolicy: OnFailure
We can apply that, and watch the pods in Kubernetes. After about a minute a pod should start running.
$ kubectl apply -f cron/cronjob.yml
cronjob.batch/laravel-in-kubernetes-scheduler created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
[...]
laravel-in-kubernetes-scheduler-27173731-z2cmg 0/1 Completed 0 38s
$ kubectl logs laravel-in-kubernetes-scheduler-27173731-z2cmg
No scheduled commands are ready to run.
Our scheduler is now running correctly.
Kubernetes by default will keep the last 3 executions of our CronJob for us to inspect. We can use those to have a look at logs.
After 5 minutes you should see 3 Completed
pods for the scheduler, and you can run logs on each of them.
$ kubectl get pods
kubectl get pods
NAME READY STATUS RESTARTS AGE
[...]
laravel-in-kubernetes-scheduler-27173732-pgr6t 0/1 Completed 0 2m46s
laravel-in-kubernetes-scheduler-27173733-qg7ld 0/1 Completed 0 106s
laravel-in-kubernetes-scheduler-27173734-m8mdp 0/1 Completed 0 46s
Our scheduler is now running successfully.
Cron in a container
The other way to run the cron, is run a dedicated container with cron.
We previously built our cron image together with our other images, and we can use that image in a container.
In the same cron
directory we created we can create a deployment.yml
file to contain our cron running in a Deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-in-kubernetes-cron
labels:
tier: backend
layer: cron
spec:
replicas: 1
selector:
matchLabels:
tier: backend
layer: cron
template:
metadata:
labels:
tier: backend
layer: cron
spec:
containers:
- name: cron
image: [your_registry_url]/cron:v0.0.1
envFrom:
- configMapRef:
name: laravel-in-kubernetes
- secretRef:
name: laravel-in-kubernetes
We can apply that, and we should see a cron container pop up, and if we check the logs, we should start seeing some scheduler messages pop up after a while.
$ kubectl apply -f cron/deployment.yml
deployment.apps/laravel-in-kubernetes-cron created
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
[...]
laravel-in-kubernetes-cron-844c45f6c9-4tdkv 1/1 Running 0 80s
$ kubectl logs laravel-in-kubernetes-cron-844c45f6c9-4tdkv
No scheduled commands are ready to run.
No scheduled commands are ready to run.
No scheduled commands are ready to run.
No scheduled commands are ready to run.
No scheduled commands are ready to run.
The scheduler is now running successfully in a container.
We now have the scheduler running successfully in Kubernetes
Onto next
Next, we'll look at exposing our application through a Load Balancer, using the Nginx Ingress