地形陷阱

图片

我们将重点介绍一些陷阱,包括与循环,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 , .

    , .

    • plan. . , Terraform , , , .
    • , . , , . , create_before_destroy. , : apply, apply .
    • . , (, aws_security_group instance cluster_instance), , Terraform. — terraform state. terraform state mv, :

      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE — , , NEW_REFERENCE — , . , aws_security_group instance cluster_instance :

      $ terraform state mv \
         aws_security_group.instance \
         aws_security_group.cluster_instance

      Terraform, , aws_security_group.instance, aws_security_group.cluster_instance. terraform plan , , .

    • . . , 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: ».

All Articles