Terraform Basics : Start here!
Topics :
1. What is Terraform ?
2. Important Commands and it’s meaning(init, plan, and apply)
3. Core concepts :
Providers
Resources
Count and for for_each
Module
State
Input Values
Output Values
Local Variables
WorkSpaces
4. Directory Structure
For Practical guide check this out
Let’s begin !
I hope I can make this interesting for you to learn. If you have never before read about Terraforms, this is a good place to start with. I have kept the practical implementation to minimal with just syntax and it’s explanation.
Infrastructure as code. Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently.
Infrastructure is described using a high-level configuration syntax. This allows a blueprint of your datacenter to be versioned and treated as you would any other code.
Installation : https://www.terraform.io/downloads.html
Just add the package to a PATH variable to complete the installation!
Important Terraform commands :
- Write — Author infrastructure as code.
- Plan — Preview changes before applying.
- Apply — Create infrastructure in real time.
Write
You write Terraform configuration just like you write code. Same way you write any Java or Python code.
terraform init -> This command will download the dependencies/providers.
Plan
This step tells about the actions that will be performed to create resources. Terraform has a “planning” step where it generates an execution plan. The execution plan shows what Terraform will do when you call apply. This lets you avoid any surprises when Terraform manipulates infrastructure.
Apply
The actual resources/infrastructure is created in this stage.
Terraform Language
The core ideology of a terraform language is to declare resources which represent your infrastructure. All other features exist to make the resources more flexible and convenient.
Syntax :
resource “aws_vpc” “main” { cidr_block = var.base_cidr_block}<BLOCK TYPE> “<BLOCK LABEL>” “<BLOCK LABEL>” { # Block body <IDENTIFIER> = <EXPRESSION> # Argument}
Resource is anything which can be a target of CRUD operations like a server, storage, database, subnets, VPCs.
Blocks are containers used for other content configuration which helps give flexibility to Resources. Blocks can have 0 or more labels, and has a body where we can assign names and do all kinds of configuration.
Example of a Block :
variable “base_cidr_block” { description = “A /16 CIDR range definition, such as 10.1.0.0/16 which VPC will use
default = “10.1.0.0/16”}
We declare a variable base_cidr_block which can be used in any part of you root/child modules. Think of it as the variable we define in programming languages like Java/Python.
Terraform Important Concepts before diving to code :
- Providers
Every resource type is implemented by a provider; without providers, Terraform can’t manage any kind of infrastructure.
Example of a Provider (taken from Terraform registry)
terraform {
required_providers { aws = { source = “hashicorp/aws” version = “3.43.0” } }}provider “aws” {# Configuration options}
We are using AWS as our infrastructure destination. The above code will help Terraform identify the Provider and download them, so that we can create resources in AWS without any complaints.
Some providers on the Registry are developed and published by HashiCorp, some are published by platform maintainers, and some are published by users and volunteers.
2. Resources
Resources are the most important element in the Terraform language. Each resource block describes one or more infrastructure objects, such as S3 bucket, VPC, any compute instances.
Syntax :
resource "aws_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}
Creates a resource named aws_instance.web with ami image and instance_type passed as parameter
3. count and for_each Meta Arguments
The count and for_each meta arguments allow us to create multiple instances of any resource. The main difference between them is that count expects a non-negative number, whereas for_each accepts a list or map of values.
Count :
variable “my_list” { default = [“first”, “second”, “third”]}resource “null_resource” “default” { count = length(var.my_list) triggers = { list_index = count.index list_value = var.my_list[count.index] }}
For_each :
variable “instances” { type = map(string)}resource “aws_instance” “server” { for_each = var.instances ami = each.value instance_type = “t2.micro” tags = { Name = each.key }
}
for_each is far more superior when it comes to creating instances, because on update of a terraform file, the terraform apply command doesn’t waste time in recreating the already defined infrastructure.
4. Data Sources
Use of data sources allows a Terraform configuration to make use of information defined outside of Terraform, or defined by another separate Terraform configuration.
Example :
data “aws_ami” “example” { most_recent = true owners = [“self”] tags = { Name = “app-server” Tested = “true” }}
A data block requests that Terraform read from a given data source (“aws_ami”) and export the result under the given local name (“example”). The goal is to query a source and then give you information as the response which can be used in your code. The return response “example” can be used elsewhere in your terraform configuration.
The above code will return the ami image descriptions like name, owner_id, image_id .
Remember data sources are read-only views into the state of pre-existing components external to our configuration.
Check this for descriptive explanation.
5. State
Terraform stores the configuration details of your infrastructure in the state.
As we know not every resource supports tag, so when we create a resource we need the unique id by which we can identify our own resource, so Terraform manages its own state structure to maintain the real world mapping to our local Terraform configuration.
Terraform state is stored locally in a file “terraform.tfstate”. However because of security, it is advised to store it in cloud itself. This file can be stored in s3 buckets or Terraform cloud itself.
Whenever we update any resource in Terraform code, the updated code is compared to the state file to identify what are the changes that need to be done on infrastructure, so that we don’t end up creating the resources from scratch.
terraform plan command gives a very detailed description of update which is to be done.
6. Modules
As the keyword suggests, using this feature we can segregate our code in chunks for best design principles.
Your own Terraform module configuration (usually called the root module) can call other child modules to use their resources.
Module Syntax :
module “webserver_cluster” { source = “../../../modules/services/webserver-cluster”}
Source is the child module location which we will use in our code, and “webserver_cluster” is the identifier by which it is referenced.
7. Input Values
Variables in terraform are a great way to define centrally controlled reusable values. Terraform CLI defines the following optional arguments for variable declarations:
- default — A default value which then makes the variable optional.
- type — This argument specifies what value types are accepted for the variable.
- description — This specifies the input variable’s documentation.
- validation — A block to define validation rules, usually in addition to type constraints.
- sensitive — Limits Terraform UI output when the variable is used in configuration.
Example :
variable “template” { type = string default “vivek”}
Creates a variable template of type string, with default value “vivek”
8. Output Values
Output variables provide a convenient way to get useful information about your infrastructure. As you might have noticed, computation is done during deployment which generates tons of output values based on your configuration. So using output variables you can extract values which were generated during or after deployment.
Example :
output “public_ip” { value = upcloud_server.server_name.network_interface[0].ip_address}
The above code extracts the IP address from an upcloud_server resource which was created during deployment. There is no way that we could have known IP address before the deployments.
9. Local Values
Local variables as the keyword suggests can not be used in other modules, they are defined to restrict the scope of the variable to your root module.
Syntax :
locals { vpc_id = module.network.vpc_id}//Usage in a resourceresource “aws_instance” “example” { # … tags = local.vpc_id}
10. WorkSpaces
Workspace is similar to the branching strategy we adopt in VCS. We have dev, prod, staging environments in a Agile methodology. Similarly Workspaces is a convenient way for versioning of your project.
Terraform starts with a single workspace named “default”. This workspace is special both because it is the default and also because it cannot ever be deleted.
We create new workspaces using this command :
terraform workspace new workspaceName
You’re now in a new, empty workspace. Workspaces isolate their state, so if you run “terraform plan” Terraform will not see any existing state for this configuration.
A common use for multiple workspaces is to create a parallel, distinct copy of a set of infrastructure in order to test a set of changes before modifying the main production infrastructure. For example, in any application we test our changes in stages like development, integration, staging before going to production to rule out any possible causes of bugs.
Directory structure in basic Terraform project :
- main.tf -> Has core logic to create
- variables.tf -> Consists of variables definition and declaration which will be used across the module
- outputs.tf -> Consists of variables which will be assigned after deployment starts .
Conclusion :
This covers the basics of Terraform which will help in deep diving into Terraform development for different providers.
Please reach out to me at vivek.sinless@gmail.com for any freelancing work.