Professional Documents
Culture Documents
their
features) required to access it through SSH.
For that it’s necessary to deploy following AWS services :
– VPC (Virtual Private Cloud)
– Security Group
– Subnet
– Route Table
– Internet Gateway
– And finally the EC2 instance
For a better manageability I do recommend to create one dedicated file per service listed above. This will also give a
good overview what the Terraform Configuration is made of :
joc@joc:$ tree
1
.
2
├── aws-config.tf
3
├── create-igw.tf
4
5 ├── create-instance.tf
6 ├── create-rt.tf
7 ├── create-sbn.tf
8 ├── create-sg.tf
9
├── create-vpc.tf
10
├── variables_output.tf
11
└── variables.tf
12
13
0 directories, 9 files
Variables
Using variables in a Terraform Configuration is a good practice. Like with all other tools and programming languages,
their purpose is to create a value only once and to reuse it across the whole deployment configuration.
The best is to create a dedicated file to store them. Here’s what mine looks like :
1 # variables.tf
2
4 ######################################
5
6
variable "aws_region" {
7
description = "AWS region"
8
type = string
9
default = "eu-central-1"
10
}
11
12
variable "owner" {
13
15 type = string
16 }
17
18 variable "aws_region_az" {
19
description = "AWS region availability zone"
20
type = string
21
default = "a"
22
}
23
24
25
# Variables for VPC
26
######################################
27
28
29 variable "vpc_cidr_block" {
32 default = "10.0.0.0/16"
33
}
34
35
variable "vpc_dns_support" {
36
description = "Enable DNS support in the VPC"
37
type = bool
38
default = true
39
}
40
41
42 variable "vpc_dns_hostnames" {
44 type = bool
45 default = true
46
}
47
48
49
# Variables for Security Group
50
######################################
51
52
variable "sg_ingress_proto" {
53
description = "Protocol used for the ingress rule"
54
type = string
55
56 default = "tcp"
57 }
58
59 variable "sg_ingress_ssh" {
60 description = "Port used for the ingress rule"
61 type = string
62
default = "22"
63
}
64
65
variable "sg_egress_proto" {
66
description = "Protocol used for the egress rule"
67
type = string
68
default = "-1"
69
70 }
71
72 variable "sg_egress_all" {
74 type = string
75
default = "0"
76
}
77
78
variable "sg_egress_cidr_block" {
79
description = "CIDR block for the egress rule"
80
type = string
81
default = "0.0.0.0/0"
82
83 }
84
85
87 ######################################
88
89 variable "sbn_public_ip" {
99 default = "10.0.1.0/24"
100 }
101
102
104 ######################################
105
106
variable "rt_cidr_block" {
107
description = "CIDR block for the route table"
108
type = string
109
default = "0.0.0.0/0"
110
}
111
112
113
# Variables for Instance
114
115 ######################################
116
128 }
129
type = string
default = "gp2"
variable "root_device_size" {
type = string
default = "50"
Each of these variable will be used to describe our environment and they will need to be prefixed by var. when we will
be using them.
Let’s start with the first step.
Provider
As already said, Terraform supports infrastructure deployment to several Cloud providers. Thus, the first things to do
in our Configuration is to define the provider we want to use.
As the providers’ API have several versions available, it’s important to define a version constraint to force Terraform
to select a single version
that all parts of your configuration are compatible with. If you don’t do that you take the risk to use a newer version
that might not be compatible with your Configuration.
A good way to force the maximum version we want to work with is to use the ~> operator. This will allow to use only
patch releases within a specific minor release :
# aws_config.tf
1
2
terraform {
3
required_providers {
4
aws = {
5
6
source = "hashicorp/aws"
8 }
9 }
10
}
11
12
provider "aws" {
13
profile = "default"
14
region = var.aws_region
15
}
As I don’t set my AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (security), Terraform will use the one
coming from my environment variables.
VPC
VPC (Virtual Private Cloud) is one of the fundamental component to create when starting an AWS project. It allows to
launch resources in a virtual and isolated network. Creating a VPC is optional, but it becomes mandatory when you
want to create interconnections with others networks or if you want to isolate some EC2 instances in a subnet
unreachable from outside :
# create-vpc.tf
1
2
resource "aws_vpc" "vpc" {
3
cidr_block = var.vpc_cidr_block
4
5 enable_dns_hostnames = var.vpc_dns_hostnames
6 enable_dns_support = var.vpc_dns_support
7
8 tags = {
9 "Owner" = var.owner
10
"Name" = "${var.owner}-vpc"
11
}
12
}
This defines the IP range that will be used and enable DNS support and hostname so the instance can get a DNS name.
Security Group
To control inbound and outbound access to an EC2 instance, it’s required to create a Security Group.
A Security Group is like the local firewall of the instance. If we want to be able to connect to the instance via SSH we
need to create an ingress rule allowing our IP to connect to TCP port 22.
In order to be able to connect to the instance from wherever we are located (home, office, …), let’s store our current
public IP using the data sources module of Terraform. Then it can be used as a variable to define the value of
the cidr_blocks argument in the ingress attributes :
1 # create-sg.tf
2
3 data "http" "myip" {
4 url = "http://ipv4.icanhazip.com"
5
}
6
7
resource "aws_security_group" "sg" {
8
name = "${var.owner}-sg"
9
description = "Allow inbound traffic via SSH"
10
vpc_id = aws_vpc.vpc.id
11
12
ingress = [{
13
15 protocol = var.sg_ingress_proto
16 from_port = var.sg_ingress_ssh
17 to_port = var.sg_ingress_ssh
18
cidr_blocks = ["${chomp(data.http.myip.body)}/32"]
19
ipv6_cidr_blocks = []
20
prefix_list_ids = []
21
security_groups = []
22
self = false
23
24
}]
25
26
27 egress = [{
29 protocol = var.sg_egress_proto
30 from_port = var.sg_egress_all
31
to_port = var.sg_egress_all
cidr_blocks = [var.sg_egress_cidr_block]
32 ipv6_cidr_blocks = []
33
prefix_list_ids = []
34
security_groups = []
35
self = false
36
37
}]
38
39
tags = {
40
"Owner" = var.owner
41
42 "Name" = "${var.owner}-sg"
}
A second rules exists in this Security Group to allow the instance to connect outside.
Subnet
A subnet must be created inside the VPC, with its own CIDR block, which is a subset of the VPC CIDR block :
1 #create-sbn.tf
2
If you plan to create more than one subnet, think about deploying them into different Availability Zones.
Internet Gateway
We also need an Internet Gateway to enable access over the Internet.
# create-igw.tf
1
2
4 vpc_id = aws_vpc.vpc.id
5
6 tags = {
7 "Owner" = var.owner
8 "Name" = "${var.owner}-igw"
9
}
10
}
If the traffic of a subnet is routed to the Internet Gateway, the subnet is known as a public subnet. That means that all
instances connected to this subnet can connect the Internet through the Internet Gateway.
To define this association, we need a Route Table :
Route Table
1 #create-rt.tf
2
8
gateway_id = aws_internet_gateway.igw.id
9 }
10
11 tags = {
12 "Owner" = var.owner
13
"Name" = "${var.owner}-rt"
14
}
15
16
}
The link between the subnet and the route table is done by creating an association :
2 subnet_id = aws_subnet.subnet.id
3 route_table_id = aws_route_table.rt.id
4 }
Instance
The network layer is now ready. We can create the EC2 instance in the subnet of our VPC :
1 # create-instance.tf
2
14
encrypted = false
15 volume_size = var.root_device_size
16 volume_type = var.root_device_type
17 }
18
19
tags = {
20
"Owner" = var.owner
21
"Name" = "${var.owner}-instance"
22
"KeepInstanceRunning" = "false"
23
}
24
}
Init
Everything is ready. To start the deployment, we need first of all to initialize our working directory :
1 joc@joc:$ terraform init
2
5
Initializing provider plugins...
6
- Finding latest version of hashicorp/http...
7
- Finding hashicorp/aws versions matching "~> 3.26"...
8
- Installing hashicorp/http v2.0.0...
13
Terraform has created a lock file .terraform.lock.hcl to record the provider
14
selections it made above. Include this file in your version control repository
15
so that Terraform can guarantee to make the same selections by default when
16
you run "terraform init" in the future.
17
18
Terraform has been successfully initialized!
19
20
You may now begin working with Terraform. Try running "terraform plan" to see
21
22
any changes that are required for your infrastructure. All Terraform commands
24
26 rerun this command to reinitialize your working directory. If you forget, other
27 commands will detect it and remind you to do so if necessary.
joc@joc:$
Validate
Then we can check if the code of our Configuration is syntactically valid :
1 joc@joc:$ terraform validate
4 joc@joc:$
Plan
Terraform can show what will be deployed using the plan command. This is very useful to check all the resources
before creating them.
As the output is quite long I’ll truncate it and keep only some lines :
1 joc@joc:$ terraform plan
2 var.owner
3
Configuration owner
4
5
Enter a value: joc
6
7
8
An execution plan has been generated and is shown below.
9
Resource actions are indicated with the following symbols:
10
+ create
11
12
Terraform will perform the following actions:
13
14 ...
15 ...
16 ...
19
Changes to Outputs:
20
+ public_ip = (known after apply)
21
------------------------------------------------------------------------
22
23
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
24
can't guarantee that exactly these actions will be performed if
25
"terraform apply" is subsequently run.
26
27
joc@joc:~$
At line n° 5 we are prompted to give a name to the owner of the Configuration. This is because we didn’t set a default
value to the variable “owner” (see variables.tf). As you may have noticed, this variable is regularly used to name the
resources as well as to tag them. This will be helpful when working with these resources.
Apply
Finally we can apply the execution plan and everything will be created in few minutes only :
1 joc@joc:$ terraform apply
2 var.owner
3
Configuration owner
4
5
Enter a value: joc
6
7
8
An execution plan has been generated and is shown below.
9
Resource actions are indicated with the following symbols:
10
+ create
11
12
Terraform will perform the following actions:
13
14 ...
15 ...
16 ...
aws_vpc.vpc: Creating...
22 aws_route_table.rt: Creating...
25 aws_route_table_association.rt_sbn_asso: Creating...
26
aws_route_table_association.rt_sbn_asso: Creation complete after 1s [id=rtbassoc-
04b8fd0f0984d648b]
27
29 aws_instance.instance: Creating...
joc@joc:$
The last line displays the public IP of the newly created instance. The is done thanks to the output variable defined
into the file variables_output.tf :
# variable_output.tf
1
2
4 #########################################
5
6 output "public_ip" {
7 value = aws_instance.instance.public_ip
8
}
We should now be able to connect to the instance :
joc@joc:$ ssh -i "~/joc-key-pair.pem" ec2-user@18.194.28.183
1
That’s it ! Let’s see what it looks like from the AWS Console…
VPC :
Security Group :
Subnet :
Internet Gateway :
Route Table :
Instance :