我们将重点介绍一些陷阱,包括与循环,if语句和部署技术有关的陷阱,以及与整个Terraform有关的更一般的问题:- count和for_each参数有局限性;
- 零停机部署限制
- 即使一个好的计划也可能失败;
- 重构有其窍门;
- 延迟的一致性与延迟一致。
Count和for_each参数有局限性
在本章的示例中,count参数和for_each表达式在循环和条件逻辑中得到积极使用。它们的性能很好,但是有两个重要的限制需要您了解。- 在count和for_each中,无法引用任何资源输出变量。
- count和for_each不能在模块配置中使用。
Count和for_each无法引用资源的任何输出变量
假设您需要部署多个EC2服务器,并且由于某些原因您不想使用ASG。您的代码可能像这样:resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
我们将依次考虑它们。由于为count参数分配了一个静态值,因此此代码可以正常工作:当您运行apply命令时,它将创建三个EC2服务器。但是,如果您想在当前AWS区域内的每个可用性区域(Availability Zone或AZ)中部署一台服务器?您可以让代码从aws_availability_zones数据源中加载区域列表,然后循环遍历每个区域,并使用count参数并在其中按索引访问数组,从而在其中创建EC2服务器:resource "aws_instance" "example_2" {
count = length(data.aws_availability_zones.all.names)
availability_zone = data.aws_availability_zones.all.names[count.index]
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
data "aws_availability_zones" "all" {}
由于count参数可以毫无问题地引用数据源,因此该代码也可以正常工作。但是,如果您需要创建的服务器数量取决于某些资源的输出会怎样?为了证明这一点,最简单的方法是获取random_integer资源,您可能会从名称中猜测它返回一个随机整数:resource "random_integer" "num_instances" {
min = 1
max = 3
}
这段代码生成一个从1到3的随机数。让我们看看如果尝试在aws_instance资源的count参数中使用该资源的结果输出会发生什么:resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
如果对此代码执行Terraform计划,则会出现以下错误:Error: Invalid count argument
on main.tf line 30, in resource "aws_instance" "example_3":
30: count = random_integer.num_instances.result
The "count" 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 count depends on.
Terraform要求在创建或修改任何资源之前,在计划阶段计算count和for_each。这意味着count和for_each可以引用文字,变量,数据源,甚至可以引用资源列表(前提是它们的长度可以在计划期间确定),但是不能引用资源的计算出的输出变量。count和for_each不能在模块配置中使用
有时您可能会想将count参数添加到模块配置中:module "count_example" {
source = "../../../../modules/services/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}
此代码尝试在模块内部使用count来创建webserver-cluster资源的三个副本。或者,也许您想根据某些布尔条件使模块连接为可选,将其count参数分配为0。此代码看起来很合理,但是由于执行terraform计划,您将得到以下错误:Error: Reserved argument name in module block
on main.tf line 13, in module "count_example":
13: count = 3
The name "count" is reserved for use in a future version of Terraform.
不幸的是,在Terraform 0.12.6发布时,不支持在模块资源中使用count或for_each。根据Terraform 0.12发行说明(http://bit.ly/3257bv4),HashiCorp计划在将来添加此功能,因此视您何时阅读本书而定,该功能可能已经可用。要确定是否存在,请在此处查看Terraform更改日志。将create_before_destroy块与ASG结合使用是一种出色的解决方案,用于组织停机时间为零,但有一个细微差别:不支持自动缩放规则。或者,更准确地说,这会在每次部署时将ASG大小重置为min_size,如果您使用自动缩放规则来增加正在运行的服务器数量,则可能会出现问题。例如,webserver-cluster模块包含一对aws_autoscaling_schedule资源,该资源在上午9点将集群中的服务器数量从2个增加到10个。例如,如果您在上午11点进行部署,则新的ASG不会以10个引导,而只有两个服务器,并且将一直保持这种状态,直到第二天的上午9点。此限制可以通过几种方式来规避。- 将aws_autoscaling_schedule中的递归参数从0 9 * * *(“在9am运行”)更改为0-59 9-17 * * *(“从9am至5pm每分钟运行”)。如果ASG已经有十台服务器,则重新执行此自动缩放规则不会更改任何内容,这是我们需要的。但是,如果ASG组最近已部署,则此规则可确保在一分钟内其服务器数量达到10个。这不是一个完全优雅的方法,从十台服务器到两台服务器的大跃迁,反之亦然,也会给用户带来问题。
- 创建一个使用AWS API确定ASG中活动服务器数量的用户定义脚本,使用外部数据源调用它(请参阅第249页的“外部数据源”部分),并将ASG组的required_capacity参数设置为此脚本返回的值。因此,每个新的ASG实例将始终以与旧Terraform代码相同的容量运行,并使维护工作复杂化。
当然,理想情况下,Terraform应该对停机时间为零的部署具有内置支持,但是截至2019年5月,HashiCorp团队尚未计划添加此功能(详细信息请参见此处)。正确的计划可能无法成功实施。
有时,当您执行plan命令时,您会获得完全正确的部署计划,但是apply命令返回错误。例如,尝试添加与您在第2章之前创建的IAM用户所使用的名称相同的aws_iam_user资源。resource "aws_iam_user" "existing_user" {
# IAM,
# terraform import
name = "yevgeniy.brikman"
}
现在,如果执行plan命令,Terraform将会乍一显示非常合理的部署计划:Terraform will perform the following actions:
# aws_iam_user.existing_user will be created
+ resource "aws_iam_user" "existing_user" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "yevgeniy.brikman"
+ path = "/"
+ unique_id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
如果执行apply命令,则会出现以下错误:Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.
on main.tf line 10, in resource "aws_iam_user" "existing_user":
10: resource "aws_iam_user" "existing_user" {
当然,问题在于具有该名称的IAM用户已经存在。这不仅会发生在IAM用户身上,而且几乎会发生在任何资源上。有人可能手动或使用命令行创建了此资源,但由于匹配标识符可能会导致冲突。这个错误有很多风味,常常使Terraform初学者感到惊讶。关键是,terraform plan命令仅考虑Terraform状态文件中指定的资源。如果资源是以其他方式创建的(例如,通过单击AWS控制台手动创建),它们将不会进入状态文件,因此,Terraform在执行plan命令时不会考虑它们。结果,乍一看正确的计划将不会成功。从中可以吸取两个教训。- 如果您已经开始使用Terraform,请不要使用其他任何东西。如果使用Terraform管理基础架构的一部分,则无法再对其进行手动修改。否则,您不仅冒着出现Terraform错误的风险,而且还否定了IaC的许多好处,因为代码将不再是您基础结构的准确表示。
- - , import. Terraform , terraform import. Terraform , . import . . , : _. ( aws_iam_user.existing_user). — , . , ID aws_iam_user (, yevgeniy.brikman), ID aws_instance EC2 ( i-190e22e5). , , .
import, aws_iam_user, Terraform IAM 2 (, yevgeniy.brikman ):
$ terraform import aws_iam_user.existing_user yevgeniy.brikman
Terraform API AWS, IAM aws_iam_user.existing_user Terraform. plan Terraform , IAM , .
, , , Terraform, . , Terraforming (http://terraforming.dtan4.net/), AWS .
— , , . , , . — , . , Terraform IaC, , « » , .
, — . IDE . , , Terraform , .
, webserver-cluster cluster_name:
variable "cluster_name" {
description = "The name to use for all the cluster resources"
type = string
}
, foo. bar. , - .
, webserver-cluster cluster_name , name ALB:
resource "aws_lb" "example" {
name = var.cluster_name
load_balancer_type = "application"
subnets = data.aws_subnet_ids.default.ids
security_groups = [aws_security_group.alb.id]
}
name - , Terraform . ALB, -. , , , .
, , Terraform. aws_security_group webserver-cluster:
resource "aws_security_group" "instance" {
# (...)
}
instance. , ( ) cluster_instance:
resource "aws_security_group" "cluster_instance" {
# (...)
}
? : .
Terraform ID . , iam_user IAM AWS, aws_instance — ID AWS EC2. (, instance cluster_instance, aws_security_group), Terraform , . , Terraform , .
, .
- . . , Terraform . , , . plan create_before_destroy.
…
API , AWS, . , , . , ; , , API-.
, , API- AWS EC2. API «» (201 Created) , . , , AWS , , . , , , (404 Not Found). , EC2 AWS, , .
API , . , AWS SDK , Terraform 6813 (https://github.com/hashicorp/terraform/issues/6813):
$ terraform apply
aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
The subnet ID 'subnet-xxxxxxx' does not exist
, (, ) - ( ID ), Terraform . ( 6813) , , Terraform . , . terraform apply , .
«Terraform: ».