Laravel in Kubernetes Part 9 - Deploying the Laravel Scheduler

Laravel in Kubernetes Part 9 - Deploying the Laravel Scheduler
Photo by J Taubitz / Unsplash

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.

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: registry.gitlab.com/laravel-in-kubernetes/laravel-app/cli:v0.0.1
            command:
              - php
            args:
              - artisan
              - schedule:run
          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: registry.gitlab.com/laravel-in-kubernetes/laravel-app/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

Laravel in Kubernetes Part 10 - Exposing the application
Our application is now successfully deployed in Kubernetes, but we need to expose it to the outside world. We can access it locally by running kubectl port-forward svc/laravel-in-kubernetes-webserver 8080:80 and going to http://localhost:8080. We need to expose our application to the outside world …