TerraformでModuleを使ってみた。
ドキュメントによるとTerraform構成にはルートモジュールと呼ばれるモジュールが存在し、メイン作業ディレクトリ内の.tf
ファイルはすべてルートモジュールになるとのこと。
リソースを別なモジュールに分割し、子モジュールとすることでリソースを簡潔に定義できたり、複数回呼び出して再利用性を高めることができるらしい。
Moduleを試す
今回はVPCを作成し、そこにEC2を1台起動するTerraformをmoduleを使ってやってみる。
ディレクトリ構成は下記としてみる。
terraform(作業ディレクトリ)以下にdevelopmentディレクトリとmodulesディレクトリを作成する。
developmentディレクトリがルートモジュールの設置場所となる。ここにmain.tf
,variables.tf
,config.tfvars
を設置する。
developmentディレクトリ配下でterraform apply
を実行すると、modules配下の*.tf
ファイルが読み込まれて実行される。
modulesディレクトリ配下には ec2, vpc といったリソース単位のディレクトリを作成し、それぞれの配下にmain.tf
ファイルを設置する。
vpcディレクトリ配下にはoutput.tf
というファイルを設置する。これはec2リソースのmain.tf
からvpcリソース設定情報を読み込むための情報を出力するためのもの。
terraform/ ├── modules/ | ├── ec2/ | | └── main.tf | └── vpc/ | ├── main.tf | └── output.tf | └── development/ ├── main.tf ├── variables.tf └── config.tfvars
development以下の構成
development/main.tf
を下記とする。
provider "aws" { region = "${var.region}" profile = "${var.profile}" } # VPC module "module_vpc" { source = "../modules/vpc" } # EC2 module "module_ec2" { source = "../modules/ec2" vpc_id = "${module.module_vpc.vpc_id}" subnet_public_a_id = "${module.module_vpc.subnet_public_a_id}" subnet_public_c_id = "${module.module_vpc.subnet_public_c_id}" }
module
ブロックはmodule "module_name" {}
の形で定義する。"module_name"の部分はterraform内で使うローカルの名前であり、好きな名前を設定する。
ここではモジュールの中で../module/vpc
と../module/ec2
を呼び出せるように指定をしている。
これはルートモジュールが存在するディレクトリの相対パスとなる。
"module_ec2"では"module_vpc"で作成したVPCのvpc idやサブネットIDを利用するため、これらの値を../modules/vpc/output.tf
から取得する。
code>../modules/vpc/output.tfで出力したい情報をoutput output_name {}
の形で定義し、development/main.tf
の"module_ec2"ブロックの中で"${module.module_name.output_name}"のような形で変数として定義して参照する。
provider情報は下記のように切り出した。
development/variables.tf
variable "region" {} variable "profile" {}
development/config.tfvars
region = "ap-northeast-1" profile = "default"
modules以下の構成
modules/vpc/以下の構成
modules/vpc/main.tf
にVPCなど必要なリソースを作成するように定義する。
modules/vpc/main.tf
# VPC resource "aws_vpc" "this" { cidr_block = "10.1.0.0/16" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "true" tags { Name = "tf-sample-vpc" } } # InternetGateway resource "aws_internet_gateway" "this" { vpc_id = "${aws_vpc.this.id}" } # RouteTable resource "aws_route_table" "public" { vpc_id = "${aws_vpc.this.id}" route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.this.id}" } tags { Name = "tf-sample-public-rt" } } # Subnet resource "aws_subnet" "public_a" { vpc_id = "${aws_vpc.this.id}" cidr_block = "10.1.1.0/24" availability_zone = "ap-northeast-1a" tags { Name = "tf-sample-public-a-subnet" } } resource "aws_subnet" "public_c" { vpc_id = "${aws_vpc.this.id}" cidr_block = "10.1.2.0/24" availability_zone = "ap-northeast-1c" tags { Name = "tf-sample-public-c-subnet" } } resource "aws_subnet" "public_d" { vpc_id = "${aws_vpc.this.id}" cidr_block = "10.1.3.0/24" availability_zone = "ap-northeast-1d" tags { Name = "tf-sample-public-d-subnet" } } # SubnetRouteTableAssociation resource "aws_route_table_association" "public_a" { subnet_id = "${aws_subnet.public_a.id}" route_table_id = "${aws_route_table.public.id}" } resource "aws_route_table_association" "public_c" { subnet_id = "${aws_subnet.public_c.id}" route_table_id = "${aws_route_table.public.id}" }
modules/vpc/output.tf
で他のmoduleから参照したい情報をoutput output_name {}
の形で定義する。
value変数は、modules/vpc/mian.tf
の中の取得したい情報を${resource_type.resource_name.attributes}
の形式として指定すれば格納される。
今回のケースでは、modules/ec2/
で、modules/vpc/main.tf
のresource "aws_vpc" "this" {}
で作成したvpcのid、resource "aws_route_table_association" "public_c"
,resource "aws_route_table_association" "public_a"
で作成したサブネットIDを取得して利用したいので、output.tf
で以下のような形式で指定する。
modules/vpc/output.tf
output "vpc_id" { value = "${aws_vpc.this.id}" } output "subnet_public_a_id" { value = "${aws_subnet.public_a.id}" } output "subnet_public_c_id" { value = "${aws_subnet.public_c.id}" }
VPCのIDは作成時に割り当てられるので、attributesを参照して取得する。 その他VPCのattributesの内容は適宜Terraformのドキュメントを参照されたし。
こうしておけば、development/main.tf
内のec2モジュールの指定のところで、${module.module_name.output_name}
の形、VPCだと${module.module_vpc(development/main.tfのvpcのモジュール名).vpc_id(modules/vpc/output.tfのOutputラベル名)}
でを受け取れるようにしておけば、ec2モジュールでvpcのidが利用可能になる。
サブネットに関しても同様。
development/main.tf
module "module_ec2" { source = "../modules/ec2" // vpc module(../module/vpc/output.tf)の内容を元に値を取得 vpc_id = "${module.module_vpc.vpc_id}" subnet_public_a_id = "${module.module_vpc.subnet_public_a_id}" subnet_public_c_id = "${module.module_vpc.subnet_public_c_id}" }
modules/ec2/以下の構成
modules/ec2/main.tf
は下記となる。
ルートモジュール内で定義した変数を、子モジュール側でinput variableとして定義今回で言えば、vpc_id, subnet_public_a_id, subnet_public_c_id の値)する必要があり、今回はmain.tf内に含めている(。
modules/ec2/main.tf
variable "vpc_id" {} variable "subnet_public_a_id" {} variable "subnet_public_c_id" {} # Security Group resource "aws_security_group" "this" { name = "tf-example-sg" vpc_id = "${var.vpc_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"] } description = "tf-example-sg" } # EC2 resource "aws_instance" "this_t2" { ami = "ami-2a69be4c" instance_type = "t2.micro" disable_api_termination = false key_name = "aws-key-pair" vpc_security_group_ids = ["${aws_security_group.this.id}"] subnet_id = "${var.subnet_public_a_id}" tags { Name = "tf-example-ec2" } } resource "aws_eip" "this" { instance = "${aws_instance.this_t2.id}" vpc = true }
これでOK。
実行
下記の形で実行してみる。
$ cd ~/terraform/development $ terraform init -var-file=config.tfvars $ terraform plan -var-file=config.tfvars $ terraform apply -var-file=config.tfvars
削除したい場合は以下を実行する。
$ cd ~/terraform/development $ terraform destroy
まとめ
TerraformのModule機能を試した。
.tf
ファイルを簡潔に分割できそうではあるが、モジュール間の変数受け渡しは少々面倒で、子モジュールでoutput valuesで定義した値を、ルートモジュールの子モジュールブロック内で受け取り、別な子モジュール側でinput variablesを定義して利用する必要がある。
変数宣言する部分が増えたこと、それにより複雑化するときの管理コストの増加が懸念ではある。