Focus sur AWS Transit Gateway : partie 3, l’automatisation
La solution décrite dans cet article est disponible en Open-Source ! Si vous préférez voir et toucher plutôt que lire, c’est par ici : Github Transit Gateway Automation
Dans le précédent article, j’expliquais ma méthode pour traduire des besoins réseaux fonctionnels (quels VPCs doivent parler avec quels VPC) en configuration Transit Gateway de manière systématique. Si la méthode est efficace, je ne peux pas nier qu’elle est barbante à mettre en œuvre à cause du grand nombre de routes inutiles qu’elle génère. Dans cet article, nous allons donc voir comment ajouter de l’automatisation avec CloudFormation (on peut aussi le faire avec Terraform, mais c’est bien plus facile donc beaucoup moins drôle).
Mais avant de se lancer, que doit-on automatiser au juste ? Bien entendu, la création de la Transit Gateway et de ses tables de routages doit l’être, mais c’est une opération à faire une seule fois et elle ne présente pas de challenge particulier : un template CloudFormation permet la mise en œuvre sans problème.
En revanche, l’ajout de nouveaux VPC au cours du temps dans des “bulles” choisies est un problème autrement plus complexe et intéressant. Dans la suite, nous allons supposer que la création de la Transit Gateway et de ses tables de routages (les bulles) est déjà traitée et nous concentrer uniquement sur l’ajout d’un nouveau VPC dans notre réseau.
Que veut dire ajouter un nouveau VPC ?
Supposons que nous ayons déjà une Transit Gateway et que nous voulons automatiser l’ajout d’un nouveau VPC. Nous devons donc :
- Créer un nouveau VPC (qui l’eut cru) ;
- Créer un Attachment vers la Transit Gateway ;
- Ajouter les routes appropriées vers la Transit Gateway dans les Route Tables du VPC ;
- Associer le nouvel Attachment à sa bulle, c’est à dire à sa Transit Gateway Route Table (TGWRT) ;
- Créer les routes et les blackholes appropriés dans les autres bulles / TGWRT (voir la partie 2 pour se rafraîchir la mémoire sur ces concepts).
Puisqu’on souhaite utiliser CloudFormation, cette séquence d’action n’est pas si évidente. La création d’un VPC est bien entendu triviale, mais dès la deuxième étape un problème se pose : comment connaît-on l’ID de la Transit Gateway ? Avec Terraform, on adresse ce problème aisément à l’aide d’un bloc “data” qui permet de rechercher la Transit Gateway grâce à un tag par exemple, mais CloudFormation ne dispose pas nativement d’un mécanisme équivalent.
Autre problème, nos trois premières étapes vont se dérouler dans un compte AWS, celui de l’application ou du projet pour lequel on crée le nouveau VPC, mais les deux dernières étapes se dérouleront dans le compte “Network” qui héberge la Transit Gateway. Sauf exception, ce sont deux comptes séparés. Là encore, si Terraform permet de traiter des opérations cross-accounts à l’aide de différents “provider”, CloudFormation ne s’en sort pas aussi bien.
Enfin, les actions des étapes 4 et 5 sont dépendantes de la “bulle” du nouveau VPC et sont complètement différentes d’une bulle à l’autre. On pourrait utiliser les “Conditions” de CloudFormation, mais cela rendrait le template difficile à lire et maintenir si on se met à gérer un grand nombre de bulles.
Devant ces difficultés, il y a deux approches possibles.
Primo, CloudFormation permet de définir des “Custom Resources”, c’est-à-dire des fonctions Lambda dans lesquelles on écrit absolument ce qu’on veut. Avec ça, on peut faire toutes les recherches d’ID et toutes les opérations cross-account souhaitées. Mais conserver la robustesse de CloudFormation en s’appuyant massivement sur des Custom Resources est… difficile. On évite donc d’en faire à moins que ça ne soit absolument nécessaire.
Secondo, on peut remarquer que notre déploiement VPC peut se réaliser avec seulement 2 templates CloudFormation successifs :
- un pour faire les actions dans le compte “applicatif” (créer le VPC, etc…) ;
- un pour faire les actions dans le compte “network” en fonction de la bulle choisie.
Il nous “suffirait” donc d’avoir un système capable d’orchestrer le déploiement de deux templates CloudFormation dans des comptes AWS différents et de sélectionner le second template en fonction d’une variable. Si ce système pouvait également aller chercher l’ID d’une Transit Gateway, on aurait gagné…
Une Step Function à la rescousse
A ce stade, l’utilisation d’une Step Function devient de plus en plus tentante. AWS Step Functions est un service serverless permettant d’orchestrer n’importe quel workflow en le représentant sous forme d’automate à états finis où chaque état correspond en général à l’invocation d’une fonction Lambda. Le service permet ainsi d’exécuter des lambda en séquence ou en parallèle, ou encore de faire des choix en fonction du résultat d’une lambda.
Pour résoudre notre problème, on peut donc faire une Step Function qui :
- Recherche l’ID de la Transit Gateway et de manière générale tous les paramètres dont on a besoin pour faire les étapes 1 à 3 de notre ajout de nouveau VPC ;
- Assume un rôle prédéfini dans le compte AWS cible et déploie le template CloudFormation du nouveau VPC, avec les paramètres établis à l’étape précédente ;
- Attend la fin du déploiement ;
- Recherche l’ID du nouvel attachement (c’est un Output du déploiement du VPC) et les autres paramètres nécessaires pour les étapes 4 et 5 ;
- Assume un rôle prédéfini dans le compte AWS “network”, choisi un template CloudFormation en fonction de la bulle désirée et le déploie avec les paramètres établis à l’étape précédente ;
- Attend la fin du déploiement.
Toutes les étapes sont faisables avec des lambdas sauf les étapes d’attente pour lesquelles Lambda n’est pas idéal :
- Ce n’est pas très propre d’attendre dans une lambda et cela coûte de l’argent pour rien ;
- Il pourrait se trouver qu’un déploiement prenne plus de 15 minutes (bon, en vrai ça en prend 2, mais concentrons nous sur les principes) et donc dépasse le temps d’exécution maximum d’une Lambda.
“Attendre” dans une Step Function est en fait assez facile en utilisant deux états spéciaux mis à disposition par le service :
- “Choice” qui permet de choisir la prochaine étape en fonction de la valeur d’une variable ;
- “Wait” qui est simplement un état d’attente paramétrable, comme un “sleep” dans un programme.
En utilisant les deux conjointement on peut facilement mettre en place une logique d’attente :
Pour l’étape de déploiement dans le compte “Network”, on veut pouvoir choisir le template en fonction du nom de la “bulle”. Une solution simple est de mettre les templates dans S3 en faisant en sorte que la clé de chacun puisse être déduite du nom de la bulle. Par exemple, on a une variable “Bubble” = “shared-services” et on sait alors qu’on doit trouver “/templates/shared-services.yml” dans un bucket S3 prédéfini. C’est simple, et ça permet facilement d’ajouter de nouveaux templates/bulles.
Au global, la solution proposée ressemble à ça :
Cliquez ici pour le schéma en grand format
Notez que la Step Function est dans un compte AWS désigné “Manager Account”. En effet, comme on doit de toute façon agir dans plusieurs comptes pour faire notre déploiement, il y a une certaine logique à déporter notre automatisation dans un compte dédié. Mais il pourrait être tout aussi logique que le compte “Network” contenant la Transit Gateway soit aussi le compte hébergeant la Step Function. L’implémentation de référence proposée sur Github vous permet de faire ce qui vous convient le mieux.
Une bulle, un template
Il est intéressant de s’attarder un peu sur le fait qu’on utilise un template différent pour chaque bulle. Dans ce genre de cas, on a généralement un problème : comment gérer le fait que les templates pourraient avoir différents jeux de paramètres ?
C’est une excellente question et un problème potentiellement très complexe, mais heureusement… les paramètres sont toujours les mêmes ! D’ailleurs, la structure des templates est toujours la même également. Voyons pourquoi.
Pour configurer un nouvel attachment selon sa bulle, que doit-on faire ? Si on se réfère à la méthode du précédent article, mettre un VPC dans une bulle “A” signifie :
- Associer l’attachment du VPC avec la Transit Gateway Route Table (TGWRT) correspondant à la bulle “A”
- Créer une route vers cet attachment dans toutes les bulles/TGWRT avec lesquelles la bulle “A” doit communiquer
- Créer un blackhole pour la route de cet attachment dans toutes les bulles/TGWRT avec lesquelles la bulle “A” ne doit pas communiquer
Etant donné que pour chaque bulle, la bulle “A” peut soit communiquer soit pas (il n’y a pas d’entre deux du genre “je peux communiquer un peu mais pas trop”), on se rend compte qu’on va devoir ajouter soit une route soit un blackhole dans chaque TGWRT. Ainsi, les templates sont toujours similaires, avec le même nombre de ressources :
- Une ressource pour faire l’association ;
- Une ressource pour chaque TGWRT pour créer soit une route soit un blackhole.
Dans tous les cas, on a besoin de connaître les mêmes informations dans le template :
- L’ID du nouvel attachment
- Le CIDR Block du nouveau VPC
- Les ID de toutes les TGWRT
Pour les ID des TGWRT, on peut se baser sur les exports CloudFormation. Reste à connaître l’ID de l’attachment et le CIDRBlock du VPC : ce sont les deux seuls paramètres attendus par tous les “templates de bulle”.
Voici par exemple le template “shared-services.yml” (disponible dans le Github) :
Notez l’usage des “ImportValue” pour trouver les ID des tables de routage existantes.
Revenir à une expérience un peu plus native grâce aux Custom Resources
A ce stade, on parle de faire une Step Function pour déployer notre nouveau VPC, avec l’appui de 2 templates CloudFormation. Or, écrire un JSON de paramètres pour aller lancer une Step Function est relativement éloigné de l’expérience CloudFormation native.
Et puis que se passe-t-il quand je veux détruire le VPC ? Ou si je veux modifier sa bulle ?
La solution proposée prévoit d’autres Step Function pour faire ça mais cela nous éloigne encore plus de l’expérience IaC native.
Ici, une “Custom Resource” peut aider. Si je simplifie un brin, une Custom Resource CloudFormation est essentiellement une lambda que CloudFormation appelle en disant “Il faut [CREATE | UPDATE | DELETE]” et en passant directement les paramètres de la ressource.
Par exemple, la Custom Resource suivante appelle une lambda dont on trouve l’ARN via un export CloudFormation pré-existant, c’est le paramètre “ServiceToken”. “ServiceToken” est le seul paramètre obligatoire, tous les autres paramètres sont considérés comme “custom” et passés à la Lambda :
Toute la logique derrière est à la main de la lambda. Cela fait des Custom Resources des outils extrêmement puissants : en théorie, il n’y a absolument rien d’impossible à faire avec ce mécanisme. Par exemple, avec un (très) gros effort, on pourrait parfaitement imaginer avoir un ensemble de Custom Resources qui permette de faire des déploiements sur Azure avec CloudFormation… mais le fait que quelque chose soit possible n’en fait pas pour autant une bonne idée.
Dans le cas qui nous intéresse, une Custom Resource nous permet d’avoir un “aiguilleur” qui pourra appeler la bonne Step Function avec les bons paramètres en fonction de l’opération qu’on souhaite réaliser :
Grâce à cela on revient à une expérience native CloudFormation pour déployer nos VPC dans nos bulles Transit Gateway : on a juste à déployer un template CF avec quelques paramètres, comme d’habitude.
Ca a l’air intéressant, mais c’est très théorique tout ça
Je me rends bien compte que malgré tous mes efforts, il est impossible d’appréhender correctement une solution aussi complexe avec seulement des mots et 4 schémas.
C’est pourquoi, si vous voulez approfondir, je ne peux que vous encourager à aller sur le Github contenant mon implémentation de référence : un readme bien étoffé (en anglais) vous permettra de déployer la solution vous même, pas à pas, depuis rien (il vous faudra tout de même au moins un compte AWS). Vous pourrez ainsi beaucoup mieux appréhender la solution et en constater les bénéfices et les limites. Le coût sera négligeable dans le cadre d’un bac à sable d’entreprise, mais pour ceux qui n’ont qu’un compte personnel à disposition, sachez que tester la solution sur une journée entière avec 2 ou 3 VPC attachés à une Transit Gateway ne devrait pas vous coûter plus de 5$ pour 8 heures en Irlande.