Skip to main content

iac


Infrastructure as Code (IaC) Documentation

Complete guide to managing and provisioning infrastructure through code using tools like Terraform, AWS CloudFormation, and Ansible.

What is Infrastructure as Code?

Infrastructure as Code (IaC) is the practice of managing and provisioning computing infrastructure through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.

Benefits of IaC

  • Version Control: Track infrastructure changes in Git
  • Consistency: Identical environments every time
  • Automation: Eliminate manual configuration
  • Reusability: Share and reuse infrastructure templates
  • Documentation: Code serves as documentation
  • Cost Reduction: Optimize resource usage

IaC Tools Comparison

ToolTypeBest ForLanguage
TerraformDeclarativeMulti-cloudHCL
AWS CloudFormationDeclarativeAWS-specificJSON/YAML
AnsibleProceduralConfiguration ManagementYAML
PulumiDeclarativeModern programmingPython, TypeScript, Go
ChefProceduralConfiguration ManagementRuby DSL

Terraform

Terraform is an open-source IaC tool that allows you to build, change, and version infrastructure safely and efficiently.

Terraform Basics

Directory Structure:

terraform-project/
├── main.tf # Main configuration
├── variables.tf # Input variables
├── outputs.tf # Output values
├── terraform.tfvars # Variable values
└── modules/ # Reusable modules
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf

Basic Terraform Configuration

main.tf:

# Configure the AWS Provider
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
}
}

provider "aws" {
region = var.aws_region
}

# Create VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "${var.project_name}-vpc"
Environment = var.environment
ManagedBy = "Terraform"
}
}

# Create Subnet
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]

map_public_ip_on_launch = true

tags = {
Name = "${var.project_name}-public-${count.index + 1}"
}
}

# Create Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id

tags = {
Name = "${var.project_name}-igw"
}
}

# Create Security Group
resource "aws_security_group" "web" {
name = "${var.project_name}-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id

ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "${var.project_name}-web-sg"
}
}

# Launch EC2 Instance
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
subnet_id = aws_subnet.public[0].id

vpc_security_group_ids = [aws_security_group.web.id]

user_data = file("${path.module}/scripts/user_data.sh")

tags = {
Name = "${var.project_name}-web-${count.index + 1}"
}
}

# Data source for latest Amazon Linux AMI
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]

filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}

variables.tf:

variable "aws_region" {
description = "AWS region for resources"
type = string
default = "us-east-1"
}

variable "project_name" {
description = "Project name for resource tagging"
type = string
}

variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}

variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}

variable "availability_zones" {
description = "List of availability zones"
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}

variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}

variable "instance_count" {
description = "Number of instances to launch"
type = number
default = 2
}

outputs.tf:

output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}

output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}

output "web_instance_ids" {
description = "IDs of web instances"
value = aws_instance.web[*].id
}

output "web_instance_public_ips" {
description = "Public IPs of web instances"
value = aws_instance.web[*].public_ip
}

Terraform Commands

# Initialize Terraform
terraform init

# Format configuration files
terraform fmt

# Validate configuration
terraform validate

# Plan changes
terraform plan

# Apply changes
terraform apply

# Apply with auto-approve
terraform apply -auto-approve

# Destroy infrastructure
terraform destroy

# Show current state
terraform show

# List resources in state
terraform state list

# Import existing resource
terraform import aws_instance.example i-1234567890abcdef0

# Refresh state
terraform refresh

# Output specific value
terraform output vpc_id

Terraform Modules

Create reusable modules for common patterns:

modules/web-app/main.tf:

variable "app_name" { type = string }
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string) }

resource "aws_lb" "main" {
name = "${var.app_name}-alb"
internal = false
load_balancer_type = "application"
subnets = var.subnet_ids

tags = {
Name = "${var.app_name}-alb"
}
}

output "lb_dns_name" {
value = aws_lb.main.dns_name
}

Using the module:

module "web_app" {
source = "./modules/web-app"
app_name = "myapp"
vpc_id = aws_vpc.main.id
subnet_ids = aws_subnet.public[*].id
}

output "app_url" {
value = module.web_app.lb_dns_name
}

AWS CloudFormation

AWS CloudFormation provides IaC for AWS resources using JSON or YAML templates.

CloudFormation Template Example

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Web application infrastructure'

Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Description: Environment name

InstanceType:
Type: String
Default: t3.micro
AllowedValues: [t3.micro, t3.small, t3.medium]
Description: EC2 instance type

Mappings:
RegionAMI:
us-east-1:
AMI: ami-0c55b159cbfafe1f0
us-west-2:
AMI: ami-0d1cd67c26f5fca19

Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${Environment}-vpc

PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${Environment}-public-subnet

InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${Environment}-igw

AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway

SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Web server security group
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${Environment}-web-sg

WebServer:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [RegionAMI, !Ref 'AWS::Region', AMI]
InstanceType: !Ref InstanceType
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref SecurityGroup
Tags:
- Key: Name
Value: !Sub ${Environment}-web-server

Outputs:
VPCId:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub ${Environment}-VPC

WebServerPublicIP:
Description: Public IP of web server
Value: !GetAtt WebServer.PublicIp

CloudFormation Commands

# Create stack
aws cloudformation create-stack \
--stack-name my-stack \
--template-body file://template.yaml \
--parameters ParameterKey=Environment,ParameterValue=prod

# Update stack
aws cloudformation update-stack \
--stack-name my-stack \
--template-body file://template.yaml

# Delete stack
aws cloudformation delete-stack --stack-name my-stack

# Describe stack
aws cloudformation describe-stacks --stack-name my-stack

# List stacks
aws cloudformation list-stacks

# Validate template
aws cloudformation validate-template \
--template-body file://template.yaml

Ansible

Ansible is an agentless automation tool for configuration management, application deployment, and task automation.

Ansible Playbook Example

---
- name: Configure web servers
hosts: webservers
become: yes
vars:
app_name: myapp
app_port: 3000

tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600

- name: Install required packages
apt:
name:
- nginx
- nodejs
- npm
state: present

- name: Create app directory
file:
path: "/opt/{{ app_name }}"
state: directory
mode: '0755'

- name: Copy application files
copy:
src: "{{ playbook_dir }}/app/"
dest: "/opt/{{ app_name }}/"
mode: '0644'

- name: Install npm dependencies
npm:
path: "/opt/{{ app_name }}"
state: present

- name: Configure Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/{{ app_name }}
notify: restart nginx

- name: Enable Nginx site
file:
src: /etc/nginx/sites-available/{{ app_name }}
dest: /etc/nginx/sites-enabled/{{ app_name }}
state: link
notify: restart nginx

- name: Start application
systemd:
name: "{{ app_name }}"
state: started
enabled: yes

handlers:
- name: restart nginx
service:
name: nginx
state: restarted

Nginx template (nginx.conf.j2):

server {
listen 80;
server_name {{ ansible_hostname }};

location / {
proxy_pass http://localhost:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

Ansible Commands

# Run playbook
ansible-playbook -i inventory playbook.yml

# Check syntax
ansible-playbook playbook.yml --syntax-check

# Dry run (check mode)
ansible-playbook playbook.yml --check

# Run specific tags
ansible-playbook playbook.yml --tags "configuration"

# Run ad-hoc command
ansible webservers -i inventory -m ping

# Gather facts
ansible webservers -i inventory -m setup

Best Practices

General IaC Best Practices

  1. Version Control Everything
# .gitignore for Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
.terraform.lock.hcl
  1. Use Modules for Reusability
  2. Implement State Management
  3. Use Variables and Secrets Management
  4. Document Your Infrastructure
  5. Test Before Applying
  6. Implement CI/CD for Infrastructure

Security Best Practices

# Use AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db/password"
}

resource "aws_db_instance" "main" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

# Encrypt sensitive outputs
output "db_password" {
value = aws_db_instance.main.password
sensitive = true
}

CI/CD for Infrastructure

GitHub Actions for Terraform

name: Terraform CI/CD

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
terraform:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0

- name: Terraform Init
run: terraform init

- name: Terraform Format
run: terraform fmt -check

- name: Terraform Validate
run: terraform validate

- name: Terraform Plan
run: terraform plan
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Troubleshooting

Common Issues

Terraform state locked:

# Force unlock (use carefully)
terraform force-unlock LOCK_ID

CloudFormation stack stuck:

# Cancel update
aws cloudformation cancel-update-stack --stack-name my-stack

Ansible connection issues:

# Test connection
ansible all -i inventory -m ping -vvv

Resources

Next Steps


Need IaC implementation help? Contact us at contact@techdocs.co.in

Was this page helpful?