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 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.
We want to create a new Kubernetes CronJob object, in which we can specify how to run the scheduler.
We'll start by creating a new folder in our deployment repo called
$ mkdir -p cron
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
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
Next, we'll look at exposing our application through a Load Balancer, using the Nginx Ingress