Terraform vs CloudFormation : le guide définitif

Publié: 2022-03-11

Si, comme moi, vous avez parcouru Internet pour vous aider à choisir entre CloudFormation et Terraform comme votre prochain outil d'infrastructure en tant que code (IaC) sans trouver de réponse définitive, j'ai longtemps partagé votre douleur. Maintenant, j'ai une expérience significative avec les deux outils et je peux prendre une décision éclairée sur celui à utiliser.

TL;DR

Pour votre projet IaC sur AWS, choisissez CloudFormation, car :

  1. CloudFormation fait une distinction entre le code (c'est-à-dire les modèles) et les instanciations du code (c'est-à-dire les piles). Dans Terraform, il n'y a pas une telle distinction. Plus d'informations à ce sujet dans la section suivante.
  2. Terraform ne gère pas très bien la gestion des dépendances de base. Plus à ce sujet dans une section ultérieure.

Différencier le code et les instanciations

Une différence entre CloudFormation et Terraform est la façon dont le code et les instanciations sont liés les uns aux autres au sein de chaque service.

CloudFormation a le concept d'une pile , qui est l'instanciation d'un modèle. Le même modèle peut être instancié à l'infini par un client donné dans un compte donné, sur plusieurs comptes ou par différents clients.

Terraform n'a pas un tel concept et nécessite une relation un à un entre le code et son instanciation. Cela reviendrait à dupliquer le code source d'un serveur Web pour chaque serveur que vous souhaitez exécuter, ou à dupliquer le code chaque fois que vous devez exécuter une application au lieu d'exécuter la version compilée.

Ce point est assez trivial dans le cas d'une configuration simple, mais il devient rapidement un point douloureux majeur pour les opérations de moyenne à grande envergure. Dans Terraform, chaque fois que vous devez créer une nouvelle pile à partir de code existant, vous devez dupliquer le code. Et copier/coller des fichiers de script est un moyen très simple de vous saboter et de corrompre des ressources que vous n'aviez pas l'intention de toucher.

Terraform n'a en fait pas de concept de piles comme CloudFormation, ce qui montre clairement que Terraform a été construit à partir de zéro pour avoir une correspondance un à un entre le code et les ressources qu'il gère. Cela a ensuite été partiellement rectifié par le concept d' environnements (qui ont depuis été renommés "espaces de travail"), mais la façon de les utiliser rend incroyablement facile le déploiement dans un environnement indésirable. En effet, vous devez exécuter terraform workspace select avant le déploiement, et l'oubli de cette étape entraînera le déploiement dans l'espace de travail précédemment sélectionné, qui peut ou non être celui que vous souhaitez.

En pratique, il est vrai que ce problème est atténué par l'utilisation de modules Terraform, mais même dans le meilleur des cas, vous auriez besoin d'une quantité importante de code passe-partout. En fait, ce problème était si aigu que les gens devaient créer un outil wrapper autour de Terraform pour résoudre ce problème : Terragrunt.

Gestion de l'état et autorisations

Une autre différence importante entre CloudFormation et Terraform est la façon dont ils gèrent chacun l'état et les autorisations.

CloudFormation gère les états de pile pour vous et ne vous donne aucune option. Mais les états de la pile CloudFormation ont été solides d'après mon expérience. De plus, CloudFormation permet aux utilisateurs moins privilégiés de gérer des piles sans avoir toutes les autorisations nécessaires requises par la pile elle-même. En effet, CloudFormation peut obtenir les autorisations d'un rôle de service attaché à la pile plutôt que les autorisations de l'utilisateur exécutant l'opération de pile.

Terraform vous oblige à lui fournir des back-ends pour gérer les états. La valeur par défaut est un fichier local, ce qui est totalement insatisfaisant étant donné :

  1. La robustesse de votre fichier d'état est entièrement liée à la robustesse de la machine sur laquelle il est stocké.
  2. Cela rend à peu près impossible le travail d'équipe.

Vous avez donc besoin d'un état robuste et partagé, qui sur AWS est généralement obtenu en utilisant un compartiment S3 pour stocker le fichier d'état, accompagné d'une table DynamoDB pour gérer la simultanéité.

Cela signifie que vous devez créer manuellement un compartiment S3 et une table DynamoDB pour chaque pile que vous souhaitez instancier, et également gérer manuellement les autorisations pour ces deux objets afin d'empêcher les utilisateurs moins privilégiés d'accéder aux données auxquelles ils ne devraient pas avoir accès. Si vous n'avez que quelques piles, ce ne sera pas trop un problème, mais si vous avez 20 piles à gérer, cela devient très lourd.

À propos, lors de l'utilisation des espaces de travail Terraform, il n'est pas possible d'avoir une table DynamoDB par espace de travail. Cela signifie que si vous souhaitez qu'un utilisateur IAM disposant d'autorisations minimales puisse effectuer des déploiements, cet utilisateur pourra manipuler les verrous de tous les espaces de travail, car les autorisations DynamoDB ne sont pas affinées au niveau de l'élément.

Gestion des dépendances

Sur ce point, CloudFormation et Terraform peuvent être un peu délicats. Si vous modifiez l'ID logique (c'est-à-dire le nom) d'une ressource, les deux considéreront que l'ancienne ressource doit être détruite et qu'une nouvelle doit être créée. C'est donc généralement une mauvaise idée de changer l'ID logique des ressources dans l'un ou l'autre des outils, en particulier pour les piles imbriquées dans CloudFormation.

Comme mentionné dans la première section, Terraform ne gère pas les dépendances de base. Malheureusement, les développeurs de Terraform n'accordent pas beaucoup d'attention à ce problème de longue date, malgré le manque apparent de solutions de contournement.

Étant donné qu'une bonne gestion des dépendances est absolument essentielle pour un outil IaC, de tels problèmes dans Terraform remettent en question son adéquation dès que des opérations critiques pour l'entreprise sont impliquées, telles que le déploiement dans un environnement de production. CloudFormation donne une sensation beaucoup plus professionnelle, et AWS est toujours très attentif à s'assurer qu'il offre des outils de qualité production à ses clients. Depuis toutes ces années que j'utilise CloudFormation, je n'ai jamais rencontré de problème avec la gestion des dépendances.

CloudFormation permet à une pile d'exporter certaines de ses variables de sortie, qui peuvent ensuite être réutilisées par d'autres piles. Pour être honnête, cette fonctionnalité est limitée, car vous ne pourrez pas instancier plus d'une pile par région. En effet, vous ne pouvez pas exporter deux variables portant le même nom et les variables exportées n'ont pas d'espaces de noms.

Terraform n'offre pas de telles installations, il vous reste donc des options moins souhaitables. Terraform vous permet d'importer l'état d'une autre pile, mais cela vous donne accès à toutes les informations de cette pile, y compris les nombreux secrets stockés dans l'état. Alternativement, une pile peut exporter certaines variables sous la forme d'un fichier JSON stocké dans un compartiment S3, mais encore une fois, cette option est plus lourde : vous devez décider quel compartiment S3 utiliser et lui donner les autorisations appropriées, et écrire tous les code de plomberie vous-même du côté de l'écrivain et du côté du lecteur.

L'un des avantages de Terraform est qu'il dispose de sources de données. Terraform peut ainsi interroger des ressources non gérées par Terraform. Cependant, en pratique, cela a peu de pertinence lorsque vous souhaitez écrire un modèle générique, car vous n'assumerez alors rien sur le compte cible. L'équivalent dans CloudFormation est d'ajouter plus de paramètres de modèle, ce qui implique donc des répétitions et un potentiel d'erreurs ; cependant, d'après mon expérience, cela n'a jamais été un problème.

Pour en revenir au problème de la gestion des dépendances de Terraform, un autre exemple est que vous obtenez une erreur lorsque vous essayez de mettre à jour les paramètres d'un équilibreur de charge et obtenez ce qui suit :

 Error: Error deleting Target Group: ResourceInUse: Target group 'arn:aws:elasticloadbalancing:us-east-1:723207552760:targetgroup/strategy-api-default-us-east-1/14a4277881e84797' is currently in use by a listener or a rule status code: 400, request id: 833d8475-f702-4e01-aa3a-d6fa0a141905

Le comportement attendu serait que Terraform détecte que le groupe cible est une dépendance d'une autre ressource qui n'est pas supprimée, et par conséquent, il ne devrait pas essayer de le supprimer, mais il ne devrait pas non plus générer d'erreur.

Opérations

Bien que Terraform soit un outil de ligne de commande, il est très clair qu'il s'attend à ce qu'un humain l'exécute, car il est très interactif. Il est possible de l'exécuter en mode batch (c'est-à-dire à partir d'un script), mais cela nécessite des arguments supplémentaires en ligne de commande. Le fait que Terraform ait été développé pour être exécuté par des humains par défaut est assez déroutant, étant donné que le but d'un outil IaC est l'automatisation.

Terraform est difficile à déboguer. Les messages d'erreur sont souvent très basiques et ne vous permettent pas de comprendre ce qui ne va pas, auquel cas vous devrez exécuter Terraform avec TF_LOG=debug , ce qui produit une énorme quantité de sortie à parcourir. Pour compliquer cela, Terraform effectue parfois des appels d'API à AWS qui échouent, mais l'échec n'est pas un problème avec Terraform. En revanche, CloudFormation fournit des messages d'erreur raisonnablement clairs avec suffisamment de détails pour vous permettre de comprendre où se situe le problème.

Un exemple de message d'erreur Terraform :

 Error: error reading S3 bucket Public Access Block: NoSuchBucket: The specified bucket does not exist status code: 404, request id: 19AAE641F0B4AC7F, host id: rZkgloKqxP2/a2F6BYrrkcJthba/FQM/DaZnj8EQq/5FactUctdREq8L3Xb6DgJmyKcpImipv4s=

Le message d'erreur ci-dessus affiche un message d'erreur clair qui ne reflète pas le problème sous-jacent (qui dans ce cas était un problème d'autorisations).

Ce message d'erreur montre également comment Terraform peut parfois se peindre dans un coin. Par exemple, si vous créez un compartiment S3 et une ressource aws_s3_bucket_public_access_block sur ce compartiment, et si, pour une raison quelconque, vous apportez des modifications au code Terraform qui détruit ce compartiment, par exemple, dans le piège « le changement implique la suppression et la création » décrit ci-dessus— Terraform se bloquera en essayant de charger le aws_s3_bucket_public_access_block mais échouera continuellement avec l'erreur ci-dessus. Le comportement correct de Terraform serait de remplacer ou de supprimer aws_s3_bucket_public_access_block selon le cas.

Enfin, vous ne pouvez pas utiliser les scripts d'assistance CloudFormation avec Terraform. Cela peut être gênant, surtout si vous espérez utiliser cfn-signal, qui indique à CloudFormation qu'une instance EC2 a fini de s'initialiser et est prête à répondre aux requêtes.

Syntaxe, communauté et restauration

En termes de syntaxe, Terraform a un bon avantage par rapport à CloudFormation : il prend en charge les boucles. Mais d'après ma propre expérience, cette fonctionnalité peut s'avérer un peu dangereuse. Typiquement, une boucle serait utilisée pour créer un certain nombre de ressources identiques ; cependant, lorsque vous souhaitez mettre à jour la pile avec un nombre différent, il se peut que vous deviez lier les anciennes et les nouvelles ressources (par exemple, en utilisant zipmap() pour combiner les valeurs de deux tableaux qui se trouvent maintenant être de tailles différentes car un tableau a la taille de l'ancienne taille de boucle et l'autre a la taille de la nouvelle taille de boucle). Il est vrai qu'un tel problème peut se produire sans boucles, mais sans boucles, le problème serait beaucoup plus évident pour la personne qui écrit le script. L'utilisation de boucles dans un tel cas obscurcit le problème.

Que la syntaxe de Terraform ou la syntaxe de CloudFormation soit meilleure est principalement une question de préférences. CloudFormation ne prenait initialement en charge que JSON, mais les modèles JSON sont très difficiles à lire. Heureusement, CloudFormation prend également en charge YAML, qui est beaucoup plus facile à lire et autorise les commentaires. Cependant, la syntaxe de CloudFormation a tendance à être assez verbeuse.

La syntaxe de Terraform utilise HCL, qui est une sorte de dérivé JSON et est assez idiosyncratique. Terraform offre plus de fonctions que CloudFormation, et elles sont généralement plus faciles à comprendre. On pourrait donc affirmer que Terraform a un léger avantage sur ce point.

Un autre avantage de Terraform est son ensemble facilement disponible de modules gérés par la communauté, ce qui simplifie la rédaction de modèles. Un problème pourrait être que ces modules pourraient ne pas être suffisamment sécurisés pour se conformer aux exigences d'une organisation. Ainsi, pour les organisations nécessitant un niveau de sécurité élevé, la révision de ces modules (ainsi que des versions ultérieures au fur et à mesure) peut être une nécessité.

De manière générale, les modules Terraform sont beaucoup plus flexibles que les piles imbriquées CloudFormation. Une pile imbriquée CloudFormation a tendance à masquer tout ce qui se trouve en dessous. À partir de la pile imbriquée, une opération de mise à jour montrerait que la pile imbriquée sera mise à jour mais ne montre pas en détail ce qui va se passer à l'intérieur de la pile imbriquée.

Un dernier point, qui pourrait être controversé en fait, est que CloudFormation tente de restaurer les déploiements ayant échoué. C'est une fonctionnalité assez intéressante mais qui peut malheureusement être très longue (par exemple, cela peut prendre jusqu'à trois heures à CloudFormation pour décider qu'un déploiement sur Elastic Container Service a échoué). En revanche, en cas d'échec, Terraform s'arrête là où il se trouvait. Qu'une fonction de restauration soit une bonne chose ou non est discutable, mais j'en suis venu à apprécier le fait qu'une pile est maintenue dans un état de fonctionnement autant que possible lorsqu'une attente plus longue s'avère être un compromis acceptable.

À la défense de Terraform contre CloudFormation

Terraform présente des avantages par rapport à CloudFormation. Le plus important, à mon avis, est que lors de l'application d'une mise à jour, Terraform vous montre toutes les modifications que vous êtes sur le point d'apporter, y compris l'exploration de tous les modules qu'il utilise. En revanche, CloudFormation, lors de l'utilisation de piles imbriquées, vous indique uniquement que la pile imbriquée doit être mise à jour, mais ne fournit pas de moyen d'approfondir les détails. Cela peut être frustrant, car ce type d'informations est assez important à connaître avant d'appuyer sur le bouton "go".

CloudFormation et Terraform prennent en charge les extensions. Dans CloudFormation, il est possible de gérer des ressources dites « personnalisées » en utilisant une fonction AWS Lambda de votre propre création comme back-end. Pour Terraform, les extensions sont beaucoup plus faciles à écrire et font partie du code. Il y a donc un avantage pour Terraform dans ce cas.

Terraform peut gérer de nombreux fournisseurs de cloud. Cela permet à Terraform d'être en mesure d'unifier un déploiement donné sur plusieurs plates-formes cloud. Par exemple, supposons que vous ayez une seule charge de travail répartie entre AWS et Google Cloud Platform (GCP). Normalement, la partie AWS de la charge de travail serait déployée à l'aide de CloudFormation et la partie GCP à l'aide du gestionnaire de déploiement cloud de GCP. Avec Terraform, vous pouvez à la place utiliser un seul script pour déployer et gérer les deux piles sur leurs plates-formes cloud respectives. De cette façon, vous n'avez qu'à déployer une pile au lieu de deux.

Non-arguments pour Terraform vs CloudFormation

Il y a pas mal de non-arguments qui continuent de circuler sur Internet. Le plus important est que, comme Terraform est multi-cloud, vous pouvez utiliser un seul outil pour déployer tous vos projets, quelle que soit la plate-forme cloud sur laquelle ils sont réalisés. Techniquement, c'est vrai, mais ce n'est pas le gros avantage que cela peut sembler être, en particulier lors de la gestion de projets typiques à cloud unique. La réalité est qu'il existe une correspondance presque univoque entre les ressources déclarées dans (par exemple) CloudFormation et les mêmes ressources déclarées dans un script Terraform. Étant donné que vous devez connaître les détails des ressources spécifiques au cloud de toute façon, la différence se résume à la syntaxe, qui n'est pas le plus gros problème dans la gestion des déploiements.

Certains affirment qu'en utilisant Terraform, on peut éviter la dépendance vis-à-vis d'un fournisseur. Cet argument ne tient pas dans le sens où en utilisant Terraform, vous êtes enfermé par HashiCorp (le créateur de Terraform), de la même manière qu'en utilisant CloudFormation, vous êtes enfermé par AWS, et ainsi de suite pour d'autres cloud plates-formes.

Le fait que les modules Terraform soient plus faciles à utiliser est pour moi de moindre importance. Tout d'abord, je pense qu'AWS veut délibérément éviter d'héberger un référentiel unique pour les modèles CloudFormation basés sur la communauté en raison de la responsabilité perçue des failles de sécurité et des violations de divers programmes de conformité.

À un niveau plus personnel, je comprends parfaitement les avantages de l'utilisation de bibliothèques dans le cas du développement de logiciels, car ces bibliothèques peuvent facilement exécuter des dizaines de milliers de lignes de code. Dans le cas d'IaC, cependant, la taille du code est généralement bien inférieure et ces modules font généralement quelques dizaines de lignes. Utiliser le copier/coller n'est en fait pas une si mauvaise idée dans le sens où cela évite les problèmes de compatibilité et de délégation de votre sécurité à des inconnus.

L'utilisation du copier/coller est mal vue par de nombreux développeurs et ingénieurs DevOps, et il y a de bonnes raisons à cela. Cependant, mon point de vue est que l'utilisation du copier/coller pour des extraits de code vous permet de l'adapter facilement à vos besoins, et il n'est pas nécessaire d'en faire une bibliothèque et de passer beaucoup de temps à le rendre générique. La difficulté de maintenir ces extraits de code est généralement très faible, à moins que votre code ne soit dupliqué dans, disons, une douzaine de modèles ou plus. Dans un tel cas, s'approprier le code et l'utiliser en tant que piles imbriquées a du sens, et les avantages de ne pas se répéter sont probablement plus importants que l'ennui de ne pas pouvoir voir ce qui va être mis à jour à l'intérieur de la pile imbriquée lorsque vous effectuez une mise à jour opération.

Conclusion entre CloudFormation et Terraform

Avec CloudFormation, AWS souhaite fournir à ses clients un outil solide comme le roc qui fonctionnera comme prévu à tout moment. L'équipe de Terraform aussi, bien sûr, mais il semble qu'un aspect crucial de leur outillage, la gestion des dépendances, ne soit malheureusement pas une priorité.

Terraform peut avoir sa place dans votre projet, en particulier si vous avez une architecture multi-cloud, auquel cas les scripts Terraform sont un moyen d'unifier la gestion des ressources entre les différents fournisseurs de cloud que vous utilisez. Mais vous pouvez toujours éviter les inconvénients de Terraform dans ce cas en utilisant uniquement Terraform pour gérer les piles déjà implémentées à l'aide de leurs outils IaC respectifs spécifiques au cloud.

Le sentiment général de Terraform par rapport à CloudFormation est que CloudFormation, bien qu'imparfait, est plus professionnel et fiable, et je le recommanderais certainement pour tout projet qui n'est pas spécifiquement multi-cloud.