Terraform import ブロックの実活用

Tech

2023.12.16

Topics

こんにちは、tkgです。

この記事はNHN テコラス Advent Calendar 2023の16日目の記事です。

今更ではありますが、Terraform 1.5で実装された importブロックについて触ってみましたので備忘録的にまとめたいと思います。
公式リファレンスには AWSのEC2インスタンスが例として記述されていますが、実際の環境をimportブロックを利用してどうコード化していくかという視点で記載していきます。

元々どうやって既存リソースのコード化(import)をしていたか

terraform import コマンド自体はかなり前から用意されていましたので、私はこちらを利用してStateとコード、実際のリソースとの差分を埋めるような形にしていました。
各リソースをtfファイルに記述の上でコマンドを実行、terraform planで差分を埋めるような作業で、コード自体はTerraformerやFormer2などサードパーティ製品で生成または手作業で準備するなどする必要がありました。

例 EC2の場合

1. 別途生成したリソース設定を記述 または 下記のようなコードをtfファイルに書く

resource "aws_instance" "this" {}

2. terraform importコマンドを実行
terraform import aws_instance.this i-xxxxxxxxxxx

3. terraform planを実行、差分の記述をする
生成したコードの場合差分を埋める、直接記述した場合記述を一旦消してからplanを実行、差分出力から必要な項目を記述する

サードパーティ製品の出力ではコードのプロバイダバージョンの差分が存在することがあったり、terraform plan の結果から差分を元に記述するなど、リソースからコードを起こすのはまあまあ大変だった印象です。

importブロックの動作について

公式リファレンス通りにはなりますが、基本的に下記のような構文でimportを行っていきます。

import {
	to = aws_instance.example
	id = "i-xxxxxxxxxxxx"
}


こちらを記述した上で、対象となるリソースを記述していきます。
公式リファレンスに変更される可能性が示唆されているものにはなりますが、リソース自体を生成するオプションが用意されていますので、そちらを実行することで実環境からのコード生成が可能です。

terraform plan -generate-config-out=hogehoge.tf

importブロックとresourceが存在している状態で terraform apply を実施することでstateに反映され、コード管理が実現できるという仕組みです。

実際にインポートしてみる

それでは実際の環境をコード化してみたいと思います。
今回コード化を行う環境は下記構成図の赤枠で囲ったものです。

準備するもの

対象のAWSアカウントに対してTerraformが実行できるプロバイダ設定はあらかじめ行っておく必要があります。
今回は下記記事でご紹介していた際のprovider設定とほぼ同一のものを準備しました。(宣伝)
Terraform利用時のアクセスキー管理から脱却!assume_roleを活用して複数環境の管理を楽にする方法

locals {
  aws_id = [展開先のAWSID]
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket         = "[バケット名]"
    dynamodb_table = "[DynamoDBのテーブル名]"
    key            = "[stateを置きたいディレクトリ]/terraform.tfstate"
    region         = "ap-northeast-1"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

variable "role" {
  type        = string
  default     = "default"
  description = "select iam role"
}

provider "aws" {
  region = "${local.region}"
  assume_role {
    role_arn     = "arn:aws:iam::${local.aws_id}:role/Techorus-terraform-${var.role}-role"
    session_name = "terraform"
    external_id  = "[iamで指定しているexternal_id]"
  }
}

上記コードのみの状態でterraform init および terraform plan(No changes) が実行できることを確認します。

実際にimportを実施する

準備ができたら、実際にimportブロックを作成していきます。今回は import.tfとしてファイルを作成、記述しました。
idに指定するパラメーターは各リソースによって異なるので、取り込みたい対象のリソースのリファレンスの下部 importについての記述を参考に記載します。

今回は EC2、RDS、ALBをコード化していきます。

import {
    to = aws_instance.web_1
    id = "i-xxxxxxxxxxxxxxx"
}

import {
    to = aws_db_instance.rds_1
    id = "tkg-test-rds"
}

import {
    to = aws_lb.front_alb
    id = "arn:aws:elasticloadbalancing:ap-northeast-1:[展開先のAWSID]:loadbalancer/app/tkg-test-alb/ee4fab7b80076f59"
}

import {
    to = aws_lb_target_group.alb_target
    id = "arn:aws:elasticloadbalancing:ap-northeast-1:[展開先のAWSID]:targetgroup/tkg-test-tg/80768af5767da182"
}

import {
    to = aws_lb_listener.alb_listener
    id = "arn:aws:elasticloadbalancing:ap-northeast-1:[展開先のAWSID]:listener/app/tkg-test-alb/ee4fab7b80076f59/e00543683ea454e1"
}

上記import.tfを作成して、 terraform plan -generate-config-out=generated.tf を実施します。
すると生成機能は実験段階である旨と今後変更される可能性がある警告とエラーが出力されました。

Plan: 2 to import, 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Config generation is experimental
│ 
│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.
╵
╷
│ Error: expected default_action.0.order to be in the range (1 - 50000), got 0
│ 
│   with aws_lb_listener.alb_listener,
│   on generated.tf line 10:
│   (source code not available)
│ 
╵
╷
│ Error: Conflicting configuration arguments
│ 
│   with aws_instance.web_1,
│   on generated.tf line 14:
│   (source code not available)
│ 
│ "ipv6_address_count": conflicts with ipv6_addresses
╵
╷
│ Error: Conflicting configuration arguments
│ 
│   with aws_instance.web_1,
│   on generated.tf line 15:
│   (source code not available)
│ 
│ "ipv6_addresses": conflicts with ipv6_address_count
╵
╷
│ Error: Missing required argument
│ 
│   with aws_lb_target_group.alb_target,
│   on generated.tf line 37:
│   (source code not available)
│ 
│ The argument "target_failover.0.on_deregistration" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│ 
│   with aws_lb_target_group.alb_target,
│   on generated.tf line 38:
│   (source code not available)
│ 
│ The argument "target_failover.0.on_unhealthy" is required, but no definition was found.
╵
╷
│ Error: Missing required argument
│ 
│   with aws_lb_target_group.alb_target,
│   on generated.tf line 41:
│   (source code not available)
│ 
│ The argument "target_health_state.0.enable_unhealthy_connection_termination" is required, but no definition was found.
╵

エラーが表示されておりますが、 generated.tf 自体は生成されているようです。
内容的から排他パラメーターのコンフリクト等が発生しているようなので、エラー箇所の修正と生成されたコードから “null” や “{}” など初期値であるものが指定されているコードを削除して下記のようなコードで再度 terraform plan を実行してみます。

# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform
resource "aws_lb_listener" "alb_listener" {
  alpn_policy       = null
  certificate_arn   = "arn:aws:acm:ap-northeast-1:[展開先のAWSID]:certificate/9041b977-626a-47a6-ac8c-450fa9913a8d"
  load_balancer_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:[展開先のAWSID]:loadbalancer/app/tkg-test-alb/ee4fab7b80076f59"
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  default_action {
    target_group_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:[展開先のAWSID]:targetgroup/tkg-test-tg/80768af5767da182"
    type             = "forward"
  }
  mutual_authentication {
    ignore_client_certificate_expiry = false
    mode                             = "off"
  }
}

# __generated__ by Terraform
resource "aws_lb_target_group" "alb_target" {
  connection_termination             = null
  deregistration_delay               = "300"
  ip_address_type                    = "ipv4"
  load_balancing_algorithm_type      = "round_robin"
  load_balancing_cross_zone_enabled  = "use_load_balancer_configuration"
  name                               = "tkg-test-tg"
  port                               = 80
  protocol                           = "HTTP"
  protocol_version                   = "HTTP2"
  slow_start                         = 0
  target_type                        = "instance"
  vpc_id                             = "vpc-08c54ee99a8e9e6e4"
  health_check {
    enabled             = true
    healthy_threshold   = 5
    interval            = 30
    matcher             = "200"
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    timeout             = 5
    unhealthy_threshold = 2
  }
  stickiness {
    cookie_duration = 86400
    enabled         = false
    type            = "lb_cookie"
  }
}

# __generated__ by Terraform from "arn:aws:elasticloadbalancing:ap-northeast-1:[展開先のAWSID]:loadbalancer/app/tkg-test-alb/ee4fab7b80076f59"
resource "aws_lb" "front_alb" {
  desync_mitigation_mode                                       = "defensive"
  drop_invalid_header_fields                                   = false
  enable_cross_zone_load_balancing                             = true
  enable_deletion_protection                                   = true
  enable_http2                                                 = true
  enable_tls_version_and_cipher_suite_headers                  = false
  enable_waf_fail_open                                         = false
  enable_xff_client_port                                       = false
  idle_timeout                                                 = 60
  internal                                                     = false
  ip_address_type                                              = "ipv4"
  load_balancer_type                                           = "application"
  name                                                         = "tkg-test-alb"
  preserve_host_header                                         = false
  security_groups                                              = ["sg-0bc2af0d193d9a169"]
  subnets                                                      = ["subnet-01ab151723768f96c", "subnet-03c722ee50f9b4137"]
  xff_header_processing_mode                                   = "append"
  access_logs {
    bucket  = "tkg-test-dev-alb-web-accesslog"
    enabled = true
  }
  subnet_mapping {
    subnet_id            = "subnet-01ab151723768f96c"
  }
  subnet_mapping {
    subnet_id            = "subnet-03c722ee50f9b4137"
  }
}

# __generated__ by Terraform from "tkg-test-rds"
resource "aws_db_instance" "rds_1" {
  allocated_storage                     = 20
  auto_minor_version_upgrade            = true
  availability_zone                     = "ap-northeast-1a"
  backup_retention_period               = 0
  backup_target                         = "region"
  backup_window                         = "03:00-06:00"
  ca_cert_identifier                    = "rds-ca-2019"
  copy_tags_to_snapshot                 = false
  customer_owned_ip_enabled             = false
  db_name                               = "techorus"
  db_subnet_group_name                  = "tkg-test-vpc"
  delete_automated_backups              = true
  deletion_protection                   = false
  engine                                = "mysql"
  engine_version                        = "8.0.34"
  iam_database_authentication_enabled   = false
  identifier                            = "tkg-test-rds"
  instance_class                        = "db.t4g.micro"
  iops                                  = 3000
  kms_key_id                            = "arn:aws:kms:ap-northeast-1:[展開先のAWSID]:key/a85fac58-665d-479d-9e29-43723646bdd5"
  license_model                         = "general-public-license"
  maintenance_window                    = "mon:00:00-mon:03:00"
  max_allocated_storage                 = 100
  monitoring_interval                   = 0
  multi_az                              = false
  network_type                          = "IPV4"
  option_group_name                     = "default:mysql-8-0"
  parameter_group_name                  = "default.mysql8.0"
  performance_insights_enabled          = false
  performance_insights_retention_period = 0
  port                                  = 3306
  publicly_accessible                   = false
  skip_final_snapshot                   = true
  storage_encrypted                     = true
  storage_throughput                    = 125
  storage_type                          = "gp3"
  username                              = "techorus_test"
  vpc_security_group_ids                = ["sg-02e2685fe9d622880"]
}

# __generated__ by Terraform
resource "aws_instance" "web_1" {
  ami                                  = "ami-012261b9035f8f938"
  associate_public_ip_address          = true
  availability_zone                    = "ap-northeast-1a"
  disable_api_stop                     = false
  disable_api_termination              = false
  ebs_optimized                        = false
  get_password_data                    = false
  hibernation                          = false
  iam_instance_profile                 = "tkg-test_cloudwatch-ssm"
  instance_initiated_shutdown_behavior = "stop"
  instance_type                        = "t3.nano"
  monitoring                           = false
  placement_partition_number           = 0
  private_ip                           = "10.0.100.79"
  source_dest_check                    = true
  subnet_id                            = "subnet-02a7819a2e4f546d1"
  tags = {
    Name   = "tkg-test-ec2-al2-1"
    backup = "true"
  }
  tags_all = {
    Name   = "tkg-test-ec2-al2-1"
    backup = "true"
  }
  tenancy                     = "default"
  vpc_security_group_ids      = ["sg-0b9bc9b1f09387a70", "sg-0e5c49aeb001258da"]
  capacity_reservation_specification {
    capacity_reservation_preference = "open"
  }
  cpu_options {
    amd_sev_snp      = null
    core_count       = 1
    threads_per_core = 2
  }
  credit_specification {
    cpu_credits = "standard"
  }
  enclave_options {
    enabled = false
  }
  maintenance_options {
    auto_recovery = "default"
  }
  metadata_options {
    http_endpoint               = "enabled"
    http_protocol_ipv6          = "disabled"
    http_put_response_hop_limit = 1
    http_tokens                 = "optional"
    instance_metadata_tags      = "disabled"
  }
  private_dns_name_options {
    enable_resource_name_dns_a_record    = false
    enable_resource_name_dns_aaaa_record = false
    hostname_type                        = "ip-name"
  }
  root_block_device {
    delete_on_termination = true
    encrypted             = true
    iops                  = 3000
    kms_key_id            = "arn:aws:kms:ap-northeast-1:[展開先のAWSID]:key/6b70a3f3-a980-46b5-b5f1-674142a2b2e6"
    tags = {
      Name = "tkg-test-ec2-al2-1"
    }
    throughput  = 125
    volume_size = 50
    volume_type = "gp3"
  }
}

上記コードでterraform plan を実行したところ、下記出力の通りエラー無くimportが行われるようになりました。
こちらで terraform applyを実施するとリソースがtfstateに反映されimportが完了となります。

利用したimportブロックはリソース重複のエラーが以後のplan/applyで発生してしまうため、コメントアウトまたは削除を行います。

$ terraform plan 

~中略~

Plan: 5 to import, 0 to add, 0 to change, 0 to destroy.

$ terraform apply

~中略~

Apply complete! Resources: 5 imported, 0 added, 0 changed, 0 destroyed.

まとめ

コード生成時に出力された警告のとおり、まだ完成された仕組みではないようでしたが、サードパーティ製品を利用せずにコードの生成が可能な点がかなり魅力的な機能だなと思いました。
APIで取得できる値が概ねすべて出力されるようで、リソースによっては手動でのカスタマイズが必要になってくる状況のようです。
また、import不可なリソースも存在しますので、そこは別途準備する必要はあります。(今回の場合、aws_lb_target_group_attachmentは別途足してあげる必要があります)

ですがその上記の様な作業だけで一旦のコード化が可能で、AWSでいうリホストように、ここからカスタマイズ、管理しやすい形になおして行く足がかりになるかなと思います。
ただし、各リソースブロックがどういう動作をしているものか、AWS APIの機能や動作仕様との対比のような Terraformや各Providerへの理解はかなり必要である機能ではあるかなと思います。

テックブログ新着情報のほか、AWSやGoogle Cloudに関するお役立ち情報を配信中!

tkg

2016年入社のインフラエンジニアです。 写真が趣味。防湿庫からはレンズが生え、押入れには機材が生えます。 2D/3DCG方面に触れていた時期もありました。

Recommends

こちらもおすすめ

Special Topics

注目記事はこちら