ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

Terraform Workspace を利用したディレクトリ構成での属性値の受け渡しについて(ゼロから始める Terraform 講座~その3.5~)

こんにちは、こんばんわ!クラウドインテグレーション事業部の魚介系エンジニア松尾です。

前回ご紹介させていただいた Terraform の記事は、「Terraform Workspace を利用したディレクトリ構成でのパラメータの運用パターンをご紹介!(ゼロから始める Terraform 講座~その3~)」ということで、複数の環境を単一のディレクトリ構成で運用している場合のパラメータの調整方法について記載させていただきました。

今回の投稿は前回の記事の応用編として、モジュール間のパラメータの受け渡し方法について一助となる情報を紹介させていただきます。(前回の記事とセットということで、~その3.5~とさせていただきました!)

これまで投稿させていただいた以下の記事と併せて、ご参考にいただければ幸いです!

ゼロから始める Terraform 講座~その1~ - ForgeVision Engineer Blog

Terraform プロジェクトの効果的なディレクトリ構成パターン(ゼロから始める Terraform 講座~その2~) - ForgeVision Engineer Blog

Terraform Workspace を利用したディレクトリ構成でのパラメータの運用パターンをご紹介!(ゼロから始める Terraform 講座~その3~) - ForgeVision Engineer Blog

注:本記事は AWS 利用者向けの記事となり、AWS 環境をベースにコードの記載をさせていただいております。

はじめに

本記事をご覧いただく上での前提事項

本記事内では基本的な Terraform の使い方などには言及しません。
一部のディレクトリ構成で活用できる応用的な手順となりますので、予めご容赦下さい。

また、今回の記事は前回の記事「Terraform Workspace を利用したディレクトリ構成でのパラメータの運用パターンをご紹介!(ゼロから始める Terraform 講座~その3~)」との関係性が強い記事となります。
Terraform で count メタ引数や三項演算子を利用したことが無い方は、そちらの記事をまずはご確認いただければ幸いです。

count メタ引数の使い方(復習)

前回の記事でご紹介した通り、count メタ引数(以降 count)を利用すると同じコードで複数のリソースを作成することが可能です。
count を利用してリソースを作成した場合、そのリソースの属性値を取得するためにインデックス番号を指定する必要があります。
以下は EC2 インスタンスを3台作成し、インデックスを指定して特定の EC2 インスタンスの属性値を取得する例です。

  • count 実装例
resource "aws_instance" "fv-server" { 
  count          =  3  # EC2 インスタンスを 3 つ作成する
  ami            =  "ami-xxxxxxxx" 
  instance_type  =  "t3.large" 
  tags  =  { 
    Name  =  "FV-Server-${count.index}" 
  } 
}

output "instance_ip_addr" {
  value = aws_instance.fv-server[0].private_ip  # 作成された最初の EC2 インスタンスのプライベート IP アドレスをモジュール外で参照できるよう output する
}

※インデックス番号は「0」から始まるため、2台目を指定する場合は「1」、3台目は「2」となります。

上記のような形でインデックス番号を参照する場合は特に気にする必要はないのですが、count + 三項演算子を利用する場合には注意が必要です。
その詳細について事項で説明します。

count + 三項演算子 を利用した場合

ここで count + 三項演算子 を利用した場合の注意点を説明します。
以下のコードは、count + 三項演算子を用いて ELB のターゲットグループに EC2 を追加する例です。

resource "aws_instance" "fv-server" { 
  count          =  var.env == "production" ? 1 : 0  # env 変数が production の場合に EC2 インスタンスを 1 つ作成する
  ami            =  "ami-xxxxxxxx" 
  instance_type  =  "t3.large" 
  tags  =  { 
    Name  =  "FV-Server-${var.env}" 
  } 
}

resource "aws_lb_target_group" "fv_elb_tg" {
  count =  var.env == "production" ? 1 : 0  # env 変数が production の場合に ELB ターゲットグループを 1 つ作成する
  name  = "fv-elb-01"
  port                 = 80
  protocol             = "TCP"

~~~ 省略 ~~~

  tags = {
    name    = "fv-elb-01"
  }
}

resource "aws_lb_target_group_attachment" "fv-elb_tg_attachment" {
  count            =  var.env == "production" ? 1 : 0  # env 変数が production の場合に ELB ターゲットグループに EC2 をアタッチする
  target_group_arn = aws_lb_target_group.fv_elb_tg[0].arn  # 作成された ELB ターゲットグループを指定
  target_id        = aws_instance.fv-server[0].id  # 作成された ELB ターゲットグループに EC2 を紐づけ
  port = 80
}

※variables(環境変数)に productiondevelopment などの変数が準備されていることを前提としております。

count を利用してリソースを作成した場合、作成されるリソースが1つであってもインデックス番号を付与する必要があるため、aws_lb_target_group_attachment ブロックではインデックス番号の指定が必要です。
なお、Terraform の仕様として各ブロックは空のパラメータがあるとエラーとなってしまいますが、上記のように同条件の count + 三項演算子を全てのブロックで用いることでパラメータが空になることがないためエラーを回避することが可能となります。

それでは次に注意する点に関してですが、count は resourcemodule 及び data ブロックでしか利用ができません。
つまり output では count が利用できないため、以下のような通常の属性値の受け渡し方だと参照元リソースが作成されない環境は空パラメータとなるためエラーとなります。

resource "aws_instance" "fv-server" { 
  count          =  var.env == "production" ? 1 : 0 
  ami            =  "ami-xxxxxxxx" 
  instance_type  =  "t3.large" 
  tags  =  { 
    Name  =  "FV-Server-${var.env}" 
  } 
}

resource "aws_lb_target_group" "fv_elb_tg" {
  count =  var.env == "production" ? 1 : 0
  name  = "fv-elb-01"
  port                 = 80
  protocol             = "TCP"

~~~ 省略 ~~~

  tags = {
    name    = "fv-elb-01"
  }
}

resource "aws_lb_target_group_attachment" "fv-elb_tg_attachment" {
  count            =  var.env == "production" ? 1 : 0
  target_group_arn = aws_lb_target_group.fv_elb_tg[0].arn
  target_id        = aws_instance.fv-server[0].id
  port = 80
}

output "instance_ip_addr" {
  value = aws_instance.fv-server[0].private_ip  # variables が production 以外の場合リソースが作成されないため、空パラメータとなる
}

こういったケースを回避するために用意されている関数について事項で説明します。

one 関数を利用してパラメータを制御する

前述のケースを回避する方法として最もシンプルな解決方法は one 関数を用いる方法です。
one 関数は 0 または 1 つの要素を受け取り、受け取った要素が 0 の場合、自動的に Null 値を挿入してくれる特殊な関数となります。

Terraform は空パラメータの場合エラーとなりますが、Null 値が入ったパラメータは省略される仕様の為エラーと判定されません。
以下が one 関数を利用した実装パターンです。

resource "aws_instance" "fv-server" { 
  count          =  var.env == "production" ? 1 : 0
  ami            =  "ami-xxxxxxxx" 
  instance_type  =  "t3.large" 
  tags  =  { 
    Name  =  "FV-Server-${var.env}" 
  } 
}

resource "aws_lb_target_group" "fv_elb_tg" {
  count =  var.env == "production" ? 1 : 0
  name  = "fv-elb-01"
  port                 = 80
  protocol             = "TCP"

~~~ 省略 ~~~

  tags = {
    name    = "fv-elb-01"
  }
}

resource "aws_lb_target_group_attachment" "fv-elb_tg_attachment" {
  count            =  var.env == "production" ? 1 : 0
  target_group_arn = aws_lb_target_group.fv_elb_tg[0].arn
  target_id        = aws_instance.fv-server[0].id
  port = 80
}

output "instance_ip_addr" {
  value = one(aws_instance.fv-server[*].private_ip)  # EC2 作成されていた場合はプライベート IP を出力し、作成されていなければ Null が挿入されパラメータが省略される
}

複数の環境を単一のディレクトリ構成で運用している場合にはどうしても count + 三項演算子を利用するケースが出てくるため、この one 関数もセットで覚えていただければより効率的に実装を進めることができるのではないかと思います。

最後に

今回の投稿は前回に引き続き、複数の環境を単一のディレクトリ構成で運用している場合でのパラメータの受け渡し方法について記載させていただきました。
今回は one 関数のみご紹介させていただきましたが、Terraform ではパラメータを効率的に取り出す手段がいくつも準備されておりますので、次回以降の記事ではそういった手段も紹介できれまと思います。

では次回もお楽しみに!!!