Infrastructure Provisioning with Terraform and Heroku

Infrastructure Provisioning with Terraform and Heroku

In this article, we are going to explore terraform and provision a Heroku infrastructure for a node js app.

Prerequisites

  • I assume you have programming experience with at least Javascript on the server-side.

  • I assume you have used Heroku before to host a web application

  • You need the Heroku CLI. You can get that here

  • You have no experience with any DevOps tool but you can open a command-line interface on your laptop

Objectives

By the end of this tutorial you should be able to:

  • Explain what infrastructure provisioning is and why it is essential in the development life cycle of your web applications

  • Explain terraform and how it differs from other DevOps tool

  • Provision a Heroku Infrastructure for a node js application that uses a PostgreSQL database and Redis as a cache

Grab a drink! 1511102074_jason-statham-drinks-coffee.gif

What is Infrastructure as Code?

Infrastructure as code(IaC) is the process of provisioning, configuring and managing your cloud resources in a way that it is both human-readable and machine consumable. In simpler terms, it means we are able to set up the cloud environment our web application will live in from database connection, networking, operating systems and managing servers. This is possible through the use of a high declarative coding language to automate this process and this automation eliminates the need for developers to manually provision and manage servers, operating systems, database connections, storage, and other infrastructure elements every time they want to develop, test, or deploy a software application.

Why is such process essential?

Traditionally, system administrators would physically put up servers, configure them and deploy applications on them manually. This approach gave rise to many problems such as:

  • Increase in cost of running such servers as more money will be spent on hardware and the space to house them

  • Difficulty to scale quick and fast

  • Inconsistency during configuration

  • Security issues

But with the rise of IaC and Cloud Computing, applications are deployed faster, as you can quickly set up your complete infrastructure by running a script. You can do that for every environment, from development to production, passing through staging, QA, and more. IaC solves the problem of inconsistency as manual tasks during the configuration of multiple servers are eliminated by having the config files themselves be the single source of truth. That way, you guarantee the same configurations will be deployed over and over, without discrepancies. By employing cloud computing along with IaC, you dramatically reduce your costs. That’s because you won’t have to spend money on hardware, hire people to operate it, and build or rent physical space to store it. IaC tools are in varieties and are classified into 4 parts: Provisioning, Configuration management, Packaging and Monitoring tools

Enter Terraform

Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.- Hashicorp

What this simply means is that terraform allows us to bootstrap or initialize our infrastructure. It is not a configuration tool as configuration tools to install and manage software on a machine that already exists. Terraform enables any configuration management tool to be used to set up a resource once it has been created. Terraform focuses on the higher-level abstraction of the datacenter and associated services, without sacrificing the ability to use configuration management tools to do what they do best. It also embraces the same codification that is responsible for the success of those tools, making entire infrastructure deployments easy and reliable. It does this by using a special declarative language known as the HASHICORP CONFIGURATION LANGUAGE(HCL)

This language is used in configuration files with a .tf extension to detail the infrastructure setup. The good thing about terraform is that it supports every if not all cloud providers, from aws, azure and GCP to IBM Cloud. It also allows you to provision infrastructure on many of these cloud providers simultaneously. This means that you can orchestrate an AWS and OpenStack cluster simultaneously while enabling 3rd-party providers like Cloudflare and DNSimple to be integrated to provide CDN and DNS services. This enables Terraform to represent and manage the entire infrastructure with its supporting services, instead of only the subset that exists within a single provider. It provides a single unified syntax, instead of requiring operators to use independent and non-interoperable tools for each platform and service.

Getting Started

  • Installation

To use terraform, you need to install the terraform cli. Here is how to do it depending on the operating system you are using:

MacOSX

You can install terraform on mac using the homebrew package manager, simply do:

  $ brew install hashicorp/tap/terraform

To update to the latest, run

  $ brew upgrade hashicorp/tap/terraform

After installing, you can verify the installation by running:

   $ terraform -help
   Usage: terraform [-version] [-help] <command> [args]

   The available commands for execution are listed below.
  The most common, useful commands are shown first, followed 
  by less common or more advanced commands. 

Windows

To install on windows, you need the chocolatey package manager. If you don't have Chocolatey installed on your system, kindly follow this link to install.

After installing choco, simply run in your powershell terminal:

    choco install terraform 

Linux Ubuntu:

Add the Hashicorp CPG key:

    curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

Add the official HashiCorp Linux repository.

 $ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"

Update and install.

  $ sudo apt-get update && sudo apt-get install terraform

Don't forget to verify your installation by running:

   $ terraform --version

Provisioning your first infrastructure

We are going to create our first infrastructure and to do that, we are going to create a directory and create a file called main.tf in it.

Open your terminal and do:

  mkdir terraform_example

Then enter the directory you just created:

    cd terraform_example && touch main.tf

Open your file you created in your text editor, I'm using Visual Studio Code so I'll simply do:

    code main.tf

Before we proceed into writing our first configuration, we need to understand the concept of the hashicorp configuration language (HCL). HCL (HashiCorp Configuration Language) is a configuration language built by HashiCorp. The goal of HCL is to build a structured configuration language that is both human and machine-friendly for use with command-line tools but specifically targeted towards DevOps tools, servers, etc. HCL is also fully JSON compatible. That is, JSON can be used as a completely valid input to a system expecting HCL. This helps to make systems interoperable with other systems.

HCL is heavily inspired by libucl, nginx configuration, and others similar. The syntax looks like this:

  service {
key = "value"
  }

Also, to provision infrastructure using terraform, we need to understand the concepts of providers and resources. Terraform is used to create, manage, and update infrastructure resources such as physical machines, VMs, network switches, containers, databases and more. Almost any infrastructure type can be represented as a resource in Terraform. A provider on the other hand is responsible for understanding API interactions and exposing resources. Most providers configure a specific infrastructure platform (either cloud or self-hosted). Providers can also offer local utilities for tasks like generating random numbers for unique resource names. So in effect, a provider is simply the platform you want your infrastructure to live in e.g AWS, Digital Ocean, Heroku, e.t.c.

Now let us get back to our main.tf. We need to tell terraform first the platform our infrastructure will live in, in this case, Heroku and to do that we simply declare it in our main.tf file like this:

     provider "heroku" {}

Now, we need terraform to communicate with Heroku's API and for that to happen we need to initialize it. To do that we simply run this command in our terminal:

      $ terraform init

Terraform will download the API's needed in your current directory and you will see a .terraform directory created in your current working directory. You might encounter errors during the initialization. No need to panic, simply run :

      $ terraform 0.13 upgrade

Now after you have successfully done this, run the init command again.

Terraform will need a way to connect to your personal Heroku account and for that to happen it requires your email and API token and to get your API token, simply connect to your heroku account via your terminal by running :

    $ heroku login

After logging in, you need to create an API token, simply do that by running:

     $ heroku authorizations:create  --description my_api_token 

The --description parameter is a human-readable name to indicate the purpose or identity of each authorization.

After that, you need to set Your API key and email address as environment variables by running :

      $ export HEROKU_API_KEY=<TOKEN> HEROKU_EMAIL=<EMAIL>

Next, we need to create our Heroku application that provides us with a Heroku URL and to do that, we simply add this to our main.tf file:

         resource "heroku_app"  "node_app" {
         name = "node-test-app-02"
         region = "us" 
         } 

Now let us understand what we have here:

heroku_app: This tells terraform you will be creating an app resource on Heroku.

node_app - Terraform requires you give all your resources a particular name and for this configuration, we will be calling the Heroku app we'll be creating, "node_app" on terraform.

name: This is what our app will be called on heroku.

region: This is the region where our app should be deployed. By default on Heroku, it is the 'United States'.

Then we need to check if we have configured this properly and to do that, we simply run :

     $ terraform plan

This command checks for error and for current changes to your infrastructure. If there were no errors, we can simply apply this change to heroku :

     $ terraform apply 

You will see a new app on your heroku dashboard like this:

Screenshot from 2020-09-13 19-32-05.png

Now as stated at the beginning of this article, our node js application will use a PostgreSQL database and Redis as a cache, we will add that as resources to our terraform file.In the main.tf simply add :

     resource "heroku_addon" "database" {
     app  = heroku_app.node_app.name
     plan = "heroku-postgresql:hobby-basic"
     }

     resource "heroku_addon" "redis-db" {
    app  = heroku_app.node_app.name
    plan = "heroku-redis:hobby-dev"
    }

The app variable simply tells terraform to attach the following resource being created to a particular application on heroku. Recall terraform can create multiple infrastructures at a go, so this tells heroku which of the following app such resources should be attached to. Now we run:

      $ terraform plan 

then:

     $ terraform apply 

The following resources will be created on heroku and will be available with the following environment variables; DATABASE_URL and REDIS_URL respectively.

Now we spin up a simple node js server by doing:

      $ mkdir -p src/index.js && cd src &&npm init -y && npm install express pg pg-hstore

then in our package.json, we add this to our start script:

        "start": "node index.js"

then we simply add this to our javascript file:

          const express = require('express');
          const { Client } = require('pg');
          const redis = require('redis');
           const app = express();

            // connect postgresql database 
            const client = new Client({
           connectionString: DATABASE_URL
          });

         client.connect()
         .then(() => {
          console.log('database connected')
        })

       // connect redis 

       const redisClient = redis.createClient();

        redisClient.on('connect', ()=>{
         console.log('redis connected');
        });

        redisClient.on('error', ()=>{
         console.error('connection error');
        });

        app.get('/', function(req, res){
          res.send({ message: "Terraform app" })
        });

        app.listen(4000, console.log('server is running..'))

then we add the following to our terraform file:

          resource "heroku_build" "deploy_code" {
          app = heroku_app.node_app.id

          source = {

           path = "./src"
           }
          }

Launch the app's web process by scaling-up, we add :

        resource "heroku_formation" "example" {
       app        = heroku_app.node_app.name
        type       = "web"
        quantity   = 1
        size       = "Standard-1x"
       depends_on = ["heroku_build.deploy_code"]
      }

then we run:

      $ terraform apply

If everything deploys without any errors, you can test the URL on postman by opening it on https://<name of your heroku app>.herokuapp.com.

You can also delete the infrastructure you just created by simply running

         $ terraform destroy

Conclusions

We have finally been able to use Terraform to provision infrastructure and deploy our node js app, however, it is important to note that terraform is best used when bootstrapping an infrastructure for the first time and not used as a configuration management tool. To manage and update our infrastructure resources from time to time, tools like Ansible and Chef stand out for this purpose.

I'd appreciate questions or any feedback you have, kindly leave them in the comment section below.

Thank you for reading!