SO! Let's recap my achievements for today. Sadly, much of what I read today on the internet was extremely unfriendly in terms of "what the hell do I _actually_ do" when it came to running custom commands after deployment, so I'll quickly recap the specifics where I found documentation not 100% hand-holding-friendly. The rest you can find on Amazon's extensive documentation site.
Today I wanted get started on my new blog code. I'm very tired of using Wordpress (I still love you Wordpress, honest) with one of the biggest factors I wish to address being the ability to simply deploy on Amazon Elastic Beanstalk. So today, it's get an RDS MySQL instance connected, throw Laravel on it, and then run any outstanding migrations (or just run them to start with). Dead simple.
Much of this I learnt from the developer guide on AWS documentation, which was extremely useful. Following this guide, you'll get a new local git repo where you can throw laravel 4's app base onto (currently found here). I won't go into it, it's already awesomely documented there on how to get to this point in the Beanstalk PHP getting started guide (Linked again in case you still haven't read that before reading on here).
Elastic Beanstalk has become even more awesome for users of Laravel, as 'composer install' is automatically run when it see's you have composer.json in your root folder of your app. So this helps a bunch in the fact we don't have to worry about either a) uploading the entire local vendor directory b) running composer on the instance ourselves after we push an update.
You'll notice after you follow the guide on using eb, you'll have yourself a directory called .elasticbeanstalk which is automatically added to the .gitignore. In there, you'll need to make some quick adjustments to your environment's configuration file. (Not the 'config' file, but the file that is 'optionsettings.EnvironmentNameHere'.
[aws:elasticbeanstalk:container:php:phpini] document_root=/public composer_options= zlib.output_compression=Off memory_limit=256M allow_url_fopen=On max_execution_time=60 display_errors=On
The above is what I'm working with, you'll notice you need to ensure your document_root is the /public folder of your app, fairly straight forward, and that I turned on errors so that I can get some feedback as I develop 'in the cloud'. (10 points to me for using buzz-word-phrase).
If you wish to log into the created EC2 instances that are running your code (say, when shit goes wrong), it doesn't hurt to setup a key pair in your EC2 Management dashboard and then update the information under [aws:autoscaling:launchconfiguration] as follows in my example.
[aws:autoscaling:launchconfiguration] InstanceType=t1.micro EC2KeyName=mysecretkeypairname
Now we'll need to make sure that when our instances update or deploy for the first time, they run artisan migrate. We'll setup laravel's database configuration shortly so keep your pants on.
Make a new directory in your root directory called .ebextensions - This will be where all your commands run either before, as, or after your application is deployed. You can see a full list of things you can do (and when) in some more great documentation.
In our new folder, I made a new file called 01migrate.config, as it's run alphabetically and one day I may need more tasks to run from artisan after a deployment. The file simply contains the call to artisan migrate.
container_commands: artisanmigrate: command: "php artisan migrate --env=elastic" leader_only: true
In a nut shell, we're calling the command we're about to run 'artisanmigrate', it's running 'php artisan migrate --env=elastic', and I only want the leader of the environment (as we could have multiple instances all trying to do this at the same time) to run this command. The environment flag is important, as is (at time of writing) having 'php' in the command. I believe container_commands runs after your application is unzipped, but not before permissions are fixed up, thus it fails to run with it's shebang.
Now, you should be familiar with laravel environments, and so we find ourselves creating a new directory under app/config/ so go ahead and make mkdir app/config/elastic now.
Inside that, we'll have our database.php file which will have database settings. It needs to look like the following;
<?php return array( 'default' => 'mysql', 'connections' => array( 'mysql' => array( 'driver' => 'mysql', 'host' => $_SERVER['RDS_HOSTNAME'], 'port' => $_SERVER['RDS_PORT'], 'database' => $_SERVER['RDS_DB_NAME'], 'username' => $_SERVER['RDS_USERNAME'], 'password' => $_SERVER['RDS_PASSWORD'], 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', ), ), );
Elastic Beanstalk will give us the hostname, and other settings via the $_SERVER global, so we'll just throw these into the array that Laravel is asking for.
The fun part, where I'm sure there's a much better way of achieving this, is in your app/start.php file. As an elastic beanstalk hostname will change with ever environment you setup, I didn't want to either hardcode what was generated for the environment, use a cname domain, nor did I want to specifically say this deploy is always an elastic deployment. Hence the following code that replaces the default env detection (small addition really);
$elastic_hostname = isset($_SERVER['RDS_HOSTNAME']) ? $_SERVER['SERVER_NAME'] : 'none-existant-hostname'; $env = $app->detectEnvironment(array( 'local' => array('localhost'), 'elastic' => array($elastic_hostname) ));
Basically saying where the server global RDS_HOSTNAME is set, I'll assume we're the elastic environment - so make this hostname = the elastic environment.
Ta da, once you git commit, git aws.push, and grab yourself a coffee while instances are rebuilt on Amazon's end, you should have a working copy of Laravel 4 connected to MySQL in their RDS. Huzzah! After all this, ensure your migrations folder has the sessions migration instructions in there (seek laravel documentation), setup your sessions driver to be the mysql database (again, seek someone else's documentation) and voila.
Some Notes:
When using eb on the command line to generate and update your instances, the RDS that is created with the environment WILL BE TERMINATED when you stop and/or delete the environment. So it's important to note, this setup is MOSTLY for testing on the fly. However, and in my opinion, if you were to use Elastic in production (which I so totally will when I get around to it), what I would do is NOT create a RDS when I create a new elastic application/environment, but create it manually in AWS's console/dashboard - and then in the elastic/database.php configuration - specifically put in the credentials there for the connection.
Of course thereafter I would need to setup the security access between the Elastic/EC2 and RDS myself - but then regardless of what happens to the environment - the RDS instance would always persist.
Great tutorial. I use the similar method but with much more ease. I have created my DigitalOcean server with Cloudways. It provides build-in Elastic Beanstalk feature. So all I need to do is to enable it on my Cloudways platform.
Your post was priceless.
Many thanks,
Fred
Thanks for the article, it helped me a lot !
^_^ good stuff
Hi Darren, how do you handle saving off Laravel logs on EB so they get picked up by the daily logrotate? (That is, the files in /var/app/current/app/storage/logs/*.txt) or do you just use a log aggregation service?
Typically I stick logs in a database table, or where that’s unavailable, throw off an email to myself. I should be using NoSQL services like DynamoDB for this but haven’t gotten around to setting that up yet (take note I think records are still limited to 64kb/128kb worth of data per entry so sometimes it’s not ideal….) but it is stupidly quick.
There’s some neat tricks you can do with throw files to S3, but it’s not something I would look into personally.
Great guide! How are your thoughts about a queue listener? I mean, like the migration command the queue:listen command can be fired but as the documentation says something to ensure that the service doesn’t stop is useful.
It’s not ideal (in my experience) to use Artisan’s listener on Elastic Beanstalk. There’s a lot of reasons, but mostly jobs fail and are hard to then restart automatically (which EC2 machine(s) is running the job, what’s monitoring it etc). You should do it using custom AMI images to have these sorts of things built into the instances when they’re brought up, but typically I use iron.io.
Using an external queue manager is great for another reason when using Elastic Beanstalk. When you shoot the request off, and it comes back to your Elastic Beanstalk instances, the load balancer will give it to the machine that’s doing the least. So rather than one instance looking after all the queues, all your instances can look after the queues.
Hi Dazz, great post! Unique!!!
I’m getting there error message and didn’t found solution.
“Module: AWSEBAutoScalingGroup ConfigSet: null] Failed on instance with return code: 1 Output: Error occurred during build: Command artisanmigrate failed.”
You know what it could be?
Thanks in advance!
Sounds like something failed in the artisan command. Perhaps artisan couldn’t find the right configuration for doing migrations.
You’ll need to look for eb-log(s) for the output from artisan. Sadly it doesn’t inform you of the error encountered without going onto the EC2 system that was build for elastic, look at the log – if nothing further is there, you’ll find a staging directory for the “new” version that’s being pushed – you’ll need to run artisan from the command line manually to see if you can reproduce the error.
Hope that points you in the right direction.
Thank you Darren!!! You are right!!!
I’ve got this error cause the arisan command fails.
If doing a deploy with command migration over a RDS with the same tables, just change “php artisan migrate” to “php artisan migrate:refresh” and it works like magic.
Cheers from Brazil! 😉
Hey Darren, thanks alot for sharing this.
I have a little problem that my laravel git repo starts one level higher so I have
/myrepo
—-.git
—-.ebsettings
—-.elasticbeanstalk
—-myproject
——–app
——–public
——–…
—-vendor
So in the optionsettings file I set the document root to
document_root=/myproject/public
But since composer.json is under /myproject this is probably not going to work out of the box right?
Any suggestions what I can do?
I’m also a bit confused as you seem to imply the composer update is automatic if it finds a composer.json but then I found this file from 2 months ago which says it needs to be configured like so https://gist.github.com/rmclain/6195165
btw the error I get following all the steps you’ve described is this so I think that’s when composer install/update didn’t run?
Warning: require(/var/app/current/myproject/bootstrap/../vendor/autoload.php): failed to open stream: No such file or directory in /var/app/current/myproject/bootstrap/autoload.php on line 17 Fatal error: require(): Failed opening required ‘/var/app/current/myproject/bootstrap/../vendor/autoload.php’ (include_path=’.:/usr/share/pear:/usr/share/php’) in /var/app/current/myproject/bootstrap/autoload.php on line 17
How did you get on with this?
You’re right, vendor will install somewhere different to where your current setup is looking. I can have a little fiddle later on and adjust the directory. I have done this previously, but my only issue was with artisan commands – I ended up making a symlink because artisan didn’t like the new vendor location.
Hey, Nice article. But I have already set up an eb environment directly on aws. Now I want to host my app on the beanstalk… how do i go about that?
You ask me some good questions.
Google suggests http://stackoverflow.com/questions/14207203/aws-elastic-beanstalk-using-eb-to-attach-git-repo-to-existing-eb-environment
Ok. I followed through and I pushed. I got the error ERROR [Instance: i-f6740ec2 Module: AWSEBAutoScalingGroup ConfigSet: Hook-PreAppDeploy] Failed on instance with return code: 1 Output: Error occurred during build: Command hooks failed .
Did sudo /usr/bin/composer.phar self-update on my ec2, still got same error
Sorry, I had mis-spelled .ebextensions. Now i corrected that and git aws.push successfully. Now I tried acessing my elasticbeanstalk url and I get “Forbidden You don’t have permission to access / on this server.” My git repo is here: https://github.com/TechyTimo/vml4/tree/master
Sounds like you need to investigate your .elasticbeanstalk folder (which is typically kept out of version control for security reasons) and for your optionsettings. ensure document_root is set to /public over /
But that would just be a guess. I can’t tell without seeing more settings or your eb logs.
Nice article man, very well detailed!
Cheers sir!
Thanks. This was helpful – EB wasn’t too happy about how I sent artisan commands. As a side note: It seems like the commands won’t run if there is a new “leader” created in the scaling group.
I’ve read that when a new leader takes charge, it doesn’t get the leader commands – but I haven’t been able to confirm that. Not sure if it’s still an issue. Was quite some time about, May I believe, when it was a serious problem.
Cheers for the guide.
I found I could only get my ebextension files to run when they were included within git repo. Did you find this?
Most correct. As my other comment suggested, I was completely nub and had .ebextensions in my gitignore file which caused me a little grief for a bit. xD
From what I can tell the AWS documentation never mentions adding the ebextension files to your Git repo. Given the .elasticbeanstalk files aren’t placed under version control, this is pretty confusing.
As two quick tips for the guide, I would suggest clarifying what you mean by “root directory” when first referring to the .ebextensions, given that you previously discussed setting the document_root. It might also be worth explicitly stating that this guide is for Laravel 4 only. Those using Laravel 3 would also need to run the “migrate:install” command. I’m not sure if they would then run into trouble when next running aws.push, given “migrate:install” throws an error when run for the second time.
The title states Laravel 4. I have previously looked into Laravel 3 and you do run into issues when you ask for migrate:install twice (and sadly, the initial kind of needs to be set that way in L3).
You can however run migrate:install and in the commands list state you wish to ignore any errors a command produces (but you wouldn’t get feedback on the initial migrate install if it fails to add the table.)
But yes, it’s confusing about ebextensions and that’s why I originally added ebextensions to ignore, then made another comment below that I was nub and shouldn’t have. xD
I’ll look into the document root references shortly.
Thanks for your feedback btw 😀
Just a quick follow up, after you made the .ebextensions folder and put in your migration command to run, run ‘eb update’ and allow elastic beanstalk to grab the updated configuration files. Doing git aws.push doesn’t appear to include the settings.
This also applies for branches and making new containers, you need to still push your configuration to the environment.
Actually this may not be true. Just ensure you’re not nub and add ./ebextensions to your .gitignore file.