Walid Shaita

 

At GumGum, we work completely with AWS and have more than 100+ services to build, automate, maintain, and secure.

We are used to automating most of our services using Ansible on AWS but the more services you need to manage, the harder it gets to automate everything.

Moreover, Ansible is mostly a configuration management tool, which means they are designed to install and manage software on existing servers. We could technically use Ansible to provision most of the resources we need, but we realized that Ansible does not perfectly fit as it's more a procedural style where you write code that specifies, step-by-step, how to achieve some desired end state compared to an orchestration tools where you write code that specifies your desired end state, and the IAC tool itself is responsible for figuring out how to achieve that state.

We decided to find an orchestration tool that works with AWS to provision all of our infrastructure. In that research, the most used orchestration tool was CloudFormation and Terraform.

We decided to use Terraform because of the following reasons:

  • It's open source
  • Active community
  • The syntax is much cleaner
  • Code reuse
  • Speed
  • Plan

The good thing of having a configuration tool and an orchestration tool is that they are not mutually exclusive, you can easily use Ansible to configure EC2 instances or create AMIs then use Terraform for provisioning of the infrastructure around the EC2 instance (ASG, LC, Load Balancer...).

In this post, we are going to discuss how we implemented Terraform as our GumGum IAC.

 

Implementation of Terraform

 

Before using an orchestration tool like Terraform, you need to think about code reusability as we have multiple regions, accounts, and environments (dev/stage/prod) and not copying pasting the same code across the different environment

You can achieve that by designing your Terraform framework using modules.

 

Modules

 

There are multiple ways of implementing modules with Terraform. At GumGum, we decided to create one module per application in order to easily duplicate the application in different regions/environment. In addition to that, we wanted to isolate an application from one to another to reduce dependencies between modules.

 

Multiple Environments

 

That being said, let's say your production app consist on a single c5.2xlarge EC2 instance.

## app.tf
resource "aws_instance" "example" {
  ami                  = "ami-example"
  instance_type        = "c5.2xlarge"
  key_name             = "key-example"
  availability_zone    = "us-east-1a"
  subnet_id            = "subnet-example"
  security_groups      = ["sg-example"]
}

Later on, your developers ask for a t2.micro EC2 dev instance of the production app in order to execute some test.

## app-prod.tf
resource "aws_instance" "example_prod" {
  instance_type        = "c5.2xlarge"
  ami                  = "ami-example"
  key_name             = "key-example"
  availability_zone    = "us-east-1a"
  subnet_id            = "subnet-example"
  security_groups      = ["sg-example"]
}

## app-dev.tf
resource "aws_instance" "example_dev" {
  instance_type        = "t2.micro"
  ami                  = "ami-example"
  key_name             = "key-example"
  availability_zone    = "us-east-1a"
  subnet_id            = "subnet-example"
  security_groups      = ["sg-example"]
}

The simplest way to answer your developers is to reproduce the same code and change the instance type, but you need to copy paste the same code twice for a simple change in the instance type. This is where modules become interesting!

In order to avoid copy-paste, you will create a module as simple as that:

## github.com/example/app
resource "aws_instance" "example_dev" {
  ami               = "ami-example"
  instance_type     = "${var.instance_type}"
  key_name          = "key-example"
  availability_zone = "us-east-1a"
  subnet_id         = "subnet-example"
  security_groups   = ["sg-example"]
}

You can then upload this module into SCM tools like GitHub or BitBucket and reference it in your Terraform files.

Then, you'll need to reference your module:

## app-prod.tf
module "production" {
  source        = "github.com/example/app"
  instance_type = "c5.5xlarge"
}

## app-dev.tf
module "development" {
  source        = "github.com/example/app"
  instance_type = "t2.micro"
}

And voila! No copy paste! This example shows a single EC2 instance app, but in practice, apps are way more complex than that (Load Balancer, ASG, Launch configuration...) and the use of modules reduce a lot the complexity of the reusable code.

 

Working with a Team

 

At GumGum, we are using Bitbucket as our Version Control service. Using an SCM with Terraform is very useful to manage and maintain our entire infrastructure without breaking things within a team.

The modules are written and maintained by the GumGum DevOps team, for every change in a module, we create a Pull Request and ask for at least one of our DevOps team member to review it.

Once reviewed and merged, we tag the module and reference it later with the specific tag. The master branch should always belong to our live infrastructure.

For style guide, we are using pre-commit with terraform fmthttps://pre-commit.com/.

For every commit that a developer does, the terraform style is fixed and we check for merge conflicts. Once the pre-commit passed, the developers can create the Pull Request that will be reviewed and merged later on.

 

## .pre-commit-config.yml
repos:
- repo: git://github.com/antonbabenko/pre-commit-terraform
  sha: v1.4.0
  hooks:
    - id: terraform_fmt
- repo: git://github.com/pre-commit/pre-commit-hooks
  sha: v1.2.0
  hooks:
    - id: check-merge-conflict

 

Terragrunt

 

Terragrunt is a wrapper on top of Terraform that implements the best practices of Terraform and integrates well with modules and remote state management.

We use Terragrunt in order to reference the modules and provide associated input variables. Our DevOps team implements and manage the Terraform modules where the developers will use Terragrunt to execute the modules and build their infrastructure based on their need.

In this example, you can see that we are defining the input variables that will be used by our tf_jmanage Terraform module.  This will create the dev environment for our jmanage application.

If developers need a prod environment for their jmanage application, they'll just need to specify different input variables that match with their environment.

With 2 simple files containing few variables, we are able to bring up 2 different environments for a specific service. If tomorrow, we need the same application in a different region, we'll just need to change the region, run terragrunt apply (= terraform apply) et voila!

 

Introducing an IAC tool at GumGum helped a lot the DevOps team to maintain and manage the entire infrastructure. We are now able to bring up new services in a more effective way. In addition to that, it gives us the opportunity to delegate more to the developers, and reduce borders between development and operations.

 

 

Guides