Infrastructure as Code (IaC) is an approach to infrastructure automation where infrastructure is defined in a code format, typically using a high-level language, and managed in a version-controlled repository.
IaC enables you to automate the provisioning and management of infrastructure resources such as servers, networks, databases, and storage in a way that is reproducible, scalable, and consistent.
Sounds great, doesn’t it?
Let’s dive into the topic and see how tools like Terraform and Ansible help DevOps folks manage infrastructure with a practical example - a basic Terraform project that creates a VPC with an EC2 instance.
By using IaC, you define your infrastructure as code, which means you can create, update, and destroy your infrastructure using code. This is done through configuration files, scripts, templates, or other code-based tools.
With IaC, you can easily manage and automate the infrastructure, and the changes may be version-controlled and tracked over time.
Different tools and services help here: examples are Terraform or CloudFormation. These tools enable you to define infrastructure resources in code, deploy and manage them automatically, and integrate with other tools in your development and operations workflows.
Other helpful tools that help maintain the infrastructure are Ansible and Puppet. The correct group name for these tools is “Configuration Management”, not IaC. This is because these tools use configuration files, unlike more complex, multi-file Terraform-style projects.
Overall, IaC is a powerful approach to automating infrastructure management, reducing manual errors, and improving collaboration between development and operations teams.
Terraform is an open-source infrastructure as code (IaC) tool that lets users manage and automate the deployment of infrastructure resources in a cloud environment. It was developed by HashiCorp, the same company behind other popular DevOps tools such as Vagrant, Packer, and Consul.
Terraform allows you to define your infrastructure in a code format using a declarative language called HashiCorp Configuration Language (HCL) or JSON. You can create and manage resources such as virtual machines, load balancers, databases, and networks, across multiple cloud platforms like AWS, Azure, Google Cloud Platform, and others.
One of the key features of Terraform is its ability to create a dependency graph of your infrastructure resources and manage them in a safe and efficient way.
Terraform can also be integrated with other DevOps tools like Ansible, Chef, and Puppet, enabling you to create more comprehensive automation workflows.
Overall, Terraform is a powerful automation tool that provides greater control, consistency, and scalability for your infrastructure resources.
Here's an example of a basic Terraform project that creates a VPC with an EC2 instance.
Glossary:
First, we need to define the providers we’ll use in project.
providers.tf
provider "aws" {
region = "eu-central-1"
profile = "myaws"
}
This is the important part. If you’re working with lots of AWS configurations, you should use profiles called configurations.
Create your configuration using awscli and be sure to name it. Next, put this name in `profile` field in providers. This way, Terraform will know what configuration use.
We’ll use the S3 bucket as a tfstate storage. This will allow us to have access to our saved config remotely.
backend.tf
terraform {
required_version = "~> 1.3.7"
backend "s3" {
bucket = "project-tfstates"
profile = "project"
key = "infrastructure.tfstate"
region = "eu-central-1"
encrypt = "true"
}
}
We want to have our variables stored as clear as possible. So be a professional programmer and keep config separate - use locals and variables files. 🙂
locals.tf
locals {
name = "project"
tags = {
Project = local.name
Purpose = "Playing with terraform"
Terraformed = "True"
}
environment = "PLAYGROUND"
}
systems = {
amazonlinux = "ami-0dcc0ebde7b2e00db"
}
variables.tf
variable "default_ssh_range" {
type = list(string)
default = ["123.123.123.123/32", "321.321.321.321/32"]
}
Here we’re defining sample IPs to whitelist for security groups to access machines via ssh. Change this to match your IP.
vpc.tf:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.13.0"
name = "${local.name}-vpc"
private_subnet_suffix = "private"
private_subnet_tags = { "Name" = "${local.name}-subnet-private" }
public_subnet_tags = { "Name" = "${local.name}-subnet-public" }
cidr = "10.100.0.0/16"
azs = ["eu-central-1a", "eu-central-1b", "eu-central-1c"]
private_subnets = ["10.100.0.0/20", "10.100.16.0/20", "10.100.32.0/20"]
public_subnets = ["10.100.208.0/20", "10.100.224.0/20", "10.100.240.0/20"]
enable_nat_gateway = false
manage_default_network_acl = true
default_network_acl_tags = { Name = "${local.name}-default" }
manage_default_route_table = true
default_route_table_tags = { Name = "${local.name}-default" }
enable_flow_log = true
create_flow_log_cloudwatch_log_group = true
create_flow_log_cloudwatch_iam_role = true
flow_log_max_aggregation_interval = 60
enable_dns_hostnames = true
enable_dns_support = true
tags = local.tags
}
module "sg_ssh" {
source = "terraform-aws-modules/security-group/aws"
version = "4.9.0"
name = "${local.name}-ssh"
description = "Allow SSH traffic from mihurocks IPs"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = var.default_ssh_range
ingress_rules = ["ssh-tcp"]
egress_cidr_blocks = ["0.0.0.0/0"]
egress_rules = ["all-all"]
tags = local.tags
}
module "sg_web" {
source = "terraform-aws-modules/security-group/aws"
version = "4.9.0"
name = "${local.name}-web"
description = "Allow all HTTP/HTTPS traffic"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = ["0.0.0.0/0"]
ingress_rules = ["http-80-tcp", "https-443-tcp"]
tags = local.tags
}
ec2.tf
module "ec2" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "~> 3.0"
name = "${local.environment} machine"
ami = local.systems.amazonlinux
instance_type = "t2.micro"
monitoring = true
subnet_id = element(local.public_subnets, 0)
vpc_security_group_ids = [local.sg_ssh_id, local.sg_web_id]
user_data = file("globals/synchronize_ssh_keys.sh")
associate_public_ip_address = true
tags = local.tags
}
resource "aws_eip" "ec2_eip" {
vpc = true
tags = { Name = "${local.environment} machine" }
}
resource "aws_eip_association" "ec2_assoc" {
instance_id = module.ec2.id
allocation_id = aws_eip.ec2_eip.id
}
Here I’m using the user data attribute to setup my public key for the root user. Sample bash script to do that:
#!/bin/bash
echo "ssh-rsa yor-public-ssh-key" >> /root/.ssh/authorized_keys
EOF
To run this project, you would follow these steps:
Terraform will then create the AWS VPC and EC2 instances according to the configuration defined in the project.
Note that this is a very basic example.
In the real world, you’d typically define many more resources and use variables, modules, and other Terraform features to manage a more complex infrastructure.
Ansible is an open-source configuration management and orchestration tool that helps teams automate the deployment and management of infrastructure resources. It was developed by Red Hat and is now maintained by the Ansible community.
With Ansible, you can define the desired state of your infrastructure resources, such as servers, applications, and network configurations, using a declarative language called YAML.
Ansible uses SSH or WinRM to connect to target machines and apply configuration changes. It doesn’t require a central server to manage the configurations; instead, it uses a push-based model where you can run playbooks on-demand.
Ansible enables you to:
Companies from a variety of industries, including finance, healthcare, technology, and more, use Ansible for managing infrastructure resources.
Here’s an example of a basic Ansible project that installs the Nginx web server on a group of Centos servers:
hosts:
[webservers]
web1.example.com
web2.example.com
web3.example.com
playbook.yml:
- hosts: webservers
become: true
tasks:
- name: Install Nginx web server
yum:
name: nginx
state: present
notify:
- start nginx service
handlers:
- name: start nginx service
service:
name: nginx
state: started
In this example, we first define a group of servers called "webservers" in the hosts file. Then, we define a playbook that runs on the webservers group, installs Apache using the "apt" module, and starts the Apache service using a handler.
The "become: true" statement is used to escalate privileges to run the tasks as a privileged user.
To run this project, follow these steps:
Ansible will then connect to the servers defined in the hosts file, install Apache, and start the Apache service according to the configuration defined in the playbook.yml file.
Note that this is a very basic example. In a real-world scenario, you’d typically define many more tasks and use variables, roles, and other Ansible features to manage a more complex infrastructure.
Terraform and Ansible are both tools that can be used to manage infrastructure, but they serve different purposes. Terraform is used to provision and manage infrastructure resources, while Ansible is used for configuration management and application deployment.
That being said, it’s possible to use Ansible to automate the deployment of Terraform configurations. You can use the "command" or "shell" module in Ansible to run Terraform commands on the target servers.
Here's an example playbook that uses the "command" module to run Terraform commands:
- hosts: my_servers
become: true
tasks:
- name: Initialize Terraform
command: terraform init
args:
chdir: /path/to/terraform/directory
- name: Apply Terraform plan
command: terraform apply -auto-approve
args:
chdir: /path/to/terraform/directory
In this example, we define a playbook that runs on the "my_servers" group of servers. The "become: true" statement is used to escalate privileges to run the tasks as a privileged user.
We then define two tasks: the first task runs the "terraform init" command to initialize the Terraform configuration. The second task runs the "terraform apply -auto-approve" command to apply the Terraform plan.
To run this playbook, you would run the following command:
ansible-playbook my_playbook.yml -i inventory.ini
Note that this is a very basic example. In the real world, you’d usually define many more tasks and use variables, roles, and other Ansible features to manage a more complex infrastructure with Terraform.
I hope that the examples of IaC in action with Terraform and Ansible showed you how easy it can be. The IaC market is constantly evolving, and novel solutions arise to address new challenges each year. Terraform or Ansible are two tools in the vast IaC ecosystem that can make a real difference to your project when combined.