[Technical Edition] Coding infrastructure configuration. Deploying AWS instances using terraform

table of contents
My name is Teraoka and I am an infrastructure engineer
This time, we will be looking at technical aspects, following on from the knowledge section from last time
infrastructure as codeLet's try it out for ourselves
Last timeIt's just longCheck out the Knowledge Blog below:
In this article,Terraformusing a tool called
I'd like to demonstrate how to build a single EC2 instance on AWS
■A little introduction: what is terraform?
a configuration management tool developed by HashiCorp that
automates infrastructure construction and configuration using code.
This companyVagrant(which I always rely on).
Development is very active and updates are frequent, so it's a tool I'm personally paying close attention to.
...Okay, let's get started right away
■Preparing the terraform execution environment
First, prepare the execution environment. This is essential before we can begin (
a zip file is provided, so simply download it using wget. Easy and convenient.
Unzip it and add it to your system's PATH).
$ wget https://releases.hashicorp.com/terraform/0.9.0/terraform_0.9.0_linux_amd64.zip $ unzip terraform_0.9.0_linux_amd64.zip $ mv terraform /usr/bin/ $ terraform -v Terraform v0.9.0
....It's done!
That's all it takes to set up the execution environment.
■ Creating a template file
To put it simply,
the steps to create an infrastructure with a specific configuration,
a template file is a code document that describes
Terraform then uses this file to build the infrastructure.
First, prepare a working directory and create a template file in it
$ mkdir /var/tmp/terraform $ cd /var/tmp/terraform $ touch main.tf
The template file extension should be "*.tf".
Terraform will recognize files with this extension as templates.
So, without further ado, here is the template file I created to build a single EC2 instance ↓
(I prepared it in advance. It's a cheat code that's even used in cooking shows.)
variable "aws_access_key" {} variable "aws_secret_key" {} variable "region" { default = "ap-northeast-1" } variable "images" { default = { us-east-1 = "ami-1ecae776" us-west-2 = "ami-e7527ed7" us-west-1 = "ami-d114f295" eu-west-1 = } } provider "aws" { access_key = "${var.aws_access_key}" secret_key = "${var.aws_secret_key}" region = "${var.region}" } resource "aws_vpc" "default" { cidr_block = "172.30.0.0/16" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "false" tags { Name = "default" } } resource "aws_internet_gateway" "gw" { vpc_id = "${aws_vpc.default.id}" } resource "aws_subnet" "public_subnet_a" { vpc_id = "${aws_vpc.default.id}" availability_zone = "ap-northeast-1a" cidr_block = "${cidrsubnet(aws_vpc.default.cidr_block, 4, 1)}" } resource "aws_route_table" "public_route" { vpc_id = "${aws_vpc.default.id}" route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.gw.id}" } } resource "aws_route_table_association" "public_route_a" { subnet_id = "${aws_subnet.public_subnet_a.id}" route_table_id = "${aws_route_table.public_route.id}" } resource "aws_security_group" "ec2_terraform_test" { name = "ec2_terraform_test" vpc_id = "${aws_vpc.default.id}" ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_instance" "terraform-test" { ami = "${var.images["ap-northeast-1"]}" instance_type = "t2.micro" key_name = "XXXXXXXX" vpc_security_group_ids = [ "${aws_security_group.ec2_terraform_test.id}" ] subnet_id = "${aws_subnet.public_subnet_a.id}" associate_public_ip_address = "true" root_block_device { volume_type = "gp2" volume_size = "8" } tags { Name = "terraform-test" } } output "public ip" { value = "${aws_instance.terraform-test.public_ip}" }
...I see. (
I will explain each part of the code one by one below ↓)
Defining a Provider
provider "aws" { access_key = "${var.aws_access_key}" secret_key = "${var.aws_secret_key}" region = "${var.region}" }
Let's start with this part. In Terraform, you first need to configure the provider.
In fact, Terraform supports cloud services such as Azure and Google Cloud, so
you need to tell it "which provider's configuration you want to describe" beforehand.
In this case, we want to build an EC2 instance on AWS, so we specify AWS.
Defining variables
variable "aws_access_key" {} variable "aws_secret_key" {} variable "region" { default = "ap-northeast-1" }
You can define variables using the `variable` block.
Values stored in these variables can be called and used from other blocks.
The values in these variables are called in the part where the provider is defined.
This refers to parts like "var.aws_access_key".
Incidentally, looking at the code, you might wonder, "Where are the values being stored in the variables?" But
with Terraform, you can write the values you want to store in variables in a separate file, and
it will automatically refer to that file and store them in the variables.
This file also has a specific extension; it needs to be "*.tfvars".
aws_access_key = "AKIAXXXXXXXXXXXXXXXX" aws_secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
The contents look like this: "Variable name="value""
Resource definition
resource "aws_vpc" "default" { cidr_block = "172.30.0.0/16" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "false" tags { Name = "default" } }
This section describes the configuration of resources that you normally create from the management console.
In Terraform, "VPC" and "EC2" are all treated as resources.
When defining a VPC, you specify "cidr_block".
When defining a security group, you specify "ingress" and "egress", and
for an EC2 instance, you specify "ami" and "instance_type", etc.
Incidentally, the description items are not something that the person writing the code can freely decide.
There are conventions specific to Terraform, and if you write them incorrectly, you will get a syntax error (
see the official documentation below for details. (It's all in English)).
AWS PROVIDER: Terraform by HashiCorp
Output
output "public ip" { value = "${aws_instance.terraform-test.public_ip}" }
If you don't get a response after deploying your code, you won't know what happened.
By writing an output block, you can receive the results of the deployment.
The code above retrieves the public IP address of the created EC2 instance.
■ Let's actually deploy it. But before that
Naturally, the results are reflected immediately after deployment,
even if there are mistakes in the code you've written. (This is not good for your mental health.)
So, let's try using the dry run feature in Terraform.
It's a handy feature that allows you to verify whether there are any mistakes in the code beforehand and check the execution plan.
If there are any syntax errors here, you'll get an error message, so you can notice them (
try typing "terraform plan").
$ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. The Terraform execution plan has been generated and is shown below. Resources are shown in alphabetical order for quick scanning. Green resources will be created (or destroyed and then created if an existing resource exists), yellow resources are being changed in-place, and red resources will be destroyed. Cyan entries are data sources to be read. Note: You + aws_instance.terraform-test ami: "ami-cbf90ecb" associate_public_ip_address: "true" availability_zone: "<computed> " ebs_block_device.#: "<computed> " ephemeral_block_device.#: "<computed> " instance_state: "<computed> " instance_type: "t2.micro" ipv6_addresses.#: "<computed> " key_name: "XXXXXXXXXX" network_interface_id: "<computed> " placement_group: "<computed> " private_dns: "<computed> " private_ip: "<computed> " public_dns: "<computed> " public_ip: "<computed> " root_block_device.#: "1" root_block_device.0.delete_on_termination: "true" root_block_device.0.iops: "<computed> " root_block_device.0.volume_size: "8" root_block_device.0.volume_type: "gp2" security_groups.#: "<computed> " source_dest_check: "true" subnet_id: "${aws_subnet.public_subnet_a.id}" tags.%: "1" tags.Name: "terraform-test" tenancy: "<computed> " vpc_security_group_ids.#: "<computed> " + aws_internet_gateway.gw vpc_id: "${aws_vpc.default.id}" + aws_route_table.public_route route.#: "1" route.~2599208424.cidr_block: "0.0.0.0/0" route.~2599208424.egress_only_gateway_id: "" route.~2599208424.gateway_id: "${aws_internet_gateway.gw.id}" route.~2599208424.instance_id: "" route.~2599208424.ipv6_cidr_block: "" route.~2599208424.nat_gateway_id: "" route.~2599208424.network_interface_id: "" route.~2599208424.vpc_peering_connection_id: "" vpc_id: "${aws_vpc.default.id}" + aws_route_table_association.public_route_a route_table_id: "${aws_route_table.public_route.id}" subnet_id: "${aws_subnet.public_subnet_a.id}" + aws_security_group.ec2_terraform_test description: "Managed by Terraform" egress.#: "1" egress.482069346.cidr_blocks.#: "1" egress.482069346.cidr_blocks.0: "0.0.0.0/0" egress.482069346.from_port: "0" egress.482069346.ipv6_cidr_blocks.#: "0" egress.482069346.prefix_list_ids.#: "0" egress.482069346.protocol: "-1" egress.482069346.security_groups.#: "0" egress.482069346.self: "false" egress.482069346.to_port: "0" ingress.#: "1" ingress.2541437006.cidr_blocks.#: "1" ingress.2541437006.cidr_blocks.0: "0.0.0.0/0" ingress.2541437006.from_port: "22" ingress.2541437006.ipv6_cidr_blocks.#: "0" ingress.2541437006.protocol: "tcp" ingress.2541437006.security_groups.#: "0" ingress.2541437006.self: "false" ingress.2541437006.to_port: "22" name: "ec2_terraform_test" owner_id: "<computed> " vpc_id: "${aws_vpc.default.id}" + aws_subnet.public_subnet_a assign_ipv6_address_on_creation: "false" availability_zone: "ap-northeast-1a" cidr_block: "172.30.16.0/20" ipv6_cidr_block_association_id: "<computed> " map_public_ip_on_launch: "false" vpc_id: "${aws_vpc.default.id}" + aws_vpc.default assign_generated_ipv6_cidr_block: "false" cidr_block: "172.30.0.0/16" default_network_acl_id: "<computed> " default_route_table_id: "<computed> " default_security_group_id: "<computed> " dhcp_options_id: "<computed> " enable_classiclink: "<computed> " enable_dns_hostnames: "false" enable_dns_support: "true" instance_tenancy: "default" ipv6_association_id: "<computed> " ipv6_cidr_block: "<computed> " main_route_table_id: "<computed> " tags.%: "1" tags.Name: "default" Plan: 7 to add, 0 to change, 0 to destroy.
If the program runs successfully without errors,
the result will show which resources will be added when the deployment is actually performed.
In this case, it says "Plan: 7 to add," so a total of 7 resources will be added.
If you get a result like the one below, unfortunately, you need to fix the bug in your code.
2 error(s) occurred: * aws_security_group.ec2_terraform_test: egress.0: invalid or unknown key: cidr_block * aws_security_group.ec2_terraform_test: ingress.0: invalid or unknown key: cidr_block
■ Try actually deploying
Now that we've confirmed that there are no errors in the dry run, let's actually deploy it.
Deployment can also be done with a single command: simply type "terraform apply".
$ terraform apply aws_vpc.default: Creating... assign_generated_ipv6_cidr_block: "" => "false" cidr_block: "" => "172.30.0.0/16" default_network_acl_id: "" => "<computed> " default_route_table_id: "" => "<computed> " default_security_group_id: "" => "<computed> " dhcp_options_id: "" => "<computed> " enable_classiclink: "" => "<computed> " enable_dns_hostnames: "" => "false" enable_dns_support: "" => "true" instance_tenancy: "" => "default" ipv6_association_id: "" => "<computed> " ipv6_cidr_block: "" => "<computed> " main_route_table_id: "" => "<computed> " tags.%: "" => "1" tags.Name: "" => "default" aws_vpc.default: Creation complete (ID: vpc-XXXXXXXX) aws_internet_gateway.gw: Creating... vpc_id: "" => "vpc-XXXXXXXX" aws_security_group.ec2_terraform_test: Creating... description: "" => "Managed by Terraform" egress.#: "" => "1" egress.482069346.cidr_blocks.#: "" => "1" egress.482069346.cidr_blocks.0: "" => "0.0.0.0/0" egress.482069346.from_port: "" => "0" egress.482069346.ipv6_cidr_blocks.#: "" => "0" egress.482069346.prefix_list_ids.#: "" => "0" egress.482069346.protocol: "" => "-1" egress.482069346.security_groups.#: "" => "0" egress.482069346.self: "" => "false" egress.482069346.to_port: "" => "0" ingress.#: "" => "1" ingress.2541437006.cidr_blocks.#: "" => "1" ingress.2541437006.cidr_blocks.0: "" => "0.0.0.0/0" ingress.2541437006.from_port: "" => "22" ingress.2541437006.ipv6_cidr_blocks.#: "" => "0" ingress.2541437006.protocol: "" => "tcp" ingress.2541437006.security_groups.#: "" => "0" ingress.2541437006.self: "" => "false" ingress.2541437006.to_port: "" => "22" name: "" => "ec2_terraform_test" owner_id: "" => "<computed> " vpc_id: "" => "vpc-XXXXXXXX" aws_subnet.public_subnet_a: Creating... assign_ipv6_address_on_creation: "" => "false" availability_zone: "" => "ap-northeast-1a" cidr_block: "" => "172.30.16.0/20" ipv6_cidr_block_association_id: "" => "<computed> " map_public_ip_on_launch: "" => "false" vpc_id: "" => "vpc-XXXXXXXX" aws_internet_gateway.gw: Creation complete (ID: igw-XXXXXXXX) aws_route_table.public_route: Creating... route.#: "" => "1" route.3460203481.cidr_block: "" => "0.0.0.0/0" route.3460203481.egress_only_gateway_id: "" => "" route.3460203481.gateway_id: "" => "igw-XXXXXXXX" route.3460203481.instance_id: "" => "" route.3460203481.ipv6_cidr_block: "" => "" route.3460203481.nat_gateway_id: "" => "" route.3460203481.network_interface_id: "" => "" route.3460203481.vpc_peering_connection_id: "" => "" vpc_id: "" => "vpc-XXXXXXXX" aws_subnet.public_subnet_a: Creation complete (ID: subnet-XXXXXXXX) aws_route_table.public_route: Creation complete (ID: rtb-XXXXXXXX) aws_route_table_association.public_route_a: Creating... route_table_id: "" => "rtb-XXXXXXXX" subnet_id: "" => "subnet-XXXXXXXX" aws_route_table_association.public_route_a: Creation complete (ID: rtbassoc-XXXXXXXX) aws_security_group.ec2_terraform_test: Creation complete (ID: sg-XXXXXXXX) aws_instance.terraform-test: Creating... ami: "" => "ami-cbf90ecb" associate_public_ip_address: "" => "true" availability_zone: "" => "<computed> " ebs_block_device.#: "" => "<computed> " ephemeral_block_device.#: "" => "<computed> " instance_state: "" => "<computed> " instance_type: "" => "t2.micro" ipv6_addresses.#: "" => "<computed> " key_name: "" => "XXXXXXXX" network_interface_id: "" => "<computed> " placement_group: "" => "<computed> " private_dns: "" => "<computed> " private_ip: "" => "<computed> " public_dns: "" => "<computed> " public_ip: "" => "<computed> " root_block_device.#: "" => "1" root_block_device.0.delete_on_termination: "" => "true" root_block_device.0.iops: "" => "<computed> " root_block_device.0.volume_size: "" => "8" root_block_device.0.volume_type: "" => "gp2" security_groups.#: "" => "<computed> " source_dest_check: "" => "true" subnet_id: "" => "subnet-XXXXXXXX" tags.%: "" => "1" tags.Name: "" => "terraform-test" tenancy: "" => "<computed> " vpc_security_group_ids.#: "" => "1" vpc_security_group_ids.766820655: "" => "sg-XXXXXXXX" aws_instance.terraform-test: Still creating... (10s elapsed) aws_instance.terraform-test: Still creating... (20s elapsed) aws_instance.terraform-test: Creation complete (ID: i-XXXXXXXXXXXXXXXX) Apply complete! Resources: 7 added, 0 changed, 0 destroyed. The state of your infrastructure has been saved to the path below. This state is required to modify and destroy your infrastructure, so keep it safe. To inspect the complete state use the `terraform show` command. State path: Outputs: public ip = XX.XX.XXX.XX
Yes, the public IP address of the instance created was returned in the Outputs section.
Let's check if the instance has actually been created from the Management Console.

....It worked (amazing)
At the same time, a file called "terraform.tfstate" is quietly created.
This file stores the "current state of the infrastructure" in JSON format
, and Terraform compares this file with the definition file (.tf file) and the actual state of the resources to
determine which operation to perform on the target resource, such as creation, modification, or deletion.
In other words, if there is an inconsistency in this file, you will not be able to deploy normally, so
you need to be careful when handling it.
Summary
I tried using terraform to "code infrastructure configuration"
The code written is highly readable, and the basic commands are simple to use, so I got the impression that it is easy to use
These tools are often mentioned together with the word devops
If developers and operators are to work together, to prevent the system from becoming a black box,
I'd love to make good use of this tool!
I won't tell you that this article turned out to be longer than the knowledge section
That's all, thank you for reading!
0
