Devops
DevopsIntermediate

Infrastructure as Code: Best Practices with Terraform and CloudFormation

DeveloperHat Team
5 min read
IaCTerraformCloudFormationAWSInfrastructure

TL;DR

Master Infrastructure as Code using Terraform and AWS CloudFormation for efficient cloud resource management.

Infrastructure as Code: Best Practices with Terraform and CloudFormation

Infrastructure as Code (IaC) is a fundamental DevOps practice that enables teams to manage and provision infrastructure through code rather than manual processes.

$1

$1

1. Version Control

2. Reproducibility

3. Consistency

4. Automation

5. Documentation

$1

$1

``hcl

main.tf

terraform {

required_providers {

aws = {

source = "hashicorp/aws"

version = "~> 4.0"

}

}

}

provider "aws" {

region = "us-west-2"

}

resource "aws_instance" "web" {

ami = "ami-0c55b159cbfafe1f0"

instance_type = "t2.micro"

tags = {

Name = "WebServer"

Environment = "Production"

}

}

`

$1

`hcl

variables.tf

variable "environment" {

type = string

description = "Environment name"

default = "development"

}

variable "instance_type" {

type = string

description = "EC2 instance type"

default = "t2.micro"

}

outputs.tf

output "instance_ip" {

value = aws_instance.web.public_ip

description = "Public IP of the web server"

}

`

$1

$1

`yaml

AWSTemplateFormatVersion: '2010-09-09'

Description: 'Web Server Infrastructure'

Parameters:

EnvironmentName:

Type: String

Default: Development

Resources:

WebServerInstance:

Type: AWS::EC2::Instance

Properties:

InstanceType: t2.micro

ImageId: ami-0c55b159cbfafe1f0

Tags:

- Key: Name

Value: WebServer

- Key: Environment

Value: !Ref EnvironmentName

Outputs:

WebServerIP:

Description: Public IP of the web server

Value: !GetAtt WebServerInstance.PublicIp

`

$1

$1

`hcl

modules/vpc/main.tf

module "vpc" {

source = "terraform-aws-modules/vpc/aws"

name = var.vpc_name

cidr = var.vpc_cidr

azs = var.availability_zones

private_subnets = var.private_subnet_cidrs

public_subnets = var.public_subnet_cidrs

enable_nat_gateway = true

enable_vpn_gateway = false

tags = {

Environment = var.environment

Terraform = "true"

}

}

`

$1

`yaml

Resources:

NetworkStack:

Type: AWS::CloudFormation::Stack

Properties:

TemplateURL: https://s3.amazonaws.com/templates/network.yaml

Parameters:

EnvironmentName: !Ref EnvironmentName

ApplicationStack:

Type: AWS::CloudFormation::Stack

Properties:

TemplateURL: https://s3.amazonaws.com/templates/application.yaml

Parameters:

VpcId: !GetAtt NetworkStack.Outputs.VpcId

`

$1

$1

`hcl

backend.tf

terraform {

backend "s3" {

bucket = "terraform-state-bucket"

key = "prod/terraform.tfstate"

region = "us-west-2"

dynamodb_table = "terraform-locks"

encrypt = true

}

}

`

$1

`hcl

resource "aws_dynamodb_table" "terraform_locks" {

name = "terraform-locks"

billing_mode = "PAY_PER_REQUEST"

hash_key = "LockID"

attribute {

name = "LockID"

type = "S"

}

}

`

$1

$1

`hcl

Using AWS Secrets Manager

data "aws_secretsmanager_secret_version" "db_password" {

secret_id = "prod/db/password"

}

resource "aws_db_instance" "database" {

password = data.aws_secretsmanager_secret_version.db_password.secret_string

# Other configuration...

}

`

$1

`json

{

"Version": "2012-10-17",

"Statement": [

{

"Effect": "Allow",

"Action": [

"s3:ListBucket",

"s3:GetObject",

"s3:PutObject"

],

"Resource": [

"arn:aws:s3:::terraform-state-bucket",

"arn:aws:s3:::terraform-state-bucket/*"

]

}

]

}

`

$1

$1

`hcl

test/main_test.go

package test

import (

"testing"

"github.com/gruntwork-io/terratest/modules/terraform"

"github.com/stretchr/testify/assert"

)

func TestTerraformWebServer(t *testing.T) {

terraformOptions := &terraform.Options{

TerraformDir: "../",

Vars: map[string]interface{}{

"environment": "test",

},

}

defer terraform.Destroy(t, terraformOptions)

terraform.InitAndApply(t, terraformOptions)

instanceID := terraform.Output(t, terraformOptions, "instance_id")

assert.NotEmpty(t, instanceID)

}

`

$1

`yaml

test-template.yaml

AWSTemplateFormatVersion: '2010-09-09'

Transform: AWS::Serverless-2016-10-31

Resources:

TestFunction:

Type: AWS::Serverless::Function

Properties:

Handler: index.handler

Runtime: nodejs14.x

CodeUri: ./test

Events:

TestApi:

Type: Api

Properties:

Path: /test

Method: get

`

$1

$1

`yaml

name: Terraform CI

on:

push:

branches: [ main ]

pull_request:

branches: [ main ]

jobs:

terraform:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v2

- name: Setup Terraform

uses: hashicorp/setup-terraform@v1

- name: Terraform Init

run: terraform init

- name: Terraform Format

run: terraform fmt -check

- name: Terraform Plan

run: terraform plan

- name: Terraform Apply

if: github.ref == 'refs/heads/main'

run: terraform apply -auto-approve

`

$1

`groovy

pipeline {

agent any

environment {

AWS_CREDENTIALS = credentials('aws-credentials')

}

stages {

stage('Terraform Init') {

steps {

sh 'terraform init'

}

}

stage('Terraform Plan') {

steps {

sh 'terraform plan -out=tfplan'

}

}

stage('Terraform Apply') {

when {

branch 'main'

}

steps {

sh 'terraform apply -auto-approve tfplan'

}

}

}

}

`

$1

$1

`hcl

Using Infracost

provider "aws" {

region = "us-west-2"

}

resource "aws_instance" "web" {

instance_type = "t2.micro"

# Other configuration...

# Monthly cost: ~$8.50

}

resource "aws_s3_bucket" "storage" {

bucket = "my-app-storage"

# Monthly cost: ~$0.023 per GB

}

`

$1

`hcl

locals {

common_tags = {

Environment = var.environment

Project = var.project_name

Owner = var.team

CostCenter = var.cost_center

}

}

resource "aws_instance" "web" {

# ... other configuration ...

tags = merge(

local.common_tags,

{

Name = "WebServer"

}

)

}

`

$1

$1

`hcl

resource "aws_backup_plan" "example" {

name = "tf_example_backup_plan"

rule {

rule_name = "tf_example_backup_rule"

target_vault_name = aws_backup_vault.example.name

schedule = "cron(0 12 * ? )"

lifecycle {

delete_after = 14

}

}

}

resource "aws_backup_selection" "example" {

name = "tf_example_backup_selection"

plan_id = aws_backup_plan.example.id

iam_role_arn = aws_iam_role.example.arn

resources = [

aws_db_instance.example.arn,

aws_ebs_volume.example.arn

]

}

`

$1

`hcl

provider "aws" {

alias = "primary"

region = "us-west-2"

}

provider "aws" {

alias = "dr"

region = "us-east-1"

}

module "primary" {

source = "./application"

providers = {

aws = aws.primary

}

}

module "dr" {

source = "./application"

providers = {

aws = aws.dr

}

}

``

$1

Implementing Infrastructure as Code requires:

1. Proper planning and architecture

2. Version control integration

3. Automated testing and deployment

4. Security best practices

5. Cost optimization

Remember to:

  • Keep code DRY and modular
  • Use version control
  • Implement proper testing
  • Monitor costs
  • Document everything
  • $1

    1. [Terraform Documentation](https://www.terraform.io/docs)

    2. [AWS CloudFormation User Guide](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide)

    3. [Infrastructure as Code Best Practices](https://docs.aws.amazon.com/whitepapers/latest/introduction-devops-aws/infrastructure-as-code.html)

    4. [Terraform Best Practices](https://www.terraform-best-practices.com/)

    Why This Matters

    Understanding the business and technical context helps you make informed decisions rather than blindly following patterns.

    Trade-offs to Consider

    Every architectural decision involves trade-offs. Consider your specific requirements, team expertise, and scale when evaluating options.

    When NOT to Use This

    Knowing when a solution doesn't apply is as valuable as knowing when it does. Consider alternatives for your specific situation.

    Decision Framework

    Use this framework to evaluate whether this approach is right for your use case based on your specific constraints and requirements.