Terraform cloud deployments

Automated deployments have lots of benefits:

  • Saves time
  • reduces possibility of human error
  • disaster recovery - you can recover resources much faster
  • living blueprint - another admin can look at the automation script and get a good understanding of the infrastructure

Terraform can automate cloud infrastructure:

  • Infrastructure as code - write code that represents the desired end state
    • Download it to your machine and it turns script files into infrastructure
  • Cross platform - can run on any cloud provider
  • Should store in a git repo
  • Made by Hashicorp
  • Lower-level than ansible or puppet
  • Ingress blocks let you set a port to allow incoming connections on specified IPs
  • Egress lets your machine connect to the internet

Installation

You have to download Terraform, then configure AWS as the root user:

  1. Go to https://www.terraform.io/ and copy the commands to install
  2. Log into your AWS console as root
  3. Go to IAM > Users
  4. Select Create User
  5. Enter a name, and do not allow access to the AWS Management Console
  6. On Set Permissions, select Attach policies directly, and then select the box beside AdministratorAccess in the Permissions policies list. This policy grants Terraform access to AWS.
  7. On the next page, skip the settings and select Create User.

Create basic EC2 instance

  1. Create a directory to store your terraform config files.
  2. Create a file with a .tf extension:
    provider "aws" {                                                # provider
        profile = "profile-name"                                    # if you have creds for more than one aws profile
    	region = "us-east-1"
    }
    
    resource "aws_instance" "my-server-1" {                         # resource block, "values" here are specific to AWS
    	ami				            = "ami-04b4f1a9cf54c11d0"       # AMI ID
    	associate_public_ip_address	= "true"                        # give the instance a public IP
    	instance_type			    = "t2.micro"                
    	key_name			        = "production-key"              # key pair - look in EC2 > Resources > Key pairs
    	vpc_security_groups_ids		= [ "sg-000511aaefb840967" ]    # security group - EC2 > Network & Security > Security Groups
    	tags = {                                                    # custom tags
    		Name = "Web Server 1"                                   # name of EC2 instance in list
    	}
    }
    
  3. Export environment variables using the keys for the terraform user:
    export AWS_ACCESS_KEY_ID="<key>"
    export AWS_SECRET_ACCESS_KEY="<key>"
    
  4. Change to a directory with terraform.tf files, and run the init command:
    cd config/
    terraform init
    
  5. Check the syntax on your config file:
    terraform plan
    
  6. If the plan command succeeds with no errors, you can apply the config file:
    terraform apply
    

Adding security

provider "aws" {                            
	profile = "terraform-provisioner"
	region = "us-east-1"
}

resource "aws_instance" "my-server-1" {
	ami				            = "ami-0684e5b823fb79036"
	associate_public_ip_address	= "true"
	instance_type			    = "t2.micro"
	key_name			        = "production-key"
	vpc_security_group_ids		= [aws_security_group.external_access.id]   # variable for security group created below
	tags = {
		Name = "Web Server 1"
	}
}
	resource "aws_security_group" "external_access" {   # create new security group resource named in tf as "external_access"
		name		= "my_sg"                           # sg group name in AWS
		description	= "Allow OpenSSH and Apache"        
		ingress {                                       # allow SSH connections on port 22 from this IP
			from_port	= 22
			to_port		= 22
			protocol	= "tcp"
			cidr_blocks	= [ "172.11.59.105/32" ]
			description	= "Home Office IP"
		}
		ingress {                                       # allow HTTP connections on port 80 from this IP
			from_port	= 80
			to_port		= 80
			protocol	= "tcp"
			cidr_blocks	= [ "172.11.59.105/32" ]
			description	= "Home Office IP"
		}
		egress {                                        # allow public internet access (any IP)
			from_port	= 0
			to_port		= 0
			protocol	= "-1"
			cidr_blocks	= ["0.0.0.0/0"]
		}
	
	}

Destroying resources

Run terraform destroy to destroy all resources defined in a config file:

  • when resources are not in use
  • when you need to reset an environment

Terraform and Ansible

Use terraform to create our initial server and infra builds, and then ansible to automate future enhancements:

  • Terraform can also launch the initial Ansible run so you only have to run a single script

To combine these tools, you have to create a script in the same directory as the .tf config file, and then reference the script in the terraform config as user_data. The User data field in AWS is where you manually add scripts that you want to run when the machine is created.

Script

This bootstrap.sh script updates the machine with ansible, then runs ansible pull to get the local.yaml file from the repo:

#!/bin/bash
sudo apt update
sudo apt install -y ansible
sudo ansible-pull -U https://github.com/rjseymour66/ansible.git

Terraform config

This script references bootstrap.sh in the user_data field:

provider "aws" {
	profile = "terraform-provisioner"
	region = "us-east-1"
}

resource "aws_instance" "my-server-1" {
	ami				= "ami-0684e5b823fb79036"
	associate_public_ip_address	= "true"
	instance_type			= "t2.micro"
	key_name			= "production-key"
	vpc_security_group_ids		= [aws_security_group.external_access.id]
	user_data = file("bootstrap.sh")                                            # script ref
	tags = {
		Name = "Web Server 1"
	}
}
	resource "aws_security_group" "external_access" {
		name		= "my_sg"
		description	= "Allow OpenSSH and Apache"
		ingress {
			from_port	= 22
			to_port		= 22
			protocol	= "tcp"
			cidr_blocks	= [ "108.26.179.236/32" ]
			description	= "Home Office IP"
		}
		ingress {
			from_port	= 80
			to_port		= 80
			protocol	= "tcp"
			cidr_blocks	= [ "108.26.179.236/32" ]
			description	= "Home Office IP"
		}
		egress {
			from_port	= 0
			to_port		= 0
			protocol	= "-1"
			cidr_blocks	= ["0.0.0.0/0"]
		}
	
	}