Dev Zone
December 16, 2023

DevOps as Coders: Embracing Infrastructure as Code (IaC)

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.

Understanding Infrastructure as Code (IaC) in DevOps

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: Empowering Infrastructure Automation

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:

  • VPC - virtual private cloud.
  • EC2 - virtual private server instance provided by AWS (Amazon EC2)
  • S3 bucket (Simple Storage Service) - storage service provided by AWS. We will use it to store our Terraform configuration state.
  • aws cli - AWS command line tool. We will need it to enable Terraform communication with our AWS account. To install it, use the official documentation.
  • tfstate - Terraform state file, created and modified each time we deploy our changes to infrastructure. This is the most important file because it contains information about the state of infrastructure. Fixing it manually is a massive pain.
  • ansible playbook - this is the yaml configuration file that contains the instructions for setting up the environment, executed top to bottom.

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:

  1. Install Terraform
  2. Install aws cli
  3. Create a new project directory, place files listed there
  4. Initialize the directory by running terraform init
  5. Preview the changes by running terraform plan
  6. Apply the changes by running terraform apply

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: Mastering Configuration Management and Orchestration

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:

  • Automate infrastructure management: You can automate the deployment and management of software and system configurations, reducing manual errors and saving time.
  • Enforce policy and compliance: You can enforce policies and compliance requirements across your infrastructure, ensuring that your systems are secure and compliant.
  • Scale infrastructure management: You can easily manage large-scale infrastructures, and Ansible is designed to work well in complex and heterogeneous environments.
  • Integrate with other tools: Ansible can be integrated with other DevOps tools such as Jenkins, Docker, Kubernetes, and more.

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:

  1. Install Ansible
  2. Create the hosts and playbook.yml files in a new directory
  3. Verify connectivity to the target servers by running ansible webservers -m ping
  4. Run the playbook by running ansible-playbook playbook.yml

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.

Combining Forces: Using Ansible and Terraform Together

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.

Real-World Use Cases of DevOps with IaC, Terraform, and Ansible

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.

Wrap up

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.

December 16, 2023