Cloud-1 - Infrastructure as Code and Configuration Management
Introduction :
Infrastructure as Code (IaC) is the process of managing and provisioning computing infrastructure through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools. Instead of manually clicking through a cloud console, every resource — from a VPC to an EC2 instance to a firewall rule — is described in code, stored in Git, reviewed, and applied automatically. This ensures consistency, repeatability, auditability, and the ability to destroy and recreate an entire environment in minutes.
Project goals :
Cloud-1 is a 1337 project aimed at mastering cloud deployments. The objective is to use AWS to provision a complete network architecture (VPC, Subnets, Internet Gateway, Security Groups, EC2 Instances) using Terraform for infrastructure provisioning, and then configure all software on those instances using Ansible for configuration management — keeping the two concerns cleanly separated.
Tool Overview :
| Tool | Role | Approach |
|---|---|---|
| Terraform | Provision cloud resources (VMs, networks, firewalls) | Declarative — describe what you want |
| Ansible | Configure software on existing machines | Procedural — describe how to configure |
| AWS CLI | Interact with AWS API from the terminal | Imperative — run commands manually |
Walkthrough :
:one: AWS Credentials and Provider Configuration :
Never hardcode credentials in Terraform files. Use environment variables or an aws configure profile. Define the provider in a dedicated provider.tf file and pin the version:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-west-3"
# Credentials are read from env vars:
# AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
}
# Set credentials in your shell (never commit these):
$ export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
$ export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
:two: Provisioning Network Infrastructure :
Define the complete network topology in Terraform. A well-structured VPC separates public-facing resources from private ones:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# network.tf
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags = { Name = "cloud1-vpc" }
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "eu-west-3a"
map_public_ip_on_launch = true
tags = { Name = "cloud1-public-subnet" }
}
resource "aws_internet_gateway" "gw" {
vpc_id = aws_vpc.main.id
tags = { Name = "cloud1-igw" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.gw.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
:three: Security Groups (Virtual Firewalls) :
Security Groups act as stateful firewalls — they track connections, so you only need to allow inbound traffic and return traffic is automatically permitted. Define one per service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# security_groups.tf
resource "aws_security_group" "web" {
name = "cloud1-web-sg"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH from admin only"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"] # restrict SSH to your IP
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
:four: Deploying EC2 Compute Instances :
Define instances referencing the network resources by their Terraform IDs. Use data sources to look up the latest Ubuntu AMI dynamically:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# compute.tf
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
resource "aws_key_pair" "deployer" {
key_name = "cloud1-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.deployer.key_name
tags = { Name = "cloud1-web" }
}
output "web_public_ip" {
value = aws_instance.web.public_ip
}
:five: Configuration Management with Ansible :
Once Terraform provisions the instances, Ansible connects over SSH to configure software. Generate an inventory file from Terraform outputs:
1
2
3
4
5
6
# Generate inventory dynamically after terraform apply
$ terraform output -raw web_public_ip > inventory.ini
# Or write a proper inventory file:
# inventory.ini
[webservers]
1.2.3.4 ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa
Write a playbook to install and configure Nginx:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# playbook.yml
---
- name: Configure Web Server
hosts: webservers
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install Nginx
apt:
name: nginx
state: present
- name: Deploy site configuration
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/cloud1
notify: Reload Nginx
- name: Enable site
file:
src: /etc/nginx/sites-available/cloud1
dest: /etc/nginx/sites-enabled/cloud1
state: link
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
Run the playbook:
1
$ ansible-playbook -i inventory.ini playbook.yml
:six: Applying and Managing Terraform State :
The Terraform workflow always follows three commands:
1
2
3
4
5
6
7
8
# Preview all planned changes (safe — no changes made)
$ terraform plan
# Apply the changes (prompts for confirmation)
$ terraform apply
# Destroy all managed resources
$ terraform destroy
Terraform stores the current state of all managed resources in terraform.tfstate. For team environments, store this file remotely in an S3 bucket with DynamoDB locking to prevent concurrent state corruption:
1
2
3
4
5
6
7
8
9
10
# backend.tf
terraform {
backend "s3" {
bucket = "cloud1-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-3"
dynamodb_table = "cloud1-state-lock"
encrypt = true
}
}
Questions and answers
:question: What is idempotent infrastructure and why does it matter?
Idempotence means that applying a configuration multiple times produces the exact same end state as applying it once. If a resource already exists and matches the desired configuration, Terraform or Ansible will take no action. This property is critical for reliability — you can safely re-run
terraform applyoransible-playbookafter a partial failure and the tool will only act on resources that need to be created or changed, never duplicating work.
:question: Why separate provisioning (Terraform) from configuration (Ansible)?
The separation respects distinct responsibilities. Terraform manages the lifecycle of immutable infrastructure — creating, modifying, and destroying cloud resources like VMs, networks, and storage. Ansible manages the state of software on existing machines — installing packages, writing config files, managing services. This boundary means you can reprovision infrastructure without touching application config, and vice versa. It also maps cleanly to different team ownership (platform team vs. application team).
:question: What is a Security Group in AWS and how does it differ from a NACL?
A Security Group is a stateful virtual firewall attached to EC2 instances. Stateful means it automatically tracks connection state — if you allow inbound traffic on port 80, the response traffic is automatically permitted without an explicit outbound rule. A Network Access Control List (NACL) is stateless and attached to a subnet — inbound and outbound rules must both be explicitly defined. Security Groups are the primary tool for EC2 firewall rules; NACLs add a coarser-grained second layer of defense.
:question: What is the Terraform state file and why must it be protected?
terraform.tfstateis a JSON file recording the current known state of all resources Terraform manages, including their IDs, attributes, and relationships. Terraform reads this file to calculate the diff between the current state and the desired state on eachplan/apply. It often contains sensitive data (passwords, private IPs, ARNs). It must be protected from deletion (it cannot be reconstructed without re-importing every resource), protected from concurrent modification (use DynamoDB locking), and secured from unauthorized access (encrypt at rest in S3 with access controls).
:question: What is the difference between terraform plan and terraform apply?
terraform planis a dry run — it reads the current state and the configuration, calculates what changes would be made (create, update, destroy), and prints a human-readable diff. No changes are made to any infrastructure.terraform applyexecutes the changes, first showing the same plan and asking for confirmation (or using-auto-approveto skip). Always runplanfirst in CI/CD or production environments to review changes before committing them.
:question: What is an Ansible handler and when is it triggered?
A handler is a task in Ansible that is only executed when notified by another task that actually made a change. For example, an Nginx configuration template task notifies a “Reload Nginx” handler. If the configuration file already exists with the correct content (no change), the notification is never sent, and Nginx is not needlessly reloaded. Handlers run once at the end of a play, even if notified multiple times — preventing redundant service restarts.
Ressources :
- Terraform Documentation : https://developer.hashicorp.com/terraform/docs
- Terraform AWS Provider : https://registry.terraform.io/providers/hashicorp/aws/latest/docs
- Ansible Documentation : https://docs.ansible.com/
- Ansible Best Practices : https://docs.ansible.com/ansible/latest/tips_tricks/ansible_tips_tricks.html
- AWS VPC Guide : https://docs.aws.amazon.com/vpc/
- AWS EC2 Getting Started : https://docs.aws.amazon.com/ec2/
- Remote Terraform State with S3 : https://developer.hashicorp.com/terraform/language/settings/backends/s3