As a fairly new user of Laravel and Elastic Beanstalk I soon found my self in the need to schedule operations, like most of us do.
In the past I had a
In AWS ECS we can use this without adding cron in to the container
https://github.com/spatie/laravel-cronless-schedule
This is how you can start the cronless schedule:
php artisan schedule:run-cronless
See working .ebextentions
configuration at the end of answer.
The answers to this question is of course the most obvious and if you're even the slightest in to Laravel you surely know the answer: Scheduling!
I won't bore you with explaining the brilliant thing that is Laravel Scheduling since you can read about it in the documentation yourself.
But the key thing we need to take with us is that Laravel Scheduling uses crontab to execute, as described in the documentation:
* * * * * php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1
Which brings us to the next, and a bit more tricky, question...
At first glance the answer to this question may seem pretty straight forward. I found this in the AWS Knownledge Center: How do I create a cron job on EC2 instances in an Elastic Beanstalk environment?
Here they describe how to setup a cron job on your Elastic Beanstalk EC2 machine using .ebextentions. In short what it does is creating a new file in the directory /etc/cron.d/
in which we put our desired cron job.
Files in this directory is then processed by crontab as the root
user. There are some of the traps I walked in to, as I've commented below:
files:
# The name of the file should not contain any dot (.) or dash (-), this can
# cause the script not to run. Underscore (_) is OK.
"/etc/cron.d/mycron":
# This permissions is important so that root user can run the script.
mode: "000644"
# As the file is run by the root user it needs to be the owner of the file.
owner: root
# For consistency it's a good idea to have root as the group aswell.
group: root
# NOTE: We need to explicitly tell the cron job to be run as the root user!
content: |
* * * * * root /usr/local/bin/myscript.sh
# There need to be a new line after the actual cron job in the file.
Once we have stayed clear all of those traps, it's time to put in our Laravel Scheduling cron job from above. That should look something like this:
files:
"/etc/cron.d/schedule_run":
mode: "000644"
owner: root
group: root
content: |
* * * * * root php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1
This won't really work in most cases though. That's because the Laravel Scheduler won't have access to your ENV variables and must noticeably not your database settings.
I found the answer to this here: How to Get Laravel Task Scheduling Working on AWS Elastic Beanstalk Cron
So a big shout out to George Bönnisch; I salute you sir for sharing this!
So with this last piece of the puzzle I was finally able to get the setup to work properly:
File structure:
[Project root]
|-- .ebextensions
| |-- cronjob.config
cronjob.config:
files:
"/etc/cron.d/schedule_run":
mode: "000644"
owner: root
group: root
content: |
* * * * * root . /opt/elasticbeanstalk/support/envvars && /usr/bin/php /var/www/html/artisan schedule:run 1>> /dev/null 2>&1
commands:
remove_old_cron:
command: "rm -f /etc/cron.d/*.bak"
Since one of the key features of Elastic Beanstalk is that it can autoscale and add more servers when needed, you might want to have a look on the new feature in Laravel Scheduling: Running Tasks On One Server.
In many cases you don't want your cron job to be executed on more than just one server. For example if you have a scheduled command for sending emails you don't want those sent multiple times.
NOTE: This requires that you use memcached or redis as your cache engine, as stated in the documentation. If you don't, have a look at the AWS service Elasticache.
NOTE 2: When using onOneServer()
you must give the scheduled task a name using the name()
method (before calling onOneServer()
). Like so:
$schedule->command('my:task')
->name('my:task')
->daily()
->onOneServer();
A simpler approach is to use the new Periodic Tasks feature. Using the .ebextensions
for cron jobs may lead to multiple machines running the same job or other race conditions with auto-scaling.
Jobs defined in cron.yaml
are loaded only by the Worker environment and are guaranteed to run only by one machine at a time (the leader). It has a nice syncing mechanism to make sure there's no duplication. From the docs:
Elastic Beanstalk uses leader election to determine which instance in your worker environment queues the periodic task. Each instance attempts to become leader by writing to an Amazon DynamoDB table. The first instance that succeeds is the leader, and must continue to write to the table to maintain leader status. If the leader goes out of service, another instance quickly takes its place.
Place cron.yaml
in the root of the project:
version: 1
cron:
- name: "schedule"
url: "/worker/schedule"
schedule: "* * * * *"
One thing to take into consideration is that in Beanstalk periodic tasks are designed to make an HTTP POST request to a URL in your application that in turn triggers the job you want to run. This is similar to how it also manages queues with SQS.
For Laravel specifically, you may create the routes and controllers to handle each scheduled job. But a better approach is to use Laravel's scheduler and have a single route that you call every minute.
This package will create those routes automatically for you https://github.com/dusterio/laravel-aws-worker
If you are running into trouble with the DynamoDB create Leader Table permissions when triggering a deploy from CodePipeline, it's because the CodePileline service role needs dynamodb:CreateTable
. For instructions check these StackOverflow Question
Official Elastic Beanstalk Periodic Tasks Docs