Armadilhas da Terraform

imagem

Vamos destacar várias armadilhas, incluindo aquelas relacionadas a loops, instruções if e técnicas de implantação, bem como questões mais gerais que dizem respeito à Terraform como um todo:

  • os parâmetros count e for_each têm limitações;
  • Restrições de implantação de tempo de inatividade zero
  • até um bom plano pode falhar;
  • a refatoração pode ter seus truques;
  • a consistência diferida é consistente ... com o diferimento.

Os parâmetros count e for_each têm limitações


Nos exemplos deste capítulo, o parâmetro count e a expressão for_each são usados ​​ativamente em loops e lógica condicional. Eles têm um bom desempenho, mas têm duas limitações importantes que você precisa conhecer.

  • Em count e for_each, nenhuma variável de saída de recurso pode ser referenciada.
  • count e for_each não podem ser usados ​​na configuração do módulo.

Count e for_each não podem fazer referência a nenhuma variável de saída de um recurso


Imagine que você precisa implantar vários servidores EC2 e, por algum motivo, não deseja usar o ASG. Seu código pode ser assim:

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

Vamos considerá-los por sua vez.

Como o parâmetro count recebe um valor estático, esse código funciona sem problemas: quando você executa o comando apply, ele cria três servidores EC2. Mas se você deseja implantar um servidor em cada zona de disponibilidade (zona de disponibilidade ou AZ) na região atual da AWS? Você pode fazer com que seu código carregue a lista de zonas da fonte de dados aws_availability_zones e depois percorra cada uma delas e crie um servidor EC2 nela usando o parâmetro count e acessando a 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" {}

Esse código também funcionará bem, pois o parâmetro count pode fazer referência a fontes de dados sem problemas. Mas o que acontece se o número de servidores que você precisa criar depender da saída de algum recurso? Para demonstrar isso, a maneira mais fácil é usar o recurso random_integer, que, como você pode imaginar pelo nome, retorna um número inteiro aleatório:

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

Este código gera um número aleatório de 1 a 3. Vamos ver o que acontece se tentarmos usar a saída do resultado desse recurso no parâmetro count do recurso aws_instance:

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

Se você executar o plano de terraform para esse código, receberá o seguinte erro:

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.

O Terraform exige que a contagem e o for_cada sejam computados durante a fase de planejamento, antes que quaisquer recursos sejam criados ou modificados. Isso significa que count e for_each podem se referir a literais, variáveis, fontes de dados e até listas de recursos (desde que seu comprimento possa ser determinado durante o planejamento), mas não às variáveis ​​de saída calculadas do recurso.

count e for_each não podem ser usados ​​na configuração do módulo


Em algum momento, você pode tentar adicionar o parâmetro count à configuração do 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"
}

Esse código tenta usar count dentro do módulo para criar três cópias do recurso cluster de servidor da web. Ou talvez você queira tornar a conexão do módulo opcional, dependendo de alguma condição booleana, atribuindo seu parâmetro de contagem a 0. Esse código parecerá bastante razoável, mas como resultado da execução do plano de terraform, você receberá o seguinte erro:

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.

Infelizmente, no momento em que o Terraform 0.12.6 foi lançado, o uso de count ou for_each no recurso do módulo não era suportado. De acordo com as notas de versão do Terraform 0.12 (http://bit.ly/3257bv4), a HashiCorp planeja adicionar esse recurso no futuro, portanto, dependendo de quando você ler este livro, ele poderá já estar disponível. Para ter certeza, consulte o registro de alterações do Terraform aqui .


Usar o bloco create_before_destroy em combinação com o ASG é uma excelente solução para organizar implantações com tempo de inatividade zero, exceto por uma nuance: as regras de dimensionamento automático não são suportadas. Ou, para ser mais preciso, isso redefine o tamanho do ASG de volta para min_size cada vez que é implantado, o que pode ser um problema se você usar regras de dimensionamento automático para aumentar o número de servidores em execução.

Por exemplo, o módulo webserver-cluster contém um par de recursos aws_autoscaling_schedule, que às 9 horas aumentam o número de servidores no cluster de dois para dez. Se você implantar, digamos, às 11h, o novo ASG não inicializará com dez, mas apenas com dois servidores, e permanecerá nesse estado até as 9h do dia seguinte.

Essa limitação pode ser contornada de várias maneiras.

  • Altere o parâmetro de recorrência em aws_autoscaling_schedule de 0 9 * * * ("execute às 9h") para algo como 0-59 9-17 * * * ("execute a cada minuto das 9h às 17h"). Se o ASG já tiver dez servidores, a re-execução dessa regra de escala automática não mudará nada, e é disso que precisamos. Mas se o grupo ASG tiver sido implantado recentemente, essa regra garantirá que em um minuto o número de seus servidores chegue a dez. Essa não é uma abordagem totalmente elegante, e grandes saltos de dez para dois servidores e vice-versa também podem causar problemas aos usuários.
  • Crie um script personalizado que use a API da AWS para determinar o número de servidores ativos no ASG, chame-o usando uma fonte de dados externa (consulte a seção "Fonte de dados externa" na página 248) e defina o parâmetro de capacidade desejada do grupo ASG para o valor retornado por este script. Assim, cada nova instância ASG sempre será executada com a mesma capacidade que o antigo código Terraform e complica sua manutenção.

Obviamente, idealmente, o Terraform deveria ter suporte embutido para implantações com tempo de inatividade zero, mas a partir de maio de 2019, a equipe da HashiCorp não planejava adicionar essa funcionalidade (os detalhes estão aqui ).

O plano correto pode ser implementado sem êxito.


Às vezes, quando você executa o comando plan, obtém um plano de implantação completamente correto, mas o comando apply retorna um erro. Por exemplo, tente adicionar o recurso aws_iam_user com o mesmo nome que você usou para o usuário do IAM que você criou anteriormente no capítulo 2:

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

Agora, se você executar o comando plan, o Terraform exibirá à primeira vista um plano de implantação bastante razoável:

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.

Se você executar o comando apply, obtém o seguinte erro:

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

O problema, é claro, é que um usuário do IAM com esse nome já existe. E isso pode acontecer não apenas com usuários do IAM, mas também com quase qualquer recurso. É possível que alguém tenha criado esse recurso manualmente ou usando a linha de comando, mas seja como for, os identificadores correspondentes levam a conflitos. Há muitos sabores nesse erro que geralmente surpreendem os iniciantes do Terraform.

O ponto principal é que o comando do plano de terraform considera apenas os recursos especificados no arquivo de status do Terraform. Se os recursos forem criados de alguma outra maneira (por exemplo, manualmente, clicando no console da AWS), eles não entrarão no arquivo de status e, portanto, o Terraform não os levará em consideração ao executar o comando plan. Como resultado, o plano correto à primeira vista não terá êxito.

Duas lições podem ser aprendidas com isso.

  • Se você já começou a trabalhar com o Terraform, não use mais nada. Se parte da sua infraestrutura for gerenciada usando o Terraform, você não poderá mais modificá-la manualmente. Caso contrário, você não apenas corre o risco de obter erros estranhos do Terraform, mas também nega os muitos benefícios do IaC, pois o código não será mais uma representação precisa da sua infraestrutura.
  • - , 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