My External Storage

Jul 26, 2021 - 5 minute read - Comments - terraform

Terraformでimportを使う理由とfor_eachをつかったリソース定義に実リソースをimportする方法

for_each文を使ったTerraformのリソース定義に対してもimportができるよという話。
なお、「どうしてそのようなことをする必要があるのか?」を説明する前置きのほうが長い。

TL;DR

  • terraformで新規に定義を作るときはterraform importから始めるのが良い
  • 複数の開発環境などで柔軟に利用できるリソースを定義する場合は`文を使う
  • for_each文を使ったリソース定義にimportするときは配列表記を使う

対応するterraformのバージョンは0.13以上ならば問題ないはず。2021年07月時点最新版の1.0.x系でも動作する。

凡人がTerraformで新規リソースを最速で定義する

Terraformで新しいリソースを作るときに最速な方法はなんだろうか? 「オレはPaaSのすべてのパラメータを理解している」というような凄腕でもない限り最速手順は手動で実リソースを作ってimportだ。 やはり調査したり編集しながらリソースを作る場合はコンソールなどから作る方が圧倒的に効率が良い。 詳細については以下のブログなどが参考になるだろう。

importしたリソース定義を使って他の環境にもリソースを作成していく

組織開発の中ではdevelopment/staging/pdoduction環境のようにクラウド上の構成を複数用意していることが多い。 そしてそれぞれの環境に対して共通のTerraform定義をapplyしていくことがほとんどだろう。 私の場合はmodule化してTerraformのリソース定義を各環境で共有している。 そのため前述の手順でリソースを作るときは次のような手順で行なう。

  1. dev環境に実リソースをTerraformを使わず手動で作る
  2. 実リソースをimportするためのTerraformリソースの定義をmoudleに作る
  3. 手動で作ったリソースをmoduleにterraform importする
  4. 各環境でterraform applyしていく
    • dev環境では差分なし、他の環境では新規実リソースが生成される

この時必要になるテクニックとして、moduleには若干環境ごとに構成が異なる実リソースを作成できる拡張性をもたせておく。 よくやるのが次のようなオプションだ。

  • 検証がしやすい様にdevだけワイルドカード的なルールをSecurityGroupに追加しておく
  • dev/stgだけ踏み台サーバ、動作確認用のサーバからのアクセスを許可しておく

…などだ。この様な環境構成を設計するのに最適なのがfor_eachを使った構成だ。

for_eachを使ったリソース定義

for_eachconcatを組み合わせると可変長引数を使っているかの様に動的な数のリソースを宣言できる。

下の例は変数の値によって作成される数が変わるセキュリティーグループルールのリソース定義だ。

  • 必ずdefault_groupからのssh接続を許可するルールが生成される
  • additional_ids変数に要素があれば、その要素を許可するセキュリティーグループIDとしてルールが生成される
variable "additional_ids" {
  description = "環境によって追加でsshを許可したいセキュリティーグループのIDリスト"
  type        = list(string)
}

# foo module内に定義されたリソースとする。
resource "aws_security_group_rule" "dynamic_rules" {
  for_each = toset(concat([aws_security_group.default_group.id], var.additional_ids))

  security_group_id        = aws_security_group.target_group.id
  type                     = "ingress"
  description              = "allow access by sg id"
  from_port                = 443
  to_port                  = 443
  protocol                 = "tcp"
  source_security_group_id = each.key
}

for_eachに与える要素はSetかMapの必要がある。またリストのマージをスマートにできないので少し冗長に書いてある。

ではfor_each文を使ってこのようなTerraformの定義を用意した場合、手動作成した実リソースを冒頭に書いたようにこの定義へimportするにはどうしたら良いのだろうか?

キーの名前をつかって配列アクセスのようにimportする

importする際には基本的にリソース名が必要になる。for_eachを使ったリソースの場合、for_eachによって生成されるキーがリソース名の一部になる。これを使ってimportを行えば良い。
たとえばfoo moduleaws_security_group_rule.dynamic_rulesから生成されるリソースはaws_security_group.target_group.id"sg-hogeohge1"だった場合、 module.foo.aws_security_group_rule.dynamic_rules["sg-hogeohge1"]というようなリソース名になる。 よって先ほど例にあげたaws_security_group_ruleをimportする場合は次のようになる。

# target_group sgのidがsg-foofoo1、default_group sgのidがsg-hogeohge1だった場合
$ terraform import \
    'module.foo.aws_security_group_rule.dynamic_rules["sg-hogeohge1"]' \
    sg-foofoo1_ingress_tcp_443_443_sg-hogehoge1

終わりに

背景の説明が長くなったが、for_eachを使った定義でimportする方法を紹介した。
Terraformのfor_eachfor_each文の外に置かれたリソースがfor_eachで増えるので通常のプログラム言語のループのスコープで考えると難しい。

完全に余談だが、aws_security_group_ruleはsecurity group rule idでimportしたいんだけれどaws cliが対応していないのだろうか?

関連記事