Feb 08, 2016
Table of contents:
Almost every type of application will require scheduled jobs in one form or another. This could be automated emails, generated reports, or periodic notifications to your users.
Cron Jobs are a simple way to trigger these types of processes on a given schedule. But setting up Cron Jobs in a world of ephemeral servers is not so straight forward.
In today’s tutorial we’re going to be looking at setting up Cron jobs on AWS’ Elastic Beanstalk.
If you’re reading this tutorial I’m going to assume you are either already using Elastic Beanstalk or you are interested in getting started.
This won’t be a tutorial on setting up and deploying an Elastic Beanstalk application as we’re only going to be looking at Cron Jobs. But hopefully this will answer any questions you might have concerning running scheduled jobs and point you in the right direction to learn more about using Elastic Beanstalk in general.
For those of you who don’t know, Elastic Beanstalk is kind of like the middle ground between Infrastructure-as-a-Service and Platform-as-a-Service. Elastic Beanstalk allows you to quickly deploy an application to AWS without worrying about the infrastructure or complexity by automatically dealing with the details of capacity provisioning, load balancing, scaling, and application health monitoring.
However, AWS servers are ephemeral, so you can’t just SSH in and set up a Cron Job like you would with a regular old Linux box.
There are a couple of different methods for setting up scheduled jobs in Elastic Beanstalk, but in this tutorial we’re going to be using cron.yaml
method.
In the cron.yaml
file, you define the scheduled jobs that you want to perform (we will look at this in more detail later in this post).
Those jobs will be picked up and sent as a POST
request to an Worker application that will accept the request and process the job.
So in order for this to work, you will also need to set up a Worker Environment. This is essentially just another version of your application that is not publicly accessible.
In the root of your project you will need to create a new file called cron.yaml
.
This file will list all of the cron jobs for the application:
version: 1
cron:
- name: "Daily Update"
url: "/workers/cron/daily_update"
schedule: "0 */24 * * *"
Each job has a name
, a url
and a schedule
.
The url
is the url of your application that should accept the job and the schedule
is a regular Cron Job schedule.
You can have as many Cron Jobs as you want by simply listing them in this file. I’ll not go into teaching you the Cron syntax as there are already lots of places online that you can find this information.
Next we need to define a route to accept the Cron Job request and send it to the Controller:
use Illuminate\Contracts\Routing\Registrar;
class WorkersRoutes
{
/**
* Define the routes
*
* @param Registrar $router
* @return void
*/
public function map(Registrar $router)
{
$router->post("workers/cron/{job}", [
"as" => "workers.cron",
"uses" => "WorkersController@cron",
]);
}
}
As you can see, this is a fairly standard Laravel route definition. We will accept the name of the job as a parameter and pass it to the WorkersController
.
Next we will create the Controller that will accept and process the request:
class WorkersController extends Controller
{
/**
* Accept and process the cron job
*
* @param string $job
* @return Response
*/
public function cron($job)
{
Cron::job($job)->run();
return response()->json([]);
}
}
As you can see, first I convert the name of the Cron Job that is passed as a parameter to the method into a Job
object.
Next I run the Cron Job and then I can return an empty 200
response to indicate to AWS that the job was processed successfully.
If something goes wrong an Exception
will be thrown which will return a non 200
response so AWS will know something has went wrong.
Next I can create the Cron Manager
class that will be responsible for getting the correct Job
class to handle the request:
class Manager
{
/**
* Get the Job object
*
* @param string $name
* @return Job
*/
public function job($name)
{
$namespace = sprintf("Acme\Cron\Jobs\%s", studly_case($name));
if (class_exists($namespace)) {
return new $namespace();
}
throw new InvalidCronJob("invalid_cron_job");
}
}
When we accept the name of the class we will generate the namespace and check to make sure the class exists.
If the class does exist we will instantiate a new instance and return it from the method.
If the class does not exist we can throw an Exception that will bubble up to the surface and return the correct HTTP response as we saw in Dealing with Exceptions in a Laravel API application.
Optionally, you can create a Laravel Facade to get that nice syntax that we saw in the Controller. I show you how to do this in almost every Laravel tutorial. Most recently we saw it in Adding Intercom support to your Laravel application.
Finally we can define the Job
classes that will process each request.
First I will define an interface that each implementation should conform to:
interface Job
{
/**
* Run the Cron Job
*
* @return void
*/
public function run();
}
Next I can add each job as a class that will have the responsibility for processing that action:
class DailyUpdate implements Job
{
/**
* Run the Cron Job
*
* @return void
*/
public function run()
{
// Run the job
}
}
There is a lot to get your head around when setting up Cron Jobs in Elastic Beanstalk as having ephemeral servers is quite a bit different from having a Linux box that you can just SSH into and setup what you want.
But once you get this set up and working, it’s actually really nice to use.
Now whenever I need to set up a new Cron Job I can just create the implementation class, then add definition to cron.yaml
and deploy the application. This is much easier than SSHing into a server and manually setting up the Cron Job.
The learning curve for AWS can be pretty steep, but I’d say it’s definitely worth it in the end!