This will be Part 1 out of 2 of the Terraform primer series.
Welcome to Terraform 101, where we dive deep into the world of Terraform and its powerful capabilities. In this comprehensive guide, we will explore Terraform 101, Terraform 101, and Terraform 101 once again, understanding what Terraform is and how it can revolutionize your infrastructure provisioning process. Get ready to unlock the full potential of Terraform and embark on an exciting journey of automation and efficiency.
Terraform 101
What is Terraform? Terraform is fairly easy to pickup and make sense of mostly due to the comprehensive and informative Getting Started guide. It does a great job at introducing the core components of Terraform such as resources, inputs, outputs, etc.
Here you’ll be focusing on how to bring all this components together to create real-world infrastructures. We’ll start by provisioning a single web server and move on to provision the infrastructure for a highly available Web Application.
You can find complete sample code for this post at: https://github.com/osodevops/terraform-azure-confluent-platform. Note that all the code samples are written for Terraform 0.8.x.
Requirements
- Terraform AWS Account
Install Terraform
Simple follow the [install instructions here] to install Terraform. To confirm that the installation is successful, simply run the terraform command.
Configure Terraform
Now that you’ve installed Terraform, you need to give it access to our AWS account to be able to provision resources. There’re a couple of ways this can be done;
Environment Variables
You can export your AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
as environment variables and Terraform will automatically use them before performing any operation.
export AWS_ACCESS_KEY_ID=(your access key id)
export AWS_SECRET_ACCESS_KEY=(your secret access key)
Terraform Provider
Providers generally are an IaaS (e.g. AWS, GCP, Microsoft Azure, OpenStack), PaaS (e.g. Heroku), or SaaS services (e.g. Atlas, DNSimple, CloudFlare).
A Terraform provider is responsible for understanding API interactions and exposing resources. Since you’re using AWS, you need to configure it by providing our credentials.
# Configure the AWS Provider
provider "aws" {
access_key = "(your access key id)"
secret_key = "(your secret access key)"
region = "us-east-1"
}
This tells Terraform that you are going to be using the AWS provider and that you wish to deploy your infrastructure in the “eu-youst-1” region.
The above block of code will typically be at the top of a file named main.tf
which you will look at in more detail as you progress.
Provisioning Infrastructure
Terraform code is written in the HCL syntax in files that have the extension “.tf”. Being a declarative language, our goal is to describe our infrastructure and have Terraform create it for us.
Server
Let’s start off by deploying a single server using Terraform. This will be in the form of and Amazon EC2 instance.
resource "aws_instance" "example" {
ami = "ami-2d39803a"
instance_type = "t2.micro"
}
Each resources specifies a type (“awsinstance”), a name (“example”) and a set of parameters to configure it.
In a terminal, go into the folder where you created main.tf, and run the “terraform plan” command:
The plan command lets you see what Terraform will do before actually doing it. Resources with a plus sign (+) are going to be created, resources with a minus sign (-) are going to be deleted, and resources with a tilde sign (~) are going to be modified.
To actually create the instance, run the “terraform apply” command:
That’s it! you’ve now deployed a server on AWS using Terraform. This isn’t very exciting so lets make this server more useful.
Web Server
Let’s start by giving the server you just created a name by adding a tag.
resource "aws_instance" "example" {
ami = "ami-2d39803a"
instance_type = "t2.micro"
tags {
Name = "webserver"
}
}
Let’s run the plan command to see our changes:
Now you can apply the change:
Our server still doesn’t do anything useful. Let’s turn it into a web server. Let’s fix that by running a simple web server that always returns the text “Hello, World”.
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p 8080 &
To keep this example simple, you’re going to run the script above as part of the EC2 Instance’s User Data, which AWS will execute when the instance is booting:
resource "aws_instance" "example" {
ami = "ami-2d39803a"
instance_type = "t2.micro"
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p "${var.server_port}" &
EOF
tags {
Name = "webserver"
}
}
The “<<-EOF” and “EOF” are Terraform’s heredoc syntax, which allows you to create multiline strings without having to put “\n”.
By default, AWS does not allow any incoming or outgoing traffic from an EC2 Instance. To allow the EC2 Instance to receive traffic on port “${var.serverport}”, you’ll create a security group that allows incoming requests on port “${var.serverport}” from any IP
resource "aws_security_group" "example" {
name = "example-securitygroup"
ingress {
from_port = "${var.server_port}"
to_port = "${var.server_port}"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Note that in the security group above, we copied & pasted port 8080. To keep your code DRY and to make it easy to configure the code, Terraform allows you to define input variables:
variable "server_port" {
description = "The port the server will use for HTTP requests"
default = 8080
}
You should put the above code in the same directory as main.tf
in a file called variables.tf
.
We can now use this via the interpolation syntax:
resource "aws_security_group" "example" {
name = "example-securitygroup"
ingress {
from_port = "${var.server_port}"
to_port = "${var.server_port}"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
In Terraform, every resource has attributes that you can reference using the same syntax as interpolation. You can find the list of attributes in the documentation for each resource. For example, the awssecuritygroup attributes include the ID of the security group. We can now attach this to our EC2 instance by adding this to the server resource block by referencing the id security group that’ll be created.
The syntax is “${TYPE.NAME.ATTRIBUTE}”. When one resource references another resource, you create an implicit dependency.
resource "aws_instance" "server" {
....
vpc_security_group_ids = [
"${aws_security_group.example.id}"
]
}
If you run the plan command, you’ll see that Terraform wants to replace the original EC2 Instance with a new one that has the new user data (the “-/+” means “replace”) and to add a security group:
Everything looks good so you will apply the plan:
We need to get the IP address of the server so you can confirm that our server is running and displaying the “Hello World” message you expect. We could simple apply this change, wait for the server to be launched and log into the AWS console and look for the Elastic IP assigned,fortunately, you can do better by specify an output variable:
output "public_ip" {
value = "${aws_instance.example.public_ip}"
}
Put the above code in the same directory as main.tf
in a file called outputs.tf
. Terraform will by default parse all “.tf” files in the working directory.
Let’s run apply again:
We now have our public IP and can test our webserver:
> curl http://54.173.220.62:"${var.server_port}"
Hello, World
Success!