【初心者】Terraform importを使ってみる

# 1. はじめに
 - 「IaC管理されていないシステムをIaC化したい」、ということに取り組んでいる案件があり、Terraform importを使って既存のリソースをTerraform管理化しているとのこと。
 - 「Terraform importって何？」が理解できることを目的に、Terraform importを用いて、Terraformの管理外のAWSリソースをどのようにTerraform管理下に組み込むのかの概念・手順を、基本的な構成で確認する。

# 2. やったこと
- Cloud9 にTerraform 実行環境を作成する。
- VPCとSubnetをTerraformから作成する。
- 作成したSubnetの中に、マネコンからEC2インスタンスを作成する。
- マネコンから作成したEC2インスタンスを「Terraform import」を用いて、Terraform管理下に取り込む。
- 管理下に取り込んだEC2インスタンスのインスタンスタイプをTerraformを用いて変更する。

# 3. 構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189302/91dcf3d1-4b98-e869-f7c2-2c42ea5409be.png)


# 4. 手順

## 4.1 Terraform実行環境の作成
- 基本的にこちらの記事「[Cloud9上にTerraformの実行環境を作ってみた](https://dev.classmethod.jp/articles/cloud9-terraform/)」通りに環境を構築する。各種資材のバージョンは最新(2023/12時点)のものに変更した。
    - Cloud9のOS: Amazon Linux 2023
    - Terraform: v1.6.6

## 4.2 VPCとsubnetをTerraformから作成
- VPCとsubnetだけを作成する定義ファイル(main.tf)を作成する。書き方はこちらの記事「[AWSのVPCなどのネットワークをTerraformで作成する](https://sagantaf.hatenablog.com/entry/2020/10/23/231609)」などを参照。

``` main.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "5.31.0"
    }
  }
}
provider "aws" {
  region = "us-west-1"
}
resource "aws_vpc" "mksamba_tf_vpc" {
  cidr_block                       = "10.0.0.0/16" # VPCに設定したいCIDRを指定
  enable_dns_support               = "true" # VPC内でDNSによる名前解決を有効化するかを指定
  enable_dns_hostnames             = "true" # VPC内インスタンスがDNSホスト名を取得するかを指定
  instance_tenancy                 = "default" # VPC内インスタンスのテナント属性を指定
  assign_generated_ipv6_cidr_block = "false" # IPv6を有効化するかを指定
  tags = {
    Name  = "mksamba_tf_vpc"
  }
}
resource "aws_subnet" "mksamba_tf_subnet_a" {
  vpc_id                          = aws_vpc.mksamba_tf_vpc.id # VPCのIDを指定
  cidr_block                      = "10.0.0.0/24" # サブネットに設定したいCIDRを指定
  assign_ipv6_address_on_creation = "false" # IPv6を利用するかどうかを指定
  map_public_ip_on_launch         = "true" # VPC内インスタンスにパブリックIPアドレスを付与するかを指定
  availability_zone               = "us-west-1a" # サブネットが稼働するAZを指定
  tags = {
    Name = "mksamba_tf_subnet_1a"
  }
}
```

- terraform init / plan / applyを実行して、VPCとsubnetが作成されることを確認する。
```
$ terraform init
$ terraform plan
$ terraform apply
```

## 4.3 マネコンからEC2インスタンスを作成
- Terraformから作成したVPC/Subnetを指定して、マネコンからインスタンスを作成する。インスタンスタイプ はt2.microとする。

## 4.4 EC2インスタンス情報のTerraform import
- マネコンから作成してしまったインスタンスをTerraformから管理できるようにするため、Terraform import作業を行う。

### 4.4.1 main.tfの事前修正
- まず、tfファイル内に、importしたいリソースの情報を入れる定義(空の枠)を作る必要がある。今回はインスタンスを取り込むため、「resource "aws_instance"」 の項目を追加し、適当な名前(import_instance)を付ける。中身は空のままにする。

``` (追加箇所のみ) main.tf
resource "aws_instance" "import_instance" {
}
```

### 4.4.2 terraform import コマンドの実行

- terraform import コマンドを実行し、インスタンスの情報を取り込む。

```
$ terraform import aws_instance.import_instance i-xxxxxxxxxxxxxxxxx

aws_instance.import_instance: Importing from ID "i-xxxxxxxxxxxxxxxxx"...
aws_instance.import_instance: Import prepared!
  Prepared aws_instance for import
aws_instance.import_instance: Refreshing state... [id=i-xxxxxxxxxxxxxxxxx]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
```

:::note warn
importコマンドの実行前にtfファイル内にリソース定義を作っていない場合、以下のようなエラーになりimportに失敗する。
:::

```
$ terraform import aws_instance.test i-xxxxxxxxxxxxxxxxx
Error: resource address "aws_instance.test" does not exist in the configuration.
Before importing this resource, please create its configuration in the root module. For example:
resource "aws_instance" "test" {
  # (resource arguments)
}
```

- terraform state show コマンドで、importしたインスタンスの情報が表示されることを確認する(長いので一部抜粋して記載)。AMI、AZ、インスタンスタイプなどの各種情報が取得できている。

```
$ terraform state show aws_instance.import_instance
# aws_instance.import_instance:
resource "aws_instance" "import_instance" {
    ami                                  = "ami-0a07b0077b66673f1"
    arn                                  = "arn:aws:ec2:us-west-1:xxxxxxxxxxxx:instance/i-xxxxxxxxxxxxxxxxx"
    associate_public_ip_address          = false
    availability_zone                    = "us-west-1a"
    cpu_core_count                       = 1
    cpu_threads_per_core                 = 1
...
...
}
```

### 4.4.3 tfファイルの修正
- Terraform importした直後は、tfstateファイルは自動的に更新され、対象のインスタンスがTerraform管理下に入った状態になり、例えば terraform destoyした場合はVPC/subnetとともに、インスタンスも削除される。
- ただし、main.tfファイルは枠を作っただけの状態のままで、中身は自動追加はされていないため、これ以後にこのインスタンスの管理をしていくためには定義情報を追記する必要がある。
- main.tfの「resource "aws_instance" "import_instance"」の箇所(今は何も中身がない)に、「terraform state show aws_instance.import_instance」の出力結果をいったん全て張り付ける。
- 「terraform state show aws_instance.import_instance」の出力結果には、AMIなどの管理上必要な情報がある一方、ARNやインスタンスIDなど、本来tfファイルには記載すべきではない情報も含まれている。出力結果を全て張り付けた後に terraform planを実行すると、多数エラーが出るため、エラーの原因となる行(ARNなど)を全て削除する。
- terraform planを実行した時のエラー例(一部抜粋)は以下の通り。エラーが出る行(ARNやcpu_core_countなど)をすべて削除する。

```
$ terraform plan
╷
│ Error: Value for unconfigurable attribute
│ 
│   with aws_instance.import_instance,
│   on main.tf line 40, in resource "aws_instance" "import_instance":
│   40:     arn                                  = "arn:aws:ec2:us-west-1:xxxxxxxxxxxx:instance/i-xxxxxxxxxxxxxxxxx"
│ 
│ Can't configure a value for "arn": its value will be decided automatically based on the result of applying this configuration.
╵
╷
│ Error: Conflicting configuration arguments
│ 
│   with aws_instance.import_instance,
│   on main.tf line 43, in resource "aws_instance" "import_instance":
│   43:     cpu_core_count                       = 1
│ 
│ "cpu_core_count": conflicts with cpu_options.0.core_count
...
...
```
- 不要な行の削除後、最終的にterraform planの結果が「No changes. Your infrastructure matches the configuration.」となることを確認する。

```
$ terraform plan
aws_vpc.mksamba_tf_vpc: Refreshing state... [id=vpc-xxxxxxxxxxxxxxxxxxx]
aws_instance.import_instance: Refreshing state... [id=i-xxxxxxxxxxxxxxxxxxx]
aws_subnet.mksamba_tf_subnet_a: Refreshing state... [id=subnet-xxxxxxxxxxxxxxxxxxx]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
```

### 4.4.4 Terraform 経由でのインスタンスの設定変更

- main.tf ファイルの該当インスタンスに関する以下の箇所を変更する。
    - 変更前：　instance_type = "t2.micro"
    - 変更後：　instance_type = "t2.small"
- terraform plan / apply して、インスタンスタイプがt2.smallに変更されたことを確認する。

```
$ terraform apply
aws_instance.import_instance: Refreshing state... [id=i-xxxxxxxxxxxxxxxxx]
aws_vpc.mksamba_tf_vpc: Refreshing state... [id=vpc-xxxxxxxxxxxxxxxxx]
aws_subnet.mksamba_tf_subnet_a: Refreshing state... [id=subnet-xxxxxxxxxxxxxxxxx]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_instance.import_instance will be updated in-place
  ~ resource "aws_instance" "import_instance" {
        id                                   = "i-xxxxxxxxxxxxxxxxx"
      ~ instance_type                        = "t2.micro" -> "t2.small"
        tags                                 = {
            "Name" = "mksamba-qiita-out-of-terraform"
        }
      + user_data_replace_on_change          = false
        # (28 unchanged attributes hidden)

        # (8 unchanged blocks hidden)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.import_instance: Modifying... [id=i-xxxxxxxxxxxxxxxxx]
aws_instance.import_instance: Modifications complete after 1m0s [id=i-xxxxxxxxxxxxxxxxx]

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

# 5. 所感
- 既存のAWSリソースをインポートしてTerraform管理下とするための基本的な概念が分かった。
- 今回はインポート対象がインスタンスだけだったが、実際にシステムのIaC化を行おうとすると、様々な種類のリソースをインポートする必要があり大変かもしれない。

# 6. その他参考サイト
- [Terraform import のススメ 〜開発効率化編〜](https://tech.layerx.co.jp/entry/improve-iac-development-with-terraform-import)
- [【Terraform】terraform importを試す](https://qiita.com/empty948/items/f5707be8077dba995978)
- [Terraform importで既存のAWS環境を取り込む](https://zoo200.net/terraform-import/)