In the previous tutorial, you learned when and why to use Terraform modules. In this tutorial, you will use modules from the Terraform Registry to provision an example environment on AWS. The concepts you use in this tutorial will apply to any modules from any source.
»Prerequisites
Although the concepts in this tutorial apply to any module, this tutorial uses Amazon Web Services (AWS) modules.
To follow this tutorial you will need:
- An AWS account Configure one of the authentication methods described in our AWS Provider Documentation. The examples in this tutorial assume that you are using the Shared Credentials file method with the default AWS credentials file and default profile.
- The Terraform CLI
If you don't have an AWS account or Terraform installed locally, complete this tutorial in an interactive lab from your web browser. Launch it here.
»Use the Terraform Registry
Open the Terraform Registry page for the VPC module in a new browser tab or window.
You will see information about the module, as well as a link to the source repository. On the right side of the page, you will see a dropdown interface to select the module version, as well as instructions to use the module to provision infrastructure.
When calling a module, the source
argument is required. In this example,
Terraform will search for a module in the Terraform registry that matches the
given string. You could also use a URL or local file path for the source of your
modules. See the Terraform
documentation for a list of
possible module sources.
The other argument shown here is the version
. For supported sources, the
version will let you define what version or versions of the module will be
loaded. In this tutorial, you will specify an exact version number for the modules
you use. You can read about more ways to specify versions in the module
documentation.
Other arguments to module blocks are treated as input variables to the modules.
»Create Terraform configuration
In this tutorial, you will use modules to create an example AWS environment using a Virtual Private Cloud (VPC) and two EC2 instances.
You can follow this tutorial by manually building the directory structure and files we describe in the tutorial, or use the following commands to clone this GitHub repo.
Clone the GitHub repository.
$ git clone https://github.com/hashicorp/learn-terraform-modules.git
Change into that directory in your terminal.
$ cd learn-terraform-modules
Check out the ec2-instances
tag into a local branch.
$ git checkout tags/ec2-instances -b ec2-instances
For this tutorial, your main.tf will look like following.
# Terraform configuration
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "us-west-2"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.21.0"
name = var.vpc_name
cidr = var.vpc_cidr
azs = var.vpc_azs
private_subnets = var.vpc_private_subnets
public_subnets = var.vpc_public_subnets
enable_nat_gateway = var.vpc_enable_nat_gateway
tags = var.vpc_tags
}
module "ec2_instances" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.12.0"
name = "my-ec2-cluster"
instance_count = 2
ami = "ami-0c5204531f799e0c6"
instance_type = "t2.micro"
vpc_security_group_ids = [module.vpc.default_security_group_id]
subnet_id = module.vpc.public_subnets[0]
tags = {
Terraform = "true"
Environment = "dev"
}
}
This configuration includes three blocks:
provider "aws"
defines your provider. Depending on the authentication method you chose, you may need to include additional arguments in the provider block.module "vpc"
defines a Virtual Private Cloud (VPC), which will provide networking services for the rest of your infrastructure.module "ec2_instances"
defines two EC2 instances within your VPC.
»Set values for module input variables
In order to use most modules, you will need to pass input variables to the
module configuration. The configuration that calls a module is responsible for
setting its input values, which are passed as arguments in the module block.
Aside from source
and version
, most of the arguments to a module block will
set variable values.
On the Terraform registry page for the AWS VPC module, you
will see an Inputs
tab that describes all of the input
variables
that module supports.
Some input variables are required, meaning that the module doesn't provide a default value — an explicit value must be provided in order for Terraform to run correctly.
Within the module "vpc"
block, review the input variables you are setting. You
can find each of these input variables documented in the Terraform
registry
name
will be the name of the VPC within AWS.cidr
describes the CIDR blocks used within your VPC.azs
are the availability zones that will be used for the VPC's subnets.private_subnets
are subnets within the VPC that will contain resources that do not have a public IP address or route.public_subnets
are subnets that will contain resources with public IP addresses and routes.enable_nat_gateway
if true, the module will provision NAT gateways for your private subnets.tags
specify the tags for each of the resources provisioned by this configuration within AWS.
Note: The configuration above uses Availability Zones valid for the
us-west-2
region configured earlier in this tutorial. If you use a different
region, you will need to use matching Availability Zones.
You can also review the arguments for the module "ec2_instances"
block by
comparing them to the module
documentation.
When creating EC2 instances, you need to specify a subnet and security group for them to use. This example will use the ones provided by the VPC module.
Note: This example uses an AMI ID for the us-west-2
region, which you
configured in the provider
block. If you are using a different region, you
will need an AMI ID appropriate for that region. You can see AMI IDs in the AWS
console
for the region you are using.
»Define root input variables
Using input variables with modules is very similar to how you use variables in
any Terraform configuration. A common pattern is to identify which module input
variables you might want to change in the future, and create matching variables
in your configuration's variables.tf
file with sensible default values. Those
variables can then be passed to the module block as arguments.
Not all module input variables need to be set using variables in your
configuration. For instance, you might want this VPC to always have a NAT
gateway enabled, because the application you are provisioning requires it. In
that case, using a variable to set enable_nat_gateway
would be
counterproductive.
You will need to define these variables in your configuration to use them.
If you have cloned the git repository mentioned earlier in this tutorial, your
variables.tf
will look like the below.
Otherwise, copy and paste the following into variables.tf
:
# Input variable definitions
variable "vpc_name" {
description = "Name of VPC"
type = string
default = "example-vpc"
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "vpc_azs" {
description = "Availability zones for VPC"
type = list
default = ["us-west-2a", "us-west-2b", "us-west-2c"]
}
variable "vpc_private_subnets" {
description = "Private subnets for VPC"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "vpc_public_subnets" {
description = "Public subnets for VPC"
type = list(string)
default = ["10.0.101.0/24", "10.0.102.0/24"]
}
variable "vpc_enable_nat_gateway" {
description = "Enable NAT gateway for VPC"
type = bool
default = true
}
variable "vpc_tags" {
description = "Tags to apply to resources created by VPC module"
type = map(string)
default = {
Terraform = "true"
Environment = "dev"
}
}
»Define root output values
Modules also have output values, which are defined within the module with the
output
keyword. You can access them by referring to module.<MODULE NAME>.<OUTPUT NAME>
. Like input variables, module outputs are listed under the
outputs
tab in the Terraform
registry.
Module outputs are usually either passed to other parts of your configuration, or defined as outputs in your root module. You will see both uses in this tutorial.
Inside your configuration's directory, outputs.tf
will need to contain:
output "vpc_public_subnets" {
description = "IDs of the VPC's public subnets"
value = module.vpc.public_subnets
}
output "ec2_instance_public_ips" {
description = "Public IP addresses of EC2 instances"
value = module.ec2_instances.public_ip
}
In this example, the value of the vpc_public_subnets
will come from the
public_subnets
output from the module named vpc
, and
ec2_instance_public_ips
is defined as module.ec2_instances.public_ip
.
»Provision infrastructure
Initialize your Terraform configuration by running terraform init
.
$ terraform init
Initializing modules...
Downloading terraform-aws-modules/ec2-instance/aws 2.12.0 for ec2_instances...
- ec2_instances in .terraform/modules/ec2_instances/terraform-aws-modules-terraform-aws-ec2-instance-ed6dcd9
Downloading terraform-aws-modules/vpc/aws 2.21.0 for vpc...
- vpc in .terraform/modules/vpc/terraform-aws-modules-terraform-aws-vpc-2417f60
Initializing the backend...
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.44.0...
# ...
Terraform has installed the provider and both of the modules your configuration refers to.
Now run terraform apply
to create your VPC and EC2 instances:
$ terraform apply
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:
# module.ec2_instances.aws_instance.this[0] will be created
+ resource "aws_instance" "this" {
+ ami = "ami-0c5204531f799e0c6"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
# ...
Plan: 22 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
You will notice that many more resources than just the VPC and EC2 instances will be created. The modules we used define what those resources are.
Respond to the prompt with yes
to apply the changes and continue.
You should see the instance IP addresses in your configuration's output:
# ...
Outputs:
ec2_instance_public_ips = [
"34.220.43.248",
"34.208.3.72",
]
vpc_public_subnets = [
"subnet-0ecd7a5db2a78879d",
"subnet-005a1cfe9fbbf596c",
]
»Understand how modules work
When using a new module for the first time, you must run either terraform init
or terraform get
to install the module. When either of these commands are run,
Terraform will install any new modules in the .terraform/modules
directory
within your configuration's working directory. For local modules, Terraform will
create a symlink to the module's directory. Because of this, any changes to
local modules will be effective immediately, without having to re-run terraform get
.
After following this tutorial, your .terraform/modules
directory will look
something like this:
.terraform/modules
├── ec2_instances
│ └── terraform-aws-modules-terraform-aws-ec2-instance-ed6dcd9
├── modules.json
└── vpc
└── terraform-aws-modules-terraform-aws-vpc-2417f60
»Clean up your infrastructure
Now you have seen how to use modules from the Terraform registry, how to configure those modules with input variables, and how to get output values from those modules.
Before moving on to the next tutorial, destroy the infrastructure you created
by running the terraform destroy
command:
$ terraform destroy
module.vpc.aws_eip.nat[1]: Refreshing state... [id=eipalloc-019208f8c88f9c7d6]
module.vpc.aws_eip.nat[0]: Refreshing state... [id=eipalloc-0046def26f17a2eb2]
module.vpc.aws_vpc.this[0]: Refreshing state... [id=vpc-0d6e08bf4f27de4ab]
# ...
Plan: 0 to add, 0 to change, 22 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
module.vpc.aws_route_table_association.private[1]: Destroying... [id=rtbassoc-0cc54bc0390c1af5e]
module.vpc.aws_route_table_association.private[0]: Destroying... [id=rtbassoc-0d435e5616f58a9fc]
# ...
module.vpc.aws_internet_gateway.this[0]: Destruction complete after 11s
module.vpc.aws_vpc.this[0]: Destroying... [id=vpc-0d6e08bf4f27de4ab]
module.vpc.aws_vpc.this[0]: Destruction complete after 0s
Destroy complete! Resources: 22 destroyed.
After you respond to the prompt with yes
, Terraform will destroy the
infrastructure you created.
Note: Failing to destroy the infrastructure you created during this tutorial could result in charges from AWS.
»Next steps
In this tutorial, you have learned how to:
- Use modules in your Terraform configuration
- Manage module versions
- Use the Terraform Registry
- Configure module input variables
- Use module output values
In the next tutorial, you will host a website in an S3 bucket by creating and calling a child module from the configuration you built in this tutorial.