Search

Part 0 - Terraform Deep Dive: End-to-End Cloud-Agnostic Infrastructure as Code

Intro

In this multi-part blog series we will talk about one of our favorite tools at Zaden Technologies: Terraform! Terraform is a product by HashiCorp that provides an Infrastructure as Code (IaC) solution for provisioning cloud resources using different cloud services providers (e.g. AWS, Azure, GCP, Gitlab, etc). We will walk you through some technical use cases and examples using Terraform to spin up a fully functioning virtual private cloud (VPC) with a software factory workload. In addition to diving deep into what features Terraform provides, we will spin up our first VPC on AWS with full connectivity to an AWS Transit Gateway (TGW), which centralizes network connectivity amongst VPCs and on-premise infrastructure.

Terraform Deep Dive Series Parts

Infrastructure as Code

Infrastructure as code, or IaC, is continually gaining popularity in the industry due to the increasing number of problems it solves in infrastructure management:

  1. Configuration Drift and Reproducibility: By using code to generate infrastructure, the same environment can be consistently and reliably created on demand. For projects that don’t take advantage of IaC, over time an environment will drift away from its original state which leads to situations where it can be difficult to determine the root cause of problems - a phenomenon known as configuration drift. With IaC, no environment gets special treatment and fresh new environments are easily created and destroyed; enabling true cattle vs pet environments.

  2. Idempotence & Convergence: Continuing the last point above, idempotence means that your IaC codified configurations have no side effects on your environment regardless of how many times you apply that infrastructure code. Good IaC makes use of a declarative design model, meaning only the actions needed to bring the environment to the desired state are executed; if the environment is already in the desired state, no actions are taken.

  3. Easing collaboration: Having the IaC in a version control system like Git allows teams to collaborate on infrastructure. Team members can get specific versions of the code, create their own environments for testing, or other scenarios. Also, if a change to critical infrastructure is made mistakenly, going back to a previously-known good state is a simple matter of, for example, a ‘git revert’ (finally, we have easy infrastructure rollbacks!).

  4. Self-service infrastructure: A pain point that often existed for developers before moving to cloud infrastructure was the delays required to have operations and IT teams create the infrastructure they needed to build new features and tools. With the elasticity of the cloud allowing resources to be created on-demand, developers can provision the infrastructure they need, when they need it.


Terraform Deep Dive

More than providing IaC, Terraform enables infrastructure engineers to build, change, and version control cloud infrastructure definitions safely and efficiently. Key terraform enablers are: Terraform’s HashiCorp Configuration Language (HCL), Providers, and State.


HCL

The HashiCorp Configuration Language (HCL) is a configuration language developed by HashiCorp. HCL is used with HashiCorp’s cloud infrastructure automation tools, primarily Terraform. The language was created with the goal of being both human and machine friendly. It is JSON compatible, making it interoperable with other systems outside of the Terraform product line (e.g. Terragrunt’s Terratest).


--- Syntax Overview---

terraform {
   required_version = ">= 0.15.1"

   required_providers {
      aws = {
         source  = "hashicorp/aws"
         version = "3.38.0"
      }
      local = {
         source  = "hashicorp/local"
         version = "2.1.0"
      }
   }
}

...
provider "aws"{
   region = var.region
}    

HCL syntax is comprised of blocks that define a variety of configurations available to Terraform. Provider plugins give more details about the available base Terraform configurations - more on providers later

Terraform blocks are comprised of key = value pairs, which accept values of type string, number, boolean, map, and list. Terraform HCL also supports a variety of expressions that can be interacted with, if desired, with a number of built-in functions.

See Terraform’s Configuration Syntax documentation for more details.

Providers

Terraform relies on plugins called "providers" to interact with cloud providers, SaaS providers, and other APIs. These plugins (e.g., azurerm, aws) control what type of resources a user can create, control, and manage using Terraform. You can explore the available Terraform plugins here. Each provider adds a set of resource types and/or data sources that Terraform can manage. Every resource type is implemented by a provider; without providers, Terraform can't manage any kind of infrastructure.

For more information on Terraform Providers, see the following: Terraform Providers Intro Terraform Providers Configuration


State

Terraform stores information about your infrastructure in a state file. This state file keeps track of resources created by your configuration and maps them to real-world resources codified by your provider plugin of choice. Terraform State helps provide idempotence to Terraform, giving it the ability to know if a resource is present and prevents it from being created again when the same configuration executes. There are two ways to store state: (1) locally via a tfstate file (not recommended for most use cases) or (2) remotely via a remote backend (e.g., S3, azurerm, pg, etc).

Remote state storage unlocks a lot of decentralized capabilities for development teams as it allows users to use one or many workspaces to control individual deployment environments; as well as production, staging, and test. This also makes management of A/B testing and blue/green environments painless!


--- S3 Backend with auto encryption and Dynamo DB locking---
terraform {
  backend "s3" {
    encrypt              = true
    bucket               = "olympus-software-factory-backend"
    key                  = "tfstate"
    workspace_key_prefix = "environment"
    dynamodb_table       = "terraform-state-lock-dynamo"
    region               = "us-east-2"
  }
 }    

For more on Terraform State, see here.


Why Terraform?

Monoglot that actually makes sense:

Cloud providers usually provide their own native IaC solution. For example:

Unfortunately, this requires DevOps engineers to learn multiple Domain-Specific Languages to accomplish the same results. Terraform does a great job of unifying the management of cloud service providers under one Infrastructure-as-Code solution.

Infrastructure as Code with Terraform

Multi-cloud management:

At Zaden, we use Terraform to unify our multi-cloud deployments under one workflow. The infrastructure Terraform manages can be hosted on public clouds like Amazon Web Services, Azure, and Google Cloud Platform, or on-prem in private clouds such as OpenStack, VMWare vSphere, or CloudStack.

Zaden DevOps Engineers use Terraform to manage multi-cloud deployment to AWS, Azure, and GCP

Terraform infrastructure integrations also allow you to manage software and services, including databases like MySQL, source control systems like GitHub, configuration management tools like Chef, and much more. There are over 100 publicly available infrastructure integrations. You should seriously check out all the providers supported by Terraform. It is very powerful to be able to describe all of your infrastructure in Terraform. Even if you are only using one cloud, Terraform is a great option and can make it easier to leverage multi-cloud later.


Testability:

Another huge reason we use Terraform as our desired IaC solution at Zaden is that it unlocks automated unit, integration, and acceptance testing.

Starting from the left side of the Systems Engineering “V”, one can use a tool like terraform-compliance to translate and codify infrastructure requirements. Terraform-compliance makes use of behavior-driven development (BDD) declaratives written in Gherkin to describe the behavior of the infrastructure system architecture. This is very similar to Cucumber BDD to describe software system architecture.


  Scenario Outline: Ensure that specific tags are defined
      Given I have resource that supports tags defined
      When it has tags
      Then it must contain tags
      Then it must contain "<tags>"
      And its value must match the "<value>" regex
      
      Examples:
       | tags        | value              |
       | Name        | .+                 |
       | application | .+                 |
       | role        | .+                 |
       | environment | ^(prod\|uat\|dev)$ |
terraform-compliance in action

The terraform-compliance cli allows us to perform automated acceptance tests of our infrastructure system requirements. The engineers at Zaden have made use of this tool for model-based systems engineering (MBSE), allowing our engineers and system architects to query architecture models or even perform automated Formal Quality Tests (FQTs).


Tools like Terratest by Terragrunt provide a way to perform automated unit and integration tests. If we can test our software projects, why shouldn’t we test our software-defined infrastructure?! Zaden Technologies engineers make great use of the Terratest-provided helper functions and patterns for common infrastructure testing tasks. It provides a short feedback loop and helps detect bugs as soon as possible. It also makes it possible to meet requirements with minimal complexity while enabling better code design. Terratest can also be used to test other infrastructure technologies like Kubernetes, Docker, and Packer.


package test

import (
	"testing"

	"github.com/gruntwork-io/terratest/modules/terraform"
	"github.com/stretchr/testify/assert"
)

func TestTerraformHelloWorldExample(t *testing.T) {
	// retryable errors in terraform testing.
	terraformOptions := terraform.WithDefaultRetryableErrors(t, 					&terraform.Options{
		TerraformDir: "../examples/terraform-hello-world-example",
	})

	defer terraform.Destroy(t, terraformOptions)

	terraform.InitAndApply(t, terraformOptions)

	output := terraform.Output(t, terraformOptions,		"hello_world")
	assert.Equal(t, "Hello, World!", output)
}

We will dive deeper into automated infrastructure testing using these tools in Part 2 of this series, where we will look at Test Driven Development using Terratest.


Summary

Terraform is a tool we swear by here at Zaden. We use it every day to manage our multi-cloud deployments in GCP, AWS, and Azure allowing us to be both cloud-agnostic and cloud service provider redundant. Terraform does a good job of complementing configuration management tools like Chef, Puppet, Ansible, etc. Creating a workflow that allows higher-level tasks that fall outside of the scope of either tool individually. While Terraform is an ideal tool for provisioning infrastructure across many cloud providers in a seamless way, Ansible/Chef/Puppet can complete application installs and make fine-grained configuration settings (i.e., STIG controls) on a newly provisioned Azure VM or AWS EC2 instance prior to putting it into service. In fact, this workflow is a key enabler in our Olympus Software Factory (more on this later in another blog :) ), which can be deployed on all three cloud service providers mentioned above.

In the next chapter of this series, we will dive into Module Based Infrastructure as Code: Using Terraform to modularize Cloud Resource Deployment.

49 views0 comments

Recent Posts

See All