我遇到了一个非常令人困惑的Terraform资源问题,它在ACM中自动生成SSL证书的生成和DNS验证,以获取(Terraform管理的)托管区域列表。代码也可以找到in this gist。
我首先引导托管区域引用这个特定于环境的变量。
hosted_zones = [
{
domain = "site1.com"
zone_id = "MANUALLY FILL"
}
]
我用于构建区域的块似乎可靠地工作。
resource "aws_route53_zone" "zones" {
count = "${length(var.hosted_zones)}"
name = "${lookup(var.hosted_zones[count.index], "domain")}"
}
在构建区域之后,我手动将区域ID复制到变量中,因为我没有想出一个聪明的方法来自动化它,因为HCL的局限性和我缺乏经验。
我可以使用...
为每个托管区域可靠地生成裸证书和splat证书resource "aws_acm_certificate" "cert" {
count = "${length(var.hosted_zones)}"
domain_name = "${lookup(var.hosted_zones[count.index], "domain")}"
subject_alternative_names = ["*.${lookup(var.hosted_zones[count.index], "domain")}"]
validation_method = "DNS"
tags {
Project = "${var.project}"
Environment = "${var.environment}"
}
}
当我尝试自动化证书的DNS验证时,事情变得多毛。单个托管区域有a good example in the documentation,但我无法将其成功移植到多个托管区域。我的尝试......
resource "aws_route53_record" "cert_validation" {
count = "${length(var.hosted_zones)}"
name = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_name[count.index]}"
type = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_type[count.index]}"
zone_id = "${var.zone_override != "" ? var.zone_override : lookup(var.hosted_zones[count.index], "zone_id")}"
records = ["${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_value[count.index]}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
count = "${length(var.hosted_zones)}"
certificate_arn = "${aws_acm_certificate.cert.*.arn[count.index]}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn[count.index]}"]
}
我在第一次运行时看到的错误是:
* module.acm.aws_route53_record.cert_validation: 1 error(s) occurred:
* module.acm.aws_route53_record.cert_validation: Resource 'aws_acm_certificate.cert' does not have attribute 'domain_validation_options.0.resource_record_value' for variable 'aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_value'
令人讨厌的部分是,如果我评论validation
资源,apply
成功,然后取消注释它们并重新运行也会成功。
我已尝试(感觉如何)element()
lookup()
,list()
和map()
的每个排列,以通过第一个资源块的输出中的索引来定位证书,但我遇到了记录在案的“平面列表”限制,这是我最接近成功的。我想了解为什么解决方法是必要的,所以我可以消除它。这感觉就像一个语法问题,或者我试图让HCL表现得更像OO语言而不是它。
感谢您提供任何可能有用的经验!
答案 0 :(得分:4)
我有类似的情况,解决问题的关键是使用locals和flatten()。该方法也适用于您,因此您不需要两次传递来创建资源。
在这种情况下,有多个域,每个域都有子域,这些子域将出现在证书的subjectAltName部分中。例如:
├── preview.example.com
│ ├── app.preview.example.com
│ └── www.preview.example.com
├── demo.example.com
│ ├── app.demo.example.com
│ └── www.demo.example.com
├── staging.example.com
│ ├── app.staging.example.com
│ └── www.staging.example.com
└── example.com
├── app.example.com
└── www.example.com
为实现这一目标,我们首先设置一些变量:
variable "domains" {
type = "list"
default = [
"demo.example.com",
"preview.example.com",
"staging.example.com",
"example.com"
]
}
variable "subdomains" {
type = "list"
default = [
"app",
"www"
]
}
接下来,我们创建包含子域为SAN的证书资源。
resource "aws_acm_certificate" "cert" {
count = "${length(var.domains)}"
domain_name = "${element(var.domains, count.index)}"
validation_method = "DNS"
subject_alternative_names = ["${
formatlist("%s.%s",
var.subdomains,
element(var.domains, count.index)
)
}"]
}
接下来,我们需要一个局部变量来压缩所产生的域和子域集。
这是必需的,因为terraform不支持版本0.11.7的嵌套列表语法
通过element()
插值和`list [count]。
locals {
dvo = "${flatten(aws_acm_certificate.cert.*.domain_validation_options)}"
}
我们接下来需要查找Route 53区域,我们可以在后续的Route 53记录中使用它:
data "aws_route53_zone" "zone" {
count = "${length(var.domains) > 0 ? 1 : 0}"
name = "example.com."
private_zone = false
}
然后,我们创建Route 53 DNS记录,这些记录将使用证书中的数据填充 DNS验证的资源。我们在子域中添加一个,以便我们也有一个 记录未包含在子域列表中的基本域。
resource "aws_route53_record" "cert_validation" {
count = "${length(var.domains) * (length(var.subdomains) + 1)}"
zone_id = "${data.aws_route53_zone.zone.id}"
ttl = 60
name = "${lookup(local.dvo[count.index], "resource_record_name")}"
type = "${lookup(local.dvo[count.index], "resource_record_type")}"
records = ["${lookup(local.dvo[count.index], "resource_record_value")}"]
}
最后,我们创建将等待证书的证书验证资源 发出的。
resource "aws_acm_certificate_validation" "cert" {
count = "${length(var.domains) * (length(var.subdomains) + 1)}"
certificate_arn = "${element(aws_acm_certificate.cert.*.arn, count.index)}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn}"]
}
最后一个资源的一个警告是,它将为每个资源创建一个资源实例 请求证书,但每个实例将取决于所有域中的所有FQDN 子域。这不会影响AWS中的任何内容,但是terraform代码将不会继续/完成 直到颁发所有证书。
这应该在单个应用运行中起作用,在第一遍中不需要-target
任何资源,
虽然how long it takes for the validations to
complete周围有明显的已知问题
通过terraform执行,因此可能需要第二次传递,尽管不会更改代码或计划/应用调用。
答案 1 :(得分:1)
经过一些实验后,我最终利用-target=aws_acm_certificate.cert
作为解决方法,以避免我看到的遗漏属性错误。我上面使用的语法是正确的,错误是apply
在验证步骤引用生成的属性之前需要完成证书的结果。
此外,我使用MANUAL FILL
找到了zipmap
步骤的优雅解决方案。结果看起来像这样......
变量:
hosted_zones = [
"foo.com"
]
hosted_zones
模块的输出:
output "hosted_zone_ids" {
value = "${zipmap(var.hosted_zones, aws_route53_zone.zones.*.zone_id)}"
}
然后,我的证书生成/验证模块如下所示,其中var.hosted_zone_map
是上一个zipmap
的输出,它创建了托管区域域名到指定区域ID的映射:
resource "aws_acm_certificate" "cert" {
count = "${length(keys(var.hosted_zone_map))}"
domain_name = "${element(keys(var.hosted_zone_map), count.index)}"
subject_alternative_names = ["*.${element(keys(var.hosted_zone_map), count.index)}"]
validation_method = "DNS"
tags {
Project = "${var.project}"
Environment = "${var.environment}"
}
}
resource "aws_route53_record" "cert_validation" {
count = "${length(keys(var.hosted_zone_map))}"
zone_id = "${lookup(var.hosted_zone_map, element(keys(var.hosted_zone_map), count.index))}"
name = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_name[count.index]}"
type = "${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_type[count.index]}"
records = ["${aws_acm_certificate.cert.*.domain_validation_options.0.resource_record_value[count.index]}"]
ttl = 60
}
resource "aws_acm_certificate_validation" "cert" {
count = "${length(keys(var.hosted_zone_map))}"
certificate_arn = "${aws_acm_certificate.cert.*.arn[count.index]}"
validation_record_fqdns = ["${aws_route53_record.cert_validation.*.fqdn[count.index]}"]
}
啪啪声的定位绝对是跟踪此事的最棘手和最少记录的部分,所以希望这有助于其他人。