Provisioning Heroku Infrastructure Using Terraform
For a long period of time, infrastructure that runs most applications or systems has been managed manually either via a Dashboard Console, CLI tools, or some form of scripting. As systems have evolved and become more complex, with more team members managing this infrastructure, finding a way to efficiently manage and/or quickly spin up new environments for either testing or live systems can be a massive game changer for most companies - especially in the current agile and fast-paced world of technology innovation. In the DevOps ecosystem, there are a lot of tools and ways to keep up with the fast-paced innovation, managing your infrastructure as code is one of many ways to improve overall team efficiency and delivery of software products or features.
In the last couple of years, many tools have been created that enable automation, and make it easier to provision infrastructure, particularly in cloud-native environments. Most of the cloud providers have built tools that allow one to easily spin up infrastructure. For this blog, I will focus on a tool that uses code to define and provision infrastructure in any cloud or platform Providers environment. - the tool is Terraform!
Terraform is a tool that allows one to define infrastructure as code (iac) - The language used is a declarative configuration language known as HCL or optionally JSON.
With Terraform being just the tool, one needs to select a particular "Provider" to create actual infrastructure. In our case, we will use Heroku as a provider to be creating infrastructure in. There's a long list of providers that Terraform currently supports, that list can be found here To create Heroku resources using terraform, we will follow these steps;
Define what version of Terraform to use and the corresponding compatible version for the Provider.
Ensure authentication with the correct account for the Provider.
Use HCL to define Heroku resources.
Use Terraform to view the diff of our infrastructure
Apply changes to remote infrastructure
1. Define Terraform and Provider versions
There's a list of Terraform versions to use, for this example blog we will version v0.12.31
, and the corresponding compatible version for the Provider, Heroku is v2.1
. Within a root directory, create a providers.tf
file and add these lines below
terraform {
required_version = "0.12.31"
}
terraform {
required_providers {
heroku = {
version = "2.1"
}
}
}
Current directory structure would look like below;
├── example
│ ├── providers.tf
2. Authenticate with the Provider
First, run the $ terraform init
command to ensure the correct version has been installed on your local machine and properly configured. To ensure resources are created in the correct Provider ( Heroku ) account, Login to Heroku via CLI using $ heroku login
command.
The $ terraform init
command also initializes the location for where your infrastructure state will be stored. In our case above, the state of the infrastructure will be stored locally in a file that is automatically generated after we define a few Heroku resources. This state file is usually stored as terraform.state
in the root directory.
3. Define Heroku resources using HCL
Lets create a main.tf
file that will have the following lines of code that define 9(nine) Heroku resources, ie a Heroku Pipeline with staging and production apps, each app having a Postgres and NewRelic add-on.
# define pipeline, with staging and production apps
resource "heroku_pipeline" "demo-pipeline" {
name = "demo-pipeline"
}
resource "heroku_app" "demo-app-staging" {
name = "demo-staging"
region = "us"
}
resource "heroku_pipeline_coupling" "demo-app-pipeline-coupling" {
app = heroku_app.demo-app-staging.id
pipeline = heroku_pipeline.demo-pipeline.id
stage = "staging"
}
resource "heroku_app" "demo-app-prod" {
name = "demo-prod"
region = "us"
}
resource "heroku_pipeline_coupling" "demo-app-pipeline-coupling-prod" {
app = heroku_app.demo-app-prod.id
pipeline = heroku_pipeline.demo-pipeline.id
stage = "production"
}
# define add-ons for staging app
resource "heroku_addon" "newrelic-staging" {
app = heroku_app.demo-app-staging.id
plan = "newrelic:wayne"
}
resource "heroku_addon" "pg-staging" {
app = heroku_app.demo-app-staging.id
plan = "heroku-postgresql:hobby-dev"
}
# define add-ons for production app
resource "heroku_addon" "newrelic-prod" {
app = heroku_app.demo-app-prod.id
plan = "newrelic:wayne"
}
resource "heroku_addon" "pg-prod" {
app = heroku_app.demo-app-prod.id
plan = "heroku-postgresql:hobby-dev"
}
The directory structure should look something like this;
├── example
│ ├── providers.tf
│ ├── main.tf
4. View diff of our infrastructure
Within the root directory, run the $ terraform plan
command - which will show the diff between the local state infrastructure and what needs to be created in the Provider's (Heroku) actual infrastructure. The output of the above command will have a list of resources that either need to be "added", "changed" or "destroyed". In our example above, it would have around 9 resources to add... with console output as this -> Plan: 9 to add, 0 to change, 0 to destroy.
5. Apply changes to Remote Infrastructure
Once we verify the resources we need to create via the $ terraform plan
command, we can then run the $ terraform apply
command that does the "behind the scenes" work of making actual API calls to the Provider and creating the resources we have defined in our main.tf
file. We can then log in to our Heroku account and verify that the above 9 resources have been successfully created.
Conclusion
At any given time, we can now know the state of our infrastructure by simply referencing the terraform.state
file located in our root directory. Also, a backup of this state is stored in another file named the terraform.state.backup
. With terraform, we can not only manage infrastructure with a version control system but can also quickly within minutes if not seconds provision infrastructure environments much much easily.
In our next blog, we will explore how to manage the state of our infrastructure using Terraform Cloud.
Sources:
- Terraform Official Documentation