Closed24

Terraformで困っている部分

ピン留めされたアイテム
ユータユータ

2/14最新時点の苦戦ポイント

  • 環境差分によって生じるリソース数の違いを共通モジュールでインポートする方法

環境差分によるインポートリソース数が異なる場合について

VPCやIGWなど異なる環境でも数が変わらないものは必要な分だけリソースを宣言して宣言したリソースに対してimportをすれば問題はない。
環境差異によるパラメータは環境ディレクトリ側で変数化すればOK

main.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = var.cidr_block
  tags = {
    Name      = "${var.Tag_Name}-vpc"
    Terraform = "True"
  }
}

問題はリソースの数が異なる場合だ。

  • 本番環境
    • Subnet:6
    • NAT:3
  • 検証環境
    • Subnet:4
    • NAT:1

リソース数が異なるので上記のようにリソースを全部書きだそうとすると検証環境側に余計なリソースを新規作成してしまう問題点がある。

この問題を解決する方法としてfor_eachを使った対象リソースのループ処理で解決できるのはないかとリーダーからもアドバイスを頂き検証してみた。
https://www.terraform.io/language/meta-arguments/for_each

ディレクトリ構成

現在のディレクトリ構成を整理する。

$ tree
.
├── env
│   ├── prd
│   │   ├── main.tf
│   │   └── vpc.tf
|   |    ~~~~~~~~~~~~~~~~~~~~~ # リソース単位でtfファイルが存在
│   └── sandbox
│       ├── main.tf
│       └── vpc.tf
|         ~~~~~~~~~~~~~~~~~~~~~ # リソース単位でtfファイルが存在
|└── modules
|   └── vpc
|        ├── main.tf
|        ├── outputs.tf
|        └── variables.tf
|    ~~~~~~~~~~~~~~~~~~~~~ # リソース単位でディレクトリが存在

modules/vpc/main.tf

main.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = var.cidr_block
  tags = {
    Name      = "${var.Tag_Name}-vpc"
    Terraform = "True"
  }
}

resource "aws_internet_gateway" "terraform-igw" {
  vpc_id = aws_vpc.terraform-vpc.id
  tags = {
    Name      = "${var.Tag_Name}-igw"
    Terraform = "True"
  }
}

resource "aws_subnet" "terraform-subnet" {
  for_each = toset(var.subnet_list)

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet[0]
  tags = {
    Name      = "${var.Tag_Name}-${var.subnet_type[0]}-subnet-a"
    Terraform = "True"
  }
}

modules/vpc/variables.tf

variables.tf
variable "Tag_Name" {
  type        = string
  description = "Tag"
}

variable "cidr_block" {
  type        = string
  description = "vpcのサブネットです"
}

variable "subnet_list" {
  type        = list(string)
  description = "サブネット一覧"
}

variable "cidr_block-subnet" {
  type        = list(string)
  description = "各サブネットのCIDR範囲"
}

variable "subnet_type" {
  type        = list(string)
  description = "Nameタグ付与に使うパブリックかプライベートサブネットかの使い分け"
}

ポイントになるのがresource aws_subnet.terraform-subnetだ。
for_each = toset(var.subset_list)'と記載しており、本番、検証でそれぞれ異なるサブネットIDの一覧を格納する配列変数をしている。 env/<環境名>/<リソース名.tf>`にパラメータ変数を入れていて例えば検証環境のVPCリソース関連のパラメータ変数をこのようにしている。

env/sandbox/vpc.tf

vpc.tf
module "terraform-vpc" {
  source                 = "../../modules/vpc"
  Tag_Name               = "dev"
  cidr_block             = "172.26.0.0/16"
  cidr_block-subnet      = ["172.26.10.0/24","172.26.11.0/24","172.26.20.0/24","172.26.21.0/24"]
  subnet_type            = ["public", "private"]
  subnet_list = ["subnet-092b81ef7effd9b7b","subnet-0a0ade8ce186e3fde","subnet-059a56beb4eb17290","subnet-07d46fa561aa72db7"]
}

ここでsandbox側のVPCディレクトリのterraform state listは以下の通りである。

$ terraform state list
module.terraform-vpc.aws_internet_gateway.terraform-igw
module.terraform-vpc.aws_subnet.terraform-subnet["subnet-059a56beb4eb17290"]
module.terraform-vpc.aws_subnet.terraform-subnet["subnet-07d46fa561aa72db7"]
module.terraform-vpc.aws_subnet.terraform-subnet["subnet-092b81ef7effd9b7b"]
module.terraform-vpc.aws_subnet.terraform-subnet["subnet-0a0ade8ce186e3fde"]
module.terraform-vpc.aws_vpc.terraform-vpc

この状態でterraform planを実行するとこうなる

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # module.terraform-vpc.aws_subnet.terraform-subnet["subnet-059a56beb4eb17290"] must be replaced
-/+ resource "aws_subnet" "terraform-subnet" {
      ~ arn                             = "arn:aws:ec2:ap-northeast-1:XXXXXXXXXXXXXX:subnet/subnet-059a56beb4eb17290" -> (known after apply)
      ~ availability_zone               = "ap-northeast-1a" -> (known after apply)
      ~ availability_zone_id            = "apne1-az4" -> (known after apply)
      ~ cidr_block                      = "172.26.20.0/24" -> "172.26.10.0/24" # forces replacement
      ~ id                              = "subnet-059a56beb4eb17290" -> (known after apply)
      + ipv6_cidr_block_association_id  = (known after apply)
      - map_customer_owned_ip_on_launch = false -> null
      ~ owner_id                        = "XXXXXXXXXXX" -> (known after apply)
      ~ tags                            = {
          ~ "Name"      = "terraform-dev-private-subnet-a" -> "terraform-dev-public-subnet-a"
            # (1 unchanged element hidden)
        }
      ~ tags_all                        = {
          ~ "Name"      = "terraform-dev-private-subnet-a" -> "terraform-dev-public-subnet-a"
            # (1 unchanged element hidden)
        }
        # (3 unchanged attributes hidden)

      - timeouts {}
    }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Plan: 3 to add, 0 to change, 3 to destroy.

既存サブネットを3個消して新規に3個サブネットを作成しようとしていることがわかる。
細かく見るとcidr_block部分がすべて同じCIDRになっている。
moduleディレクトリ直下のmain.tfでCIDRを配列で決め打ちしていることが原因だからである。

main.tf
resource "aws_subnet" "terraform-subnet" {
  for_each = toset(var.subnet_list)

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet[0]    #### 配列変数で指定していることが原因
  tags = {
    Name      = "${var.Tag_Name}-${var.subnet_type[0]}-subnet-a"
    Terraform = "True"
  }
}

実現したいこととしてはcidr_blockの変数部分をループで回せるようにしたい。
(env/sandbox/vpc.tf部分の変数subnet_listの配列部分)
こちらもfor_eachで回せればと思ったが、for_eachを再度使うことができず、他の記事で紹介されているcountもfor_eachと組み合わせ使うことはできなかった。

│ Error: Invalid combination of "count" and "for_each"
│
│   on ../../modules/vpc/sg.tf line 19, in resource "aws_subnet" "terraform-subnet":19:   for_each = toset(var.subnet_list)
│
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be created.

結論

for_eachをすでに使っている状態で他の変数の配列をループ処理させる方法が今のところわからず考え中

ユータユータ

解決

サブネットリソースをパブリックとプライベートに分けて、AZの値を変数化することでやりたかったことが実現できた。

上のmain.tfでは1つのaws_subnetリソースでパブリック、プライベートそれぞれのサブネットリソースをインポートしようとしたが、これを2つに分けた。
modules/vpc/main.tf

main.tf
resource "aws_subnet" "terraform-public-subnet" {
  for_each = var.public-AZ

  vpc_id            = aws_vpc.terraform-vpc.id
  cidr_block        = each.value
  availability_zone = "ap-northeast-1${each.key}"

  tags = {
    Name      = "terraform-${var.Tag_Name}-public-subnet-${each.key}"
    Terraform = "True"
  }
}

resource "aws_subnet" "terraform-private-subnet" {
  for_each = var.private-AZ

  vpc_id            = aws_vpc.terraform-vpc.id
  cidr_block        = each.value
  availability_zone = "ap-northeast-1${each.key}"

  tags = {
    Name      = "terraform-${var.Tag_Name}-private-subnet-${each.key}"
    Terraform = "True"
  }
}

modules/vpc/variables.tf

variables.tf
variable "public-AZ" {
  type        = map(string)
  description = "パブリックサブネットのAZ識別子とCIDRを紐付けたMAP変数"
}

variable "private-AZ" {
  type        = map(string)
  description = "プライベートサブネットのAZ識別子とCIDRを紐付けたMAP変数"
}

これでenv側の変数はこのように値を入れていきます。

env/sandbox/vpc.tf

vpc.tf
module "terraform-vpc" {
  source     = "../../modules/vpc"
  Tag_Name   = "dev"
  cidr_block = "172.26.0.0/16"
  public-AZ  = { a = "172.26.10.0/24", c = "172.26.11.0/24" }  #AZの識別子と対応するサブネットCIDRを選択   
  private-AZ = { a = "172.26.20.0/24", c = "172.26.21.0/24" }
}

このようにしてインポートする際に配列の要素を指定します。
terraform import 'module.terraform-vpc.aws_subnet.terraform-public-subnet["a"]' subnet-XXXXXXXXX

$ terraform state list
module.terraform-vpc.aws_subnet.terraform-private-subnet["a"]
module.terraform-vpc.aws_subnet.terraform-private-subnet["c"]
module.terraform-vpc.aws_subnet.terraform-public-subnet["a"]
module.wadatsumi-vpc.aws_subnet.wadatsumi-public-subnet["c"]
$ terraform state  show 'module.terraform-vpc.aws_subnet.terraform-public-subnet["a"]'
# module.terraform-vpc.aws_subnet.terraform-public-subnet["a"]:
resource "aws_subnet" "terraform-public-subnet" {
    availability_zone               = "ap-northeast-1a"
    availability_zone_id            = "apne1-az4"
    cidr_block                      = "172.26.10.0/24"
}
# 一部リソース省略しています

cidr_blockのeach_valueは変数でPublic-AZの値で、availability_zoneに含まれるeach_keyがキーになります。
インポート時にキーであるaを指定してインポートしましたが、このときにcidr_block部分は変数で対応した値が入るようにしました。
このようにマップで変数設定することで環境ごとに作成するリソースの数が異なっていたとしても同じmoduleを使い回すことができるようになりました。

本番環境例

$ terraform state list
module.terraform-vpc.aws_subnet.terraform-private-subnet["a"]
module.terraform-vpc.aws_subnet.terraform-private-subnet["c"]
module.terraform-vpc.aws_subnet.terraform-private-subnet["d"]
module.terraform-vpc.aws_subnet.terraform-public-subnet["a"]
module.terraform-vpc.aws_subnet.terraform-public-subnet["c"]
module.terraform-vpc.aws_subnet.terraform-public-subnet["d"]

ここまで行けば後はチームで協力して各種リソースをインポートすれば当初やりたかった既存リソースのIaC化は実現できそうです。

スクラップで雑多に書き連ねた内容は記事としてまた改めて投稿しようと思います。

ユータユータ

概要

会社の既存AWSリソースをIaCするという施策に取り組んでいるが、詰まっていてどうしてもわからない部分があって広く多くの人の目に触れて意見を頂きたいと考えて書き記してみました。

実現イメージ図


Terraform IaC構成

弊社では環境ごとにAWSマルチアカウント運用を実施しており、本番環境はアカウントA、検証環境はアカウントBといった感じで分けています。
検証環境の既存リソースをterraformerでインポートした後にハードコーディングされた固定値部分を変数に直し、env/<環境名>/ディレクトリでterraform applyを実行してIaCしようと考えました。
既存リソースのコード化に関しては、以前書いた記事もご覧ください。

検証用にまずはVPCのみをインポートしてみました。
この時VPCディレクトリ内でterraformを実行したところ問題なく既存リソースをIaC化することはできました。

簡易ディレクトリ構成

$ tree
.
├── env
│   ├── production
│   └── sandbox
│       ├── main.tf
│       └── variables.tf
└── vpc
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    ├── terraform.tfstate.backup
    ├── versions.tf
    └── vpc.tf

4 directories, 8 files

vpc.tf

resource "aws_vpc" "terraform-vpc" {
  assign_generated_ipv6_cidr_block = "false"
  cidr_block                       = "10.0.0.0/16"
  enable_classiclink               = "false"
  enable_classiclink_dns_support   = "false"
  enable_dns_hostnames             = "true"
  enable_dns_support               = "true"
  instance_tenancy                 = "default"

  tags = {
    Name      = "Yuta-VPC2"
    Terraform = "True"
  }

  tags_all = {
    Name      = "Yuta-VPC"
    Terraform = "True"
  }
}


既存リソース

$ ls
main.tf  outputs.tf  provider.tf   terraform.tfstate.backup  versions.tf  vpc.tf
$ terraform plan
aws_vpc.terraform-vpc: Refreshing state... [id=vpc-0618354c8dd897278]

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.

この次にenv/sandboxディレクトリ内でvpcのディレクトリをdataリソースを使って参照し、既存リソースを参照できるか確認してみました。

main.tf

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

terraform {
  backend "s3" {
    bucket = "XXXXXXXXXXXXX"
    key    = "env/sandbox/terraform.tfstate"
    region = "ap-northeast-1"
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.70.0"
    }
  }
}

variables.tf

data "terraform_remote_state" "yuta-vpc" {
  backend = "s3"
  config = {
    bucket = "XXXXXXXXXXXXX"
    key    = "vpc/terraform.tfstate"
    region = "ap-northeast-1"
  }

}

module "vpc" {
  source = "../../vpc"
}
$ ls
main.tf  variables.tf
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.vpc.aws_vpc.terraform-vpc will be created
  + resource "aws_vpc" "terraform-vpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = false
      + enable_classiclink_dns_support   = false
      + enable_dns_hostnames             = true
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name"      = "Yuta-VPC2"
          + "Terraform" = "True"
        }
      + tags_all                         = {
          + "Name"      = "Yuta-VPC2"
          + "Terraform" = "True"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.
╷
│ Warning: Backend configuration ignored
│
│   on ../../vpc/main.tf line 2, in terraform:
│    2:     backend "s3" {
│
│ Any selected backend applies to the entire configuration, so Terraform expects provider configurations only in the root module.
│
│ This is a warning rather than an error because it's sometimes convenient to temporarily call a root module as a child module for testing purposes, but this backend
│ configuration block will have no effect.
╵

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

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.

env/sandboxディレクトリでterraform planを実行すると既存リソースではなく新規リソースとして作成しようとしています。

resourceがmoduleに変更されていますので、vpcディレクトリのstate情報をterraform state mvで変更してもう一度実行してみました。


## vpcディレクトリ
$ terraform state list
aws_vpc.terraform-vpc
$ terraform state mv aws_vpc.terraform-vpc module.vpc.aws_vpc.terraform-vpc
Move "aws_vpc.terraform-vpc" to "module.vpc.aws_vpc.terraform-vpc"
Successfully moved 1 object(s).

## env/sandboxディレクトリ
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.vpc.aws_vpc.terraform-vpc will be created
  + resource "aws_vpc" "terraform-vpc" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = false
      + enable_classiclink_dns_support   = false
      + enable_dns_hostnames             = true
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "default"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name"      = "Yuta-VPC2"
          + "Terraform" = "True"
        }
      + tags_all                         = {
          + "Name"      = "Yuta-VPC2"
          + "Terraform" = "True"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.
╷
│ Warning: Backend configuration ignored
│
│   on ../../vpc/main.tf line 2, in terraform:
│    2:     backend "s3" {
│
│ Any selected backend applies to the entire configuration, so Terraform expects provider configurations only in the root module.
│
│ This is a warning rather than an error because it's sometimes convenient to temporarily call a root module as a child module for testing purposes, but this backend
│ configuration block will have no effect.
╵

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

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.

tfstate情報を変更したにも関わらず、新規リソースとして見に行ってしまいます。
env/sandboxのtfstateも変更しようとしましたが、env/sandboxディレクトリにはまだtfstateファイルは作成されておらず、こちらは関係ないように思えます。

## env/sandboxディレクトリ
$ terraform state list
No state file was found!

State management commands require a state file. Run this command
in a directory where Terraform has been run or use the -state flag
to point the command to a specific state location.
yu_s_1985yu_s_1985

モジュールの中にtfstateやprovider、backendの設定があるのがちょっと奇妙な感じがしていて、それはモジュールの外側にあるべきではないでしょうか。
そのVPCをモジュールとして使い回すならなおさら。

VPCをの定義ではなくてVPC自体を使いまわしたいのであれば、VPCを環境とは別のところで定義しておいて、dataブロックでvpc idなり何なりを参照するか、いっそそれを固定値として渡すかのほうが見通しが良いように思えます。
今回でいうと環境ごとにVPCを定義したいんだと思いますが。

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpcs
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc

terraform state mvをしても意味がないのは、それはvpcモジュール(として定義しているディレクトリ)の中のtfstateをいじくっているだけであって、env/sandbox配下で参照しているtfstate(未作成なので何も参照していないようですが)はその情報を持っていないので新規作成になるんだと思います。
vpc配下からproviderとbackendの定義を外して、その定義をenv/sandbox配下のtfファイルに持ってきてあげてよしなにやればうまく行きそうな気がします。

…分かりづらかったらすみません。

ユータユータ

ありがとうございます。
少し簡略化した図ですが、構成のイメージとしてリソース自体は各リソースディレクトリ内で定義して、変数の固定値を環境ディレクトリ内のvariables.tfに埋め込みでvpc.tfは共通化しようと考えています。


簡易版

VPC自体を使いまわしたいのであれば、VPCを環境とは別のところで定義しておいて、dataブロックでvpc idなり何なりを参照するか、いっそそれを固定値として渡すかのほうが見通しが良いように思えます。

仰るとおりVPC自体はリソースディレクトリ内で定義し、その情報をdataで環境ディレクトリで呼び出して既存リソースとして管理したいと考えていました。(ここはdata.tfとして別だしも考えています)

既存リソースとしてimportしたリソースはリソースディレクトリのtfstateファイル(vpc/terraform.tfstate)で宣言されているから、環境ディレクトリのtfstateファイル(env/sandbox/tfstate)に書き込もうとすると別のリソース扱いになるのではないかと考えています。

解決するための方法として、環境ディレクトリのtfstateにリソースディレクトリのtfstate情報を移せば実現できるのではないかと考えていまして、、、
これが合っているのかわからないですが、もう少し調べてみます。

yu_s_1985yu_s_1985

何をしようとしているのかようやく理解しました。
結論を言うと、その構成でstagingディレクトリからmoduleでvpcディレクトリを呼び出していることが間違いです。
その書き方だとvpcディレクトリのコードをvariables.tfで読み取り、cidr_vpcとenvを変数として渡してVPCを構築しようとしていることになるのでplanをすると新規作成になります。
moduleはあくまでコードを使い回すものです。
あと、おそらくdata sourceの使い方を誤解されています。

あくまで自分ならこうする、というものですが
vpcディレクトリのほうで既にVPCを作成したのであれば、そっちのtfstateは独立させておきます。
stagingディレクトリでvpcディレクトリのtfstateを読み込もうとはせず、vpcディレクトリで作成したvpc idを変数として直接渡すか、data sourceを使ってvpc idを検索してその値を必要なところに渡してやります。
vpcとvpcs(IDを取得したいなら面倒ですが後者)のdata resourceの使い方は先にはりました

また、記載のTerraformではdata sourceでterraform_remote_stateを取得していますが、取得したterraform_remote_stateの値をどこにも使用していないので無意味な記載になってしまっています。
自分もterraform_remote_stateはあまり使わないので、使い方の説明はこの記事に任せます。
https://dev.classmethod.jp/articles/how-to-use-terraform-remote-state/
※古めの記事なので、0.11以前の書き方で書かれています。

また、data sourceについてはこの辺を見てみてください。
https://www.terraform.io/language/data-sources
https://hyoublog.com/2020/08/14/terraform-data-sources/

分かりづらい部分がありましたら、言っていただければ頑張って補足します…。

ユータユータ

ご丁寧に説明いただきありがとうございます。
まだ私自身Terraformの理解が完全に追いついていないのですが、

stagingディレクトリからmoduleでvpcディレクトリを呼び出していることが間違いです。
こちらは環境ディレクトリ内からterraform applyを実行するとvpcのリソースを読み込むため環境ディレクトリ内で新規リソースが作成から新規作成されると理解しました。

環境ディレクトリ内でterraform applyを実行する場合、moduleresoureceは使わず、dataリソースを配置してdata.tfファイル内でVPCディレクトリで作成したVPC-IDを受け取るようにして、variables.tfで変数を指定し渡すということでしょうか?

data "aws_vpcs" "terraform-vpc" {}

data "aws_vpc" "terraform-vpc" {
  count = length(data.aws_vpcs.terraform-vpc.ids)
  id    = tolist(data.aws_vpcs.terraform-vpc.ids)[count.index]
}

output "terraform-vpc"{
    value = data.aws_vpcs.terraform-vpc
}

dataリソースの理解が間違っているように思えるので昔Kindleで手に入れたTerraform本で土日復習してみます。
https://nextpublishing.jp/book/10983.html

yu_s_1985yu_s_1985

こちらは環境ディレクトリ内からterraform applyを実行するとvpcのリソースを読み込むため環境ディレクトリ内で新規リソースが作成から新規作成されると理解しました。

意図しているところが合ってるかわかりませんが、読み込んでるのはコードだけであってリソースとはちょっと違うと思います。リソースの情報を持ってるのはtfstateなので。

data "aws_vpcs" "terraform-vpc" {}

これだけだとVPCを何も絞り込みできていませんので全てのVPCを引っ張ってきてしまいます。
それだと困るならtagsやfilterで適切に条件を設定して目的のVPCの情報を取ってこれるようにしてください。
これはcliで言うところのdescribe-vpcsにあたると考えるとイメージしやすいかと。
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-vpcs.html
ドキュメントのサンプルでは全てのVPCの情報を取得して全てにflow_logの設定をしていますが、ここでやりたいことはそうではないと思います。

"aws_vpc"(≠aws_vpcs)のほうは、VPCのIDを渡してVPCの詳細情報を取得するのに使います。
このようにdata sourceを使うことによってAWS上の既存リソースの情報を取得することができます。
使うのにVPCのIDが必要なため、当然のことながらVPC IDを検索するのには使えません。

variables.tfで変数を指定し

これは別にvariables.tfでなくても、同ディレクトリ内の同階層のtfファイルならどこでも良いです。
あと、同ディレクトリ同階層の中で使うならoutputは必要ないです。
outputは戻り値のようなもので、Module内で設定すればModule外にその値を受け渡せますし、普通にModuleの外に書くとterraform apply実行時にその値を表示します。

そのあたりも実践Terraformに恐らく記載していると思うのでよく読んでみてください。

ユータユータ

試したこと

https://www.karakaram.com/moving-terraform-resources-to-another-tfstate-file/
こちらの記事を参考にリソースディレクトリ内で作成されたtfstate情報を環境ディレクトリ配下のtfstateに移せばどうなるだろうと検証してみた。

## VPCリソースディレクトリ
$ terraform state list
aws_vpc.terraform-vpc
$ terraform state mv -state-out=from.tfstate aws_vpc.terraform-vpc aws_vpc.terraform-vpc
Move "aws_vpc.terraform-vpc" to "aws_vpc.terraform-vpc"
Successfully moved 1 object(s).

## 環境ディレクトリ
$ terraform state pull > to.tfstate
$ ls
main.tf  to.tfstate  variables.tf
$ terraform state mv -state=../../vpc/from.tfstate -state-out=to.tfstate aws_vpc.terraform-vpc aws_vpc.terraform-vpc
Move "aws_vpc.terraform-vpc" to "aws_vpc.terraform-vpc"
Successfully moved 1 object(s).
$ terraform state push to.tfstate
$ terraform state list
aws_vpc.terraform-vpc

$ terraform plan
aws_vpc.terraform-vpc: Refreshing state... [id=vpc-XXXXXXXXXXXXXXXXXXX]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
~~~~~~~~~

結果は変わらず、新規作成しようとしている。
リソースがモジュール化されているので移動させたtfstate情報をモジュール化したいのだが、どうやればいいのか行き詰まっています。

ユータユータ

試したこと

頂いたアドバイスと読んだ本をもとに環境ディレクトリでVPCディレクトリのリソースIDを取得することができた。

data "aws_vpcs" "sandbox" {

  tags = {
    Terraform = "True"
  }

}

data "aws_vpc" "sandbox" {
  count = length(data.aws_vpcs.sandbox.ids)
  id    = tolist(data.aws_vpcs.sandbox.ids)[count.index]
}

# 確認出力
output "sandbox" {
  value = data.aws_vpcs.sandbox.ids
}
$ terraform apply

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.

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

Outputs:

sandbox = toset([
  "vpc-06e88241aaea0ee4e",
])

ただここからどうやって取得したVPC IDを他のところに渡して更新作業を行なえばいいかで手詰まり中
本ではWorkSpacesの本番事例は少ないと記述されていたが、出版されてから数年経過しておりいくつか他社事例もあったのでWorkSpacesによる事例のほうがいいのかな?

yu_s_1985yu_s_1985

記載されている例でもaws_vpcsで取得した情報をaws_vpcに渡していますし、なによりoutputのvalueに対してidsを渡していますよね。
それを他のresourceやmoduleに対しても渡してやればいいだけです。
ただ、aws_vpcsで取得するidsはlistであることに注意してください。性質上、複数の値を返すのでlistを返すようです。
listから個別の要素を取得するにはelement関数を使う他、
例えば
data.aws_vpcs.sandbox.ids[0]みたいな書き方で、何番目の要素を取得するか指定できたはずです。(これは最初の要素を指定した例)

参考
https://www.terraform.io/language/functions/element
https://www.terraform.io/language/expressions/splat

Terraformのドキュメントには型の情報が一切書いてないので、Terraformのドキュメントだけ読んでても分かりづらいのは確かです…。

ここまではvpcディレクトリ下で作成したVPCは独立させて他からは変更を加えない前提で話していましたが、もしやりたいことがenv/sandbox配下に既に作成したVPCの管理を移したい、ということなのであればやることは変わってきます。

ユータユータ

私の中のイメージですと、最初の構成図でも示したディレクトリ構成図にして

  1. 検証環境からリソースをインポート
  2. /env/{環境}ディレクトリからそれぞれ変数だけを変更
  3. 変更事項があったら/env/{環境}/ディレクトリ配下でterraform applyを実行しようと考えていました。


ディレクトリ構成図

ただ色々と検証してみて考え始めたのですが、既存リソースをインポートしている時点で別のAWSアカウントである本番環境リソースを同じリソースで管理するのはできないのではないかなと思い始めました。

後述でterraformerのインポートもうまくいかず悩んでいますが、インポート時点でtfstateの情報は検証環境のリソースを管理しているので当初私がやりたかったことは難しいのではないかと考えています。

そうなると最終的なディレクトリ構成図はこうなるのではないかと思います。

修正ディレクトリ構成図
ただこれは環境ごとにリソースファイルを作ることになり、土日に読んだ実践本でもIaCのアンチパターンであることは理解しています。
恥ずかしながら色々と頭の中で混乱していて明日辺りに一度リーダーに相談してみようと思います。

yu_s_1985yu_s_1985

やりたいことはわかりました。
自分も前者のイメージで良いと思いますが、アプローチが間違ってると思います。

やっぱりModuleの使い方を誤っています。
前述のとおり、Moduleで使い回せるのは記載したコードのみであって、tfstateをModuleとして使い回すことはできません。

Terraformerに引っ張られすぎているのが混乱の原因の一つだと思います。
TerraformerではtfstateとTerraformのコードの双方を出力してくれますが、Terraformerで出力したtfstateは結局捨てたほうが早い気がしますね。

Terraformerのことは忘れて一旦ディレクトリ構成だけ見ると、Module用としてALB、EC2、ECSのディレクトリを作っておいてenv/{環境}ディレクトリの中からModuleとして../../ALB../../EC2../../ECSを参照するような構成を想定してるかと思います。
必要なのは「env/{環境}ディレクトリ配下でterraformを実行した時に生成されるtfstate上で、既存リソースを管理すること」になります。
図中には「リソースデータはterraformerもしくはterraform importで取得」と書いてありますが、Moduleとして利用する各ディレクトリに必要なのはテンプレートとして使いまわしたいTerraformのコードだけであって、既存リソースの情報は含めません。
図だと、providers.tfは全て不要ですし、それは最終的にterraform applyを実行したいところであるenv/{環境}にあるべきものです。

面倒ですがterraformerで作成されたtfstateは一旦捨てて、env/{環境}下でModuleを含めて想定構成を記載し、その上でenv/{環境}でterraform importでModule含めたリソースにtfstateを一致させる、とする必要がありそうです。

terraformerの作成したtfstateをそのまま使いたいなら後者の構成にならざるを得ないと思いますが、ご認識の通りそれはterraformとして効率的な記述とは言えません。

今後この環境をどうしていきたいのでしょうか?
今後もパラメータ変化をさせる可能性が高い、もしくは同じような環境を作る可能性が高いのであれば頑張ってModuleを使った記述にしてterraform importでtfstateをコードに一致させる作業をしてもよいと思います。
ただ、それは結構な工数がかかるのでそこまでするべきかは要検討です。もちろん理想的な構成を整備すべき気持ちはわかるのですが、他にもやるべきことは恐らくあるでしょうし。

デプロイ周りがきっちり整備されていたり、外部環境から直接IPで参照されていない、など条件が揃っていればいっそのこと新しく環境を作り直してしまうというのも一つの手段です。
既に手で作りこんだ環境をTerraformに落とすのは正直かなり面倒な作業なのです…。

ユータユータ

度々アドバイスをいただき本当にありがとうございます。

terraformerで一括インポートは便利でしたが、terraformの理解不足で使い方を間違えている気がしていると感じています。
別の方からのアドバイスも頂いてterraform importでインポートして既存リソースを管理したほうがいい気がしてきました。

今後この環境をどうしていきたいのでしょうか?
今後もパラメータ変化をさせる可能性が高い、もしくは同じような環境を作る可能性が高いのであれば頑張ってModuleを使った記述にしてterraform importでtfstateをコードに一致させる作業をしてもよいと思います。

仰るとおり今IaC目指すリソースをベースに新しいプロダクトを作る未来も見据えてまして、かつ社内のIaCノウハウを溜めるという目的も兼ねてIaCを推進しています。

規模自体はかなり小さくいくつかのリソースをIaCすることを目指しているので一旦頂いたアドバイスをもとに terraform importからもう一度挑戦してみようと思います。

ユータユータ

WorkSpaces検証

参考記事↓
https://dev.classmethod.jp/articles/how-to-use-terraform-workspace/#toc-3

Workspacesを使って環境別に切り替えられないか調べてみた。

インポート前にinit.tfを作成し、terraformerを実行できる準備は済ませておく

init.tf
terraform {
  required_version = ">= 1.0"
}

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

terraform {
  required_providers {
    aws = {
      version = "~> 3.70.0"
    }
  }
}
$ terraform workspace new sandbox
$ terraformer import aws -r vpc --filter="Name=tags.Terraform;Value=True"
$ ls -R
.:
generated  init.tf  terraform.tfstate.d

./generated:
aws

./generated/aws:
vpc

./generated/aws/vpc:
outputs.tf   terraform.tfstate                    terraform.tfstate.backup  vpc.tf
provider.tf  terraform.tfstate.1644225261.backup  versions.tf

./terraform.tfstate.d:
sandbox

./terraform.tfstate.d/sandbox:

この後はSandbox側のVPCリソースのTerraformバージョンを引き上げたりしたり何なりして調整

次に本番環境のworkspacesを作成

$ terraform workspace new prd
$ terraform workspace list
  default
  prd
* sandbox

また別アカウントの本番環境のVPCリソースをterraformerでインポートするためassume_roleリソースを追加する。

init.tf
terraform {
  required_version = ">= 1.0"
}

provider "aws" {
  region = "ap-northeast-1"
  assume_role {
    role_arn  =  "arn:aws:iam::XXXXXXXXXXXXXXXXX:role/Terraform-Prd-Switch"
  }
}

terraform {
  required_providers {
    aws = {
      version = "~> 3.70.0"
    }
  }
}

次にterraformer実行時にプロファイルを変更して実行したが、本番リソースを見ずに検証環境のリソースをインポートしようとしてエラーになってしまった。

$ terraformer import aws -r vpc --filter="Name=tags.Terraform;Value=True" --profile=Prd --regions=ap-northeast-1
2022/02/07 09:54:46 aws importing region ap-northeast-1
2022/02/07 09:54:49 aws importing... vpc
2022/02/07 09:54:49 aws done importing vpc
2022/02/07 09:54:49 Number of resources for service vpc: 7
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-06f6317461965de6c
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-082a3ddd59c79bec0
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-0e810cd22c713c246
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-0fcaae8cac1aa1610
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-06e88241aaea0ee4e
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-0b34330eea1cee3c6
2022/02/07 09:54:49 Refreshing state... aws_vpc.tfer--vpc-d2b8a0b5
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-0fcaae8cac1aa1610
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-06e88241aaea0ee4e
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-082a3ddd59c79bec0
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-d2b8a0b5
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-0b34330eea1cee3c6
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-0e810cd22c713c246
2022/02/07 09:54:49 ERROR: Read resource response is null for resource aws_vpc.tfer--vpc-06f6317461965de6c
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-06f6317461965de6c
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-082a3ddd59c79bec0
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-0e810cd22c713c246
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-0fcaae8cac1aa1610
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-06e88241aaea0ee4e
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-0b34330eea1cee3c6
2022/02/07 09:54:49 ERROR: Unable to refresh resource tfer--vpc-d2b8a0b5
2022/02/07 09:54:49 Filtered number of resources for service vpc: 0
2022/02/07 09:54:49 aws Connecting....
2022/02/07 09:54:49 aws save vpc
2022/02/07 09:54:49 aws save tfstate for vpc

terraformerのGitHubを確認してもプロファイルで複数環境のリソースをインポートできる記述があるのにできない事象でつまづいている
https://github.com/GoogleCloudPlatform/terraformer/blob/master/docs/aws.md

ここまで現在わからない部分

  • 環境ディレクトリから変数を渡して環境別の使い分け
  • 別アカウントのリソースインポート方法

一つのTerraformディレクトリで複数環境の使いまわし方法が全然うまくいかない状況。

ユータユータ

Workspaces

今回の運用では使わない方向で進める

理由

  • 社内に詳しい人がいない
  • 本番運用の事例がディレクトリ別リソース管理と比べて少ない
  • workspaceのselect忘れで環境を間違えるリスクがある

今の所はリソースごとにディレクトリを切って環境ディレクトリで変数をもたせるようにする。

riddle_tecriddle_tec

riddle(in twitter) です。
自分ならこうしますというのを書いておきます。

まず目指す最終構成はこれです。
modules に定義を書き、environments から参照します。

.
├── environments
│  ├── production
│  │  ├── alb.tf
│  │  ├── ec2.tf
│  │  ├── ecs.tf
│  │  ├── main.tf
│  │  └── vpc.tf
│  └── staging
│     └── 省略.tf
└── modules
   ├── alb
   │  └── 省略.tf
   ├── ec2
   │  └── 省略.tf
   ├── ecs
   │  └── 省略.tf
   └── vpc
      ├── main.tf
      ├── outputs.tf
      └── variables.tf

modules/vpc

main.tf

resource "aws_vpc" "terraform-vpc" {
  assign_generated_ipv6_cidr_block = "false"
  cidr_block                       = var.cidr_block
  enable_classiclink               = "false"
  enable_classiclink_dns_support   = "false"
  enable_dns_hostnames             = "true"
  enable_dns_support               = "true"
  instance_tenancy                 = "default"
}

variables.tf

variables "cidr_block" {
  type = string
  description = "vpc のサブネットです"
}

environments/production

main.tf

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

terraform {
  backend "s3" {
    bucket = "XXXXXXXXXXXXX"
    key    = "env/sandbox/terraform.tfstate"
    region = "ap-northeast-1"
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.70.0"
    }
  }
}

vpc.tf

module "vpc" {
  source = "../../modules/vpc"
  cidr_block = "192.168.0.0/24"
}

ここまでの持っていき方

module の作成

まず複数の環境に別々の vpc があるとのことなので、これを作るための module を作成する必要があります。module とはいってもただの terraform resource の集合体です。

まずは適当な作業用のディレクトリを作り、そこに移動します。
その上で staging/vpc.tfproduction/vpc.tf を作ります。

以下の手順を参考に staging/vpc.tfproduction/vpc.tf に各環境の設定をインポートします。


続いて、この2つのファイルを比較しながら共通部分と独自部分を自分で整理します。
例えば以下の場合はcidr_block が違いますよね。

staging

resource "aws_vpc" "terraform-vpc" {
  assign_generated_ipv6_cidr_block = "false"
  cidr_block                       = "192.168.0.0/24"
  enable_classiclink               = "false"
  enable_classiclink_dns_support   = "false"
  enable_dns_hostnames             = "true"
  enable_dns_support               = "true"
  instance_tenancy                 = "default"
}

production

resource "aws_vpc" "terraform-vpc" {
  assign_generated_ipv6_cidr_block = "false"
  cidr_block                       = "192.168.1.0/24"
  enable_classiclink               = "false"
  enable_classiclink_dns_support   = "false"
  enable_dns_hostnames             = "true"
  enable_dns_support               = "true"
  instance_tenancy                 = "default"
}

なので modules/vpc/main.tf では以下を定義し

resource "aws_vpc" "terraform-vpc" {
  assign_generated_ipv6_cidr_block = "false"
  cidr_block                       = var.cidr_block
  enable_classiclink               = "false"
  enable_classiclink_dns_support   = "false"
  enable_dns_hostnames             = "true"
  enable_dns_support               = "true"
  instance_tenancy                 = "default"
}

variables.tf でこれを定義します。

variables "cidr_block" {
  type = string
  description = "vpc のサブネットです"
}

各環境から呼び出す

environments/staging/vpc.tf

module "vpc" {
  source = "../../modules/vpc"
  cidr_block = "192.168.0.0/24"
}

environments/production/vpc.tf

module "vpc" {
  source = "../../modules/vpc"
  cidr_block = "192.168.0.0/24"
}

を作成します。

リソースを import する

そしたら最後にenvironments/stagingenvironments/productionでそれぞれ terraform import をして、各環境に存在しているリソースをimportして state を作成します。
※この時、先にenvironments/staging/main.tf を作成し terraform init などを実行しておいてください

ユータユータ

こちらにもコメントいただきありがとうございます。
terraformerは理解不足もあり、terraform importを使って既存リソースをインポートして環境ディレクトリからterraform実行できるように挑戦してみます。

ユータユータ

アドバイスいただき改めてありがとうございます。
教わった構成で検証を行っているのですが、一つ確認したいことがあります。

現在のディレクトリ構成

terraform$ tree
.
├── env
│   ├── prd
│   │   ├── main.tf
│   │   └── vpc.tf
│   └── sandbox
│       ├── main.tf
│       └── vpc.tf
└── modules
    └── vpc
        ├── main.tf
        └── variables.tf

5 directories, 6 files

適当な作業ディレクトリで既存リソースのインポート完了後、moduleの作成を実施しました。
modules/vpc

main.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = var.cidr_block
  tags = {
    Name      = var.Tag_Name
  }
}
variables.tf
variable "cidr_block" {
  type        = string
  description = "vpcのサブネットです"

}

variable "Tag_Name" {
  type        = string
  description = "Tag名"
}

その後環境ディレクトリ配下に移動し、必要リソースを作成しました。

main.tf
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket = "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
    key    = "env/sandbox/terraform.tfstate"
    region = "ap-northeast-1"
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.70.0"
    }
  }
}
vpc.tf
module "terraform-vpc" {
  source = "../../modules/vpc"
  cidr_block = "172.26.0.0/16"
  Tag_Name = "dev"
}

この状態で、env/環境ディレクトリでterraform initを実行しterraform importを実行するとエラーになります。

$ terraform import aws_vpc.terraform-vpc vpc-06e88241aaea0ee4e
Error: resource address "aws_vpc.terraform-vpc" does not exist in the configuration.

Before importing this resource, please create its configuration in the root module. For example:

resource "aws_vpc" "terraform-vpc" {
  # (resource arguments)
}

resourceがないことから生じるエラーですが、試しに環境ディレクトリ内のリソースファイル(ここではvpc.tf)に空のリソースを追記してterraform importを実施しましたが、これだとモジュールリソースを見てくれないように感じます。

vpc.tf
module "vpc" {
  source = "../../modules/vpc"
  cidr_block = "172.26.0.0/16"
  Tag_Name = "waas-dev"
}

resource "aws_vpc" "terraform-vpc" {}
$ terraform plan
aws_vpc.terraform-vpc: Refreshing state... [id=vpc-06e88241aaea0ee4e]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create   #新規作成する
  ~ update in-place

Terraform will perform the following actions:
~~~~~~~~~~~~~~~~~~~~

importするときはresoureceを実行ディレクトリで作成すると上記のようになるのですが、importするときはモジュールディレクトリで実行すればよろしいでしょうか?

riddle_tecriddle_tec

terraform import ADDR ID のように ADDR に terraform でのリソースの位置、 ID に AWS のリソースアドレスを取ります。

environments/prdterraform import aws_vpc.terraform-vpc vpc-06e88241aaea0ee4e を実行した場合、vpc.tf には以下が記載されてないといけません。

environments/prd/vpc.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = "てきとう"
  tags = {
    Name      = "てきとう"
  }
}

しかしこれでは module が使えていないですね。


さて、今回のケースは vpc.tf に module の呼び出しが記載されています。

environments/prd/vpc.tf
module "my-vpc" {  # ←わかりやすさのために名前を変えます
  source = "../../modules/vpc"
  cidr_block = "172.26.0.0/16"
  Tag_Name = "dev"
}

そして module 側はこのようになっています。

module/vpc/main.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = var.cidr_block
  tags = {
    Name      = var.Tag_Name
  }
}

そのためこのときの terraform のリソースアドレス(リソースの位置) は module.my-vpc.aws_vpc.terraform-vpc となります。

よって今回は environments/prdterraform import module.my-vpc.aws_vpc.terraform-vpc vpc-06e88241aaea0ee4e を実行すればよいことなります。

ユータユータ

ありがとうございます!
確かめてみたら出来ました。

これで既存リソースをモジュール化してみてIaC挑戦してみようと思います。

本当にありがとうございました!!

ユータユータ

進捗状況 2/10時点

多くのアドバイスを頂き、モジュール化することで環境ディレクトリからモジュールAWSリソースを変数で環境別に使い回すことができることが確認できた。

terraformerだと異なるアカウントのリソースをassume-roleでインポートができず、困っていたがterraform importならassume-roleでできた。

terraform/env/prd/main.tf

main.tf
provider "aws" {
  region = "ap-northeast-1"

  assume_role {
    role_arn = "arn:aws:iam::XXXXXXXXXXXXX:role/Terraform-Prd-Switch"
  }
}

今考えている懸念点

  • 既存リソースのインポート作業はterraform importの場合1個ずつしかインポートできず、本番/検証で2回インポート作業が発生する(上で頂いたご指摘どおり工数が多くなる)
  • モジュールディレクトリの数を1リソース単位で作成した場合、NAT、IGW、ルートテーブル、セキュリティグループと細かくなり視認性が悪くなる

懸念点への(自分で考えた)対応策

  • 工数が多くなるが、チームにIaCのノウハウを広める目的もあるので私が検証環境で実施したことを他チームメイトに共有して実施してもらえたらチーム内のノウハウが溜まるのでそのまままずは進めてみる
  • モジュールディレクトリ内のリソースディレクトリは関連リソースを一つに固めて整理する
    • 例:VPCディレクトリならNAT、ルートテーブル、IGWをセット

terraform/modules/vpc/main.tf

main.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = var.cidr_block
  tags = {
    Name      = var.Tag_Name_vpc
    Terraform = "True"
  }
}

resource "aws_route_table" "terraform-rt" {

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = var.gateway
  }

  tags = {
    Name      = var.Tag_Name_rt
    Terraform = "True"
  }
}

resource "aws_internet_gateway" "terraform-igw" {
  tags = {
    Name      = var.Tag_Name_igw
    Terraform = "True"
  }
}

こういう感じで進めればやりたいことはできると思う。
幸い(?)対象リソースはまだそこまで複雑な仕組みでないのでディレクトリ構成とモジュール化までできれば後は協力して進めばいけると思う。
これで挑戦してみよう!

ユータユータ

新たなつまずきポイント

サブネットやNATなど本番と検証環境で数が異なる場合のインポート方法に苦戦中

本番

  • サブネット数:6
  • NAT数:3

検証

  • サブネット数:4
  • NAT数:1

モジュールディレクトリでサブネットのリソースを書こうとする場合当初考えていた方法

terraform/modules/vpc/main.tf

main.tf
resource "aws_vpc" "terraform-vpc" {

  cidr_block = var.cidr_block
  tags = {
    Name      = var.Tag_Name_vpc
    Terraform = "True"
  }
}
~~~~~~~~中略~~~~~~~~
resource "aws_subnet" "terraform-subnet-1" {

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet-1

  tags = {
    Name      = var.Tag_Name_subnet-1
    Terraform = "True"
  }
}
 
resource "aws_subnet" "terraform-subnet-2" {

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet-2

  tags = {
    Name      = var.Tag_Name_subnet-2
    Terraform = "True"
  }
}

resource "aws_subnet" "terraform-subnet-3" {

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet-3

  tags = {
    Name      = var.Tag_Name_subnet-3
    Terraform = "True"
  }
}

resource "aws_subnet" "terraform-subnet-4" {

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet-4

  tags = {
    Name      = var.Tag_Name_subnet-4
    Terraform = "True"
  }
}
# 検証環境では不要
resource "aws_subnet" "terraform-subnet-5" {

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet-5

  tags = {
    Name      = var.Tag_Name_subnet-5
    Terraform = "True"
  }
}
# 検証環境では不要
resource "aws_subnet" "terraform-subnet-6" {

  vpc_id     = aws_vpc.terraform-vpc.id
  cidr_block = var.cidr_block-subnet-6

  tags = {
    Name      = var.Tag_Name_subnet-6
    Terraform = "True"
  }
}

サブネットリソースをモジュール化する場合、本番環境に合わせるため6個作ることになるが検証環境では逆に不要なリソースになる。
for_eachを使ってループ処理させるというアドバイスもいただいたが、terraform import <リソース位置> <既存リソースID>と指定する関係上、1個1個指定しないといけないからループ構文がかけない問題がある。
参考記事

抱えている問題として2点挙げられる。

  1. 同じリソースを何回も書く手間
  2. その環境にしかない場合のモジュールの使いまわし方法

新規リソースならfor_eachcountなどを使って繰り返し処理、条件分岐で生成タイミングを環境タグで指定できそうだが、既存リソースを1個ずつインポートする問題はどうしても避けられないようと思うので、1の問題は人力でやるしかなさそう。

ただそうなると2のように検証環境では作成不要なサブネット5,6のように作っていけないリソースの制御判定をどうすればよいか🤔

このスクラップは2022/08/25にクローズされました