TeKanAid

Dark

Table of Contents
HashiCorp Vault 101 - Certified Vault Associate
Get started with HashiCorp Vault and prepare for your Vault Associate Exam
Terraform 101 - Certified Terraform Associate
Learn all you need to know to ace the Terraform Associate Exam and go beyond the certification

Terraform vs Ansible - Demo the Differences - Part 2


Discover the distinctions between Terraform and Ansible in this Terraform vs Ansible comparison demonstration. The outcome may surprise you!

IaCHashiCorpTerraformAWSAnsible
Created: January 12, 2023 | Updated: January 27, 2023

Video


Below is a video explanation and demo.

AWS Lambda Terraform Tutorial with API Gateway


Video Chapters


You can skip to the relevant chapters below:

  • 00:00 - Introduction
  • 00:33 - Demo Setup
  • 02:45 - Ansible Demo Starts
  • 15:41 - Terraform Demo Starts
  • 24:59 - Terraform and Ansible Working Together
  • 27:22 - Terraform 101 Course

Overview


Choosing the right tool for provisioning and configuring infrastructure can be tricky, as there are several options available. Among them, Terraform and Ansible are two of the most widely used. In order to determine which one is the best fit for you, it's important to understand the key differences between the two. This demo comparison will help you make an informed decision.

In this blog post, we will take a practical approach by demoing both Terraform and Ansible to see the difference. This is part 2 of a 2-part blog series. In part 1 called Terraform vs Ansible - Learn the Differences, we discussed many concepts of Infrastructure as Code and Configuration Management to understand Terraform and Ansible better. In particular, we explored the following in case you'd like to refer back to them:

  • What is Terraform?
  • What is Ansible?
  • Infrastructure as Code
  • Configuration Management
  • Infrastructure State
  • Mutable Infrastructure
  • Immutable Infrastructure
  • Idempotence
  • Imperative Programming
  • Declarative Programming

Code

Get FREE access to the source code by subscribing to my newsletter
You only need to subscribe once. Already subscribed? Enter your email to get instant access to the code.

Pre-requisites


The following is required to follow along:

  • Terraform or use GitHub's Codespaces as your development environment. The code repo is equipped with it.
  • An AWS account or if you're subscribed to the TeKanAid Academy Subscription you get your own AWS account on demand.

Ansible Demo


Let's get started with the Ansible demo.


Export the environment variables for AWS


export AWS_ACCESS_KEY_ID=
export AWS_SECRET_ACCESS_KEY=

Run the Playbook


Run the following commands which will change the director to Ansible and run the playbook.

cd Ansible
ansible-playbook playbook_create.yml

Below is the CLI output after running the playbook.

Ansible CLI Output after Creating


Now go to the AWS console and check the results in the EC2 dashboard.


AWS Console for Ansible Build

Notice the two EC2 instances created and the tag.


Destroy the Environment


Now let's destroy the environment. For Ansible, we can't just run a CLI command to use the same playbook to destroy. We need to create a new playbook and reverse the order in which we created the resources. Run the command below.


ansible-playbook playbook_destroy.yml

Below is the CLI output after running the destroy playbook.

Ansible CLI Output after Destroying


Ansible Configuration


Below is our folder structure. Notice that we have one folder for Ansible and one for Terraform.


Folder Structure


Let's dig deeper into the create and destroy playbooks.


Create Playbook


Below is the create playbook.

---

- name: Provision VPC and EC2 instances
  hosts: localhost
  connection: local
  gather_facts: false
  collections: [amazon.aws]
  vars:
    ansible_python_interpreter: /home/codespace/.python/current/bin/python
    count: 2
    vpc_cidr: 10.0.0.0/16
    subnet_cidr: 10.0.1.0/24
    instance_type: t2.micro
    region: us-east-1
    ami: ami-06878d265978313ca
    vpc_name: my_vpc
  tasks:
    - name: Create VPC
      ec2_vpc_net:
        name: "{{ vpc_name }}"
        cidr_block: "{{ vpc_cidr }}"
        region: "{{ region }}"
      register: vpc

    - name: Create subnet
      ec2_vpc_subnet:
        vpc_id: "{{ vpc.vpc.id }}"
        cidr: "{{ subnet_cidr }}"
        region: "{{ region }}"
      register: subnet

    - name: Create security group
      ec2_group:
        name: web-server-sg
        description: Security group for web server instances
        vpc_id: "{{ vpc.vpc.id }}"
        region: "{{ region }}"
        
        rules:
          - proto: tcp
            from_port: 80
            to_port: 80
            cidr_ip: 0.0.0.0/0
      register: security_group

    - name: Create key pair
      ec2_key:
        region: "{{ region }}"
        name: my_key
      register: key_pair

    - name: Create an EC2 Instance
      ec2_instance:
        key_name: "{{ key_pair.key.name }}"
        instance_type: "{{ instance_type }}"
        image_id: "{{ ami }}"
        region: "{{ region }}"
        
        vpc_subnet_id: "{{ subnet.subnet.id }}"
        security_group: "{{ security_group.group_id }}"
        exact_count: "{{ count }}"
        wait: yes
        network:
          assign_public_ip: true
        tags:
          Environment: Prod
      register: ec_instances

This is an Ansible playbook that provisions a VPC and EC2 instances in AWS. The playbook is set to run on the localhost and uses the local connection. The gather_facts variable is set to false, which means that Ansible will not gather information about the system before running the tasks. This playbook uses the amazon.aws collection, which contains the modules necessary to interact with AWS services.

The playbook then sets several variables, including the python interpreter path, the number of instances to create, the CIDR blocks for the VPC and subnet, the instance type, the region, and the AMI ID.

The playbook then performs several tasks:

  • The first task creates a VPC using the ec2_vpc_net module and the variables defined earlier. The result is registered in the vpc variable.
  • The second task creates a subnet using the ec2_vpc_subnet module and the vpc_id from the previous task. The result is registered in the subnet variable.
  • The third task creates a security group using the ec2_group module with the specified name, description and VPC id. The result is registered in the security_group variable.
  • The fourth task creates a key pair using the ec2_key module with the specified name and region, the result is registered in the key_pair variable.
  • The fifth task creates EC2 instances using the ec2_instance module. It uses the key_name, instance_type, image_id, vpc_subnet_id, security_group, and count defined earlier. The instances are launched in the specified region and they wait until the instances are running before moving on to the next task. The instances are also assigned a public IP and have a tag named Environment with the value Prod. The result is registered in the ec_instances variable

It is worth mentioning that the playbook makes use of the register keyword which allows using the output of a task in the following task.


Destroy Playbook


The destroy playbook is similar to the create one, but in reverse order. You can view it in the code repo.


Terraform Demo


Now let's move on to the Terraform demo.


Run the Terraform Demo


Run the commands below which change the directory to Terraform and runs a few Terraform commands. The explanation for each is found below.

cd Terraform
terraform init
terraform plan
terraform apply

terraform init

This command is used to initialize a Terraform working directory. It is typically the first command that should be run after writing a new Terraform configuration or cloning an existing one. The command is used to download and install any required provider plugins, as well as to set up the backend for storing state. The backend can be either a local file or a remote service such as Terraform Cloud or AWS S3.

terraform plan

The terraform plan command is used to create an execution plan. It shows you what actions Terraform will take when you call the terraform apply command. This command is used to preview the changes that will be made to your infrastructure and to ensure that the configuration is correct before applying it. It is a good practice to run the plan command before applying any changes to your infrastructure.

terraform apply

The terraform apply command is used to apply the changes defined in the Terraform configuration to the infrastructure. It reads the execution plan created by the terraform plan command and makes the necessary changes to the infrastructure. It is important to review the changes that will be made before running this command, as it can make changes to production systems. Once the command is run, Terraform will update the state file with the new infrastructure details.

In summary, terraform init command is used to initialize the terraform working directory, terraform plan command is used to create an execution plan and preview the changes that will be made, and terraform apply command is used to apply the changes to the infrastructure.

Below is CLI output of running the terraform apply command.


Terraform CLI Output after Creating


Now take a look at the AWS console.


AWS Console for Ansible Build


Destroy the Environment


We can simply issue a CLI command on the same Terraform configuration files to destroy the environment we created. Run the command below.

terraform destroy

Below is the CLI output after running the terraform destroy command.


Terraform CLI Output after Destroying


Terraform Configuration


Let's turn our attention to the Terraform configuration now.


main.tf File*


This is the main file for our Terraform configuration.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.19.0"
    }
    tls = {
      source  = "hashicorp/tls"
      version = "4.0.0"
    }
  }
}

provider "aws" {
  region = var.region
}

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical https://ubuntu.com/server/docs/cloud-images/amazon-ec2
}

resource "tls_private_key" "mykey" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "mykey" {
  key_name   = var.my_aws_key
  public_key = tls_private_key.mykey.public_key_openssh
}

resource "aws_instance" "webserver" {
  count                  = var.webserver_count
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.my_instance_type
  vpc_security_group_ids = [aws_security_group.security_group1.id]
  subnet_id              = aws_subnet.terraform-vs-ansible.id
  key_name               = aws_key_pair.mykey.key_name
  private_ip             = var.private_ips[count.index]

  tags = local.mytags
}


resource "aws_vpc" "terraform-vs-ansible" {
  cidr_block           = var.address_space
  enable_dns_hostnames = true

  tags = local.mytags
}

resource "aws_subnet" "terraform-vs-ansible" {
  vpc_id                  = aws_vpc.terraform-vs-ansible.id
  cidr_block              = var.subnet_prefix
  map_public_ip_on_launch = var.allow_public_ips
  
  tags                    = local.mytags
}

resource "aws_security_group" "security_group1" {
  name = "${var.project_name}-security-group"

  vpc_id = aws_vpc.terraform-vs-ansible.id

  ingress {
    cidr_blocks = ["0.0.0.0/0"]
    description = "HTTP Ingress"
    from_port   = var.http_port
    to_port     = var.http_port
    protocol    = "tcp"

  }

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

  tags = local.mytags
}

This is a Terraform configuration file that provisions a VPC, subnet, security group, key pair, and EC2 instances in AWS.

The configuration starts by specifying the required providers for this configuration, in this case, aws and tls are being used, also specifying the version of each one.

Then it declares the AWS provider and sets the region variable to the value of the region variable, this variable along with all other variables are defined in the variables.tf file.

The configuration then uses the data block to retrieve the most recent Ubuntu AMI that matches the specified filters, this AMI will be used to launch the instances.

Then it declares the resource tls_private_key with the name mykey and the algorithm of RSA with 4096 bits.

Then it declares the resource aws_key_pair with the name mykey and the public key from the previous private key resource.

Then it declares the resource aws_instance named webserver with the following properties:

  • count: the number of instances to launch, defined by the variable webserver_count.
  • ami: the ami id, set to the id of the AMI retrieved earlier.
  • instance_type: the type of instances, defined by the variable my_instance_type.
  • vpc_security_group_ids: the ids of the security groups to associate with the instances, defined by the variable aws_security_group.security_group1.id
  • subnet_id: the id of the subnet to launch the instances in, defined by the variable aws_subnet.terraform-vs-ansible.id.
  • key_name: the name of the key pair to associate with the instances, defined by the variable aws_key_pair.mykey.key_name.
  • private_ip: the private ip for the instances, defined by the variable private_ips[count.index]
  • tags: the tags to apply to the instances, defined by the variable local.mytags

Then it declares the resource aws_vpc named terraform-vs-ansible with the following properties:

  • cidr_block: the address space of the VPC, defined by the variable address_space.
  • enable_dns_hostnames: whether to enable DNS hostnames, set to true
  • tags: the tags to apply to the vpc, defined by the variable local.mytags

Then it declares the resource aws_subnet named terraform-vs-ansible with the following properties:

  • vpc_id: the id of the vpc to create the subnet in, defined by the variable aws_vpc.terraform-vs-ansible.id.
  • cidr_block: the cidr block of the subnet, defined by the variable subnet_prefix.
  • map_public_ip_on_launch: whether to assign public ip to instances launched in the subnet, defined by the variable allow_public_ips.
  • tags: the tags to apply to the subnet, defined by the variable local.mytags

Finally, it declares the resource aws_security_group named security_group1 with the following properties:

  • name: the name of the security group, defined by the variable ${var.project_name}-security-group
  • vpc_id: the id of the vpc that the security group belongs to, defined by the variable aws_vpc.terraform-vs-ansible.id
  • ingress: the ingress rule, it allows HTTP traffic to port 80 from any IP address.
  • egress: the egress rule, it allows all traffic to any IP address.
  • tags: the tags to apply to the security group, defined by the variable local.mytags

This Terraform configuration creates and configures a VPC, subnet, security group, key pair, and EC2 instances in AWS using the specified variables, it also uses the data block to retrieve the most recent Ubuntu AMI that matches the specified filters, this AMI will be used to launch the instances.

It is worth mentioning that the configuration uses variables for most of the properties, those variables are defined in the variables.tf file and you can find this file in the code repo.


Terraform and Ansible Differences to Note


AnsibleTerraform
Ordering ResourcesOrder matters. If you put the launch of the EC2 instance before creating the key pair, it will fail to create the EC2 instance.Order doesn't matter. Terraform has built-in intelligence to know which resources depend on which. It can also combine resources from different files.
Detection of ChangesSome elements are not detected out of the box. For example, you can't add tags after the EC2 instance is created. Since there is no state file, you can't make that change. Ansible doesn't detect the change.Terraform detects most changes out of the box. In the case of tags, it will detect those and update the EC2 instance. However, some changes that may occur with the configuration of the server created with provisioners are not detected.
External ChangesCreation outside of Ansible is considered. If you create an EC2 instance from the AWS console in the same region, VPC, availability zone, and with a public IP. Basically, it needs to have a very similar configuration to the instance that Ansible created. Ansible is configured for a certain number of EC2 instances and will count the one created from the console. This can have strange and unpredictable effects. Example: If you had a count of 2 and then create a 3rd instance outside of Ansible via the console, then run Ansible again with the same count of 2, Ansible will delete the latest instance it created. So it will take into account the instance created outside of Ansible.Terraform only manages what it creates. It generates a state file based on what it creates. Anything created outside of Terraform will not be considered and it won't know anything about it. This can lead to drift in some cases, however, this is a predictable behaviour. Drift needs to be addressed at the organizational level with education.
Destroying InfrastructureYou can't simply run a CLI command to destroy what was created. You need to create a separate playbook to destroy. It's because there is no saved state. So you need to create another playbook to destroy the resources created. You also need to delete things in reverse order to how you created them.Since Terraform saves a state file, it's simple to run terraform destroy on the same configuration files to destroy the infrastructure that was provisioned.

Conclusion


In summary, Terraform and Ansible are both tools that can assist with infrastructure management and automation. They both have the capability to provision infrastructure, but they have different methods of doing so. Terraform is better suited for gaining more control over the infrastructure and utilizing immutable servers, while Ansible is more flexible and doesn't require an agent. The choice between the two ultimately depends on the specific requirements and preferences of the user. Many customers use both tools in combination to take advantage of the strengths of each.

It is also possible to use both tools together, as Terraform can be used to set up the infrastructure and pass information about the created resources to Ansible for further configuration. Check this blog post called Jenkins, Vault, Terraform, Ansible, and Consul Delivering an End-to-End CI/CD Pipeline. It's the last part of a 4 part series on how to deploy an application in production with best practices. I explain and demo how to use both Terraform and Ansible together in conjuction with Jenkins as the CI/CD pipeline tool.

I write quite a bit about Infrastructure as Code. If you're interested in Terraform, check out the Terraform 101 course on the TeKanAid Academy site. You could also take a look at some of the blog posts such as this one Terraform to Create a Ubuntu 22.04 VM in VMware vSphere ESXi or the Terraform Import Example - AWS EC2 Instance post..

Other Posts
Terraform for Beginners - A Beginner's Guide to Automating Cloud Infrastructure
Terraform vs Ansible - Demo the Differences - Part 2
Terraform vs Ansible - Learn the Differences - Part 1
HashiCorp Vault Backup and Restore Raft Snapshots from Kubernetes to AWS S3
AWS Lambda - Terraform Configuration Example with API Gateway
Securing the Future - DevSecOps Trends for 2023
36 Top DevOps Questions to Get You Started in 2023
Terraform to Create a Ubuntu 22.04 VM in VMware vSphere ESXi
HashiCorp Packer to Build a Ubuntu 22.04 Image Template in VMware vSphere
Migrate Secrets from AWS Secrets Manager to HashiCorp Vault with Python, Docker, and GitLab
Migrate Secrets from AWS Secrets Manager to HashiCorp Vault with Terraform
env0 - A Terraform Cloud Alternative
Terraform Import Example - AWS EC2 Instance
DevOps Engineer NOT on Linux? You're MISSING OUT!
HashiCorp Vault API Tutorial and Pro Tips
HashiCorp Vault Tutorial for Beginners
Create a Pihole Docker Ad Blocker with Ansible and Terraform
Terraform vSphere Windows Example to Join an AD Domain
Build a Kubernetes k3s Cluster in vSphere with Terraform and Packer
HashiCorp Packer to Build a Ubuntu 20.04 Image Template in VMware
Consul-Template to Automate Certificate Management for HashiCorp Vault PKI
HashiCorp Vault PKI Secrets Engine Demo for Certificate Management
Jenkins, Vault, Terraform, Ansible, and Consul Delivering an End-to-End CI/CD Pipeline
Secret Zero Problem Solved for HashiCorp Vault
Hashicorp Packer, Terraform, and Ansible to Set Up Jenkins
Hashicorp Vault Azure Secrets Engine - Secure Your Azure Resources
HashiCorp Waypoint - Will it Replace Your CI/CD?
HashiCorp Boundary - Make Sure Your Human To Machine Access Is Secure
HashiCorp Packer for VMware Ubuntu Templates and Terraform for building VMs
HashiCorp Packer VMware Windows Templates and Terraform for VMs
Webblog App Part 4 – HashiStack – Nomad Consul Vault Terraform
Webblog App Part 3 - Consul Connect Service Mesh
Webblog App Part 2 - Secrets Development Phases with Vault
Webblog App Part 1 - Infrastructure as Code with Terraform
Microservices Applications'​ Life Cycle
HashiCorp Vault 201 - Vault for Apps in Kubernetes
Learn how to use HashiCorp Vault for your applications in Kubernetes