Terraform to Ansible Devops Live Project for AWS infrastructure

Terraform to Ansible Devops Live Project for AWS infrastructure

Prerequisite for this article to work for you,

  1. Have an AWS account, if you do not have one, you can create one here.

  2. Spin up an instance that you will use to get this project done or use your vagrant machine

  3. Install ansible in the instance that you spin up

In this article, you will learn how to set up AWS instances, security group, VPC, elastic load balancer, and route53 using terraform. You will get the idea behind using terraform to provision an ansible host or inventory file. This article will go straight to the point of what you want to do.

However, it is important to have a brief introduction of what terraform and ansible do. Terraform is an infrastructure as a code tool that is used to provision an environment or a template that you can reuse or share. Ansible, on the other hand, is a configuration management tool that gives you the ability to configure as many computers as possible. It is also used to automate application deployment, ad-hoc task execution, and cloud provisioning.

Setting up of security group

In your code editor, create a folder and name it EC2. Inside this folder, create a file called sec-grp.tf, and paste the below code inside it

resource "aws_security_group" "demo-sg" { name = "sec-grp" description = "Allow HTTP and SSH traffic via Terraform" tags = { type = "terraform-test-security-group" } ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }

The above snippet will create a security group with a name and the security rule where the ingress is the inbound rule while the egress is the outbound rule.

You will create another file inside the same folder, and name it main.tf, copy and paste the below code.

resource "aws_instance" "zero" { ami = "ami-830c94e3" instance_type = var.instance_type security_groups = ["elb-sg"] key_name = "terraform" tags = { Name = "mini_project_0" } } resource "aws_instance" "one" { ami = "ami-830c94e3" instance_type = var.instance_type security_groups = ["elb-sg"] key_name = "terraform" tags = { Name = "mini_project_1" } } resource "aws_instance" "two" { ami = "ami-830c94e3" instance_type = var.instance_type security_groups = ["elb-sg"] key_name = "terraform" tags = { Name = "mini_project_2" } provisioner "local-exec" { command = "echo [${aws_instance.zero.public_ip}, ${aws_instance.one.public_ip}, ${aws_instance.two.public_ip}] >> /etc/ansible/hosts" } }

Create a variable file and name it var.tf and paste the below code inside

variable "aws_region" {
    default = "us-west-2"
    description = "set aws region"
}

variable "instance_type" {
  default = "t2.micro"
  description = "create t2 micro instance"
}

The above snippet will help you spin up 3 AWS instances, and also send the public ip(s) to an ansible host file.

The next thing is to create a VPC with subnets. Create a new folder outside the current folder, you can name this folder VPC. Inside this folder, create a file and name it main.tf. Paste the below code inside

# create vpc resource "aws_vpc" "vpc" { cidr_block = var.vpc_cidr instance_tenancy = "default" enable_dns_support = var.dnsSupport enable_dns_hostnames = var.dnsHostNames tags = { Name = "${var.project_name}-vpc" } } # create internet gateway and attach it to vpc resource "aws_internet_gateway" "internet_gateway" { vpc_id = aws_vpc.vpc.id tags = { Name = "${var.project_name}-igw" } } # use data source to get all avalablility zones in region data "aws_availability_zones" "available_zones" {} # create public subnet az1 resource "aws_subnet" "public_subnet_az1" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet_az1_cidr availability_zone = data.aws_availability_zones.available_zones.names[0] map_public_ip_on_launch = true tags = { Name = "public_subnet_az1" } } # create public subnet az2 resource "aws_subnet" "public_subnet_az2" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet_az2_cidr availability_zone = data.aws_availability_zones.available_zones.names[1] map_public_ip_on_launch = true tags = { Name = "public_subnet_az2" } } # create public subnet az3 resource "aws_subnet" "public_subnet_az3" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet_az3_cidr availability_zone = data.aws_availability_zones.available_zones.names[2] map_public_ip_on_launch = true tags = { Name = "public_subnet_az3" } } # create route table and add public route resource "aws_route_table" "public_route_table" { vpc_id = aws_vpc.vpc.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.internet_gateway.id } tags = { Name = "public route table" } } # associate public subnet az1 to "public route table" resource "aws_route_table_association" "public_subnet_az1_route_table_association" { subnet_id = aws_subnet.public_subnet_az1.id route_table_id = aws_route_table.public_route_table.id } # associate public subnet az2 to "public route table" resource "aws_route_table_association" "public_subnet_az2_route_table_association" { subnet_id = aws_subnet.public_subnet_az2.id route_table_id = aws_route_table.public_route_table.id } # associate public subnet az3 to "public route table" resource "aws_route_table_association" "public_subnet_az3_route_table_association" { subnet_id = aws_subnet.public_subnet_az3.id route_table_id = aws_route_table.public_route_table.id } # create private app subnet az1 resource "aws_subnet" "private_app_subnet_az1" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_app_subnet_az1_cidr availability_zone = data.aws_availability_zones.available_zones.names[0] map_public_ip_on_launch = false tags = { Name = "private app subnet az1" } } # create private app subnet az2 resource "aws_subnet" "private_app_subnet_az2" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_app_subnet_az2_cidr availability_zone = data.aws_availability_zones.available_zones.names[1] map_public_ip_on_launch = false tags = { Name = "private app subnet az2" } } resource "aws_subnet" "private_app_subnet_az3" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_app_subnet_az3_cidr availability_zone = data.aws_availability_zones.available_zones.names[2] map_public_ip_on_launch = false tags = { Name = "private app subnet az3" } } # create private data subnet az1 resource "aws_subnet" "private_data_subnet_az1" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_data_subnet_az1_cidr availability_zone = data.aws_availability_zones.available_zones.names[0] map_public_ip_on_launch = false tags = { Name = "private data subnet az1" } } # create private data subnet az2 resource "aws_subnet" "private_data_subnet_az2" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_data_subnet_az2_cidr availability_zone = data.aws_availability_zones.available_zones.names[1] map_public_ip_on_launch = false tags = { Name = "private data subnet az2" } } # create private data subnet az3 resource "aws_subnet" "private_data_subnet_az3" { vpc_id = aws_vpc.vpc.id cidr_block = var.private_data_subnet_az3_cidr availability_zone = data.aws_availability_zones.available_zones.names[2] map_public_ip_on_launch = false tags = { Name = "private data subnet az3" } }

Create another file in this folder and name it variable.tf and paste the below code inside it.

variable "dnsSupport" { default = true } variable "dnsHostNames" { default = true } variable "region" { } variable "project_name" { } variable "vpc_cidr" { } variable "alb_security_group_id" { } variable "public_subnet_az1_id" { } variable "public_subnet_az2_id" { } variable "public_subnet_az3_id" { } variable "vpc_id" { } variable "public_subnet_az1_cidr" { } variable "public_subnet_az2_cidr" { } variable "public_subnet_az3_cidr" { } variable "private_app_subnet_az1_cidr" { } variable "private_app_subnet_az2_cidr" { } variable "private_app_subnet_az3_cidr" { } variable "private_data_subnet_az1_cidr" { } variable "private_data_subnet_az2_cidr" { } variable "private_data_subnet_az3_cidr" { }

Create another file called output.tf. This will output all our IDs and make it useful anytime we want.

output "region" { value = var.region } output "project_name" { value = var.project_name } output "vpc_id" { value = aws_vpc.vpc.id } output "public_subnet_az1_id" { value = aws_subnet.public_subnet_az1.id } output "public_subnet_az2_id" { value = aws_subnet.public_subnet_az2.id } output "public_subnet_az3_id" { value = aws_subnet.public_subnet_az3.id } output "private_app_subnet_az1_id" { value = aws_subnet.private_app_subnet_az1.id } output "private_app_subnet_az2_id" { value = aws_subnet.private_app_subnet_az2.id } output "private_app_subnet_az3_id" { value = aws_subnet.private_app_subnet_az3.id } output "private_data_subnet_az1_id" { value = aws_subnet.private_data_subnet_az1.id } output "private_data_subnet_az2_id" { value = aws_subnet.private_data_subnet_az2.id } output "private_data_subnet_az3_id" { value = aws_subnet.private_data_subnet_az3.id } output "internet_gateway" { value = aws_internet_gateway.internet_gateway }

You will go back to your EC2 folder and create another file called terraform.tfvars. In this file, you will paste the below code inside it

region = "us-west-2"
project_name = "app-server"
vpc_cidr = "10.0.0.0/16"
public_subnet_az1_cidr = "10.0.0.0/24"
public_subnet_az2_cidr = "10.0.1.0/24"
public_subnet_az3_cidr = "10.0.2.0/24"
private_app_subnet_az1_cidr = "10.0.3.0/24"
private_app_subnet_az2_cidr = "10.0.4.0/24"
private_app_subnet_az3_cidr = "10.0.5.0/24"
private_data_subnet_az1_cidr = "10.0.6.0/24"
private_data_subnet_az2_cidr ="10.0.7.0/24"
private_data_subnet_az3_cidr = "10.0.8.0/24"

In the main.tf of this same folder, you will create a module that will connect your instance with your VPC. Paste the below code inside the main.tf file.

module "vpc" { source = "../VPC" region = var.region project_name = var.project_name dnsSupport = var.dnsSupport dnsHostNames = var.dnsHostNames vpc_cidr = var.vpc_cidr public_subnet_az1_cidr = var.public_subnet_az1_cidr public_subnet_az2_cidr = var.public_subnet_az2_cidr public_subnet_az3_cidr = var.public_subnet_az3_cidr private_app_subnet_az1_cidr = var.private_app_subnet_az1_cidr private_app_subnet_az2_cidr = var.private_app_subnet_az2_cidr private_app_subnet_az3_cidr = var.private_app_subnet_az3_cidr private_data_subnet_az1_cidr = var.private_data_subnet_az1_cidr private_data_subnet_az2_cidr = var.private_data_subnet_az2_cidr private_data_subnet_az3_cidr = var.private_data_subnet_az3_cidr }

The next on the agenda is to create your elastic load balancer. Create an elb.tf file inside the EC2 folder, and paste the below code inside it

resource "aws_elb" "bar-foo" { name = "bar-alb" availability_zones = ["us-west-2a", "us-west-2b", "us-west-2d"] listener { instance_port = 80 instance_protocol = "http" lb_port = 80 lb_protocol = "http" } listener { instance_port = 443 instance_protocol = "http" lb_port = 443 lb_protocol = "http" } health_check { healthy_threshold = 3 unhealthy_threshold = 3 timeout = 5 target = "HTTP:80/" interval = 30 } instances = ["${aws_instance.zero.id}", "${aws_instance.one.id}", "${aws_instance.two.id}"] cross_zone_load_balancing = true idle_timeout = 40 tags = { Name = "bar-alb" } }

Go to your terminal, type Terraform init, this will help you initialize terraform in your code.

Next thing is to type Terraform plan, this will help you plan the code that you have written. The last thing is to type Terraform apply. This will provision every code you have written in your AWS console.

You are about to provision your route 53. Since every IP address is already pointed at your elastic load balancer. You will create a route.tf file in the EC2 folder, this will make it easy to point your elb to route 53 and your domain. In the route.tf file, paste the below code inside it.

data "aws_route53_zone" "hosted_zone" { name = var.domain_name } # create a record set in route 53 resource "aws_route53_record" "site_domain" { zone_id = data.aws_route53_zone.hosted_zone.zone_id name = var.record_name type = "A" alias { name = aws_elb.bar-foo.dns_name zone_id = var.zone_id evaluate_target_health = true } }

In the var.tf of this folder, paste the below code in it

variable "record_name" { default = "www" description = "sub domain name" } variable "zone_id" { default = "bar-alb-1844813931.us-west-2.elb.amazonaws.com" description = "zone id" }

Type terraform plan and terraform apply to help you provision route 53 on the AWS console.

ANSIBLE

Since you already provision terraform to send public ips to host file. You will go to /etc/ansible/hosts on your terminal. Edit the host file to look like below

[webserver1] 54.213.85.244 ansible_ssh_user=ubuntu ansible_ssh_private_key_file=/home/ubuntu/.ssh/terraform.pem [webserver2] 52.42.3.41 ansible_ssh_user=ubuntu ansible_ssh_private_key_file=/home/ubuntu/.ssh/terraform.pem [webserver3] 34.217.212.125 ansible_ssh_user=ubuntu ansible_ssh_private_key_file=/home/ubuntu/.ssh/terraform.pem

Create another folder called ansible in your home directory, cd into this folder. Create a file and name it inventory. Paste the above code inside it.

Cd back into the home directory, cd into .ssh, and create the key pair that was used to spin up your 3 instances. Cd back into the home directory, cd into ansible directory. Switch to root user, ping the 3 instances by typing the below code

ansible all -m ping -i inventory

When your ping is successful. Create a apache.yml file. This will help us to install apache in the 3 instances. Paste the below code inside the file

--- - name: webserver hosts: all become: true sudo: yes tasks: - name: install apache2 apt: name=apache2 update_cache=yes state=latest - name: enabled mod_rewrite apache2_module: name=rewrite state=present notify: - restart apache2 handlers: - name: restart apache2 service: name=apache2 state=restarted

In this article, you have been able to use terraform to provsion instances, VPC, security group, route 53. Terraform also sent public ip address to anisible host file. This was used to install apache into the 3 instances.

Thank you for using this article

References

  1. https://github.com/Adelakin-Adewumi/terraform-ansible-apache_server

  2. https://www.ansible.com/

  3. https://developer.hashicorp.com/terraform/language