[Technical Edition] Coding of infrastructure configuration. Try deploying an AWS instance using terraform.
My name is Teraoka and I am an infrastructure engineer.
This time it's a technical edition following the previous knowledge edition.
infrastructure as codeLet's experience it for yourself.
last timejust longPlease see the knowledge blog below.
In this article, will use a tool called
terraform to build an EC2 instance on AWS.
■A little preface, what is terraform?
a configuration management tool produced by HashiCorp
that automates infrastructure construction and settings using code.
This company itself Vagrant (I'm always indebted to them).The
development itself is very active and updates are done frequently, so it's a tool that I personally pay a lot of attention to.
...Okay, let's get started right away.
■Preparing the terraform execution environment
First, prepare the execution environment. It won't start unless you do this (
a zip file is distributed, so just use wget to get it. Easy and convenient.
Unzip it and put it in the 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!
This is all you need to prepare the execution environment (
■Creating a template file
To give you a rough explanation...
a procedure manual that says ``Create an infrastructure with this configuration''
written in code so that Terraform can interpret it.
Terraform will build the infrastructure based on this file.
First, prepare a working directory and create a template file in it.
$ mkdir /var/tmp/terraform $ cd /var/tmp/terraform $ touch main.tf
The extension of the template file should be "*.tf".
Terraform recognizes files with this extension as templates.
Now, here's the template file I created to build one EC2 instance ↓
(I created it in advance. It's a cheat that is also used on 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 = "ami-a10897d6" eu-central-1 = "ami-a8221fb5" ap-southeast-1 = "ami-68d8e93a " ap-southeast-2 = "ami-fd9cecc7" ap-northeast-1 = "ami-cbf90ecb" sa-east-1 = "ami-b52890a8" } } 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 write about each part of the code one by one below.)
Provider definition
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, it also supports cloud services such as Terraform, Azure, and Google Cloud, so you
need to first tell us which provider's configuration you want to write.
This time, I want to build an EC2 instance on AWS, so I specified AWS.
Defining variables
variable "aws_access_key" {} variable "aws_secret_key" {} variable "region" { default = "ap-northeast-1" }
Variables can be defined using variable blocks.
The value stored in this variable can be called and used from other blocks.
The value in the variable is called in the part that defines the provider.
It's a part like "var.aws_access_key".
By the way, if you look at the code, you might be wondering, "Where is the value stored in the variable?" However,
in terraform, by writing the value you want to store in the variable in a separate file,
that file is referenced and the value is automatically stored. will store it in a variable.
This file also has an extension specified, and must be "*.tfvars".
aws_access_key = "AKIAXXXXXXXXXXXXXXXX" aws_secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
The inside looks 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 part. We will describe the configuration of resources that are usually created from the management console.
In terraform, "VPC" and "EC2" are all treated as resources.
When defining a VPC, specify "cidr_block".
When defining a security group, specify "ingress", "egress", etc., and
for EC2 instances, specify "ami", "instance_type", etc.
By the way, each description item is not something that can be freely determined by the person writing the code.
There is a certain etiquette unique to terraform, and if you write it incorrectly, you will get scolded with a syntax error (
please see the official document below for details (all in English))
AWS PROVIDER: Terraform by HashiCorp
output
output "public ip" { value = "${aws_instance.terraform-test.public_ip}" }
If you deploy code and don't get the results back, you won't know what happened.
By writing an output block, you can receive the results of the deployment.
The above code receives the public IP of the created EC2 instance.
■Try deploying it. But before that
Of course, once you deploy, the results will be reflected immediately.
Even if there is a mistake in the code you wrote... (This is not good for your mental health)
So, Terraform has a dry run function, so let's use it.
This is a convenient function that allows you to check the execution plan by verifying whether there are any mistakes in the code in advance.
If you make a syntax mistake here, you will be notified with an error, so be sure to notice it (
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 didn't specify an "-out" parameter to save this plan, so when "apply" is called, Terraform can't guarantee this is what will execute. + 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.id} ”Route. Idr_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 it runs successfully without any errors,
the results will show which resources will be added when the deployment is actually performed.
This time it's "Plan: 7 to add", so a total of 7 resources will be added.
It's unfortunate if you get a result like the one below. Let's fix bugs in the 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 deploying it
Now that we have confirmed that no errors occur during the dry run, let's actually deploy it.
You can also deploy with a single command by typing "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-XXXXXXXXXXXXXXXXX) 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 of the instance created in the Outputs item was returned.
Let's check whether the instance has actually been created from the management console.
...It was done (amazing)
At the same time, a file called "terraform.tfstate" is quietly created.
This is a file that stores the "current infrastructure status" in json format.In
Terraform, by comparing this file with the definition file (.tf file) and the actual resource status, you can
create and create the target resource. Determines which operation to perform, such as modification or deletion.
In other words, if there is an inconsistency in this file, it will not be possible to deploy normally, so
you have to be careful when handling it (
■Summary
I tried using terraform to "codify the infrastructure configuration"
I had the impression that it was easy to use because the code written was highly readable and the basic commands were simple.
This kind of tool is often talked about together with the word devops.
If development staff and operations staff work together, it is necessary to prevent it from becoming a black box.
I definitely want to make effective use of the tools!
It is no secret that the article has become longer than the knowledge edition.
That's it, thank you for reading!