dshimizu/blog/alpha

とりとめのないITブログ

Terraformを使ってAWSのVPCを作成してEC2を起動した

AWSの環境を構築する場合に利用するツールとしてはAWS公式のCloudFormationがある。 だが、CloudFormation以外ではTerraformが有力な選択肢の1つになると思う。

若干必要に迫られてTerraformを触ることになったので、自分で試してみたことや調べたことをまとめておく。

Terraformとは

Hashcorp社が開発しているオープンソースのソフトウェアで、ドキュメントでは「Terraformは、インフラストラクチャを安全かつ効率的に構築、変更、およびバージョン管理するためのツールです。」と記載されている。

開発はGitHub上で行われている。

Amazon Web Services(以下、AWS)だけでなく、GCPやAzure、Heroku、OpenStackなどの非常に多くのインフラに対応している。 プロバイダというのが、Terraform上でどの環境を利用するか定義するもの。

CloudFormationとどちらが良いのかは環境によりけりだと思うが、Terraformの公式ドキュメントには、複数の環境を組み合わせる場合や、事前の実行計画を確認できるところが利点であることが挙げられている。

Terraformのセットアップ

Terraformを使うには、まずバイナリファイルをダウンロードしてそれを解凍し、できたファイルをPATHの通った場所に配置するだけ。

$ wget -O /tmp/terraform_0.11.11_linux_amd64.zip https://releases.hashicorp.com/terraform/0.11.11/terraform_0.11.11_linux_amd64.zip
$ unzip /tmp/terraform_0.11.11_linux_amd64.zip -d /usr/local/bin
$ terraform -v
Terraform v0.11.11

これでTerraformが使えるようになった。

TerraformでAWSVPCとEC2作成

早速AWSの環境を構築してみる。

作業ディレクトリの作成

まずTerraformを実行するためのテンプレートファイルを管理するディレクトリを作成する。

$ mkdir terraform-example

テンプレートはHCL(HashiCorp Configuration Language)というHashiCorp製の独自のDSLで記述する。

上記のドキュメントにもあるが、JSON互換となっている。

テンプレートファイルの作成

テンプレートファイルを作成する。拡張子は.tfとなり、Terraformは自動でこの拡張子のものを読み込んでくれる。 なので、名前はなんでも良いがここではmain.tfとしている。

$ vim main.tf

別途変数の切り出しとかもできるが、ここでは実施せず、main.tfファイル1つで管理する。

プロバイダーの設定

最初にproviderという指定をする必要がある。

複数の環境に対応しているため、「どのプロバイダーを使うのか?」を宣言する。 main.tfに以下のように記述する。

access_key/secret_keyは$HOME/.aws/credentialsに記載しておいても良い。 shared_credentials_filecredentialsファイルの場所を指定することもできる

provider "aws" {
  access_key = "anaccesskey"
  secret_key = "asecretkey"
  #shared_credentials_file = "~/.aws/credentials"
  region = "ap-northeast-1"
  profile = "default"
}
リソースの設定

Terraformでのリソースとはインフラの構成要素を指す。 利用するリソースをresourceブロックで設定する。resource "リソースタイプ(最初のパラメータ)" "リソース名(2番目のパラメータ)" {}という構文となる。 タイプと名前の組み合わせは一意である必要がある。

リソースタイプは、プロバイダーがAWSの場合はaws_*という名前でTerraformで予め定義されている。VPCであればaws_vpc、EC2であればaws_instanceとなる。 その他のAWSで利用可能なリソースはドキュメントを参照のこと。

リソース名は任意の名前を設定可能。わかりやすい名前を付ければ良い。

VPCの作成

main.tfに以下を追記する。 リソースタイプをaws_vpcとする。リソース名はterraform_example_vpcとしている。 ブロックの中にcidr_blockやTagなどのパラメータを記述する。

# 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-example-vpc"
  }
}
VPCに関連するネットワークリソースの作成

main.tfに以下を追記する。 InternetGateway, RouteTable, Subnet, およびSubnetへのRouteTableの割り当てを行う。 他のリソースタイプの値を参照したい場合は、vpc_id = "${aws_vpc.example_vpc.id}"のように、<リソースタイプ>.<リソース名>.<属性名>と指定する。

# 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 = "public"
  }
}

# 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 = "public-a"
  }
}

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 = "public-c"
  }
}

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 = "public-d"
  }
}

# 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}"
}
EC2の作成

main.tfに以下を追記する。 aws_security_group, aws_instanceのリソースタイプを使ってセキュリティグループ、EC2を作成する。 必要なネットワーク情報は上記のリソースタイプを参照するように記述する。

# Security Group
resource "aws_security_group" "this" {
    name = "APP_SG"
    vpc_id = "${aws_vpc.this.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               = "${aws_subnet.public_a.id}"

  tags {
    Name = "tf-example-ec2"
  }
}

# ElasticIP
resource "aws_eip" "this" {
    instance = "${aws_instance.this_t2.id}"
    vpc = true
}

実行

初期化

terraform initで作業ディレクトリを初期化する。初期化といっても作成した.tfが消えることはない。利用するterraformプラグイン(ここではAWS)の初期化したり設定内容とバージョンの互換性をチェックしてくれる模様。

$ terraform init

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 1.57"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
実行計画の作成

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.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

    :
    :
    :

Plan: 11 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
実行

terraform applyでテンプレートを実行してリソースを生成する。

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

    :
    :
    :

Apply complete! Resources: 11 added, 0 changed, 0 destroyed.
削除

terraform applyでテンプレートで生成されたリソースを削除する。

$ terraform destroy
aws_vpc.terraform_example_vpc: Refreshing state... (ID: vpc-084056faf393b50f1)
aws_subnet.terraform_example_subnet_public-c: Refreshing state... (ID: subnet-04568b62453decefa)
aws_security_group.terraform_example_sg: Refreshing state... (ID: sg-0e1dbb4daad93fc80)
aws_subnet.terraform_example_subnet_public-a: Refreshing state... (ID: subnet-0d1c7eed2ead4e307)
aws_subnet.terraform_example_subnet_public-d: Refreshing state... (ID: subnet-08a80915ac04d7b90)
aws_internet_gateway.terraform_example_igw: Refreshing state... (ID: igw-031a1b3bb9b08caaf)
aws_route_table.terraform_example_public_rt: Refreshing state... (ID: rtb-0f6bf85f04c90ecef)
aws_instance.terraform_example_ec2: Refreshing state... (ID: i-052a7a262f0fec3e1)
aws_route_table_association.terraform_example_rt_public-a: Refreshing state... (ID: rtbassoc-0738393cba67fe3bc)
aws_route_table_association.terraform_example_rt_public-c: Refreshing state... (ID: rtbassoc-062a44d929c881bcb)
aws_eip.terraform_example_eip: Refreshing state... (ID: eipalloc-0912a5f0e5dc2ec34)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy

Terraform will perform the following actions:

- aws_eip.terraform_example_eip

- aws_instance.terraform_example_ec2

- aws_internet_gateway.terraform_example_igw

- aws_route_table.terraform_example_public_rt

- aws_route_table_association.terraform_example_rt_public-a

- aws_route_table_association.terraform_example_rt_public-c

- aws_security_group.terraform_example_sg

- aws_subnet.terraform_example_subnet_public-a

- aws_subnet.terraform_example_subnet_public-c

- aws_subnet.terraform_example_subnet_public-d

- aws_vpc.terraform_example_vpc

Plan: 0 to add, 0 to change, 11 to destroy.

Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.

Enter a value: yes

aws_eip.terraform_example_eip: Destroying... (ID: eipalloc-0912a5f0e5dc2ec34)
aws_subnet.terraform_example_subnet_public-d: Destroying... (ID: subnet-08a80915ac04d7b90)
aws_route_table_association.terraform_example_rt_public-c: Destroying... (ID: rtbassoc-062a44d929c881bcb)
aws_route_table_association.terraform_example_rt_public-a: Destroying... (ID: rtbassoc-0738393cba67fe3bc)
aws_route_table_association.terraform_example_rt_public-a: Destruction complete after 0s
aws_route_table_association.terraform_example_rt_public-c: Destruction complete after 0s
aws_subnet.terraform_example_subnet_public-c: Destroying... (ID: subnet-04568b62453decefa)
aws_route_table.terraform_example_public_rt: Destroying... (ID: rtb-0f6bf85f04c90ecef)
aws_subnet.terraform_example_subnet_public-d: Destruction complete after 1s
aws_route_table.terraform_example_public_rt: Destruction complete after 1s
aws_internet_gateway.terraform_example_igw: Destroying... (ID: igw-031a1b3bb9b08caaf)
aws_subnet.terraform_example_subnet_public-c: Destruction complete after 1s
aws_eip.terraform_example_eip: Destruction complete after 1s
aws_instance.terraform_example_ec2: Destroying... (ID: i-052a7a262f0fec3e1)
aws_internet_gateway.terraform_example_igw: Still destroying... (ID: igw-031a1b3bb9b08caaf, 10s elapsed)
aws_instance.terraform_example_ec2: Still destroying... (ID: i-052a7a262f0fec3e1, 10s elapsed)
aws_internet_gateway.terraform_example_igw: Destruction complete after 10s
aws_instance.terraform_example_ec2: Still destroying... (ID: i-052a7a262f0fec3e1, 20s elapsed)
aws_instance.terraform_example_ec2: Still destroying... (ID: i-052a7a262f0fec3e1, 30s elapsed)
aws_instance.terraform_example_ec2: Still destroying... (ID: i-052a7a262f0fec3e1, 40s elapsed)
aws_instance.terraform_example_ec2: Still destroying... (ID: i-052a7a262f0fec3e1, 50s elapsed)
aws_instance.terraform_example_ec2: Still destroying... (ID: i-052a7a262f0fec3e1, 1m0s elapsed)
aws_instance.terraform_example_ec2: Destruction complete after 1m1s
aws_subnet.terraform_example_subnet_public-a: Destroying... (ID: subnet-0d1c7eed2ead4e307)
aws_security_group.terraform_example_sg: Destroying... (ID: sg-0e1dbb4daad93fc80)
aws_subnet.terraform_example_subnet_public-a: Destruction complete after 1s
aws_security_group.terraform_example_sg: Destruction complete after 1s
aws_vpc.terraform_example_vpc: Destroying... (ID: vpc-084056faf393b50f1)
aws_vpc.terraform_example_vpc: Destruction complete after 0s

Destroy complete! Resources: 11 destroyed.

まとめ

TerraformのセットアップとAWSVPC/EC2作成用テンプレートを作成して削除するまでをやってみた。 慣れればCloudFormationより使いやすいかもしれない。

サンプルコードをGithubに挙げておいた。

参考