Ansible vs Terraform: Choose One or Use Both?
env0 Team
Posted on March 14, 2024
In the dynamic world of DevOps, two powerhouse tools often dominate the discussion: Ansible and Terraform. Each brings unique strengths to the table, with Ansible excelling in configuration management and Terraform in robust infrastructure provisioning.
In this blog post, we will compare the two tools, and then consider examples of how to use them together. This demo will help illustrate how integrating Ansible and Terraform can lead to a more efficient and comprehensive approach to infrastructure management.
Video Walk-through
Requirements
- A free GitLab account
- Access to an AWS account, we’ll be running within the 12-month free tier
- A free env0 account
TL;DR: You can find the main repo here.
What is Ansible?
Ansible, a robust automation tool, simplifies complex IT tasks in cloud infrastructure and beyond, interfacing seamlessly with cloud provider APIs. It stands out as a key player in the world of infrastructure automation, particularly for its ability to manage software and deploy infrastructure resources, including network devices and virtual machines.
Unlike traditional automation frameworks, Ansible automates these tasks without requiring agent software, making it a favorite among DevOps teams.
It is a command-line tool designed to manage infrastructure and deploy infrastructure resources, including network devices and load balancers.
Why Consider Ansible as an Alternative to Terraform?
While Terraform is known for its provisioning infrastructure capabilities, employing the HashiCorp Configuration Language (HCL) for defining infrastructure as code (IaC), Ansible brings its strength as a configuration management tool. Ansible automates provisioning and ongoing maintenance of existing infrastructure, adapting to new cloud infrastructure needs. This makes it a versatile choice for IaC management, especially for Day 2 operations involving configuration changes and application deployment.
Terraform excels with its declarative approach within Terraform configuration, utilizing an immutable infrastructure approach which is highly effective for infrastructure cloud provisioning.
However, when it comes to the practical aspects of configuration management, such as applying updates to an existing database, an imperative approach is often more suitable. Ansible exemplifies the imperative philosophy by ordering configuration steps sequentially, outlining exactly what needs to be done at each stage.
Other Tools in the Mix
In the realm of orchestration and configuration management, other tools like Chef, Puppet, and SaltStack also play significant roles. These configuration management tools each have their unique features, but Ansible stands out for its simplicity and efficiency, especially when it comes to infrastructure lifecycle management.
Ansible Use Cases
1. Cloud Infrastructure Visibility
Ansible serves as a powerful tool for gaining insights into the same infrastructure deployed across multiple environments. Especially useful in environments where multiple IT admins work independently, Ansible can track and report on your cloud usage. This approach is particularly beneficial for established, or "brownfield," environments. It's a low-risk, high-value use case since it's read-only and doesn't require altering your production environment.
2. Compliance Management
Ansible isn't just about infrastructure management; it's also about ensuring compliance across your cloud environments, such as the major cloud providers: Google Cloud Platform (GCP), AWS, and Azure. It can enforce policies like IAM rules and standardize experiences across different public clouds. Ansible is adept at managing both mutable and immutable infrastructures, ensuring that instances adhere to tagging policies for streamlined billing and auditing, and even shutting down non-compliant resources.
3. Business Continuity
Keeping your digital operations running smoothly is crucial, and Ansible plays a key role in this. It aids in transferring and duplicating resources off-cloud, automating backup policies, and managing disruptions. By building automation strategies with Ansible, you can ensure that your business remains resilient in the face of failures or other disruptions.
4. Cloud Operations and Lifecycle Management
Ansible excels in automating day-to-day cloud operations, which includes deploying applications, managing CI/CD pipelines, and handling OS patching and maintenance, reflecting on the underlying infrastructure. This automation extends to lifecycle management, ensuring that your cloud resources are always up-to-date and functioning optimally.
By automating these routine tasks, Ansible frees up your team to focus on more strategic initiatives.
Ansible vs. Terraform
It's not always a straight-up Terraform vs. Ansible showdown, but stacking both tools against one another sheds some light on the differences between the two of them.
Infrastructure Provisioning vs Configuration Management
While Terraform is a powerhouse in provisioning infrastructure, and creating new cloud infrastructure from scratch, Ansible shines in configuration management. Ansible's automation platform allows for detailed management of both the setup and ongoing maintenance of infrastructure, making it ideal for managing infrastructure changes over time.
Provisioning Resources
Terraform's strength lies in building and changing infrastructure efficiently. It's designed to create an immutable infrastructure where changes are made by rebuilding the infrastructure from a baseline. Ansible complements this by managing the configuration of these resources, ensuring they remain in the desired state.
Community/Ecosystem and Integrations
Both Terraform and Ansible boast vibrant communities and ecosystems. But when you stack Terraform vs. Ansible head to head, Terraform is often preferred for infrastructure provisioning in cloud environments, while Ansible is celebrated for its configuration management capabilities and as a cross-domain automation solution.
Chart Comparison
Using Ansible and Terraform Together
Example 1: VM and Infrastructure
The concept of immutable infrastructure, championed by Terraform, is an ideal state many strive for. However, the reality often differs. Many organizations have adopted Infrastructure-as-Code practices but still need to maintain and configure servers in the traditional way.
Terraform excels in defining infrastructure, but the virtual machines and resources it creates often require ongoing configuration and maintenance.
This is where Ansible comes into play.
Known for its simplicity in configuration management, Ansible complements Terraform by handling the post-deployment configuration and maintenance of resources. Integrating Ansible with Terraform, especially through platforms like env0, allows for smooth IaC rollout. This includes deployment, configuration, and maintenance, all managed as code within the same source control.
In a typical setup, Terraform is used to create infrastructure like an EC2 instance on AWS. Following the infrastructure setup, Ansible takes over with a Custom Flow to configure the deployed instance, employing an Ansible playbook. This process is streamlined and efficient, allowing for repeated runs without duplicating resources.
Instead, Ansible updates the existing host as needed.
The integration is facilitated by an env0.yml file, which orchestrates the entire process. This file ensures that before running terraform plan
, necessary preparations like SSH key retrieval are made, considering that Ansible will connect to the new machine via SSH.
The installation of Ansible and the creation of an inventory file for the EC2 instance are also part of this flow. The process concludes with running the Ansible playbook, which configures the host, leveraging environment variables for seamless execution. To dig deeper check out this blog post Terraform + Ansible = Total Flexibility.
This approach, while simple in this example, lays the foundation for more complex scenarios. The key lies in the env0.yml file, which orchestrates the custom flow, neatly integrating Terraform's infrastructure provisioning capabilities with Ansible's ability at configuration management tasks. This combination simplifies managing infrastructure resources.
It also ensures that they are consistently configured and maintained, aligning with the best practices of Infrastructure-as-Code. This methodology can be extended and adapted to more complex setups, demonstrating the elasticity of combining Ansible and Terraform in modern cloud environments.
Example 2: Day n Ops
The Asian Development Bank's approach – using Terraform for Day 0 operations and Ansible for Day 1 to Day n operations – illustrates an interesting cloud infrastructure management strategy. By leveraging Terraform at the outset, they efficiently provision cloud infrastructure.
Once this foundation is established, the focus shifts to Ansible, which they utilize for ongoing configuration management and maintenance from Day 1 onwards.
This combination streamlines the entire lifecycle of their infrastructure while securing consistency, reliability, and agility in their operations. It's a testament to the power of integrating these two tools, where Terraform's strength in initial setup complements Ansible's prowess in subsequent management.
This approach offers a glimpse into best practices for cloud infrastructure management, and more insights can be gleaned from their detailed video explanation. You can learn more in this video.
Demo Time!
Let's now examine our demo example where we combine Terraform and Ansible together.
We'll start by constructing an AWS EC2 instance using Terraform and then deploy a Docker engine on it with Ansible, followed by launching a Jenkins container. Utilizing env0's custom flows, we'll orchestrate Terraform and Ansible to create the setup.
In this scenario, our objective is to swiftly set up a Jenkins server for testing and utilize env0's capability to automatically decommission the server upon reaching a predetermined time-to-live (TTL). This TTL functionality is a valuable asset for cost-saving, ensuring that the server doesn't incur unnecessary expenses if left running inadvertently.
Watch the video at the top of this blog post to see how we create a project, a template, and an environment in env0 that spins up our Jenkins machine. I've included the configuration of Terraform, Ansible, and the env0 custom flow in the following sections.
Custom Flow with env0
env0 provides the capability to implement various hooks at different stages of a Terraform execution, which proves to be highly beneficial for integrating third-party tools like Checkov for security checks or, as in our scenario, with Ansible.
To facilitate this, you simply need to create a file named env0.yml and position it at the root of your repository. For details on the specific hooks available for Terraform, refer to the env0 documentation.
env0.yml file
deploy:
steps:
terraformOutput:
after:
- terraform output -raw private_key > /tmp/myKey.pem
- chmod 400 /tmp/myKey.pem
- sed -i "s/[placeholder_app]/$(terraform output -raw public_ip)/g" Ansible/inventory
- pip3 install --user ansible
- ls -lah
- cat Ansible/inventory
- cd Ansible && ansible-playbook --private-key /tmp/myKey.pem -i inventory jenkinsPlaybook.yaml
The env0.yml file defines a set of custom workflow steps that are executed during the deployment process managed by env0. This particular configuration specifies actions to be performed after the Terraform output has been generated. Here's what each step in the deploy.steps.terraformOutput.after section does:
Private Key Extraction:
terraform output -raw private\_key > /tmp/myKey.pem
: This command extracts the private key from Terraform's output and saves it to a temporary file on the deployment environment, allowing for secure SSH access to the provisioned resources.
- File Permission Adjustment:
chmod 400 /tmp/myKey.pem
This command changes the file permissions of the private key to ensure that it is read-only by the owner, which is a security best practice for SSH keys.
- Dynamic Inventory Update for Ansible:
sed -i "s/\[placeholder\_app\]/$(terraform output -raw public\_ip)/g" Ansible/inventory
: This command replaces a placeholder in the Ansible inventory file with the public IP address of the provisioned infrastructure, outputted by Terraform. This dynamically updates the inventory to target the newly created EC2 instance.
- Ansible Installation:
pip3 install --user ansible
: This command installs Ansible using pip, Python's package manager, ensuring that the necessary tool for configuration management is present in the environment.
- Directory and File Verification:
ls -lah
: Lists all files and their permissions in the current directory to verify the presence and permissions of required files, such as the private key and Ansible inventory.
cat Ansible/inventory
: Displays the contents of the Ansible inventory file (hosts file), which is useful for debugging purposes to confirm that the inventory has been updated correctly.
- Ansible Playbook Execution:
cd Ansible && ansible-playbook --private-key /tmp/myKey.pem -i inventory jenkinsPlaybook.yaml
: This command changes the directory to the Ansible folder and runs the Ansible playbook, which is tasked with setting up Jenkins on the EC2 instance. It uses the private key saved earlier for SSH access and references the updated inventory file.
In essence, the env0.yml file orchestrates the deployment process by integrating Terraform and Ansible. It ensures that after the infrastructure is provisioned with Terraform, Ansible is correctly set up and then executed to configure the infrastructure (in this case, to install and run a Jenkins container).
Terraform Configuration
Let's now dive into the Terraform configuration.
main.tf file
Let’s break down the main.tf file to understand it. You can find it in the GitHub repo.
This Terraform code is designed to both provision and set up a network infrastructure on AWS, including an EC2 instance that could be used for a Jenkins server. Let's break it down.
- Provider Configuration:
The required_providers block specifies that this Terraform code uses the AWS provider and the TLS provider from HashiCorp, with specific versions defined for both.
The provider block configures the AWS provider with a region that is specified by a variable, allowing you to define the region dynamically when you run Terraform.
- VPC Creation:
The resource "aws_vpc" "env0" block creates a new Virtual Private Cloud (VPC) with DNS hostnames enabled. It uses variables to set the CIDR block and tags, allowing for customization of the VPC's address space and naming.
- Subnet Creation:
The resource "aws_subnet" "env0" block creates a subnet within the VPC created earlier, with a CIDR block defined by a variable.
- Security Group Setup:
The resource "aws_security_group" "env0" block sets up a security group for the VPC, defining ingress rules to allow incoming traffic on specific ports (22 for SSH, 8080 for web access, and 50000 which is typically used by Jenkins for agent connections) and a general egress rule to allow all outgoing traffic.
- Internet Gateway:
The resource "aws_internet_gateway" "env0" block attaches an internet gateway to the VPC, which is necessary for the VPC to communicate with the internet.
- Routing Table:
The resource "aws_route_table" "env0" and resource "aws_route_table_association" "env0" blocks create a route table for the VPC and associate it with the subnet. This includes a route to direct all outbound traffic to the internet gateway.
- AMI Data Source:
The data "aws_ami" "ubuntu" block looks up the latest Ubuntu 20.04 AMI that is owned by Canonical, ensuring the instance uses a recent and supported OS image.
- Elastic IP and Association:
The resource "aws_eip" "env0" and resource "aws_eip_association" "env0" blocks provision an Elastic IP (EIP) and associate it with the EC2 instance, giving it a static, public IP address.
- EC2 Instance Provisioning:
The resource "aws_instance" "env0" block provisions an EC2 instance with the found Ubuntu AMI, the instance type specified by a variable, and associates the instance with the previously created security group and subnet.
- TLS Key Pair Creation:
The resource "tls_private_key" "env0" block creates an RSA private key used for secure communication.AWS Key Pair:The resource "aws_key_pair" "env0" block uploads the public key from the TLS private key to AWS to allow secure SSH access to the EC2 instance.
In summary, this Terraform code sets up all the necessary components for a secure, accessible, and isolated environment on AWS for a Jenkins instance.
The use of variables and dynamic data sources like the AMI lookup makes the code reusable and adaptable. The EC2 instance is configured with an Elastic IP and the necessary security group rules, ready to have Docker and Jenkins installed and configured by Ansible. The setup leverages env0's custom flows for smoothly automated infrastructure management.
variables.tf file
Once again you can find the content of the variables.tf file in the repo.
Below we explain it in detail.The variables.tf file defines variables in a single file to use throughout all Terraform configuration files, allowing for more flexibility and code reusability. Here's a breakdown of each variable defined in the variables.tf file you provided.
- Prefix:
The prefix
variable is intended to be a string that will be prepended to the names of most resources created by Terraform. It helps in identifying and organizing resources, especially when managing multiple environments.
- Region:
Specifies the AWS region where the resources will be created. It has a default value of us-east-1
. This allows the user to set the region for resource deployment, and if not specified, it defaults to the US East (N. Virginia) region.
- Address Space:
The address_space
defines the CIDR block for the Virtual Private Cloud (VPC). The default value is 10.0.0.0/16
. This address space can encompass multiple subnets. Changing this value after deployment will force Terraform to create a new VPC resource.
- Subnet Prefix:
Sets the CIDR block for a subnet within the VPC. The default is 10.0.10.0/24
. This defines the range of IP addresses that can be used within this subnet.
- Instance Type:
The instance_type
determines the type of EC2 instance to be launched. The default is set to t2.micro
, which is a small, low-cost instance type ideal for testing and small-scale applications.
- AWS SSH Key:
The my_aws_key
string variable holds the name of the AWS key pair that will be used for SSH access to the EC2 instances. The default value is mykey.pem
. This key should be present in your AWS account to ensure successful SSH connections.
By using these variables, the Terraform configuration gets more dynamic and customizable. Users can easily change these parameters to suit their specific deployment needs without altering the main config files, making the code easier to maintain and scale.
outputs.tf file
output "url" {
value = "http://${aws_eip.env0.public_dns}"
}
output "public_ip" {
value = aws_eip.env0.public_ip
}
output "private_key" {
value = tls_private_key.env0.private_key_pem
sensitive = true
}
The outputs.tf file defines output values that you can easily retrieve after your infrastructure is provisioned. These outputs can be helpful for understanding important attributes of the resources that Terraform manages, or for feeding these values into other tools and scripts.
Here’s what each output in the provided outputs.tf file represents:
- URL Output:
This output generates a URL for accessing the provisioned resource, specifically using the public DNS of the Elastic IP (EIP) associated with your AWS resource which is our EC2 instance running Jenkins. The URL is formed by concatenating "http://" with the public DNS of the aws_eip.env0 resource. This URL can be used to access the EC2 instance from a web browser or a tool that can interact with HTTP endpoints.
- Public IP Output:
Provides the public IP address of the aws_eip.env0 resource. The public IP address is essential for connecting to the EC2 instance over the internet, for instance, via SSH or other network protocols.
- Private Key Output:
Outputs the private key generated by the tls_private_key.env0 resource. This key is used for secure SSH access to the EC2 instance. The sensitive = true
attribute means that Terraform will treat this output as sensitive information. When you run Terraform in the CLI, it will not display this sensitive output in the CLI output. This is crucial for security reasons, as private keys should be kept confidential and not exposed publicly.
Ansible Configuration
Now it's time to explore the Ansible configuration.
jenkinsPlaybook.yaml file
I’ll now explain the content of the jenkinsPlaybook.yaml found in our repo.
- Setting Up the Environment:
- Install pip3 and unzip: The first task is about installing python3-pip and unzip using the apt module. It also ensures that the cache is updated. We're using a loop here to try five times in case of failure, with a delay of 5 seconds between tries.
- Add Docker GPG apt Key: Here, we're adding the Docker GPG key to ensure the packages we install are authenticated and secure.
- Add Docker Repository: This adds the Docker repository to your system's repository list. We're specifying Ubuntu's focal release and setting the state to 'present' to make sure it's added.
- Installing Docker:
Update apt and install docker-ce. This updates your package list and installs the Docker engine (docker-ce).
- Setting Up Docker for Python:
Install Docker module to control Docker containers using Python.
- Pulling Jenkins Docker Image:
Pulls the custom Jenkins Docker image samgabrail/jenkins-tf-vault-ansible:latest from the Docker hub.
- Preparing Jenkins Data Directory:
Changes file ownership, group and permissions, which sets up a directory (/home/ubuntu/jenkins_data) for Jenkins data. It also adjusts the ownership and permissions so Jenkins can use it.
- Launching Jenkins in Docker:
Finally, we're creating and starting the Jenkins container. It's set to use the previously pulled Docker image and maps ports 8080 and 50000 for web interface and agent connections. The Jenkins data directory is also mounted as a volume inside the container.
This playbook ensures your environment is ready, installs Docker, sets up Python to work with Docker, pulls a custom Jenkins image, prepares a data directory, and starts Jenkins in a Docker container. This way, you have a Jenkins server up and running smoothly. It's like setting up a mini data center for Jenkins with just a few lines of code!
inventory file
Lastly, let's take a look at the content of the inventory file below:
[all:children]
jenkins
[all:vars]
ansible_user=ubuntu
ansible_python_interpreter=/usr/bin/python3
[jenkins]
jenkinsvm ansible_host=[placeholder_app]
This is an Ansible inventory file, also known as ‘the hosts file,’ which is used to define and group the hosts (servers) that Ansible will manage. The inventory file is a crucial part of Ansible's configuration as it specifies the targets of Ansible playbooks. By default, it will be found in the directory ‘etc/ansible/hosts’. Let's break down each part of the file:
- [all:children]
This section is defining a group of groups. Here, it declares jenkins
as a subgroup under the main group all
. This structure is useful for organizing your hosts and can be leveraged for scaling and managing complex environments.
- [all:vars]
This section defines variables that apply to all
the hosts in the all group, including its subgroups. In this case, it sets ansible_user as 'ubuntu', which means Ansible will use the 'ubuntu' user account for SSH connections to all the hosts. The ansible_python_interpreter
is set to /usr/bin/python3
', specifying the path to the Python interpreter on the managed hosts. This is important for environments where Python 3 is not the default Python version.
- [jenkins]
This is a specific group named jenkins
. Groups in Ansible inventory files allow you to categorize and manage hosts based on characteristics, roles, or any other classification that suits your needs. For instance, all Jenkins servers can be grouped here.
- jenkinsvm ansible_host=[placeholder_app]
Within the jenkins
group, this line defines a host named 'jenkinsvm'. The ansible_host
variable is used to specify the IP address or hostname of the jenkinsvm
server.
Here, placeholder_app
is a placeholder that the env0 custom flow will replace with the actual IP address our Jenkins server took from the Terraform output. This allows Ansible to know where to connect to execute the playbook tasks.
Conclusion
In wrapping up our discussion on Ansible, Terraform, and their roles in infrastructure management, it's important to reiterate some key differences. Ansible has evolved to function effectively in this space while not originally designed as an Infrastructure as Code (IaC) tool.
It excels as a configuration management tool and works very well for ongoing maintenance of existing infrastructure. Terraform, on the other hand, is purpose-built for IaC, focusing on the provisioning and management of infrastructure from the ground up.
This distinction is crucial for understanding how each tool fits into the broader picture of IT infrastructure management and how they can be used together for a comprehensive approach as seen in our demo example.
As a provisioning tool, OpenTofu is emerging as the Terraform alternative for Infrastructure-as-Code. Positioned as a drop-in replacement for Terraform, OpenTofu extends the ability to efficiently manage infrastructure.
Whether using OpenTofu or Terraform with Ansible or another complementary tool, you can use env0 to orchestrate the entire Infrastructure-as-Code setup. Sign up for a demo today.
Posted on March 14, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.