この記事は リンクバルアドベントカレンダー2023 の6日目の記事です。
昨日は @Togo_Yokoyama さんの記事です!
はじめに
Terraformで for_each
を使ってループさせるとき、以下のエラーが起こることがあります。
The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
これはなんのエラーか
for_each
で指定する値に、作成前のリソースの情報があると起こるエラーです。
詳しくは以下の公式ドキュメントや参考記事を御覧ください。
エラーが起こる例
例えば以下のような場合です。
AWS前提で書きます。
- moduleで、
aws_iam_role_policy_attachment
をする -
aws_iam_role_policy_attachment
するポリシーにカスタムポリシーが含まれる
ec2にアタッチするIAMロールにポリシーをアタッチする場合で考えてみましょう。
※勢いで書いて動作確認していないので、他でエラーが出るかもです🙏
module
locals {
custom_policy_arns = [
var.sample_policy_arn,
xxx,
...,
]
iam_policy_arns = concat(
var.role_policies,
custom_policy_arns
)
}
# 略
resource "aws_iam_role_policy_attachment" "ec2" {
for_each = toset(local.iam_policy_arns) # エラーになる
role = var.iam_role_name
policy_arn = each.value
}
# 略
module利用側
module "ec2_web" {
source = "path/to/sample"
role_policies = [
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
"arn:aws:iam::aws:policy/AmazonSSMPatchAssociation",
]
iam_role_name = aws_iam_role.ec2_web.name
sample_policy_arn = aws_iam_policy.sample.arn
# 略
}
resource "aws_iam_role" "ec2_web" {
name = "aaaaa"
assume_role_policy = # 略
}
resource "aws_iam_policy" "sample" {
# 略
}
# 略
解決策
何点かあります。
- エラーが起こるリソースだけ
-target
オプションか手動で先に作る - 諦めて一つ一つリソースを作る
- さきほどの例だと、カスタムポリシーのリソース数だけ
aws_iam_role_policy_attachment
を作ります
- さきほどの例だと、カスタムポリシーのリソース数だけ
-
length
とcount
を使う ※注意点あり
3つ目の例を紹介します。
aws_iam_role_policy_attachment
の部分だけ書きますね。
# before
resource "aws_iam_role_policy_attachment" "ec2" {
for_each = toset(local.iam_policy_arns) # エラーになる
role = var.iam_role_name
policy_arn = each.value
}
# after
resource "aws_iam_role_policy_attachment" "ec2" {
count = length(local.iam_policy_arns)
role = var.iam_role_name
policy_arn = var.role_policy_arns[count.index]
}
これだとエラーにならずに無事リソース作成ができます!
なぜかは知らん。
length
と count
使用時の注意点
末尾以外の aws_iam_role_policy_attachment
をなくすとリソースの再作成が起こります。
count
は list
なので、今回の例だと以下のようなstate名になります。
(ここも動作確認していないので、ちょっと違うかも)
module.ec2_web.aws_iam_role_policy_attachment.ec2[0]
module.ec2_web.aws_iam_role_policy_attachment.ec2[1]
module.ec2_web.aws_iam_role_policy_attachment.ec2[2]
# ポリシーの数だけ続く
要素数が3つだけだとして、インデックスが0のリソースを消したら、次のようにリソースは再作成されてしまいます。
-
module.ec2_web.aws_iam_role_policy_attachment.ec2[1]
→module.ec2_web.aws_iam_role_policy_attachment.ec2[0]
-
module.ec2_web.aws_iam_role_policy_attachment.ec2[2]
→module.ec2_web.aws_iam_role_policy_attachment.ec2[1]
count
と list
の仕様上これは避けられません。
ただし、リソースが削除後に state mv
をすれば、変更だけで済む?(未検証)
リソース次第な気ががしますね。。。
$ terraform state mv module.ec2_web.aws_iam_role_policy_attachment.ec2[1] module.ec2_web.aws_iam_role_policy_attachment.ec2[0]
$ terraform state mv module.ec2_web.aws_iam_role_policy_attachment.ec2[2] module.ec2_web.aws_iam_role_policy_attachment.ec2[1]
基本的に for_each
を使ったほうがいいです。
詳細は後述の参考記事をご覧ください。
参考
length
と count
の方法
count
より for_each
を使ったほうがよい理由
なお、上記記事では「count を使用したリソース作成有無 (0/1) の判定は昔から行われていた手法であるが、その場合においては count を使うほうが好ましい。」とありますが、 for_each
でも同じようなことはできます!
おわりに
おわり