Las trampas de la terraforma

imagen

Destacaremos varias dificultades, incluidas las relacionadas con los bucles, las declaraciones if y las técnicas de implementación, así como problemas más generales que afectan a Terraform en su conjunto:

  • los parámetros count y for_each tienen limitaciones;
  • Restricciones de implementación de tiempo de inactividad cero
  • incluso un buen plan puede fallar;
  • refactorizar puede tener sus trucos;
  • la consistencia diferida es consistente ... con el aplazamiento.

Los parámetros Count y for_each tienen limitaciones


En los ejemplos de este capítulo, el parámetro count y la expresión for_each se usan activamente en bucles y lógica condicional. Funcionan bien, pero tienen dos limitaciones importantes que debe conocer.

  • En count y for_each, no se puede hacer referencia a variables de salida de recursos.
  • count y for_each no se pueden usar en la configuración del módulo.

Count y for_each no pueden hacer referencia a ninguna variable de salida de un recurso


Imagine que necesita implementar varios servidores EC2 y, por alguna razón, no desea usar ASG. Su código podría ser así:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Los consideraremos a su vez.

Dado que al parámetro de recuento se le asigna un valor estático, este código funcionará sin problemas: cuando ejecute el comando de aplicación, creará tres servidores EC2. ¿Pero si desea implementar un servidor en cada zona de disponibilidad (Zona de disponibilidad o AZ) dentro de la región actual de AWS? Puede hacer que su código cargue la lista de zonas desde la fuente de datos aws_availability_zones y luego recorrer cada una de ellas y crear un servidor EC2 usando el parámetro count y accediendo a la matriz por índice:

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" {}

Este código funcionará igual de bien, ya que el parámetro count puede hacer referencia a las fuentes de datos sin ningún problema. Pero, ¿qué sucede si la cantidad de servidores que necesita crear depende de la salida de algún recurso? Para demostrar esto, la forma más fácil es tomar el recurso random_integer, que, como se puede adivinar por el nombre, devuelve un entero aleatorio:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

Este código genera un número aleatorio del 1 al 3. Veamos qué sucede si intentamos usar el resultado de este recurso en el parámetro de conteo del recurso aws_instance:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Si ejecuta el plan de terraform para este código, obtendrá el siguiente error:

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 requiere que se cuente y para cada uno durante la fase de planificación, antes de que se creen o modifiquen los recursos. Esto significa que count y for_each pueden referirse a literales, variables, fuentes de datos e incluso listas de recursos (siempre que su longitud pueda determinarse durante la planificación), pero no a variables de salida calculadas del recurso.

count y for_each no se pueden usar en la configuración del módulo


En algún momento puede verse tentado a agregar el parámetro de conteo a la configuración del módulo:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Este código intenta usar el recuento dentro del módulo para crear tres copias del recurso de servidor web-clúster. O tal vez desee hacer que la conexión del módulo sea opcional dependiendo de alguna condición booleana, asignando su parámetro de conteo a 0. Este código se verá bastante razonable, pero como resultado de ejecutar el plan de terraformación obtendrá el siguiente error:

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.

Desafortunadamente, en el momento en que se lanzó Terraform 0.12.6, el uso de count o for_each en el recurso del módulo no era compatible. De acuerdo con las notas de lanzamiento de Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp planea agregar esta función en el futuro, por lo que dependiendo de cuándo lea este libro, es posible que ya esté disponible. Para saber con certeza, consulte el registro de cambios de Terraform aquí .


Usar el bloque create_before_destroy en combinación con ASG es una excelente solución para organizar implementaciones con tiempo de inactividad cero, excepto por un matiz: las reglas de autoescalado no son compatibles. O, para ser más precisos, esto restablece el tamaño de ASG a min_size en cada implementación, lo que puede ser un problema si usa reglas de autoescalado para aumentar el número de servidores en ejecución.

Por ejemplo, el módulo de servidor web-clúster contiene un par de recursos aws_autoscaling_schedule, que a las 9 a.m. aumentan el número de servidores en el clúster de dos a diez. Si implementa, por ejemplo, a las 11 a.m., el nuevo ASG no arrancará con diez, sino con solo dos servidores, y permanecerá en este estado hasta las 9 a.m. del día siguiente.

Esta limitación se puede eludir de varias maneras.

  • Cambie el parámetro de recurrencia en aws_autoscaling_schedule de 0 9 * * * ("ejecutar a las 9 am") a algo así como 0-59 9-17 * * * ("ejecutar cada minuto de 9 am a 5 pm"). Si el ASG ya tiene diez servidores, volver a ejecutar esta regla de escalado automático no cambiará nada, que es lo que necesitamos. Pero si el grupo ASG se ha implementado recientemente, esta regla garantiza que en un minuto el número de sus servidores llegue a diez. Este no es un enfoque completamente elegante, y grandes saltos de diez a dos servidores y viceversa también pueden causar problemas a los usuarios.
  • Cree una secuencia de comandos definida por el usuario que use la API de AWS para determinar la cantidad de servidores activos en el ASG, llámela usando una fuente de datos externa (consulte la sección "Fuente de datos externa" en la página 249) y establezca el parámetro deseado_capacidad del grupo ASG en el valor devuelto por esta secuencia de comandos. Por lo tanto, cada nueva instancia de ASG siempre se ejecutará con la misma capacidad que el antiguo código de Terraform y complica su mantenimiento.

Por supuesto, idealmente, Terraform debería tener soporte incorporado para implementaciones con tiempo de inactividad cero, pero a partir de mayo de 2019, el equipo de HashiCorp no planeaba agregar esta funcionalidad (los detalles están aquí ).

El plan correcto puede implementarse sin éxito.


A veces, cuando ejecuta el comando plan, obtiene un plan de implementación completamente correcto, pero el comando apply devuelve un error. Por ejemplo, intente agregar el recurso aws_iam_user con el mismo nombre que utilizó para el usuario de IAM que creó anteriormente en el capítulo 2:

resource "aws_iam_user" "existing_user" {
   #       IAM,
   #      terraform import
   name = "yevgeniy.brikman"
}

Ahora, si ejecuta el comando de plan, Terraform mostrará a primera vista un plan de implementación muy razonable:

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.

Si ejecuta el comando de aplicación, obtendrá el siguiente error:

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" {

El problema, por supuesto, es que ya existe un usuario de IAM con ese nombre. Y esto puede suceder no solo con los usuarios de IAM, sino también con casi cualquier recurso. Es posible que alguien haya creado este recurso manualmente o usando la línea de comando, pero sea como sea, la coincidencia de identificadores genera conflictos. Hay muchos sabores a este error que a menudo sorprenden a los principiantes de Terraform.

El punto clave es que el comando de plan de terraform solo considera los recursos que se especifican en el archivo de estado de Terraform. Si los recursos se crean de alguna otra manera (por ejemplo, manualmente, al hacer clic en la consola de AWS), no entrarán en el archivo de estado y, por lo tanto, Terraform no los tendrá en cuenta al ejecutar el comando del plan. Como resultado, el plan que es correcto a primera vista no tendrá éxito.

Se pueden aprender dos lecciones de esto.

  • Si ya ha comenzado a trabajar con Terraform, no use nada más. Si parte de su infraestructura se administra con Terraform, ya no podrá modificarla manualmente. De lo contrario, no solo corre el riesgo de obtener errores extraños de Terraform, sino que también niega muchos de los beneficios de IaC, ya que el código ya no será una representación precisa de su infraestructura.
  • - , 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