こんにちは!インフラエンジニアのnaya(@NayaTaiyo)です。最近、Terraformについて学習したので、ディレクトリで分割する方法/remote_stateとmodule化の使い方をブログします。
Terraformの動作フロー
-
Terraform 初期化(terraform init)
-
ルートモジュール(カレントディレクトリ内)の.tf ファイルを読み込む
-
使用するプロバイダ(AWSやGCP、オンプレ等)やデータソース情報を取得
-
.terraformディレクトリが作成される
-
-
terraform.tfstateの読み込み
-
過去の構成情報(すでに作成されたリソースの情報)を読み込む
-
リモートで管理(s3など)している場合は、そこから取得
-
-
差分の検出(terraform plan)
-
.tfファイルとterraform.tfstateの内容を比較
-
変更差分が表示される
-
-
差分の適用(terraform apply)
-
planで出た差分をもとにデプロイ先へ反映(リソースの作成・更新・削除)
-
適用後、最新の状態がterraform.tfstate に記録される
-
Terraformはルートモジュール(カレントディレクトリ)の.tfファイルをに記述された内容を基に処理を実行します。
その際に、プロバイダ(AWSやGCP等)やどこのデータ(gitやクラウドストレージ)を基にデプロイするかを設定することが可能です。これらはterraform initコマンドを使用します。変更が生じた際には、データの取得元との整合性が取れなく、エラーが発生してしまうため、既存の.terraformディレクトリを削除してから再度terraform initコマンドを使用するか、-reconfigureオプションを使用して修正後の状態に強制的に上書きする必要があります。
remote_stateの使用例
Terraformはapply時にデフォルトではローカルに保存されているterraform.tfstateに保存されます。s3などのterraform.tfstateをリモート管理している場合はremote_stateで情報を取得することができます。
その他にも、outputsすることでルートモジュール(カレントディレクトリ)ではない.tfファイルから値を取得することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
TechBull-project/ ├── ec2.tf ├── variables.tf ├── terraform.tfvars ├── provider.tf ├── backend.tf ├── vpc/ │ ├── vpc.tf │ ├── vpc_internet_gateway.tf │ ├── vpc_subnets.tf │ ├── vpc_sg.tf │ ├── outputs.tf │ ├── variables.tf │ ├── terraform.tfvars │ ├── provider.tf │ └── backend.tf |
上記のように、特定のサービス内の機能ごとにファイルを分割することが可能となっており、チーム内での並行作業によるコンフリクト(競合)も防ぐことができます。
構成図
module化の使用例
module化を使用することで、以下の構成のように各機能をmoduleとして作成することができます。これらを呼び出すことによって環境ごとで異なる設定を可能とします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
TechBull-project/ ├── stg/ │ ├── main.tf │ └── variables.tf #cidr 10.0.0.0/16 │ ├── prod/ │ ├── main.tf │ └── variables.tf #cidr 10.1.0.0/16 │ ├── modules/ │ ├── vpc/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── outputs.tf │ ├── ec2/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── outputs.tf |
上記の例では、異なる環境でのVPCの設定変更のみになりますが、例えば、本番環境にだけLBを建てたいといった場合やNat gatewayを実装したいといった場合に、必要なmoduleを呼び出して特定の環境のみ機能を追加する、ということも可能となります。
※逆に特定の環境のみmoduleを呼び出さずに機能を追加しない、といったことも可能です。
構成図
実際の設定ファイル
先ほどのディレクトリ構成の設定ファイルとなります。
※module化はstg環境のみとなります。
環境
- EC2
- 台数:1台
- OS:Amazon Linux2
- インスタンスタイプ:t2.micro
- VPC:10.0.0.0/16
- サブネット:10.0.0.0/24
- セキュリティグループ
- ingress/egress:0.0.0.0/0
- port:80
- protocol:TCP
remote_state
※.tfファイルがディレクトリごとで分割されているので、ディレクトリごとにterraformコマンド(plan、apply等)を行う必要があります。
- TechBull-project/vpc/vpc.tf
1 2 3 4 5 6 7 |
#VPCの作成 resource "aws_vpc" "vpc" { cidr_block = var.vpc_cidr tags = { Name = var.vpc_tag_name } } |
- TechBull-project/vpc/vpc_internet_gateway.tf
1 2 3 4 5 6 7 8 |
#インターネットゲートウェイの作成 resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = { Name = var.igw_tag_name } } |
- TechBull-project/vpc/vpc_subnets.tf
1 2 3 4 5 6 7 8 9 10 11 |
#サブネットの作成 resource "aws_subnet" "ec2_subnet" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet availability_zone = var.availability_zone map_public_ip_on_launch = var.map_ip_bool tags = { Name = var.subnet_tag_name } } |
- TechBull-project/vpc/vpc_sg.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#EC2のセキュリティグループの作成 resource "aws_security_group" "ec2-sg" { name = var.ec2_sg_name description = var.ec2_sg_description vpc_id = aws_vpc.vpc.id } #EC2セキュリティグループルール (Ingress) resource "aws_security_group_rule" "ec2_ingress_from_http" { type = var.direction_type_IN from_port = var.http_port to_port = var.http_port protocol = var.transport_protocol_type security_group_id = aws_security_group.ec2-sg.id cidr_blocks = var.public_cidr_blocks } #EC2セキュリティグループルール (Egress) resource "aws_security_group_rule" "ec2_egress_http" { type = var.direction_type_OUT from_port = var.http_port to_port = var.http_port protocol = var.transport_protocol_type security_group_id = aws_security_group.ec2-sg.id cidr_blocks = var.public_cidr_blocks } |
- TechBull-project/vpc/outputs.tf
1 2 3 4 5 6 7 |
output "ec2_subnet" { value = aws_subnet.ec2_subnet.id } output "ec2-sg" { value = aws_security_group.ec2-sg.id } |
- TechBull-project/vpc/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#provider.tf variable "region" { type = string } #vpc_internet_gateway.tf variable "igw_tag_name" { type = string } #vpc.tf variable "vpc_cidr" { type = string } variable "vpc_tag_name" { type = string } #vpc_subnets variable "public_subnet" { type = string } variable "availability_zone" { type = string } variable "map_ip_bool" { type = bool } variable "subnet_tag_name" { type = string } #vpc_sg.tf variable "ec2_sg_name" { type = string } variable "ec2_sg_description" { type = string } variable "http_port" { type = string } variable "direction_type_IN" { type = string } variable "direction_type_OUT" { type = string } variable "transport_protocol_type" { type = string } variable "public_cidr_blocks" { type = list(string) } |
- TechBull-project/vpc/terraform.tfvars
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#provider.tf region = "ap-northeast-1" #vpc_internet_gateway.tf igw_tag_name = "remote-state-igw" #vpc.tf vpc_cidr = "10.0.0.0/16" vpc_tag_name = "remote-state-vpc" #vpc_subnets public_subnet = "10.0.0.0/24" availability_zone = "ap-northeast-1a" map_ip_bool = true subnet_tag_name = "remote-state-subnet" #vpc_sg.tf ec2_sg_name = "remote-state-sg" ec2_sg_description = "remote-state" http_port = 80 direction_type_IN = "ingress" direction_type_OUT = "egress" transport_protocol_type = "TCP" public_cidr_blocks = ["0.0.0.0/0"] |
- TechBull-project/vpc/provider.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
provider "aws" { region = var.region } terraform { required_version = "1.11.3" required_providers { aws = { source = "hashicorp/aws" version = "5.94.1" } } } |
- TechBull-project/vpc/backend.tf
1 2 3 4 5 6 7 8 |
terraform { backend "s3" { bucket = "tfstate-bucket-tokyo" key = "vpc/terraform.tfstate" region = "ap-northeast-1" encrypt = true } } |
- TechBull-project/ec2.tf
1 2 3 4 5 6 7 8 9 10 |
resource "aws_instance" "ec2" { ami = var.ami_id instance_type = var.instance_type subnet_id = data.terraform_remote_state.vpc.outputs.ec2_subnet vpc_security_group_ids = [data.terraform_remote_state.vpc.outputs.ec2-sg] tags = { Name = var.ec2_name } } |
- TechBull-project/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#ec2.tf variable "ami_id" { type = string } variable "instance_type" { type = string } variable "ec2_name" { type = string } #backend.tf variable "backend" { type = string } variable "bucket_name" { type = string } variable "path" { type = string } variable "state_name" { type = string } variable "encrypt_bool" { type = bool } #provider.tf variable "region" { type = string } |
- TechBull-project/terraform.tfvars
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ec2.tf ami_id = "ami-0a6fd4c92fc6ed7d5" instance_type = "t2.micro" ec2_name = "remote-state-ec2" #backend.tf backend = "s3" bucket_name = "tfstate-bucket-tokyo" path = "vpc/terraform.tfstate" state_name = "terraform.tfstate" encrypt_bool = true #provider.tf region = "ap-northeast-1" |
- TechBull-project/provider.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
provider "aws" { region = var.region } terraform { required_version = "1.11.3" required_providers { aws = { source = "hashicorp/aws" version = "5.94.1" } } } |
- TechBull-project/backend.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
terraform { backend "s3" { bucket = "tfstate-bucket-tokyo" key = "terraform.tfstate" region = "ap-northeast-1" encrypt = true } } data "terraform_remote_state" "vpc" { backend = var.backend config = { bucket = var.bucket_name key = var.path region = var.region encrypt = var.encrypt_bool } } |
※terraformブロックのkey = でterraform.tfstateファイルの保存先を指定しております。data “terraform_remote_state” ブロックのkey = ではterraform.tfstateの参照先を指定しています。
上記により、異なるディレクトリ間の値を取得することを可能にしています。
module化
※stg環境設定ファイルのみとなりますが、可変部分の値を変えることで環境ごとで異なる設定が可能です。
また、remote_stateとは違い、TechBull-project/stg/配下でのterraformコマンド(plan、apply)のみで大丈夫です。
- TechBull-project/stg/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
provider "aws" { region = var.region } terraform { required_version = "1.11.3" required_providers { aws = { source = "hashicorp/aws" version = "5.94.1" } } } # VPCモジュール呼び出し module "vpc" { source = "../module/vpc" vpc_cidr = var.vpc_cidr vpc_tag_name = var.vpc_tag_name igw_tag_name = var.igw_tag_name ec2_sg_name = var.ec2_sg_name ec2_sg_description = var.ec2_sg_description subnet_tag_name = var.subnet_tag_name public_subnet = var.public_subnet } # EC2モジュール呼び出し module "ec2" { source = "../module/ec2" subnet_id = module.vpc.public_subnet_id security_group_id = module.vpc.default_sg_id ec2_name = var.ec2_name } |
- TechBull-project/stg/variables.tf※可変部分を記述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#リージョン変数 variable "region" { type = string default = "ap-northeast-1" } #VPCモジュール変数 variable "vpc_cidr" { type = string default = "10.0.0.0/16" } variable "vpc_tag_name" { type = string default = "stg-vpc" } variable "igw_tag_name" { type = string default = "stg-igw" } variable "subnet_tag_name" { type = string default = "stg-subnet" } variable "public_subnet" { type = string default = "10.0.0.0/24" } #EC2モジュール変数 variable "ec2_sg_description" { type = string default = "stg-ec2" } variable "ec2_sg_name" { type = string default = "stg-sg" } variable "ec2_name" { type = string default = "stg" } |
※TechBull-project/stg/variables.tfのdefault = には、環境ごとで異なる設定の値を記述します。
例:stg環境ならvpc_cidr = “10.0.0.0/16” prod環境ならvpc_cidr = “10.1.0.0/16″等
環境ごとにec2_nameの値を変更したいのであれば ec2_name = “stg-ec2″(stg環境)、ec2_name = “prod-ec2″(ptod環境)等
また、terraform.tfvarsファイルを使用することで、remote_stateと同様に変数と値をマッピングすることが可能ですが、module化では、変数の管理や処理が複雑となってしまうため、本記事のmodule化では使用していません。
- TechBull-project/stg/modules/vpc/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
provider "aws" { region = var.region } terraform { required_version = "1.11.3" required_providers { aws = { source = "hashicorp/aws" version = "5.94.1" } } } #VPCの作成 resource "aws_vpc" "vpc" { cidr_block = var.vpc_cidr tags = { Name = var.vpc_tag_name } } #IGWの作成 resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = { Name = var.igw_tag_name } } #SGの作成 resource "aws_security_group" "ec2_sg" { name = var.ec2_sg_name description = var.ec2_sg_description vpc_id = aws_vpc.vpc.id } #EC2セキュリティグループルール (Ingress) resource "aws_security_group_rule" "app_ingress_from_http" { type = var.direction_type_IN from_port = var.http_port to_port = var.http_port protocol = var.transport_protocol_type security_group_id = aws_security_group.ec2_sg.id cidr_blocks = var.public_cidr_blocks } #EC2セキュリティグループルール (Egress) resource "aws_security_group_rule" "app_egress_http" { type = var.direction_type_OUT from_port = var.http_port to_port = var.http_port protocol = var.transport_protocol_type security_group_id = aws_security_group.ec2_sg.id cidr_blocks = var.public_cidr_blocks } #サブネットの作成 resource "aws_subnet" "ec2_subnet" { vpc_id = aws_vpc.vpc.id cidr_block = var.public_subnet availability_zone = var.availability_zone map_public_ip_on_launch = var.map_eip_bool tags = { Name = var.subnet_tag_name } } |
- TechBull-project/stg/modules/vpc/variables.tf ※module側の変数の値を記述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#変数のデフォルト値 variable "region" { type = string default = "ap-northeast-1" } variable "availability_zone" { type = string default ="ap-northeast-1a" } variable "http_port" { type = number default = 80 } variable "transport_protocol_type" { type = string default = "TCP" } variable "direction_type_IN" { type = string default = "ingress" } variable "direction_type_OUT" { type = string default = "egress" } variable "public_cidr_blocks" { type = list(string) default = ["0.0.0.0/0"] } variable "map_eip_bool" { type = bool default = true } #変数の型定義 variable "vpc_cidr" { type = string } variable "vpc_tag_name" { type = string } variable "igw_tag_name" { type = string } variable "ec2_sg_name" { type = string } variable "ec2_sg_description" { type = string } variable "public_subnet" { type = string } variable "subnet_tag_name" { type = string } |
※default =にmodule側の変数の値を指定します。
module側で変数の値を定義しているので、各環境(TechBull-project/stg/ TechBull-project/prod/)配下でこちらの変数を上書きされない限り、共通の設定となります。
- TechBull-project/stg/modules/vpc/outputs.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
output "vpc_id" { value = aws_vpc.vpc.id } output "igw_id" { value = aws_internet_gateway.igw.id } output "public_subnet_id" { value = aws_subnet.ec2_subnet.id } output "default_sg_id" { value = aws_security_group.ec2_sg.id } |
- TechBull-project/stg/modules/ec2/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
provider "aws" { region = var.region } terraform { required_version = "1.11.3" required_providers { aws = { source = "hashicorp/aws" version = "5.94.1" } } } #EC2インスタンスの作成 resource "aws_instance" "ec2" { ami = var.ami_id instance_type = var.instance_type subnet_id = var.subnet_id vpc_security_group_ids = [var.security_group_id] tags = { Name = var.ec2_name } } |
- TechBull-project/stg/modules/ec2/variables.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#変数のデフォルト値 variable "region" { type = string default = "ap-northeast-1" } variable "ami_id" { type = string default = "ami-0a6fd4c92fc6ed7d5" } variable "instance_type" { type = string default = "t2.micro" } #変数の型定義 variable "subnet_id" { type = string } variable "security_group_id" { type = string } variable "ec2_name" { type = string } |
※TechBull-project/stg/modules/vpc/variables.tfと同様
- TechBull-project/stg/modules/ec2/outputs.tf
1 2 3 |
output "ec2_id" { value = aws_instance.ec2.id } |
まとめ
今回は、Terraformでディレクトリを分割し、.tfファイルを整理して管理する方法について記事にしました。module化については初めて試してみたのですが、変数の受け渡しや、処理の優先順位、呼び出し元と呼び出し先の関係性など、思っていたよりも迷った部分が多く、どこで処理が実行されているのか、どの変数がどこから参照されているのかなどでハマった場面がありました。
今回の学習を通して、これまで感覚的に使っていた部分への理解が深まる良いきっかけになったと思います。最後までご覧いただき、ありがとうございました。

2000年生まれ。社会人3年目のインフラエンジニア。業務で実際に手を動かしてIT技術に触れることが少なく、技術力を身に着けるためTechBullに参加して日々スキルアップ中。コツコツと学び続け、自社開発企業への転職も実現。やってみたいと思ったことはまずやってみるというチャレンジ精神を大切にしている。