Terraform

Terraform importで既存のAWS環境を取り込む

以前、こちらの記事でTerraformerというツールを紹介しました。

このTerraformerを使うと、Terraformのファイルを事前に用意しなくても既存AWS環境をTerraformコードに落とし込むことができます。

Terraformをさわり始めたときはよく使用していたのですが、次第にTerraformに慣れてくるとTerraform自体の機能である import で取り込むことが多くなりました。

とくに実際に運用が始まるとAWS全体を取り込むことはなくなり、(基本NGですが)手動で作成した特定のリソースだけをTerraformへ取り込むことが多くなりました。特定のリソースだけであれば、importの準備は簡単にできます。

今回は、このTerraformのimport機能を使ってみます。

スポンサーリンク

import手順

例として demo-vpc という名前が付いたVPCをインポートしてみます。

取り込み用のTerraformファイルの作成

まず取り込むためにTerraformのファイルを用意します。以下のように resource 文を定義するのみでOKです。

$ cat main.tf
terraform {
  backend "local" {}
}

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

## VPC    ここのdemo-vpcという名前はただの論理名なので任意です
resource "aws_vpc" "demo-vpc" {
}
$

importコマンドでAWS上のリソースの取り込み

ファイルを作成したらimportコマンドで取り込みます。コマンドの最後で指定するIDは、Terraform公式ドキュメントの各リソースページの一番下のimportに書いてあります。

なおこのIDは、取り込み対象のリソースにより異なります。VPCの場合はvpc-idですが、s3ではバケット名になったりします。

## terraform import リソースタイプ.リソース名 取り込み対象のID
$ terraform import aws_vpc.demo-vpc vpc-0f88cxxx
...

Import successful!

...
$

リソース状態 tfstateの確認

importコマンドでAWSの状態情報をTerraformに取り込んだ形になります。今回の例ではローカルのカレントディレクトリにterraform.tfstateが新規に作成されています。

$ ls -l terraform.tfstate
-rw-r--r--  1 zoo200  staff  1828 10 30 19:56 terraform.tfstate
$
$ cat terraform.tfstate
{
  "version": 4,
  "terraform_version": "1.3.3",
  "serial": 1,
  "lineage": "7d17xxxx",
  "outputs": {},
  "resources": [
    {
      "mode": "managed",
      "type": "aws_vpc",
      "name": "demo-vpc",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "arn": "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-0f88cxxx",
            "assign_generated_ipv6_cidr_block": false,
            "cidr_block": "10.1.0.0/16",
...
$

state showコマンドでも状態情報を確認できます。

$ terraform state show aws_vpc.demo-vpc
# aws_vpc.demo-vpc:
resource "aws_vpc" "demo-vpc" {
    arn                                  = "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-0f88cxxx
    assign_generated_ipv6_cidr_block     = false
    cidr_block                           = "10.1.0.0/16"
...
$

planコマンドでTerraformのファイルと実状の差分確認

この段階でplanコマンドでTerraformを実行したらどうなるか確認してみます。

Terraformファイルは定義だけあって何も書いてない状態なので、以下のとおりVPCに割り当てたdemo-vpcという名前(タグ)が削除されてしまうことがわかります。

$ terraform plan
aws_vpc.demo-vpc: Refreshing state... [id=vpc-0f88cxxx]

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_vpc.demo-vpc will be updated in-place
  ~ resource "aws_vpc" "demo-vpc" {
        id                                   = "vpc-0f88cxxx"
      ~ tags                                 = {
          - "Name" = "demo-vpc" -> null
        }
      ~ tags_all                             = {
          - "Name" = "demo-vpc"
        } -> (known after apply)
        # (16 unchanged attributes hidden)
    }

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

差分を手動で編集する

このままではTerraformを実行できないので、tfファイルを編集してdemo-vpcという名前(タグ)を追記することで差分をなくします。

ただそれだけであるとファイルをみても何を構築しているのかわからないので、公式ドキュメントとtfstateを照らし合わせながら、最低限の設定は書いておいたほうが良いと思います。例えばvpcであればcidr_blockなどです。

$ cat main.tf
terraform {
  backend "local" {}
}

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

## VPC
resource "aws_vpc" "demo-vpc" {
  ## リソースの設定を追記する
  cidr_block = "10.1.0.0/16"

  tags = {
    "Name" = "demo-vpc"
  }
}
$

planコマンドで差分がなくなったことの確認

ファイル編集後、planコマンドを実行すると以下のように差分が出力されなくなります。

$ terraform plan
...

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.
$

以上がimportコマンドによるAWS既存リソース取り込みの基本的な流れです。

Tips

count構文を使った配列の場合

count構文を使用している場合は、リソース名に配列インデックス番号をつけて、リソースタイプ.リソース名の部分をクォーテーションで囲みます。

$ cat main.tf
...

variable "env" {
    type = string
}

## VPC
resource "aws_vpc" "demo-vpc" {
  ## e.g. 開発環境の場合のみ実行
  count        = var.env == "dev" ? 1 : 0

  cidr_block = "10.1.0.0/16"

  tags = {
    "Name" = "dev"
  }
}
$
$ terraform import -var 'env=dev' 'aws_vpc.demo-vpc[0]' vpc-0f88cxxx
...
$

クォーテーションで囲まないと [ ] 自体がシェルコマンドと認識されるためimpotコマンドがうまく動きません。

$ terraform import -var 'env=dev' aws_vpc.demo-vpc[0] vpc-0f88cxxx
zsh: no matches found: aws_vpc.demo-vpc[0]
$
$ which [
[: shell built-in command
$

for_each構文を使ったMap,連想配列の場合

for_each構文を使用している場合は、リソース名に配列キー名をつけて、リソースタイプ.リソース名の部分をクォーテーションで囲みます。

$ cat main.tf
...

variable "env" {
  type = map(string)

  default = {
    "dev"  = "10.1.0.0/16",
    "prod" = "10.2.0.0/16",
  }
}

## VPC
resource "aws_vpc" "demo-vpc" {
  ## e.g. mapをループして実行。名前がdevとprodのvpcが作られる。
  for_each = var.env

  cidr_block = each.value

  tags = {
    "Name" = each.key
  }
}
$
$ terraform import  'aws_vpc.demo-vpc["dev"]' vpc-0f88cxxx
...
$

今回は以上です〜ノシ

参考

(`・ω・´)ノ アリガトウゴザイマス!!

公式 Terraform import
Terraform import のススメ 〜開発効率化編〜