Pièges de Terraform

image

Nous mettrons en évidence quelques pièges, notamment ceux liés aux boucles, aux instructions if et aux techniques de déploiement, ainsi que des problèmes plus généraux qui concernent Terraform dans son ensemble:

  • les paramètres count et for_each ont des limites;
  • Zéro restriction de déploiement de temps d'arrêt
  • même un bon plan peut échouer;
  • le refactoring peut avoir ses astuces;
  • la cohérence différée est compatible ... avec le report.

Les paramètres Count et for_each ont des limites


Dans les exemples de ce chapitre, le paramètre count et l'expression for_each sont activement utilisés dans les boucles et la logique conditionnelle. Ils fonctionnent bien, mais ils ont deux limitations importantes que vous devez connaître.

  • Dans count et for_each, aucune variable de sortie de ressource ne peut être référencée.
  • count et for_each ne peuvent pas être utilisés dans la configuration du module.

Count et for_each ne peuvent référencer aucune variable de sortie d'une ressource


Imaginez que vous devez déployer plusieurs serveurs EC2 et que pour une raison quelconque vous ne souhaitiez pas utiliser ASG. Votre code pourrait ressembler à ceci:

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

Nous les examinerons tour à tour.

Comme le paramètre count a une valeur statique, ce code fonctionnera sans problème: lorsque vous exécutez la commande apply, il créera trois serveurs EC2. Mais si vous vouliez déployer un serveur dans chaque zone de disponibilité (zone de disponibilité ou AZ) dans la région AWS actuelle? Vous pouvez demander à votre code de charger la liste des zones à partir de la source de données aws_available_zones, puis de parcourir chacune d'elles et d'y créer un serveur EC2 en utilisant le paramètre count et en accédant au tableau par index:

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

Ce code fonctionnera tout aussi bien, car le paramètre count peut référencer des sources de données sans aucun problème. Mais que se passe-t-il si le nombre de serveurs que vous devez créer dépend de la sortie d'une ressource? Pour le démontrer, le moyen le plus simple consiste à prendre la ressource random_integer, qui, comme vous pouvez le deviner d'après le nom, renvoie un entier aléatoire:

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

Ce code génère un nombre aléatoire de 1 à 3. Voyons ce qui se passe si nous essayons d'utiliser la sortie de résultat de cette ressource dans le paramètre count de la ressource aws_instance:

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

Si vous exécutez un plan terraform pour ce code, vous obtenez l'erreur suivante:

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 requiert que count et for_each soient calculés pendant la phase de planification, avant que des ressources ne soient créées ou modifiées. Cela signifie que count et for_each peuvent faire référence à des littéraux, des variables, des sources de données et même des listes de ressources (à condition que leur longueur puisse être déterminée lors de la planification), mais pas aux variables de sortie calculées de la ressource.

count et for_each ne peuvent pas être utilisés dans la configuration du module


Parfois, vous pourriez être tenté d'ajouter le paramètre count à la configuration du module:

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

     count = 3

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

Ce code tente d'utiliser count à l'intérieur du module pour créer trois copies de la ressource de cluster de serveurs Web. Ou, vous souhaitez peut-être rendre la connexion du module facultative en fonction d'une condition booléenne, en affectant son paramètre de comptage à 0. Ce code semblera tout à fait raisonnable, mais à la suite de l'exécution du plan terraform, vous obtiendrez l'erreur suivante:

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.

Malheureusement, au moment de la sortie de Terraform 0.12.6, l'utilisation de count ou for_each dans la ressource de module n'était pas prise en charge. Selon les notes de version de Terraform 0.12 (http://bit.ly/3257bv4), HashiCorp prévoit d'ajouter cette fonctionnalité à l'avenir, donc selon le moment où vous lirez ce livre, il se peut qu'elle soit déjà disponible. Pour le savoir, consultez le journal des modifications de Terraform ici .


L'utilisation du bloc create_before_destroy en combinaison avec ASG est une excellente solution pour organiser des déploiements sans temps d'arrêt, à l'exception d'une nuance: les règles de mise à l'échelle automatique ne sont pas prises en charge. Ou, pour être plus précis, cela réinitialise la taille ASG à min_size à chaque déploiement, ce qui peut être un problème si vous avez utilisé des règles de mise à l'échelle automatique pour augmenter le nombre de serveurs en cours d'exécution.

Par exemple, le module de cluster de serveurs Web contient une paire de ressources aws_autoscaling_schedule qui, à 9 h, font passer le nombre de serveurs du cluster de deux à dix. Si vous déployez, disons, à 11 heures du matin, le nouvel ASG ne démarrera pas avec dix, mais avec seulement deux serveurs, et restera dans cet état jusqu'à 9 heures le lendemain.

Cette limitation peut être contournée de plusieurs manières.

  • Modifiez le paramètre de récurrence dans aws_autoscaling_schedule de 0 9 * * * («exécuté à 9 h») à quelque chose comme 0-59 9-17 * * * («exécuté toutes les minutes de 9 h à 17 h»). Si l'ASG a déjà dix serveurs, la réexécution de cette règle de mise à l'échelle automatique ne changera rien, ce dont nous avons besoin. Mais si le groupe ASG a été récemment déployé, cette règle garantit qu'en une minute le nombre de ses serveurs atteint dix. Ce n'est pas une approche entièrement élégante, et de grands sauts de dix à deux serveurs et vice versa peuvent également causer des problèmes aux utilisateurs.
  • Créez un script défini par l'utilisateur qui utilise l'API AWS pour déterminer le nombre de serveurs actifs dans l'ASG, appelez-le à l'aide d'une source de données externe (voir la section "Source de données externe" à la page 263) et définissez le paramètre de capacité souhaitée du groupe ASG sur la valeur renvoyée par ce script. Ainsi, chaque nouvelle instance ASG fonctionnera toujours avec la même capacité que l'ancien code Terraform et complique sa maintenance.

Bien sûr, idéalement, Terraform devrait avoir une prise en charge intégrée pour les déploiements sans temps d'arrêt, mais en mai 2019, l'équipe HashiCorp n'avait pas prévu d'ajouter cette fonctionnalité (les détails sont ici ).

Le plan correct peut être mis en œuvre sans succès.


Parfois, lorsque vous exécutez la commande plan, vous obtenez un plan de déploiement complètement correct, mais la commande apply renvoie une erreur. Par exemple, essayez d'ajouter la ressource aws_iam_user avec le même nom que vous avez utilisé pour l'utilisateur IAM que vous avez créé précédemment dans le chapitre 2:

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

Maintenant, si vous exécutez la commande plan, Terraform affichera à première vue un plan de déploiement très raisonnable:

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 vous exécutez la commande apply, vous obtenez l'erreur suivante:

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

Le problème, bien sûr, est qu'un utilisateur IAM portant ce nom existe déjà. Et cela peut se produire non seulement avec les utilisateurs IAM, mais aussi avec presque toutes les ressources. Il est possible que quelqu'un ait créé cette ressource manuellement ou en utilisant la ligne de commande, mais quoi qu'il en soit, la correspondance des identifiants entraîne des conflits. Il existe de nombreuses saveurs à cette erreur qui prennent souvent les débutants Terraform par surprise.

Le point clé est que la commande terraform plan ne prend en compte que les ressources spécifiées dans le fichier d'état Terraform. Si les ressources sont créées d'une autre manière (par exemple, manuellement, en cliquant sur la console AWS), elles n'entreront pas dans le fichier d'état et, par conséquent, Terraform ne les prendra pas en compte lors de l'exécution de la commande plan. En conséquence, le plan qui est correct à première vue sera infructueux.

Deux enseignements peuvent en être tirés.

  • Si vous avez déjà commencé à travailler avec Terraform, n'utilisez rien d'autre. Si une partie de votre infrastructure est gérée à l'aide de Terraform, vous ne pouvez plus la modifier manuellement. Sinon, vous courez non seulement le risque d'obtenir des erreurs Terraform étranges, mais vous annulez également les nombreux avantages de l'IaC, car le code ne sera plus une représentation précise de votre infrastructure.
  • - , 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