はじめに
こんにちはー
TIG DXチーム 1のゆるふわエンジニアの前原です。
前回のはじめてのTerraform 0.12 ~環境構築~に続き実践編です。実際の構築を通して、最近バージョンアップしたTerraform 0.12の構文がこんな感じで変わったよー的な話を伝えていければと思っています。
では、Terraformを用いてAWSのリソースを作成していきましょう。
構築する環境構成図
下図の環境(VPC, VPC Endpoint, NAT Gatewayなど)をTerraformで構築していきます。

- VPC
VPCは、Staging(stg)とProduction(prd)に2つのVPCを構成します。AZ(Availability Zone)は3つのゾーンを利用し、サブネットはパブリックとプライベートに分けて構成します。
- VPC Endpoint
Endpointとして、S3をセットします。
- NAT Gateway
パブリックプライベートにNAT Gatewayを構築します。コストを抑えるために1台とします。
Terraformのディレクトリ構成
最終的に以下のディレクトリ構成になります。前回の記事でお伝えしたように、1つのディレクトリにtfファイルを配置する設計にします。
. |
VPCの構築
ここでは前回の記事で作成したbackend.tf
など以外のtfファイルを作成します。
基本的にリソース単位でファイルを分けておりますが、好みでひとまとめにしても問題ありません。
VPCリソースの定義
VPCを構築するためのVPCリソースを定義します。
このリソースで必須の項目は、cidr_block
のみですが、タグを付与したいため記述します。
resource "aws_vpc" "vpc" { cidr_block = local.vpc_cidr[terraform.workspace] tags = { Name = "${terraform.workspace}-${local.project_name}" Env = "${terraform.workspace}" Project = local.project_name } } |
vpc.tf
は、ローカル変数を呼び出しているので、以下のようにvariable.tf
に定義する必要があります。
また、variable.tf
は、変数を定義するため今後も追記していきますので、これが最終的な内容でありませんのでご注意ください。
locals { project_name = "example" vpc_cidr = { stg = "10.0.0.0/16" prd = "10.1.0.0/16" } } |
準備ができたのでTerraformを実行します。
が、実行する前に構文や設定に問題がないかを確認するためのコマンドを実行します。
terraform validate
構文に問題ないかをvalidate
コマンドで確認します。
問題なければSuccess
と出力されます。
$ terraform validate |
terraform fmt
次にterraform fmt
というインデントなどのスタイルを揃えるコマンドを実行します。
terraform fmt |
terraform plan
それでは、設定に問題ないかを確認します。
$ terraform plan |
terraform apply
最後にapply
を実行し、Apply complete!
と出力されたら完了です。
$ terraform apply |
ちょっとした解説(1)
ここでは上記で説明できていない部分について解説します。
Workspace
vpc.tf
で記述されていた${terraform.workspace}
についてですが、これはWorkspaceの環境名を割り当てるための変数です。
そのため、今回の実行結果を見るとタグに指定した値にstg
と入っていることがわかります。
### terraform apply結果の抜粋 |
また、cidr_block
には、10.0.0.0/24
のネットワークアドレスが入っていることがわかります。
+ cidr_block = "10.0.0.0/16" |
これは、Workspaceによって値が変わる部分のため、以下のように2つ定義をしています。
そのため、vpc.tf
で呼び出すときは、terraform.workspace
を利用します。
locals { |
Local変数
タグのproject
には、example
という文字列が入っております。
これは、variable.tf
で定義したローカル変数が割り当てられています。
Project = local.project_name |
locals { project_name = "example" |
ネットで調べているとVariable変数を使うケースをよく見かけることがあるかと思います。
しかし、個人的には、変数の組み込みやコマンド時の変数挿入などを防ぐことができるため、Local変数
を利用しています。
SubnetやNAT Gatewayなどの構築
続いて、残りのリソースも作成していきます
Subnet
パブリックサブネットとプライベートサブネットを合計で6つ作成します。
以下のtfファイルを作成します。
resource "aws_subnet" "public_subnet" { for_each = local.subnet_numbers vpc_id = aws_vpc.vpc.id availability_zone = each.key cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 8, each.value) tags = { Name = "${terraform.workspace}-${local.project_name}-private" Env = "${terraform.workspace}" Project = local.project_name } } resource "aws_subnet" "private_subnet" { for_each = local.subnet_numbers vpc_id = aws_vpc.vpc.id availability_zone = each.key cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 8, each.value + 3) tags = { Name = "${terraform.workspace}-${local.project_name}-private" Env = "${terraform.workspace}" Project = local.project_name } } |
Internet Gatewayとルートテーブル
Internet Gatewayを作成します。
resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = { Name = "${terraform.workspace}-${local.project_name}" Env = "${terraform.workspace}" Project = local.project_name } } |
ルートテーブルを作成します。
resource "aws_route_table" "public" { vpc_id = aws_vpc.vpc.id tags = { Name = "${terraform.workspace}-${local.project_name}-public" Env = "${terraform.workspace}" Project = local.project_name } } resource "aws_route_table" "private" { vpc_id = aws_vpc.vpc.id tags = { Name = "${terraform.workspace}-${local.project_name}-private" Env = "${terraform.workspace}" Project = local.project_name } } |
パブリックサブネットとプライベートサブネットをルートテーブルに紐付けます。
resource "aws_route_table_association" "public" { for_each = local.subnet_numbers subnet_id = aws_subnet.public_subnet[each.key].id route_table_id = aws_route_table.public.id } resource "aws_route_table_association" "private" { for_each = local.subnet_numbers subnet_id = aws_subnet.private_subnet[each.key].id route_table_id = aws_route_table.private.id } |
ルーティングを定義します。
- パブリックサブネットから
0.0.0.0/0
にアクセスする場合は、Internet Gatewat
への向き先を指定 - プライベートサブネットから
0.0.0.0/0
にアクセスする場合は、NAT Gatewat
への向き先を指定
resource "aws_route" "public" { |
NAT Gateway
パブリックサブネットにNAT Gateway
を1台構築します。
NAT Gatewayは、固定グローバルIPをアタッチする必要があるのでEIP
を作成します。
また、各サブネットに対してのルートテーブルも定義します。
resource "aws_nat_gateway" "nat_gateway" { allocation_id = aws_eip.nat_gateway.id subnet_id = aws_subnet.public_subnet["ap-southeast-2a"].id depends_on = [aws_internet_gateway.igw] tags = { Name = "${terraform.workspace}-${local.project_name}" Env = "${terraform.workspace}" Project = local.project_name } } |
resource "aws_eip" "nat_gateway" { |
S3 Endpoint
S3 Endpointを作成します。
resource "aws_vpc_endpoint" "s3" { vpc_id = aws_vpc.vpc.id service_name = "com.amazonaws.${local.region}.s3" tags = { Name = "${terraform.workspace}-${local.project_name}-s3" Env = "${terraform.workspace}" Project = local.project_name } } resource "aws_vpc_endpoint_route_table_association" "private_s3" { vpc_endpoint_id = aws_vpc_endpoint.s3.id route_table_id = aws_route_table.private.id } |
現在利用しているリージョンをデータリソースから取得し、ローカル変数で定義しています。
data "aws_region" "current" {} locals { region = data.aws_region.current.name |
ちょっとした解説(2)
リソースの参照
以下は、先ほど作成したvpcのid
を取得するための構文です。
vpc_id = aws_vpc.vpc.id |
cidrsubnet
本記事のサブネットは、以下のレンジで作成しています。
- Public Subnetework
- 10.0.0.0/24
- 10.0.1.0/24
- 10.0.2.0/24
- Private subnetwork
- 10.0.3.0/24
- 10.0.4.0/24
- 10.0.5.0/24
そこで役に立つのが、cidrsubnet Functionです。cidrsubnet
は、以下のように3つの引数を持つ関数で、IPレンジをいい感じに分割してくれます。
cidrsubnet(prefix, newbits, netnum)
今回のケースで、値を割り当てると以下のかたちになります。
- prefix: 10.0.0.0/16
- newbits: 8
- netnum: 0(ここはcountでインクリメントされる)
具体的にどう変換されるかをterraform console
で確認してみます。
$ terraform console |
このようにサブネットの結果が不安な場合は、terraform console
を利用すると捗ります。
depends_on
NAT GatewayとEIPは、Internet Gatewayに依存しています。
そこで、depends_on
を記述することで明示的に依存関係を記すことで、先にInternet Gatewayを構築し、その後にEIPとNAT Gatewayを構築するという流れを確立できます。
depends_on = [aws_internet_gateway.igw] |
Production 環境の構築
コードの変更は不要です。
Workspaceのprd
に切り替えてterraform apply
するだけです。
terraform workspace selece prd |
めっちゃ簡単ですね!
ここまででVPCの構築が完了しました。
最終的なVariable.tf
本記事で作成したvariable.tf
です。
data "aws_region" "current" {} |
Terraform 0.12 変更点
この章では、Terraform 0.12とそれ以前での変更点をまとめていきます。
面倒だったブロックが不要になったよ
ブロックやダブルクォーテーション("${}"
)で囲う必要がなくなりました。
ただし、変数同士を繋いで表現する場合(NameやEnv)は、囲う必要があります(tagの部分)
Terraform 0.11系
resource "aws_vpc" "vpc" { cidr_block = "${local.vpc_cidr[terraform.workspace]}" tags = { Name = "${terraform.workspace}-${local.project_name}" Env = "${terraform.workspace}" Project = "${local.project_name}" } } |
Terraform 0.12系
resource "aws_vpc" "vpc" { |
無口なValidateが返信してくれるようになった
terraform validate
を実行するとSuccess!
って、反応が返ってくるようになりました。
Terraform 0.11系
terraform validate |
Terraform 0.12系
$ terraform validate |
Planなどの実行結果がわかりやすくなった
劇的な変化はないですが、見やすくなりました。
Terraform 0.11系
$ terraform plan |
Terraform 0.12系
$ terraform plan |
for_eachの導入
for_each
が使えるようになりました!
これは個人的には革新的で今まで抱えていた問題を解決する1つの武器となると思っています。
Terraform 0.11系
サブネットを作成するときに以下のようにcount
を利用するケースがありました。
data "aws_availability_zones" "available" { state = "available" } resource "aws_subnet" "public_subnet" { count = 3 vpc_id = aws_vpc.vpc.id availability_zone = "${data.aws_availability_zones.available.names[count.index]}" cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 8, count.index + 0) } |
一見問題ないように見えるのですが、count
を利用しているため、数字をインクリメントしてリストが作成されていきます。
その結果、リストの変更などでインデックスがずれてしまう問題が発生する可能性がありました。
# aws_subnet.public_subnet[0] will be created |
Terraform 0.12系
0.12.6
からfor_each
をマップ形式でアクセスできるようになりました。
以下のようにfor_each
で定義し、each.key
とeach.value
で各要素にアクセスできます。
resource "aws_subnet" "public_subnet" { for_each = local.subnet_numbers vpc_id = aws_vpc.vpc.id availability_zone = each.key cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 8, each.value) } resource "aws_subnet" "private_subnet" { for_each = local.subnet_numbers vpc_id = aws_vpc.vpc.id availability_zone = each.key cidr_block = cidrsubnet(aws_vpc.vpc.cidr_block, 8, each.value + 3) } |
for_each
で参照する定義をvariable.tfに追記します。
subnet_numbers = { "ap-southeast-2a" = 1 "ap-southeast-2b" = 2 "ap-southeast-2c" = 3 } |
NAT Gateway
のように1つのサブネットに作成したい場合などに役立ちます。
subnet_id = aws_subnet.public_subnet["ap-southeast-2a"].id |
ちなみに、0.12.6
より古いバージョンでterraform validate
を実行するとエラーが発生します。
Error: Reserved argument name in resource block |
lookupについて
今回の構成では、お伝えできなかったのですが、Terraform 0.12からlookupの書き方が変わったので変更点を以下に記載します。
Terraform 0.11系
instance_type = "${" } |
Terraform 0.12系
instance_type = local.ec2_config[terraform.workspace]["instance_type"] |
Terraform 0.11.14からアップグレードする方法
ここからは、すでにTerraform0.11系を利用している方のために、ざっくりですが0.12にアップグレードする方法を記載します。
Terraform アップグレード
Terraform 0.11.14でinitを実行します 2。
terraform init |
Workspaceを切り替えます(Workspace環境を前提にしています)。
terraform workspace list |
terraform plan
を実行します。
もし、planを実行し、差分が発生した場合はTerraform 0.12に対応した構文に変更する必要があります。
terraform plan |
checklistコマンド
を実行し、アップグレード可能な状態かを確認します。
問題がなければLooks good!
と出力されます。
$ terraform_0.11 0.12checklist |
terraformのバージョンを0.12に変更し、initを実行します。
terraform init |
upgradeコマンド
を実行します。
問題がなければUpgrade complete!
と出力されます。
$ terraform 0.12upgrade |
plan
を実行し、問題が発生しなければアップグレード完了です。
terrafrom plan |
まとめ
いかがでしたか?
Terraform 0.12になったことで、構文が変わって戸惑う場面もあるかと思いますが、それ以上に恩恵を授かれることを少しでも感じ取ってもらえたのではないでしょうか。また、これを機にTerraformを触ってみたいぞ! って方が増えたら嬉しいです。
今回は、VPCの話しかできなかったので、次回は更にこの環境を大きくしていき、皆さんのお役に立てる記事を書いていきたいと思います。
ありがとうございました!