dshimizu/blog/alpha

とりとめのないITブログ

TerraformのModuleを試した

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"で作成したVPCvpc 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.tfVPCなど必要なリソースを作成するように定義する。

  • 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.tfresource "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を定義して利用する必要がある。 変数宣言する部分が増えたこと、それにより複雑化するときの管理コストの増加が懸念ではある。