TECHSCORE BLOG

クラウドCRMを提供するシナジーマーケティングのエンジニアブログです。

Terraformで既存リソースを簡単コード化

以前、Happy Terraforming!で紹介させていただきましたが、弊社ではTerraformでのIaC(Infrastructure as Code)化を行っており、インフラの構築・運用をすべてコード化しています。しかし、以下のような理由でコード化されないリソースを作成してしまうことがあります。

  • 時間的な問題や検証目的でマネージメントコンソールから作成
  • Terraform導入前に作成したリソースがある
  • AWSが自動的に作成するリソース

ここでは、上記のような理由で、Terraform管理外で作成したEC2インスタンスをコード化する手順を紹介します。なお、今回の手順はTerraform 1.7.4 / AWS provider 5.40.0を使用しています。

既存リソースをコード化する方法

Terraformで既存リソースをコード化するには、手動作成と自動生成の2種類があります。

  • 手動作成

既存リソースと同じ内容のコードを手作業で作成する方法で、コード作成後、importコマンドimportブロックを使ってコードと既存リソースを紐づけます。コードと既存リソースに差分があると紐づけできないので、差分が解消できるまで手作業でコードを修正する必要があります。importコマンドが提供されたTerraform 0.7.0より実施できます。

  • 自動生成

import ブロックと terraform plan の generate-config-out オプションを使ってコードを自動生成します。コードを手作業で書く必要がないので簡単にコード化できるメリットがあります。しかし、本記事執筆時点では実験的な機能として提供されており、今後仕様が変更される可能性があります。Terraform 1.5.0から提供された新しい機能です。

この記事では、後者の自動生成について紹介します。

EC2インスタンスをコード化する

1. コード化対象インスタンスの確認

インポート対象の EC2 のインスタンスID を確認します。今回の対象インスタンスは以下のIDです。

項目
インスタンスID i-1234567890abcdef0

2. importブロックの作成

早速、上記のインスタンスをコード化します。ここでは、すでに上記インスタンスを稼働しているアカウントに対して作業できるように、Terraformの初期設定を行い「terraform init」まで完了しているものとします。

まず、コード化するには既存リソースをインポートするためのimportブロックの作成を行います。今回は、以下のようにimportブロックのみを記述したimport.tfを作成します。

import {
  to = aws_instance.uchida_test
  id = "i-1234567890abcdef0"
}

それぞれ、以下の意味となっています。

  • to:どのリソースにインポートするか指定します。今回は、aws_instanceのuchida_testというリソースにインポートします。

  • id:Terraformがインポートするリソースを特定するために使用する一意な値です。IDに何を指定すべきかは、インポートするリソースのプロバイダーのドキュメントで確認できます。今回はaws_instanceなので、インスタンスIDを指定します。

3. コード生成

次にコードを生成します。今回はec2.tfというファイルにコードを生成するので、generate-config-out オプションに ec2.tf を渡します。ファイルはterraformが新規で生成するため、存在しないファイル名を渡す必要があります。

$ terraform plan -generate-config-out=ec2.tf
aws_instance.uchida_test: Preparing import... [id=i-1234567890abcdef0]
data.aws_caller_identity.self: Reading...
aws_instance.uchida_test: Refreshing state... [id=i-1234567890abcdef0]
data.aws_caller_identity.self: Read complete after 0s [id=XXXXXXXXXXX]

Planning failed. Terraform encountered an error while generating this plan.


│ Warning: Config generation is experimental

│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.


│ Error: Conflicting configuration arguments

│   with aws_instance.uchida_test,
│   on ec2.tf line 14:
(source code not available)

│ "ipv6_address_count": conflicts with ipv6_addresses


│ Error: Conflicting configuration arguments

│   with aws_instance.uchida_test,
│   on ec2.tf line 15:
(source code not available)

│ "ipv6_addresses": conflicts with ipv6_address_count

エラーが発生しましたが、コードの生成は成功しています。エラーの内容を見てみると、1件のWarningと2件のErrorが発生しました。Warningは実験的な機能であることの通知なので問題ありません。Errorはコードにコンフリクトが発生しているというものです。生成されたコードを抜粋してみます。

      1 # __generated__ by Terraform
      2 # Please review these resources and move them into your main configuration files.
      3
      4 # __generated__ by Terraform
      5 resource "aws_instance" "uchida_test" {
      6   ami                                  = "ami-1234567890abcdef0"
      7   associate_public_ip_address          = true
      8   availability_zone                    = "ap-northeast-1a"
(snip)
     19   ipv6_address_count                   = 0
     20   ipv6_addresses                       = []
(snip)
     77     throughput            = 125
     78     volume_size           = 8
     79     volume_type           = "gp3"
     80   }
     81 }

確かに、19行目にipv6_address_count、20行目にipv6_addressesが生成されています。この2つは、どちらか一方定義するだけで良いものなので、コンフリクトします。

このように、コード生成は完璧ではないため、エラーが発生したらそれに応じて適切にコードを修正する必要があります。今回はコンフリクトしているコードの片方(ipv6_address_count)を削除しました。修正の差分は以下の通りです。

@@ -16,7 +16,6 @@
   iam_instance_profile                 = null
   instance_initiated_shutdown_behavior = "stop"
   instance_type                        = "t2.nano"
-  ipv6_address_count                   = 0
   ipv6_addresses                       = []
   key_name                             = null
   monitoring                           = false

削除したらterraform planを実行します。

$ terraform plan
aws_instance.uchida_test: Preparing import... [id=i-1234567890abcdef0]
data.aws_caller_identity.self: Reading...
aws_instance.uchida_test: Refreshing state... [id=i-1234567890abcdef0]
data.aws_caller_identity.self: Read complete after 0s [id=XXXXXXXXXXX]

Terraform will perform the following actions:

  # aws_instance.uchida_test will be imported
    resource "aws_instance" "uchida_test" {
        ami                                  = "ami-1234567890abcdef0"
        arn                                  = "arn:aws:ec2:ap-northeast-1:XXXXXXXXXXX:instance/i-1234567890abcdef0"
        associate_public_ip_address          = true
(snip)
Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.

────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

エラーは解消され、Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.という結果でした。1つのリソースがインポートされ、他に変更はないので想定通りです。

4. インポート

続いてterraform applyでインポートします。

$ terraform apply
aws_instance.uchida_test: Preparing import... [id=i-1234567890abcdef0]
data.aws_caller_identity.self: Reading...
aws_instance.uchida_test: Refreshing state... [id=i-1234567890abcdef0]
data.aws_caller_identity.self: Read complete after 0s [id=XXXXXXXXXXX]

Terraform will perform the following actions:

  # aws_instance.uchida_test will be imported
    resource "aws_instance" "uchida_test" {
        ami                                  = "ami-1234567890abcdef0"
        arn                                  = "arn:aws:ec2:ap-northeast-1:XXXXXXXXXXX:instance/i-1234567890abcdef0"
        associate_public_ip_address          = true
(snip)

Plan: 1 to import, 0 to add, 0 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.uchida_test: Importing... [id=i-1234567890abcdef0]
aws_instance.uchida_test: Import complete [id=i-1234567890abcdef0]

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

planで問題ないことが確認できたのですんなりapplyできました。これにて既存リソースをコード化してTerraform管理下に置くことができました。

なお、リソースのインポートが完了したらimport.tfは不要なので削除しても大丈夫です。

最後に

紹介したように比較的短いステップで既存リソースをコード化することができました。今回のように生成されたコードにエラーがある場合もありますが、それでも自分でコードを作成してインポートするよりはるかに楽でしょう。生成されるコードがよりよくなるのは今後に期待ですね。ぜひコードの自動生成を試してみてください。

内田 康介(ウチダ コウスケ)

気づいたらインフラエンジニア歴が10年超えてました。田舎のゆったりした空気を感じながらリモートワーク中。


シナジーマーケティング株式会社では一緒に働く仲間を募集しています。