電通総研 テックブログ

電通総研が運営する技術ブログ

Terraformでコードを変更していないリソースが known after apply となってしまう場合にどうすればよいか

こんにちは。X(クロス)イノベーション本部クラウドイノベーションセンターの柴田です。

本記事ではTerraformでコードを変更していないリソースが known after apply となってしまう場合の回避策をご紹介します。

前提

この記事は以下のTerraformのバージョンを前提とします。

新しいバージョンのTerraformでは本記事と異なる挙動をする可能性があります。

$ terraform version
Terraform v1.5.3
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v5.7.0

問題となるコードの例

以下のTerraformコードを例に考えてみましょう。

resource "aws_s3_bucket" "sample" {
  bucket_prefix = "sample-"
  tags = {
    Project = "My Project A"
  }
}

data "aws_iam_policy_document" "sample" {
  statement {
    effect    = "Allow"
    actions   = ["s3:*"]
    resources = [aws_s3_bucket.sample.arn]
  }
}

resource "aws_iam_policy" "sample" {
  name   = "SampleBucketFullAccess"
  path   = "/"
  policy = data.aws_iam_policy_document.sample.json
}

このTerraformコードを terraform apply します。

$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # data.aws_iam_policy_document.sample will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "sample" {
      + id   = (known after apply)
      + json = (known after apply)

      + statement {
          + actions   = [
              + "s3:*",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]
        }
    }

  # aws_iam_policy.sample will be created
  + resource "aws_iam_policy" "sample" {
      + arn         = (known after apply)
      + id          = (known after apply)
      + name        = "SampleBucketFullAccess"
      + name_prefix = (known after apply)
      + path        = "/"
      + policy      = (known after apply)
      + policy_id   = (known after apply)
      + tags_all    = (known after apply)
    }

  # aws_s3_bucket.sample will be created
  + resource "aws_s3_bucket" "sample" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = (known after apply)
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = "sample-"
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "Project" = "My Project A"
        }
      + tags_all                    = {
          + "Project" = "My Project A"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)
    }

Plan: 2 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_s3_bucket.sample: Creating...
aws_s3_bucket.sample: Creation complete after 1s [id=sample-20230713121614963200000001]
data.aws_iam_policy_document.sample: Reading...
data.aws_iam_policy_document.sample: Read complete after 0s [id=55239551]
aws_iam_policy.sample: Creating...
aws_iam_policy.sample: Creation complete after 1s [id=arn:aws:iam::************:policy/SampleBucketFullAccess]

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

次にS3バケット aws_s3_bucket.sampleProject タグの値を変更します。

 resource "aws_s3_bucket" "sample" {
   bucket_prefix = "sample-"
   tags = {
-    Project = "My Project A"
+    Project = "My Project B"
   }
 }

変更後のTerraformコードに対して terraform plan を実行します。

$ terraform plan
aws_s3_bucket.sample: Refreshing state... [id=sample-20230713121614963200000001]
aws_iam_policy.sample: Refreshing state... [id=arn:aws:iam::************:policy/SampleBucketFullAccess]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
 <= read (data resources)

Terraform will perform the following actions:

  # data.aws_iam_policy_document.sample will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "aws_iam_policy_document" "sample" {
      + id   = (known after apply)
      + json = (known after apply)

      + statement {
          + actions   = [
              + "s3:*",
            ]
          + effect    = "Allow"
          + resources = [
              + "arn:aws:s3:::sample-20230713121614963200000001",
            ]
        }
    }

  # aws_iam_policy.sample will be updated in-place
  ~ resource "aws_iam_policy" "sample" {
        id        = "arn:aws:iam::************:policy/SampleBucketFullAccess"
        name      = "SampleBucketFullAccess"
      ~ policy    = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = "s3:*"
                      - Effect   = "Allow"
                      - Resource = "arn:aws:s3:::sample-20230713121614963200000001"
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> (known after apply)
        tags      = {}
        # (4 unchanged attributes hidden)
    }

  # aws_s3_bucket.sample will be updated in-place
  ~ resource "aws_s3_bucket" "sample" {
        id                          = "sample-20230713121614963200000001"
      ~ tags                        = {
          ~ "Project" = "My Project A" -> "My Project B"
        }
      ~ tags_all                    = {
          ~ "Project" = "My Project A" -> "My Project B"
        }
        # (10 unchanged attributes hidden)

        # (3 unchanged blocks hidden)
    }

Plan: 0 to add, 2 to change, 0 to destroy.

すると、変更がないはずのIAM policy aws_iam_policy.samplepolicyknown after apply となってしまいました。

原因

これはData Sourceの仕様によるものです。

Data Resource Dependencies には以下のように記述されています。

Data resources have the same dependency resolution behavior as defined for managed resources. Setting the depends_on meta-argument within data blocks defers reading of the data source until after all changes to the dependencies have been applied.

In order to ensure that data sources are accessing the most up to date information possible in a wide variety of use cases, arguments directly referencing managed resources are treated the same as if the resource was listed in depends_on.

要約すると以下のとおりです。

  • Data Sourceでは depends_on に記載されたリソースのすべての変更が適用されるまで読み取りは延期される。
  • Data Sourceの引数が他のリソースを直接参照している場合、参照先のリソースがData Sourceの depends_on に含まれている場合と同じように扱われる。

つまり Data Sourceが直接参照しているリソースに変更がある場合、それらの変更が適用されたあと、Data Sourceの読み取りが再実行される ということです。

改めて先ほどの例を考えてみましょう。Terraformが以下のように判断していたことがわかります。

  1. aws_s3_bucket.sample のコードの変更が検出される。
  2. data.aws_iam_policy_document.sampleaws_s3_bucket.sample を直接参照しているため、 depends_onaws_s3_bucket.sample が含まれている場合と同じように扱われる。
  3. depends_on に含まれる aws_s3_bucket.sample の変更が検出されたため、その変更が適用されたあとで data.aws_iam_policy_document.sample の再読み取りを行う必要があると判断される。
  4. 3をうけて aws_iam_policy.samplepolicy が更新されると判断される。

回避策

Data Resource Dependencies には以下のように記述されています。

This behavior can be avoided when desired by indirectly referencing the managed resource values through a local value, unless the data resource itself has custom conditions.

どうやら Local Value を間に挟むことで先ほどの事象を回避できるようです。

試してみましょう。先ほどのTerraformコードを以下のように変更します。

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

resource "aws_s3_bucket" "sample" {
  bucket_prefix = "sample-"
  tags = {
    Project = "My Project B"
  }
}

locals {
  s3_bucket_arn = aws_s3_bucket.sample.arn
}

data "aws_iam_policy_document" "sample" {
  statement {
    effect    = "Allow"
    actions   = ["s3:*"]
    resources = [local.s3_bucket_arn]
  }
}

resource "aws_iam_policy" "sample" {
  name   = "SampleBucketFullAccess"
  path   = "/"
  policy = data.aws_iam_policy_document.sample.json
}

変更後のTerraformコードに対して terraform plan を実行します。

$ terraform plan
aws_s3_bucket.sample: Refreshing state... [id=sample-20230713121614963200000001]
data.aws_iam_policy_document.sample: Reading...
data.aws_iam_policy_document.sample: Read complete after 0s [id=55239551]
aws_iam_policy.sample: Refreshing state... [id=arn:aws:iam::************:policy/SampleBucketFullAccess]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_s3_bucket.sample will be updated in-place
  ~ resource "aws_s3_bucket" "sample" {
        id                          = "sample-20230713121614963200000001"
      ~ tags                        = {
          ~ "Project" = "My Project A" -> "My Project B"
        }
      ~ tags_all                    = {
          ~ "Project" = "My Project A" -> "My Project B"
        }
        # (10 unchanged attributes hidden)

        # (3 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

S3バケット aws_s3_bucket.sample 以外の変更が表示されないことを確認できました。

おわりに

本記事ではTerraformでコードを変更していないリソースが known after apply となってしまう場合の回避策をご紹介しました。
この記事がこの問題に悩んでいる方のお役に立てば幸いです。

最後までお読みいただき、ありがとうございました。

参考

私たちは一緒に働いてくれる仲間を募集しています!

クラウドアーキテクト

執筆:@shibata.takao、レビュー:寺山 輝 (@terayama.akira)
Shodoで執筆されました