I am deploying a full scale production application using Symfony2 and which will need to support high load.
I’ve spent a lot of time getting this right and I didn’t immediately find much supporting evidence that this was being done – so I wanted to offer some notes/guidance on how I set it up. There are obviously lots of ways to skin the cat but a lot of the solutions I see out there are single environment, single box, LAMP on one server kind of solutions which won’t scale well.
Caveat: This is also a work in progress and there are lots of things I still need to improve. But it’s working well as it stands.
First the overall objectives:
- Database should use RDS in a multi-zone configuration so that it is robust to failure. Also benefit from AWS taking care of backups/failover/redundancy.
- Webserver group with auto-scaling capacity (so it can grow and shrink based on traffic demands)
- ElasticSearch group using it’s own clustering, again for scale.
- Use of AWS SQS (SImple Queue Service) for back-end messaging
- Support for three environments (test, staging, production) on the same set of hardware (to save costs). The production environment will be the only one to take significant load.
- Ability to easily deploy to any and all of those environments using Capifony
- Code all hosted in git
- Logs rotated and searchable centrally for troubleshooting
And here are the major steps:
- First start by registering with AWS (not going to document that here, there are plenty of tutorials)
- Create an Elastic Load Balancer in AWS
- Create three CNAME records with your DNS provider pointing to the public DNS of the Load Balancer (we do this first because it can take 24 hours to propagate). Mine were like production.example.com
- Now create a RDS instance in AWS. This was suprisingly easy. And once it’s created you can add your own IP address to the DB Security group for port 3306 and then use a local client like Sequel to connect to it.
- I restored a DB backup from the existing application and created three databases (mm_test, mm_staging, mm_production) for the three environments. They all sit within a single RDS instance. (Compared to administering via phpMyAdmin which I’ve done previously, using Sequel with RDS is like lightening).
- My RDS instance was a m1.small, that should do for now, it can grow later
- I started with the Auto scaling group for the Elastic Search servers. So I needed to install the ASG Command line tools, it seems this is the only bit which isn’t visible in the AWS web console yet (shame). It was a bit of a pain to get these tools working on my machine (Mac OS X) – various stupid mistakes which I worked through.
- I started with a fresh Amazon Linux instance and set to work installing ElasticSearch. I followed the useful guide http://www.elasticsearch.org/tutorials/2011/08/22/elasticsearch-on-ec2.html to do this. Couple of key gotchas, I needed to specify the region: cloud.aws.region: eu-west-1 (because I was in EU)
I also set the memory:export ES_MIN_MEM=800M export ES_MAX_MEM=800M
- Once I’d got ElasticSearch working on a cluster and coming up green when I queried the cluster, I took an AMI (Image) of the working server.
- Then practiced creating a cluster (with ASG) of 10 machines. Amazing!
- They sat in a security group (mm-SearchGroup) I created.
- Now I want the web servers to be an auto-scaling group sat behind an Elastic Load Balancer.
Because of the way the ElasticSearch clustering works, they each need to have ElasticSearch installed on them (in client mode) to talk to the cluster. So it made sense to start with the same AMI I just created. - I created a new security group (mm-webGroup) to hold the webservers.
- SSHing into the first box, I went through the following steps to set up the server for Symfony2:
- Need to install Apache and PHP
sudo yum install http24 php54 sudo yum install git sudo yum install php54-pdo sudo yum install php54-mysql sudo yum install php54-mcrypt sudo yum install php54-xml sudo yum install php54-pecl-apc sudo yum install php54-mbstring sudo yum install php54-intl sudo yum install php54-process sudo yum install rubygems gem install sass gem install compass
Need node:
sudo yum install gcc-c++ make git cd /usr/local/src/ sudo git clone git://github.com/joyent/node.git cd node sudo ./configure sudo make sudo make install
And now less:
cd ~ sudo su - npm install -g less@1.3.2
check version with:
lessc -version
- Start Apache:
sudo service httpd start
In order for the production (EC2) machine to connect to the git hub repository on the VPS server, it must have once connected via SSH (otherwise it stalls at the RSA prompt)
Created a ~/.ssh/config file for the ec2-user user on the EC2 box which contains the definition for how to access my gitrepo
chmod g-w ~/.ssh/config (to get permissions right
ssh gitrepo (run on EC2 box). Once this is done, it should workNow I set about trying the deployment and fixed each missing item I went:
cap -d demo deploy:setup cap -d demo deploy:cold
Along the way I hit a Github API limit (which is now just 50 requests per hour – if you’re building every few minutes to test a new problem you’ll hit that quickly). I ggot around Github rate limit by getting my auth token from github:
curl -u 'githubuser' -d '{"note":"Composer 2"}' https://api.github.com/authorizations
Then adding this token to composer.json:
"config": {
"bin-dir": "bin",
"github-oauth": {
"github.com": "oauthtoken1234567"
}
}
So that when the build happens, Composer uses the token to communicate with Github.
Also had to set php.ini:
date.timezone = "Europe/London"
Eventually (after lots of attempted deployments), I got my symfony2 app deployed and the symfony check.php to come back green and OK.
None of the webservers needs to deal with SSL directly (it’s handled by the ELB). But they do have to support a health check on port 80. I set up a default virtual host on Apache to do this.
Now I was happily deploying to either of my three environments with capifony, but only on one machine – now to deploy on multiple machines:
I now took an AMI image of my built machine.
Then set up my new auto scaling group:
Create a launch config for the type of EC2 box I want in the webGroup (security)
as-create-launch-config mm-web-lc --image-id ami-xxxxxxxx --instance-type m1.medium --group mm-webGroup --key mm-key-pair
Create a group to specify what and how many and within which loadbalancers
as-create-auto-scaling-group mm-web-asg --launch-configuration mm-web-lc --availability-zones eu-west-1a --min-size 1 --max-size 3 --desired-capacity 3 --load-balancers mm-web-load-balancer
Now add to the capifony deploy.rb script to get it to deploy to all the servers in the mm-webGroup security group:
On my local machine:
sudo gem install capistrano-ec2group
then add to the top of deploy.rb
require 'capistrano/ec2group' set :aws_access_key_id, '???' set :aws_secret_access_key, '???' set :aws_params, :region => 'eu-west-1' set :aws_pvt_dns, false group :"mm-webGroup", :web, :app
Also removed the set :domain and role
xx lines from deploy.rb
Now
cap production deploy
and the build you lovingly put together, gets run three times across all three servers in the group. And a minute later, the new code is automatically deployed across all of them.
Few things still to do:
- What happens when the ASG grows, it will deploy images with an old build on – how will they get updated
- Log files – at the moment they sit on each server, need to rotate and send to S3 or ElasticSearch so we can review them
- I’ve got to configure the SSL certificate on the ELB properly