Aws_acm_certificate.app_cert.domain_validation_options是一组object,apply后才知道

Aws_acm_certificate.app_cert.domain_validation_options is a set of object, known only after apply

我一直在参加 Terraform (+ CI/CD) 研讨会,这是在早期版本的 Terraform 中讲授的,但我决定在 1.0.11 中使用 AWS 提供商 3.65.0... . 只是为了看看会有什么样的区别。我在与 ACM 打交道以获取证书时遇到障碍,我需要一些关于如何继续的建议。

我在计划阶段遇到的错误:

[ckerr@ck-vm-rhel8-localdomain recipe-app-api-devops]$ docker-compose -f deploy/docker-compose.yml run --rm terraform plan
Creating deploy_terraform_run ... done
╷
│ Error: Invalid for_each argument
│ 
│   on dns.tf line 44, in resource "aws_route53_record" "app_cert_validation_records":
│   44:   for_each = {
│   45:     for dvo in aws_acm_certificate.app_cert.domain_validation_options : dvo.domain_name => {
│   46:       name   = dvo.resource_record_name
│   47:       type   = dvo.resource_record_type
│   48:       record = dvo.resource_record_value
│   49:     }
│   50:   }
│     ├────────────────
│     │ aws_acm_certificate.app_cert.domain_validation_options is a set of object, known only after apply
│ 
│ 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.
╵
Releasing state lock. This may take a few moments...
ERROR: 1

我有一个名为 dns.tf 的文件,用于管理 DNS 和 SSL 证书,目的是让 Amazon 的证书管理器 (ACM) 获取证书。该文件看起来像这样,我已经包含了评论,希望有人能够找出我的想法哪里出了问题。

// BELIEF: this is the DNS zone we will be operating in. No issues there.
//
data "aws_route53_zone" "zone" {
  name = "${var.dns_zone_name}."
}

// BELIEF: this creates a CNAME record that points to our AWS ELB instance for our application.
// This is where it departs a little from the documentation, but I'm not sure if the documentation
// is just being a bit sparse, or if the validation records are meant to also be part of this object.
//
resource "aws_route53_record" "app" {
  zone_id = data.aws_route53_zone.zone.zone_id
  name    = "${lookup(var.subdomain, terraform.workspace)}.${data.aws_route53_zone.zone.name}"
  type    = "CNAME"
  ttl     = "300"

  records = [aws_lb.api.dns_name]
}

// BELIEF: this models the certificate for our application.
//
resource "aws_acm_certificate" "app_cert" {
  domain_name       = aws_route53_record.app.fqdn
  validation_method = "DNS"

  tags = local.common_tags

  lifecycle {
    create_before_destroy = true
  }
}

// BELIEF: to prove ownership of the domain we need to be able to prove that we can
// place certain records into the domain as part of a DNS challenge method.
// I believe this is meant to refer to just those challenge records.
// As this is the dynamic part, its unlikely that the domain validation options would
// be known ahead of time, and so this is where I'm coming unstuck.
//
resource "aws_route53_record" "app_cert_validation_records" {
  allow_overwrite = true
  zone_id         = data.aws_route53_zone.zone.zone_id
  ttl             = "60"

  for_each = {
    for dvo in aws_acm_certificate.app_cert.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      type   = dvo.resource_record_type
      record = dvo.resource_record_value
    }
  }

  name    = each.value.name
  type    = each.value.type
  records = [each.value.record]
}

// BELIEF: This doesn't create anything; its just a placeholder for the validation
// process... presumably for dependency reasons.
// It basically just associates the FQDN (in the certificate) with its validation records.
//
resource "aws_acm_certificate_validation" "app_cert_validation_process" {
  certificate_arn         = aws_acm_certificate.app_cert.arn
  validation_record_fqdns = [for record in aws_route53_record.app_cert_validation_records : record.fqdn]
}

// Reading https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-3-upgrade#resource-aws_acm_certificate
// I don't see where the problem is.

以防万一,还有另一个资源依赖于此,即使用证书的 ELB 实例:

resource "aws_lb_listener" "api_https" {
  load_balancer_arn = aws_lb.api.arn
  port              = 443
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate_validation.app_cert_validation_process.certificate_arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.api.arn
  }
}

我一直在关注 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/version-3-upgrade#resource-aws_acm_certificate,这表明计划应该有效并且我应该在输出中看到文本“(申请后知道)”。

我认为,根据我迄今为止的研究,根本原因是 AWS 提供商不能/不会提前预测域验证选项是什么......然而升级文档表明,虽然这只是应用后已知的内容,但它不会导致计划失败,我不需要任何丑陋的 -target 解决方法。

我尝试使用 depends_on,但这对计划阶段没有帮助。

如果有帮助,完整代码在 https://gitlab.com/cameron.kerr.nz/recipe-app-api-devops/ 中。

感谢阅读, 卡梅伦

PS。我之前在 https://discuss.hashicorp.com/t/aws-acm-certificate-app-cert-domain-validation-options-is-a-set-of-object-known-only-after-apply/31952 中问过这个问题,但到目前为止还没有回复。

TL;DR:这修复了我测试中的示例:

 resource "aws_acm_certificate" "app_cert" {
-  domain_name       = aws_route53_record.app.fqdn
+  domain_name       = aws_route53_record.app.name
   validation_method = "DNS"

但不是那么快,这只有效,因为您在 aws_route53_record.app 中创建的记录与最终的 fqdn 具有相同的名称。

根据 aws_route53_record 资源的文档,fqdn 是使用区域域和 name 参数构建的。

为了进一步说明,在工作示例中,我们可以看到 fqdn 未知,但 name 已知。 aws_route53_record.app_cert_validation_records 中的 for_each 不喜欢。

  # aws_route53_record.app will be created
  + resource "aws_route53_record" "app" {
      + allow_overwrite = (known after apply)
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "foo.example.com"
      + records         = [
          + "xxxx.alb.amazon.com",
        ]
      + ttl             = 300
      + type            = "CNAME"
      + zone_id         = "XXXXXXXXXXX"
    }