EDUCATION
Build and Deploy with Grunt, Bamboo, and Elastic Beanstalk
In response to Twitter feedback on our recent post “Goodbye, Sprockets! A Grunt-based Rails Asset Pipeline,” we’d like to share an overview of our current build and deploy process.
It goes a little something like this:
Local development environment
We currently have a single git-managed project containing our Rails server at the top level and our Angular project in a subdirectory of vendor
. Bower components are checked in to our repo to speed up builds and deploys. The contents of our gruntfile and the organization of our asset pipeline are described here.
We can start up our server via grunt server
(which we have configured to shell out to rails server
) or directly with rails server
for Ruby debugging.
Even though both the client and server apps are checked into the same project and share an asset pipeline, we restrict our Angular code to only communicate to the backend Rails server over APIs. This enforces a clean separation between client and server.
Bamboo build project
When Angular and Rails code is checked in to master, our Bamboo build process runs. We always push through master to production, à la the GitHub flow process. The build process comprises two stages:
Stage 1: Create Artifacts:
- Rails:
bundle install
and freeze gems. - Angular:
npm install
,grunt build
. Nobower install
is needed because we check-in ourbower_components
. Thegrunt build
step compiles, concatenates, and minifies code and assets. It also takes the unusual step of cache-busting the asset filenames and rewriting any references in view files to point to the new filenames. - The resulting artifact is saved in Bamboo and passed to Stage 2.
Stage 2: Run Tests:
- Rails: run rspec model and controller tests, and then cucumber integration tests. It was a bit tricky to get headless cucumber tests running on Bamboo’s default Amazon AMI; see details in our previous blog post.
- Angular:
grunt test
.
If the artifact creation succeeds, and the tests run on that artifact all pass, Bamboo triggers its associated deploy project. Otherwise, our team receives notifications in Hipchat of the failure.
Bamboo deploy project
After every successful build, Bamboo is configured to automatically deploy the latest build to our staging environment.
The Bamboo deployment project runs the following tasks to kick off an Elastic Beanstalk deployment:
- Write out an aws_credentials file to the build machine. We don’t store any credentials on our custom AMIs. Instead, we keep them in Bamboo as configuration variables and write them out to the build machine at deploy time.
- Run Amazon’s AWSDevTools-RepositorySetup.sh script to add aws.push to the set of available git tasks on the build machine.
- Kick off the deployment to our Elastic Beanstalk staging environment with a call to
git aws.push
from the build machine’s project root directory.
Since our project is configured to use Elastic Beanstalk, the remaining deployment-related configuration (like which Elastic Beanstalk project and stage to push the update to) is checked in to the .elasticbeanstalk and .ebextensions directories in our project and made available to the git aws.push
command. If there is interest in sharing the contents of these config files, please let us know on Twitter.
Elastic Beanstalk staging environment
After the staging deployment has been kicked off by Bamboo, we can head over to our EB console at https://console.aws.amazon.com/elasticbeanstalk and monitor the deployment while it completes. The git aws.push
command from the previous step is doing the majority of the work behind the scenes. For staging, we use Amazon’s default Rails template, and “Environment type: Single instance.” Amazon’s default Rails template manages Rails processes on each server box with a passenger + nginx proxy.
When we first decided to go to a grunt-based asset pipeline, we worried this might impact the way we deployed our servers. In fact, it does not. Our git code bundle containing our Rails app, Angular front-end, and shared assets is deployed to Elastic Beanstalk via git aws.push
, exactly as it was prior to our grunt-based asset pipeline switch.
We then do smoke testing on our staging environment.
Elastic Beanstalk production environment
After we have determined the staging release is ready to go to production, we promote the current code bundle from staging to production simply by loading up the EB console for the production stage of our project, clicking “Upload and Deploy” from the Dashboard, clicking “All Versions” in the popup, then selecting the git version currently deployed to staging.
For production, we use Amazon’s default Rails template, and “Environment type: Load balanced, auto scaling.” Elastic Beanstalk takes care of rolling updates with configured delays, aka no-downtime deployments.
Wrap up
The above system, combined with the grunt-based asset pipeline described in our previous post, allows us to iterate and deploy with confidence. Future work will focus on improving deploy times, perhaps by baking AMIs or exploring splitting our monolithic deployment artifact into multiple pieces, e.g., code and assets, npm packages, etc.