Terraform で作ったEC2インスタンスのタイプを変更するには

Terraform で作ったEC2インスタンスのタイプを変更するには、Terraformだけでは行えません。
そのままtfファイルを変更してしまうと recreate されてしまい、既存のインスタンスは破棄されてしまいます。

簡単にまとめると、以下の手順を行います。

1. InstanceをStopする
2. Instance Typeを変更する
3. tfファイルを変更する
4. InstanceをStartする

もしくは、`ignore_changes` を使うという手段もあります。


### パターン1: Instance TypeをTerraform外で変更する

* tf ファイル

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

resource "aws_instance" "instance1" {
  ami                    = "ami-29160d47" # Amazon Linux AMI 2016.03.1 x86_64 HVM GP2
  instance_type          = "t2.micro"
  key_name               = "key"
  vpc_security_group_ids = ["sg-deadbeef"]
  subnet_id              = "subnet-badcafe"

  root_block_device {
    volume_type           = "gp2"
    delete_on_termination = true
  }
}
output "instance_id" {
  value = "${aws_instance.instance1.id}"
}
```

まず、`t2.micro`でインスタンスを起動します。

```bash
$ terraform apply
(snip)
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

Outputs:

instance_id = i-08718c36c1b7e95cb
```

AWS CLIを使いInstanceをStop状態にします。
Management Consoleを使ってもよいです。
TerraformでStopしたいという[Issue](https://github.com/hashicorp/terraform/issues/1579)は上がってますが、対応は未定のようです。

```bash
$ aws ec2 stop-instances --instance-ids i-08718c36c1b7e95cb
{
    "StoppingInstances": [
        {
            "InstanceId": "i-08718c36c1b7e95cb",
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            },
            "CurrentState": {
                "Code": 64,
                "Name": "stopping"
            }
        }
    ]
}
```

AWS CLIを使い、Instance Typeを変更します。
Management Consoleを使ってもよいです。

```
$ aws ec2 modify-instance-attribute --instance-id i-08718c36c1b7e95cb --instance-type t2.nano
```

最後に、tfファイルを変更します。
これをしない場合は以下のようにリソースが作り直されます。

```bash
$ terraform plan
Refreshing Terraform state prior to plan...

aws_instance.instance1: Refreshing state... (ID: i-08718c36c1b7e95cb)

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. Cyan entries are data sources to be read.

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.instance1
    ami:                                       "ami-29160d47" => "ami-29160d47"
    availability_zone:                         "ap-northeast-1a" => "<computed>"
    ebs_block_device.#:                        "0" => "<computed>"
    ephemeral_block_device.#:                  "0" => "<computed>"
    instance_state:                            "stopped" => "<computed>"
    instance_type:                             "t2.micro" => "t2.nano" (forces new resource)
    key_name:                                  "key" => "key"
    placement_group:                           "" => "<computed>"
    private_dns:                               "ip-172-31-7-110.ap-northeast-1.compute.internal" => "<computed>"
    private_ip:                                "172.31.7.110" => "<computed>"
    public_dns:                                "" => "<computed>"
    public_ip:                                 "" => "<computed>"
    root_block_device.#:                       "1" => "1"
    root_block_device.0.delete_on_termination: "true" => "true"
    root_block_device.0.iops:                  "24" => "<computed>"
    root_block_device.0.volume_size:           "8" => "<computed>"
    root_block_device.0.volume_type:           "gp2" => "gp2"
    security_groups.#:                         "0" => "<computed>"
    source_dest_check:                         "true" => "true"
    subnet_id:                                 "subnet-badcafe" => "subnet-badcafe"
    tenancy:                                   "default" => "<computed>"
    vpc_security_group_ids.#:                  "1" => "1"
    vpc_security_group_ids.1346577894:         "sg-deadbeef" => "sg-deadbeef"


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

* tfファイル差分

```diff
-  instance_type          = "t2.micro"
+  instance_type          = "t2.nano"
```

修正後、planを実行すると無事`No changes`になります。

```
$ terraform plan
Refreshing Terraform state prior to plan...

aws_instance.instance1: Refreshing state... (ID: i-08718c36c1b7e95cb)

No changes. Infrastructure is up-to-date. This means that Terraform
could not detect any differences between your configuration and
the real physical resources that exist. As a result, Terraform
doesn't need to do anything.
```

### パターン2: `ignore_changes` を利用する

* tf ファイル

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

resource "aws_instance" "instance1" {
  ami                    = "ami-29160d47" # Amazon Linux AMI 2016.03.1 x86_64 HVM GP2
  instance_type          = "t2.micro"
  key_name               = "key"
  vpc_security_group_ids = ["sg-deadbeef"]
  subnet_id              = "subnet-badcafe"

  root_block_device {
    volume_type           = "gp2"
    delete_on_termination = true
  }

  lifecycle {
    ignore_changes = ["instance_type"]
  }
}
output "instance_id" {
  value = "${aws_instance.instance1.id}"
}
```

`lifecycle` ルールで `instance_type` が変化しても無視するようになります。
パターン1で行ったAWS CLIやManagement Consoleで自由に変更できます。

ただ、この方法だと tf ファイルでの定義と実際の状態が異なることになるため、管理上よくないことになるかもしれません。
運用方法と相談してご使用下さい。