Comprendre Terraform en 5 minutes

Understand Terraform (infra-as-code) in 5 minutes

Terraform, this is my little treat of the moment. We have to talk about it. With the explosion of the DevOps movement in recent years, infrastructure-as-code has become a must. And Terraform is the cool kid in this field.



Once upon a time

In the beginning of 2006, Amazon announced a slightly known service suite : Amazon Web Service (AWS). Heard about it ?

On August 25 of the same year, EC2 is launched in beta in the United States. For the first time, developers can launch an on-demand server in the cloud within minutes. One year later, the service is publicly open worldwide. This was about to change everything.

You have to understand that before this revolution, having a server for your app was pretty much hell. It took a request to the infrastructure service in your company. You had to wait during the manual creation of a physical server that would not move anymore.

Right now, you create, modify and destroy your server in a few clicks. Almost instantaneously. And that’s all good and well, but it’s not enough for total control.





What if you could, via your code, dynamically create and manage your infrastructure in a cloud service ? Amazon likes the idea so much that on May 15, 2010 they announce CloudFormation.

Infrastructure as a service was born and it was going to give a lot of people a lot of pleasure. The most talked about IaC tool at the moment is the subject today. Terraform.



What’s Terraform?

Terraform is an infrastructure tool as code. It is open source and written in Go by Hashicorp. Concretely, it allows you to declare, via code, what you want for your infrastructure. In structured configuration files you will be able to manage your infrastructure automatically without manual action. Whether it’s the initial provisioning, the update or the destruction of your infrastructure, it’s your code that drives everything.

In the world of infra-as-code you have several ways of doing things. Terraform goes for the so-called declarative way of doing things. You declare in your configuration files the desired state of your infrastructure. Terraform will only do the minimum to achieve the desired state.

Example: your live infrastructure already has a web server and a database running. Your config in your code corresponds perfectly to this. Then you add in your configuration that finally you want two databases. When you commit, Terraform will create an additional database and do nothing else. It has reached the desired state declared by your code. This is beautiful when you see it.





It’s beautiful because it means you can see and understand your infrastructure by only looking at your code. Code which is versioned via git ! Your infra is versioned.

Since it’s code, you can use it to test in several environments before production. Push the same code in a dev environment and you can test changes in a safe way. You can check the status of your infra in code review with your colleagues. You can iterate quickly and make important changes by changing a few lines. In short, it’s paradise for the DevOps way of doing things.

But how does it actually work? How does Terraform manage to create, modify and destroy your infrastructure via a piece of config?



How does it work?

Terraform will take the state you declared in your configuration files and push the changes to the destination provider. Let’s imagine, you want to create a server on AWS. You declare it, you push it and poof your server appears in your AWS account.

What happened under the engine is that Terraform used the AWS SDK written in Go. Via this SDK it created your server directly on the cloud. But you don’t have to bother with the SDK code or really understand what’s going on behind it. Terraform takes care of its shit and you take care of your code. This makes the infrastructure update flow very simple and very fast for you.





And the thing is that Terraform is not limited to one provider (AWS) or the Cloud in general. Almost any type of infrastructure can be represented as a resource in Terraform. Whi is CRAZY when you think about it !

From docker containers on your local machine to your cloud account on DigitalOcean, you can reproduce your infrastructure everywhere. I’m not going to give you the whole list of possible providers, it’s all over the place. And that makes Terraform even more beautiful.

Okay, enough with the theory, let’s get our hands in there.



Show me the code

Let’s start by setuping everything. I’m assuming you’re using a holy Linux distribution. If you’re not, you can look over there.



# dependencies
sudo apt-get install wget unzip

# download last version
wget https://releases.hashicorp.com/terraform/0.12.20/terraform_0.12.20_linux_amd64.zip

# unzip in bin folder
sudo unzip ./terraform_0.12.20_linux_amd64.zip -d /usr/local/bin/

# check its working
terraform -v


Now we’re going to write our first configuration file. Let’s do something simple: we’re going to create an EC2 server and a Postgres database on AWS. I’m assuming you also have an AWS account with admin credentials. First step, we’ll create a main.tf file where we’ll simply define our provider.



main.tf

# definition provider
provider "aws" {
  version = "~> 2.0"
  region = "${var.provider_region}"
  access_key = "${var.secret_access_key}"
  secret_key = "${var.secret_key}"
}


Here we simply saying that we want to work on AWS and choose our version and region. To access AWS we specify the credentials. They are referenced via a variable system. These variables are all stored in a file that we will see later.

Now that we have declared the destination, we will declare what we want to create. As usual, when we don’t know where to start, we look at the doc. Let’s create a resource.tf file where we declare an Ubuntu machine and a Postgres database.



resource.tf

resource "aws_instance" "web" {
  ami = "${data.aws_ami.ubuntu.id}"
  instance_type = "${var.server_instance_type}"

  tags = {
    Name = "${var.server_tag_name}"
  }
}

data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
  }

  owners = ["099720109477"]
}

resource "aws_db_instance" "default" {
  allocated_storage = "${var.db_allocated_storage}"
  storage_type = "${var.db_storage_type}"
  engine = "${var.db_engine}"
  engine_version = "${var.db_engine_version}"
  instance_class = "${var.db_instance_class}"
  name = "${var.db_database_name}"
  username = "${var.db_database_username}"
  password = "${var.secret_db_database_password}"
  final_snapshot_identifier = "${var.db_snapshot_identifier}"
}


Now that we’ve defined everything we wanted, we’re going to fill all this in with the corresponding variables. We create a variables.tf file dedicated to that. Note that secrets should never be in clear text in a file but for simplicity, we’ll do it like this.



# provider variables
variable "provider_region" {
 description = "Provider region"
 default = "us-east-1"
}

variable "secret_access_key" {
 description = "Provider access key"
 default = "YOUR-ACCESS-KEY"
}

variable "secret_key" {
 description = "Provider secret key"
 default = "YOUR-SECRET-KEY"
}

# instance web variables
variable "server_instance_type" {
  description = "Server instance type"
  default = "t2.micro"
}

variable "server_tag_name" {
  description = "Server tag name"
  default = "JeSuisUnDev"
}

# instance base de données RDS postgres variables
variable "db_allocated_storage" {
  description = "Allocated storage"
  default = 20
}
variable "db_storage_type" {
  description = "Storage type"
  default = "gp2"
}

variable "db_engine" {
  description = "Storage engine"
  default = "postgres"
}

variable "db_engine_version" {
  description = "Storage engine version"
  default = "11.5"
}

variable "db_instance_class" {
  description = "Storage instance class"
  default = "db.t2.micro"
}

variable "db_database_name" {
  description = "Storage database name"
  default = "postgres"
}

variable "db_database_username" {
  description = "Storage database name"
  default = "postgres"
}

variable "secret_db_database_password" {
  description = "Storage database secret password"
  default = "postgres"
}

variable "db_snapshot_identifier" {
  description = "Storage database snapshot identifier"
  default = "postgres"
}


There you go! End of configuration! It’s time to test it all. To do that, we’re going to go in stages. First we’ll initialize Terraform, validate our files, plan our actions and finally apply our changes. Let’s use the Terraform CLI!





Let’s start by typing the Terraform initialization command. This has to be done in the current folder where you created the previous files.

> terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.48.0...

Terraform has been successfully initialized!

We validate that everything is fine in our configuration.

> terraform validate

Success! The configuration is valid.

Then we plan our next move. We should only have create operations to do since we’re on an new infra.

> terraform plan

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_db_instance.default will be created
  # aws_instance.web will be created

Plan: 2 to add, 0 to change, 0 to destroy.

And now, the fateful moment that makes all the magic of this kind of tool. We’re going to apply the Terraform plan. It’s going to actually create our infrastructure in the cloud.

> terraform apply

aws_instance.web: Creating...
aws_db_instance.default: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [30s elapsed]
aws_instance.web: Creation complete after 43s [id=i-0f285230749e69a67]
aws_db_instance.default: Still creating... [50s elapsed]
aws_db_instance.default: Still creating... [1m50s elapsed]
aws_db_instance.default: Still creating... [2m50s elapsed]
aws_db_instance.default: Still creating... [3m50s elapsed]
aws_db_instance.default: Still creating... [4m0s elapsed]
aws_db_instance.default: Creation complete after 4m8s [id=terraform-20200208064624729700000001]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

There you go. You just created an infrastructure with code. Isn’t that beautiful ? Well, now that it’s done, you can check its status with a little terraform show. And most importantly, you can destroy it as easily as you created it.

> terraform destroy

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

aws_instance.web: Destroying... [id=i-0f285230749e69a67]
aws_db_instance.default: Destroying... [id=terraform-20200208064624729700000001]
aws_instance.web: Destruction complete after 30s
aws_db_instance.default: Destruction complete after 50s

Destroy complete! Resources: 0 added, 0 changed, 2 destroyed.

You now have complete control via your code over your entire infrastructure. And if you put it all into a CI/CD pipeline, nothing can stop you anymore.



ci cd


Epilogue

Want to know more ? The whole Terraform doc is waiting for you, she’s very well done. There’s also a stepper made by Hashicorp, it’s really nice for newcomers. Or you know, just play with it ! Fun ahead.

Written by

jesuisundev
I'm a dev. Right now i'm Backend Developer / DevOps in Montreal. Dev is one of my passions and I write as I speak. I talk to you daily on my Twitter. You can insult me at this e-mail or do it directly in the comments below. There's even a newsletter !

1 thought on “Understand Terraform (infra-as-code) in 5 minutes”

Leave a reply

Your email address will not be published. Required fields are marked *