Bonnes pratiques pour le style et la structure généraux

Ce document fournit des recommandations de base pour le style et la structure de vos configurations Terraform. Ces recommandations s'appliquent aux modules Terraform réutilisables et aux configurations racine.

Ce guide n'est pas une introduction à Terraform. Pour une présentation de l'utilisation de Terraform avec Google Cloud, consultez la page Premiers pas avec Terraform.

Suivre une structure de module standard

  • Les modules Terraform doivent respecter la structure de module standard.
  • Démarrez chaque module avec un fichier main.tf où les ressources sont placées par défaut.
  • Dans chaque module, incluez un fichier README.md au format Markdown. Dans le fichier README.md, incluez la documentation de base du module.
  • Placez les exemples dans un dossier examples/, avec un sous-répertoire distinct pour chaque exemple. Pour chaque exemple, incluez un fichier README.md détaillé.
  • Créez des regroupements logiques de ressources avec leurs propres fichiers et des noms descriptifs comme network.tf, instances.tf ou loadbalancer.tf.
    • Évitez d'attribuer à chaque ressource son propre fichier. Regroupez les ressources en fonction de leur objectif partagé. Par exemple, combinez google_dns_managed_zone et google_dns_record_set dans dns.tf.
  • Dans le répertoire racine du module, n'incluez que Terraform (*.tf) et les fichiers de métadonnées du dépôt (tels que README.md et CHANGELOG.md).
  • Placez toute documentation supplémentaire dans un sous-répertoire docs/.

Adopter une convention d'attribution de noms

  • Nommez tous les objets de configuration en utilisant des traits de soulignement pour délimiter plusieurs mots. Cette pratique garantit la cohérence avec la convention d'attribution de noms pour les types de ressources, les types de sources de données et les autres valeurs prédéfinies. Cette convention ne s'applique pas aux arguments de noms.

    Recommandations :

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    Option déconseillée :

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • Pour simplifier les références à une ressource qui est la seule de son type (par exemple, un équilibreur de charge unique pour un module entier), nommez la ressource main.

    • Il faut un surcroît de travail mental pour se souvenir de some_google_resource.my_special_resource.id par rapport à some_google_resource.main.id.
  • Pour différencier les ressources d'un même type les unes des autres (par exemple, primary et secondary), utilisez des noms de ressources pertinents.

  • Utilisez des noms de ressource au singulier.

  • Dans le nom de la ressource, ne répétez pas le type de ressource. Exemple :

    Recommandations :

    resource "google_compute_global_address" "main" { ... }
    

    Option déconseillée :

    resource "google_compute_global_address" "main_global_address" { … }
    

Utiliser les variables avec soin

  • Déclarez toutes les variables dans variables.tf.
  • Attribuez aux variables des noms descriptifs pertinents pour leur utilisation ou leur objectif :
    • Les entrées, les variables locales et les sorties représentant des valeurs numériques (tailles de disque ou de RAM, par exemple) doivent être nommées avec des unités (comme ram_size_gb). Les API Google Cloud ne disposant pas d'unités standards, le fait de nommer les variables avec des unités permet d'indiquer clairement l'unité d'entrée attendue pour les responsables de configuration.
    • Pour les unités de stockage, utilisez des préfixes d'unité binaire (puissances de 1 024) : kibi, mebi, gibi. Pour toutes les autres unités de mesure, utilisez des préfixes décimaux d'unités (multiples de 1 000) : kilo, mega, giga. Cette pratique correspond à celle utilisée dans Google Cloud.
    • Pour simplifier la logique conditionnelle, attribuez des noms positifs aux variables booléennes (par exemple, enable_external_access).
  • Les variables doivent comporter une description. Les descriptions sont automatiquement incluses dans la documentation générée automatiquement d'un module publié. Pour les nouveaux développeurs, les descriptions ajoutent un contexte supplémentaire que les noms descriptifs seuls ne peuvent pas fournir.
  • Attribuez des types définis aux variables.
  • Le cas échéant, indiquez les valeurs par défaut :
    • Pour les variables dont les valeurs sont indépendantes de l'environnement (telles que la taille du disque), fournissez les valeurs par défaut.
    • Pour les variables ayant des valeurs spécifiques à l'environnement (telles que project_id), ne fournissez pas de valeurs par défaut. De cette façon, le module appelant doit fournir des valeurs significatives.
  • N'utilisez des valeurs par défaut vides pour les variables (chaînes ou listes vides, par exemple) que si l'utilisation d'une variable vide est une préférence valide que les API sous-jacentes ne rejettent pas.
  • Soyez prudent lorsque vous utilisez des variables. Ne définissez des paramètres que pour les valeurs qui changent pour chaque instance ou chaque environnement. Lorsque vous décidez d'exposer une variable, assurez-vous de disposer d'un cas d'utilisation concret pour la modification de cette variable. S'il est peu probable qu'une variable soit nécessaire, ne l'exposez pas.
    • L'ajout d'une variable avec une valeur par défaut est rétrocompatible.
    • La suppression d'une variable n'est pas rétrocompatible.
    • Dans les cas où un littéral est réutilisé à plusieurs endroits, vous pouvez utiliser une valeur locale sans l'exposer en tant que variable.

Exposer les sorties

  • Organisez toutes les sorties dans un fichier outputs.tf.
  • Fournissez des descriptions pertinentes pour toutes les sorties.
  • Documentez les descriptions de sorties dans le fichier README.md. Générez automatiquement des descriptions sur le commit avec des outils tels que terraform-docs.
  • Générez toutes les valeurs utiles que les modules racine peuvent avoir besoin de référencer ou de partager. En particulier pour les modules Open Source ou hautement utilisés, exposez toutes les sorties ayant un potentiel de consommation.
  • Ne transmettez pas de sorties directement via des variables d'entrée, car cela empêche leur ajout correct au graphe de dépendance. Pour vous assurer que des dépendances implicites sont créées, assurez-vous que les sorties renvoient des attributs de ressources. Au lieu de référencer directement une variable d'entrée pour une instance, transmettez l'attribut comme indiqué ici :

    Recommandations :

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    Option déconseillée :

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

Utiliser des sources de données

  • Placez les sources de données à côté des ressources qui y font référence. Par exemple, si vous récupérez une image à utiliser lors du lancement d'une instance, placez-la avec l'instance plutôt que de collecter des ressources de données dans son propre fichier.
  • Si le nombre de sources de données devient important, envisagez de les déplacer vers un fichier data.tf dédié.
  • Pour extraire les données relatives à l'environnement actuel, utilisez l'interpolation de variables ou de ressources.

Limiter l'utilisation des scripts personnalisés

  • N'utilisez des scripts que si cela est vraiment nécessaire. L'état des ressources créées via des scripts n'est ni pris en compte, ni géré par Terraform.
    • Évitez les scripts personnalisés, si possible. Utilisez-les uniquement lorsque les ressources Terraform ne sont pas compatibles avec le comportement souhaité.
    • Tous les scripts personnalisés utilisés doivent avoir une raison clairement documentée d'existence et, idéalement, un plan d'abandon.
  • Terraform peut appeler des scripts personnalisés via des approvisionneurs, y compris l'approvisionneur local-exec.
  • Placez les scripts personnalisés appelés par Terraform dans un répertoire scripts/.

Inclure les scripts d'aide dans un répertoire distinct

  • Organisez les scripts d'aide qui ne sont pas appelés par Terraform dans un répertoire helpers/.
  • Décrivez les scripts d'aide dans le fichier README.md avec des explications et des exemples d'appels.
  • Si les scripts d'aide acceptent des arguments, incluez une vérification des arguments et une sortie --help.

Placer les fichiers statiques dans un répertoire distinct

  • Les fichiers statiques référencés par Terraform mais non exécutés (tels que les scripts de démarrage chargés sur des instances Compute Engine) doivent être organisés dans un répertoire files/.
  • Placez les documents HereDocs dans des fichiers externes, séparés de leur HCL. Référencez-les en utilisant la fonction file().
  • Pour les fichiers lus à l'aide de la fonction templatefile de Terraform, utilisez l'extension de fichier .tftpl.
    • Les modèles doivent être placés dans un répertoire templates/.

Protéger les ressources avec état

Pour les ressources avec état, telles que les bases de données, assurez-vous que la protection contre la suppression est activée. Exemple :

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Utiliser la mise en forme intégrée

Tous les fichiers Terraform doivent être conformes aux normes de terraform fmt.

Limiter la complexité des expressions

  • Limitez la complexité des expressions interpolées individuelles. Si de nombreuses fonctions sont nécessaires dans une seule expression, envisagez de la diviser en plusieurs expressions en utilisant des valeurs locales.
  • N'exécutez jamais plusieurs opérations ternaires sur une seule ligne. Utilisez plutôt plusieurs valeurs locales pour créer la logique.

Utiliser count pour les valeurs conditionnelles

Pour instancier une ressource de manière conditionnelle, utilisez le méta-argument count. Exemple :

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

Soyez prudent lorsque vous utilisez des variables spécifiées par l'utilisateur pour définir la variable count pour les ressources. Si un attribut de ressource est fourni pour une variable de ce type (comme project_id) et que cette ressource n'existe pas encore, Terraform ne peut pas générer de plan. Au lieu de cela, Terraform rapporte une erreur value of count cannot be computed. Dans ce cas, utilisez une variable enable_x distincte pour calculer la logique conditionnelle.

Utiliser for_each pour les ressources itérées

Si vous souhaitez créer plusieurs copies d'une ressource en vous basant sur une ressource d'entrée, utilisez le méta-argument for_each.

Publier les modules dans un registre

Étapes suivantes