A Toolkit for AWS CloudFormation, OpsWorks and Meteor
When I built Fetching, a Meteor application that depends on MongoDB and ElasticSearch, I needed an easy way to manage its servers and deployments. I didn’t find a ready-made solution I liked so I created Admiral. There were a few key features I was after:
Easy single click/command deployment of updates.
Ability to fun multiple applications in a cluster of services.
The ability to manage server configuration as code and in source control.
A simple, modular design that could be easily extended to support other server types (beyond MongoDB and ElasticSearch)
Ability to include only the components I need for a given project
Support for rolling server upgrades causing no downtime
I knew I wanted to go with AWS and settled on a combination of CloudFormation and OpsWorks. In particular I found this blog post to be very useful.
OpsWorks is the AWS approach to Chef and provides some niceties including a clean web UI, a bunch of existing well tested recipes, support for a variety of deployment methods, monitoring and much more.
CloudFormation is a JSON-based template language that lets you define your AWS infrastructure including nearly all AWS components (e.g. Route53 DNS, Elastic Load Balancers, Instance types and configurations, VPN stuff, etc.). AWS deals with migrating your infrastructure as your CloudFormation templates change. Using templates, once you get the hang of them, if way better than configuring everything manually and repeatedly. Since you can configure your Chef scripts from within CloudFormation templates, the combination of OpsWorks and CloudFormation is a fantastic way to manage server infrastructure as code.
Just what you need
Admiral is composed of modules that each offer discrete functionality. They all work independently and you only include what you need for a given project. You can use admiral-cloudformation to manage AWS and never deal with OpsWorks or Meteor. My hope is that new modules will be developed to handle other requirements and features.
The three current modules (ruby gems) are:
You only need to include the one you need. Dependencies are automatically resolved.
Getting Started Tutorial
In this brief tutorial we’re going to walk through setting up an AWS cluster to host your Meteor app. The basic steps are:
Create new repo(s) to hold your server configurations.
Get setup with a base CloudFormation template and configuration.
Provision and startup server instances.
Repeat the steps 1 - 3 for your MongoDB and optionally ElasticSearch cluster.
Configure your Meteor app to work with OpsWorks.
Build and push your Meteor application to S3.
Deploy your Meteor app to OpsWorks.
Repository Setup and Configuration
For each server type, I recommend creating a separate repository to store and track your configuration. For Fetching I’ve got three: opsworks-meteor, opsworks-elasticsearch and opsworks-mongo. Each one has these files:
To get started with a production ready template use:
where SERVER_TYPE is one of mongo, meteor, or elasticsearch. You’ll have to customize a couple settings in the included CloudFormation template such as security groups and DNS entries. You can simply try uploading the default template (using the command below) and follow the errors AWS returns to guide your customizations.
Although your CloudFormation-based configurations are not required to use OpsWorks, the included defaults do. You’ll need to include admiral-opsworks in your Gemfile in addition to admiral-cloudformation to work with OpsWorks via admiral.
Admiral requires a few shell environment values to be set in order to authenticate with AWS. These are not stored in the environment configuration files because it’s always a bad idea to check into source control passwords and the like.
The recommended setup uses the handy rbenv .rbenv-vars file to manage these for you. But if you already have your AWS credentials set (e.g. via your .profile) it should “just work”. The required variables for admiral-cloudformation are:
Once your CloudFormation and environment templates are customized, and you’ve set the above environment variables, you can create your AWS infrastructure with:
where ENVIRONMENT refers to a parameter JSON file such as staging or production. The default is production. This will validate your CloudFormation template then task AWS with building out your infrastructure. It manages ordering things correctly, resolving dependencies, and managing events. You can visit the AWS console to monitor the build process.
Provisioning Servers using OpsWorks
Once your infrastructure components have been built it’s time to provision and start your actual servers! To start things up simply use:
depending on the InstanceCount and InstanceType variables in your environment JSON config, you’ll get a bunch of new servers all configured according to the setup specific in your CloudFormation template.
Later if you make any configuration changes, the same command will manage creating replacement servers and taking down old servers such that there is always InstanceCount running and serving your applications.
Configure your Meteor app
Now that you’ve got servers up and running, you need to deploy your actual application. To get NPM packages installed correctly on OpsWorks you can use Chef deploy hooks to run commands after each deploy. For most projects you can simply create a deploy directory in the root of your Meteor project and add a after_deploy.rb file with this content:
This will correctly install any needed NPM modules required by standard Meteor builds whenever you deploy.
Upload your built Meteor app
You’re now all set to deploy your app and run it on AWS. Admiral relies on the OpsWorks system for deploying applications via S3. To use this you’ll need to add two more environment variables that specify to where your builds should be sent:
Then from your Meteor app root (where you’ve installed admiral-meteor using the Gemfile), you’ll use:
where TAG is an optional git tag for this release. This will build your Meteor app locally using the correct architecture, then push it to S3. You’ll have to of course create the bucket you configured above. It’s recommended you also add an appropriate ACL to secure the bucket (i.e. ensure it’s not public).
Deploy your application
Finally, now that your app has been built and pushed, you can deploy it with:
where myapp is whatever name you specified in ADMIRAL_DEPLOY_NAME. By setting these values differently for different meteor apps you can deploy many different applications on the same cluster (for example using Meteor Cluster).
Typical Work-flow (after setup)
The work-flow for managing your servers with Admiral (after the initial setup) is like this:
Make a change to your CloudFormation template or environment JSON files.
Run admiral cf update.
Your template changes are validated, pushed to AWS.
Changes to your infrastructure are made automatically.
Run admiral ow provision if necessary to update running instances.
Because CloudFormation updates can cause downtime when servers are upgraded, if you have multiple servers of a given type (for example, an ElasticSearch cluster with multiple nodes) Admiral will manage creating new servers with your changes before replacing old ones. This allows you to upgrade entire clusters with zero downtime.
Uploading new Meteor builds is really easy, you’ll almost always do:
where myapp is the name of the app you want to deploy. That’s it!
Environments and Parameters
The CloudFormation template language let’s you pass in parameters so you can adjust how each server of the same type is setup. For example, you can have a staging cluster setup with fewer servers of a smaller type and with different DNS settings than a production or testing cluster. This makes it trivial to duplicate your entire infrastructure as needed.
This works by creating a second set of JSON files that contain values to be applied to any Parameters to your CloudFormation templates. For example, here’s a production.json file for an ElasticSearch cluster:
And here’s the CloudFormation template for an ElasticSearch cluster:
To apply the production.json values to the above CloudFormation.template you’d simply:
You check in your templates and JSON configurations into your source tree and can easily migrate or rollback to specific configurations.