Terraform に入門してみる

Terraformを使うのでとりあえず入門記事を漁って見る。

とりあえず、 Terraform簡易チュートリアル on AWS - Qiita をハンズオンしてみる。

事前準備

AWS アクセスキーの発行

公式ドキュメントを参照(アクセスキー ID と秘密アクセスキーの取得 )して、アクセスキーとシークレットアクセスキーを発行する。

また、 aws-cli をインストールして aws configure を実行しておく。

Terraformのインストール

OSXなのでhomebrewを使ってインストール

brew update && brew install terraform

インストール出来た。

$ terraform -v
Terraform v0.6.14

Your version of Terraform is out of date! The latest version
is 0.6.15. You can update by downloading from www.terraform.io

AWSにEC2インスタンスを立ててみる

Terraform の設定ファイルを書く

「Terraform」の設定ファイルは「JSON」か「HCL」のどちらかで記述できる。 JSON の場合は *.tf.json 、 HCL の場合は *.tf という拡張子をつける。 HCL は「HashiCorp Configuration Language」の略で、設定ファイルを記述するために HashiCorp が独自に設計したシンタックスJSON と比較して可読性がよいらしい(いちいち{}でくくらなくてもよいのもメリット)。

とりあえず、 git init して書き始めよう。

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_instance" "hello-aws" {
  ami = "ami-a21529cc"
  instance_type = "t2.micro"
}

この、 provider とか resource をブロックを呼ぶらしい。

まず、 provider を記述する。今回は AWS に構築をしてみるので aws を指定指定する。 プロバイダの一覧は公式ドキュメント参照access_keysecret_keyaws configure で設定していない場合はここで記述する。 reagion には対象のリージョンを指定する(リージョンとアベイラビリティーゾーン)。東京は ap-northeast-1 なので、これを指定。

resource ブロックは引数を二つとり、第一引数に「作成するリソースのタイプ」を、第二引数に「リソース名」を指定する。プロバイダが AWS の場合に指定できるリソースは公式ドキュメント参照(AWS PROVIDER)。 とりあえずチュートリアルに習って、 EC2 のインスタンスを作成してみる。

リソースブロックの第一引数に aws_instance 、第二引数の名前に hello-aws を記述(AWS: aws_instance - Terraform by HashiCorp)。

ドキュメントをみると、この aws_instance には必須項目が二つある。

AMI はなんとなく ami-a21529ccUbuntu (無料枠の対象)を指定してみる。 インスタンスタイプは t2.nano が一番安いんだけど、無料枠の対象の t2.micro を指定。

インスタンスを起動する

plan で Dry Run してみる

terraform のサブコマンドに plan というのがある。このコマンドを実行することにより、どのような変更が適用されるか事前に確認することができる。

terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_instance.hello-aws
    ami:                      "" => "ami-a21529cc"
    availability_zone:        "" => "<computed>"
    ebs_block_device.#:       "" => "<computed>"
    ephemeral_block_device.#: "" => "<computed>"
    instance_state:           "" => "<computed>"
    instance_type:            "" => "t2.micro"
    key_name:                 "" => "<computed>"
    placement_group:          "" => "<computed>"
    private_dns:              "" => "<computed>"
    private_ip:               "" => "<computed>"
    public_dns:               "" => "<computed>"
    public_ip:                "" => "<computed>"
    root_block_device.#:      "" => "<computed>"
    security_groups.#:        "" => "<computed>"
    source_dest_check:        "" => "1"
    subnet_id:                "" => "<computed>"
    tenancy:                  "" => "<computed>"
    vpc_security_group_ids.#: "" => "<computed>"


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

Note が表示された。

Note: You didn’t specify an “-out” parameter to save this plan, so when “apply” is called, Terraform can’t guarantee this is what will execute.

これは、「 out パラメータを指定してプランを保存しなかったら、 apply 呼ばれた時に何を実行するか保証できないよ!」ってことだと思う。多分、 plan した後に別の人が別のところで apply した場合は、今回行った plan は意味なくなるから気をつけろよってことだと思う。

あとで、実験しよう。

あと、 terraform.tfstate というファイルが作成される。これは、 Terraform が管理しているリソースを JSON で保存しておくファイルらしい。 いまは、何も適用してないから空っぽいファイルができてるね。

{
    "version": 1,
    "serial": 0,
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {}
        }
    ]
}

apply で適用する

apply のサブコマンドを実行すると、 *.tf ファイルの記述に従って処理が適用される。

$ terraform apply
aws_instance.hello-aws: Creating...
  ami:                      "" => "ami-a21529cc"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t2.micro"
  key_name:                 "" => "<computed>"
  placement_group:          "" => "<computed>"
  private_dns:              "" => "<computed>"
  private_ip:               "" => "<computed>"
  public_dns:               "" => "<computed>"
  public_ip:                "" => "<computed>"
  root_block_device.#:      "" => "<computed>"
  security_groups.#:        "" => "<computed>"
  source_dest_check:        "" => "1"
  subnet_id:                "" => "<computed>"
  tenancy:                  "" => "<computed>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.hello-aws: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

作成されたっぽい。 computed の部分は特になにも指定してないので動的に作成される(っぽい)。 インスタンスの詳細を見るには show を使えって書いてあるので実行してみる。

$ terraform show
aws_instance.hello-aws:
  id = i-4a10aed5
  ami = ami-a21529cc
  availability_zone = ap-northeast-1a
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile =
  instance_state = running
  instance_type = t2.micro
  key_name =
  monitoring = false
  private_dns = ip-172-31-4-134.ap-northeast-1.compute.internal
  private_ip = 172.31.4.134
  public_dns = ec2-52-192-1-94.ap-northeast-1.compute.amazonaws.com
  public_ip = 52.192.1.94
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 24
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = gp2
  security_groups.# = 0
  source_dest_check = true
  subnet_id = subnet-8b78affc
  tags.# = 0
  tenancy = default
  vpc_security_group_ids.# = 1
  vpc_security_group_ids.1967208828 = sg-9257fef7

なんかイロイロ表示されましたね。 ちゃんと起動してるかチェック。

$ aws ec2 describe-instances --instance-ids "i-4a10aed5" | jq '.Reservations[].Instances[] | {"Image ID": .ImageId, "Instance ID": .InstanceId, "Status": .State.Name}'
{
  "Image ID": "ami-a21529cc",
  "Instance ID": "i-4a10aed5",
  "Status": "running"
}

起動してるっぽい。

インスタンスを変更する

ためにしに、 AMI を Ubuntu から Amazon Linux に変更してみる。

diff --git hello-aws.tf hello-aws.tf
index cbe294a..62aeca0 100644
--- hello-aws.tf
+++ hello-aws.tf
@@ -3,6 +3,6 @@ provider "aws" {
 }

 resource "aws_instance" "hello-aws" {
-  ami = "ami-a21529cc"
+  ami = "ami-f80e0596"
   instance_type = "t2.micro"
 }

terraform plan を実行。

Refreshing Terraform state prior to plan...

aws_instance.hello-aws: Refreshing state... (ID: i-4a10aed5)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

-/+ aws_instance.hello-aws
    ami:                      "ami-a21529cc" => "ami-f80e0596" (forces new resource)
    availability_zone:        "ap-northeast-1a" => "<computed>"
    ebs_block_device.#:       "0" => "<computed>"
    ephemeral_block_device.#: "0" => "<computed>"
    instance_state:           "running" => "<computed>"
    instance_type:            "t2.micro" => "t2.micro"
    key_name:                 "" => "<computed>"
    placement_group:          "" => "<computed>"
    private_dns:              "ip-172-31-4-134.ap-northeast-1.compute.internal" => "<computed>"
    private_ip:               "172.31.4.134" => "<computed>"
    public_dns:               "ec2-52-192-1-94.ap-northeast-1.compute.amazonaws.com" => "<computed>"
    public_ip:                "52.192.1.94" => "<computed>"
    root_block_device.#:      "1" => "<computed>"
    security_groups.#:        "0" => "<computed>"
    source_dest_check:        "true" => "1"
    subnet_id:                "subnet-8b78affc" => "<computed>"
    tenancy:                  "default" => "<computed>"
    vpc_security_group_ids.#: "1" => "<computed>"


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

新しいリソースを作るみたいな表示になってる。

ami: "ami-a21529cc" => "ami-f80e0596" (forces new resource) Plan: 1 to add, 0 to change, 1 to destroy.

terraform apply を実行。

aws_instance.hello-aws: Refreshing state... (ID: i-4a10aed5)
aws_instance.hello-aws: Destroying...
aws_instance.hello-aws: Destruction complete
aws_instance.hello-aws: Creating...
  ami:                      "" => "ami-f80e0596"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t2.micro"
  key_name:                 "" => "<computed>"
  placement_group:          "" => "<computed>"
  private_dns:              "" => "<computed>"
  private_ip:               "" => "<computed>"
  public_dns:               "" => "<computed>"
  public_ip:                "" => "<computed>"
  root_block_device.#:      "" => "<computed>"
  security_groups.#:        "" => "<computed>"
  source_dest_check:        "" => "1"
  subnet_id:                "" => "<computed>"
  tenancy:                  "" => "<computed>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.hello-aws: Creation complete

Apply complete! Resources: 1 added, 0 changed, 1 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

確認してみる。

$ terraform show | grep " id ="
  id = i-1cd06883
$ aws ec2 describe-instances --instance-ids "i-4a10aed5" "i-1cd06883" | jq '.Reservations[].Instances[] | {"Image ID": .ImageId, "Instance ID": .InstanceId, "Status": .State.Name}'
{
  "Image ID": "ami-a21529cc",
  "Instance ID": "i-4a10aed5",
  "Status": "terminated"
}
{
  "Image ID": "ami-f80e0596",
  "Instance ID": "i-1cd06883",
  "Status": "running"
}

古い方は terminated になってるし、新しい方は違う AMI ID で running になってる。

インスタンスの削除

インスタンスの削除は plan-destroy オプションをつけてプランファイルを作成し、 apply 実行時にプランファイルを指定して実行する。

プランファイルを作成して。

$ terraform plan -destroy -out=destroy.tfplan
efreshing Terraform state prior to plan...

aws_instance.hello-aws: Refreshing state... (ID: i-1cd06883)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Your plan was also saved to the path below. Call the "apply" subcommand
with this plan file and Terraform will exactly execute this execution
plan.

Path: destroy.tfplan

- aws_instance.hello-aws


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

削除。

$ terraform apply ./destroy.tfplan
aws_instance.hello-aws: Destroying...
aws_instance.hello-aws: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

確認してみると両方とも terminated になってた。

$ aws ec2 describe-instances --instance-ids "i-4a10aed5" "i-1cd06883" | jq '.Reservations[].Instances[] | {"Image ID": .ImageId, "Instance ID": .InstanceId, "Status": .State.Name}'
{
  "Image ID": "ami-a21529cc",
  "Instance ID": "i-4a10aed5",
  "Status": "terminated"
}
{
  "Image ID": "ami-f80e0596",
  "Instance ID": "i-1cd06883",
  "Status": "terminated"
}

References