TerraformでAWS VPCを変更する(outパラメータによる変更プランの保存と適用)

TerraformでAWS VPCを変更するコード(コマンド)

「-out」パラメータでプランを保存した場合、コードとのズレや、環境とのズレが発生していた場合にどのような挙動やメッセージとなるのかを確認する。

下記の４パターンの挙動を確認する。
パターン① 環境も変更したい内容もズレは無し
パターン② 環境にズレがある(変更したい内容と競合する部分)
パターン③ 環境にズレがある(変更したい内容と競合しない部分)
パターン④ main.tfに更新がある

## 実行環境
- Windows 10 Home (1919)
- Git Bash (git version 2.25.1.windows.1)
- AWS CLI (aws-cli/2.0.3 Python/3.7.5 Windows/10 botocore/2.0.0dev7)
- Terraform (v0.12.26)


## パターン① 環境も変更したい内容もズレは無し

#### 変更前の状態

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC"
                }
            ]
        }
    ]
}
```

この状態から、Nameタグの内容を変更する変更プランをtfplan1としていったん出力した後に適用してみる。

#### main.tf

```
$ cat main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.20.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC pattern1"
    CostGroup = "prj01"
  }
}
```

Nameタグのみ変更するmain.tf

#### 変更対象がNameタグのみであることを確認

```
$ ../terraform.exe 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.

aws_vpc.prj01VPC: Refreshing state... [id=vpc-06bc5f188ef3b2fe8]

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

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

Terraform will perform the following actions:

  # aws_vpc.prj01VPC will be updated in-place
  ~ resource "aws_vpc" "prj01VPC" {
        arn                              = "arn:aws:ec2:us-west-2:679788997248:vpc/vpc-06bc5f188ef3b2fe8"
        assign_generated_ipv6_cidr_block = false
        cidr_block                       = "10.20.0.0/16"
        default_network_acl_id           = "acl-0ec7d4e945ff1d7f0"
        default_route_table_id           = "rtb-0d64bb221c3f9d1ff"
        default_security_group_id        = "sg-03b425d2c42c1e984"
        dhcp_options_id                  = "dopt-0ebee8b328487036e"
        enable_classiclink               = false
        enable_classiclink_dns_support   = false
        enable_dns_hostnames             = false
        enable_dns_support               = true
        id                               = "vpc-06bc5f188ef3b2fe8"
        instance_tenancy                 = "default"
        main_route_table_id              = "rtb-0d64bb221c3f9d1ff"
        owner_id                         = "679788997248"
      ~ tags                             = {
            "CostGroup" = "prj01"
          ~ "Name"      = "prj01VPC" -> "prj01VPC pattern1"
        }
    }

Plan: 0 to add, 1 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.

```

#### 指示通りにoutパラメータを使い変更プランを出力

```
$ ../terraform.exe plan -out=tfplan1
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.

aws_vpc.prj01VPC: Refreshing state... [id=vpc-06bc5f188ef3b2fe8]

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

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

Terraform will perform the following actions:

  # aws_vpc.prj01VPC will be updated in-place
  ~ resource "aws_vpc" "prj01VPC" {
        arn                              = "arn:aws:ec2:us-west-2:679788997248:vpc/vpc-06bc5f188ef3b2fe8"
        assign_generated_ipv6_cidr_block = false
        cidr_block                       = "10.20.0.0/16"
        default_network_acl_id           = "acl-0ec7d4e945ff1d7f0"
        default_route_table_id           = "rtb-0d64bb221c3f9d1ff"
        default_security_group_id        = "sg-03b425d2c42c1e984"
        dhcp_options_id                  = "dopt-0ebee8b328487036e"
        enable_classiclink               = false
        enable_classiclink_dns_support   = false
        enable_dns_hostnames             = false
        enable_dns_support               = true
        id                               = "vpc-06bc5f188ef3b2fe8"
        instance_tenancy                 = "default"
        main_route_table_id              = "rtb-0d64bb221c3f9d1ff"
        owner_id                         = "679788997248"
      ~ tags                             = {
            "CostGroup" = "prj01"
          ~ "Name"      = "prj01VPC" -> "prj01VPC pattern1"
        }
    }

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

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

This plan was saved to: tfplan1

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan1"
```

#### 出力されたファイルの確認

```
$ ls -al *tfplan1*
-rw-r--r-- 1 xxx 197610 1975  7月  5 01:23 tfplan1

$ file tfplan1
tfplan1: Zip archive data, at least v2.0 to extract
```

約2KBのzipファイルですね。

```
$ unzip tfplan1
Archive:  tfplan1
  inflating: tfplan
  inflating: tfstate
  inflating: tfconfig/m-/main.tf
  inflating: tfconfig/modules.json

$ ls -al
-rw-r--r-- 1 xxx 197610 1362  7月  5 01:23 tfplan
-rw-r--r-- 1 xxx 197610 1428  7月  5 01:23 tfstate
-rw-r--r-- 1 xxx 197610  255  7月  5 01:23 tfconfig/m-/main.tf
-rw-r--r-- 1 xxx 197610   41  7月  5 01:23 tfconfig/modules.json

$ file tfplan
tfplan: data

$ file tfstate
tfstate: ASCII text

$ file tfconfig/m-/main.tf
tfconfig/m-/main.tf: ASCII text, with CRLF line terminators

$ file tfconfig/modules.json
tfconfig/modules.json: JSON data
```
```
$ cat tfstate
{
  "version": 4,
  "terraform_version": "0.12.26",
  "serial": 16,
  "lineage": "9ea1190d-b435-c622-09c8-310ec94b3088",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "prj01VPC",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:us-west-2:679788997248:vpc/vpc-06bc5f188ef3b2fe8",
            "assign_generated_ipv6_cidr_block": false,
            "cidr_block": "10.20.0.0/16",
            "default_network_acl_id": "acl-0ec7d4e945ff1d7f0",
            "default_route_table_id": "rtb-0d64bb221c3f9d1ff",
            "default_security_group_id": "sg-03b425d2c42c1e984",
            "dhcp_options_id": "dopt-0ebee8b328487036e",
            "enable_classiclink": false,
            "enable_classiclink_dns_support": false,
            "enable_dns_hostnames": false,
            "enable_dns_support": true,
            "id": "vpc-06bc5f188ef3b2fe8",
            "instance_tenancy": "default",
            "ipv6_association_id": "",
            "ipv6_cidr_block": "",
            "main_route_table_id": "rtb-0d64bb221c3f9d1ff",
            "owner_id": "679788997248",
            "tags": {
              "CostGroup": "prj01",
              "Name": "prj01VPC"
            }
          },
          "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ=="
        }
      ]
    }
  ]
}
```

```
$ cat tfconfig/m-/main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.20.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC pattern1"
    CostGroup = "prj01"
  }
}
```

```
$ cat tfconfig/modules.json
[
  {
    "Key": "",
    "Dir": "."
  }
]
```

#### プランを適用

```
$ ../terraform.exe apply tfplan1
aws_vpc.prj01VPC: Modifying... [id=vpc-06bc5f188ef3b2fe8]
aws_vpc.prj01VPC: Modifications complete after 7s [id=vpc-06bc5f188ef3b2fe8]

Apply complete! Resources: 0 added, 1 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
```

#### ちゃんと変更されたことを確認

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC pattern1"
                }
            ]
        }
    ]
}
```

ちゃんと該当箇所が変更されていることが確認できた。
マニュアル等では「-input=false」が一緒に使われているけど、これは何だ？

## パターン② 環境にズレがある(変更したい内容と競合する部分)

#### main.tf

```
$ cat main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.20.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC pattern2"
    CostGroup = "prj01"
  }
}
```

Nameタグを「pattern2」に変更する

### プランを出力

```
$ ../terraform.exe plan -out=tfplan2
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.

aws_vpc.prj01VPC: Refreshing state... [id=vpc-06bc5f188ef3b2fe8]

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

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

Terraform will perform the following actions:

  # aws_vpc.prj01VPC will be updated in-place
  ~ resource "aws_vpc" "prj01VPC" {
        arn                              = "arn:aws:ec2:us-west-2:679788997248:vpc/vpc-06bc5f188ef3b2fe8"
        assign_generated_ipv6_cidr_block = false
        cidr_block                       = "10.20.0.0/16"
        default_network_acl_id           = "acl-0ec7d4e945ff1d7f0"
        default_route_table_id           = "rtb-0d64bb221c3f9d1ff"
        default_security_group_id        = "sg-03b425d2c42c1e984"
        dhcp_options_id                  = "dopt-0ebee8b328487036e"
        enable_classiclink               = false
        enable_classiclink_dns_support   = false
        enable_dns_hostnames             = false
        enable_dns_support               = true
        id                               = "vpc-06bc5f188ef3b2fe8"
        instance_tenancy                 = "default"
        main_route_table_id              = "rtb-0d64bb221c3f9d1ff"
        owner_id                         = "679788997248"
      ~ tags                             = {
            "CostGroup" = "prj01"
          ~ "Name"      = "prj01VPC pattern1" -> "prj01VPC pattern2"
        }
    }

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

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

This plan was saved to: tfplan2

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan2"
```

この時点ではちゃんとNameタグが「pattern1」から「pattern2」へ変更される内容になっている。

#### こっそりコンソールからNameタグを変更

変更した結果を出力

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "Name",
                    "Value": "prj01VPC pattern1.2"
                },
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                }
            ]
        }
    ]
}
```

環境が、裏で変更されている状態をこれで作成。
Nameタグが「pattern1」から「pattern1.2」に変わってしまった。

#### 環境が変わってしまった状態でapply

```
$ ../terraform.exe apply tfplan2
aws_vpc.prj01VPC: Modifying... [id=vpc-06bc5f188ef3b2fe8]
aws_vpc.prj01VPC: Modifications complete after 6s [id=vpc-06bc5f188ef3b2fe8]

Apply complete! Resources: 0 added, 1 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
```

コマンドは普通に通ってしまった。

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC pattern2"
                }
            ]
        }
    ]
}
```

状態も変更されている。
なので、この変更前の環境状態はチェックしてくれるわけではなさそう。


## パターン③ 環境にズレがある(変更したい内容と競合しない部分)

#### main.tf

```
$ cat main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.20.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC pattern3"
    CostGroup = "prj01"
  }
}
```

#### こっそりコンソールからCIDRを追加

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                },
                {
                    "AssociationId": "vpc-cidr-assoc-0da88385804f491d2",
                    "CidrBlock": "10.21.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC pattern2"
                }
            ]
        }
    ]
}
```

「10.21.0.0/16」を追加した。
なので、これがどうなるのか？、具体的には警告無しで削除されてしまうのかどうかを確認する。

#### 環境が変わってしまった状態でapply

```
$ ../terraform.exe apply tfplan2

Error: Saved plan is stale

The given plan file can no longer be applied because the state was changed by
another operation after the plan was created.
```

お！ ちゃんとエラーになってくれた。
エラーメッセージもちゃんと「別の操作で状態が変更されたため・・・」となってくれている。

## パターン④ main.tfに更新がある

#### まずは環境を元にもどして状態を確認

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                },
                {
                    "AssociationId": "vpc-cidr-assoc-0da88385804f491d2",
                    "CidrBlock": "10.21.0.0/16",
                    "CidrBlockState": {
                        "State": "disassociated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC pattern3"
                }
            ]
        }
    ]
}
```

#### main.tf

```
$ cat main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.20.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC pattern4"
    CostGroup = "prj01"
  }
}
```

またまたNameタグのみ変更するmain.tfを作成

#### プランを出力

```
$ ../terraform.exe plan -out=tfplan4
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.

aws_vpc.prj01VPC: Refreshing state... [id=vpc-06bc5f188ef3b2fe8]

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

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

Terraform will perform the following actions:

  # aws_vpc.prj01VPC will be updated in-place
  ~ resource "aws_vpc" "prj01VPC" {
        arn                              = "arn:aws:ec2:us-west-2:679788997248:vpc/vpc-06bc5f188ef3b2fe8"
        assign_generated_ipv6_cidr_block = false
        cidr_block                       = "10.20.0.0/16"
        default_network_acl_id           = "acl-0ec7d4e945ff1d7f0"
        default_route_table_id           = "rtb-0d64bb221c3f9d1ff"
        default_security_group_id        = "sg-03b425d2c42c1e984"
        dhcp_options_id                  = "dopt-0ebee8b328487036e"
        enable_classiclink               = false
        enable_classiclink_dns_support   = false
        enable_dns_hostnames             = false
        enable_dns_support               = true
        id                               = "vpc-06bc5f188ef3b2fe8"
        instance_tenancy                 = "default"
        main_route_table_id              = "rtb-0d64bb221c3f9d1ff"
        owner_id                         = "679788997248"
      ~ tags                             = {
            "CostGroup" = "prj01"
          ~ "Name"      = "prj01VPC pattern3" -> "prj01VPC pattern4"
        }
    }

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

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

This plan was saved to: tfplan4

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan4"
```

#### この状態でmain.tfを更新

```
$ cat main.tf
provider "aws" {
  profile = "prj01-profile"
  region = "us-west-2"
}

resource "aws_vpc" "prj01VPC" {
  cidr_block = "10.20.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "prj01VPC pattern4.2"
    CostGroup = "prj01"
  }
}
```

#### main.tfが変わってしまった状態でapply

```
$ ../terraform.exe apply tfplan4
aws_vpc.prj01VPC: Modifying... [id=vpc-06bc5f188ef3b2fe8]
aws_vpc.prj01VPC: Modifications complete after 7s [id=vpc-06bc5f188ef3b2fe8]

Apply complete! Resources: 0 added, 1 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
```

普通に適用された。
↓環境もちゃんと変更されている。

```
$ aws ec2 describe-vpcs  --region=us-west-2
{
    "Vpcs": [
        {
            "CidrBlock": "10.20.0.0/16",
            "DhcpOptionsId": "dopt-0ebee8b328487036e",
            "State": "available",
            "VpcId": "vpc-06bc5f188ef3b2fe8",
            "OwnerId": "679788997248",
            "InstanceTenancy": "default",
            "CidrBlockAssociationSet": [
                {
                    "AssociationId": "vpc-cidr-assoc-0373fb92a40bc4aba",
                    "CidrBlock": "10.20.0.0/16",
                    "CidrBlockState": {
                        "State": "associated"
                    }
                },
                {
                    "AssociationId": "vpc-cidr-assoc-0da88385804f491d2",
                    "CidrBlock": "10.21.0.0/16",
                    "CidrBlockState": {
                        "State": "disassociated"
                    }
                }
            ],
            "IsDefault": false,
            "Tags": [
                {
                    "Key": "CostGroup",
                    "Value": "prj01"
                },
                {
                    "Key": "Name",
                    "Value": "prj01VPC pattern4"
                }
            ]
        }
    ]
}
```