TP 2 - Terraform đŸȘ et la crĂ©ation d’infrastructure avec du code đŸ‘©â€đŸ’»

Mise en place

Allez sur la plateforme AWS Academy et accédez au cours AWS Academy Learner Lab. Puis cliquez sur Modules > Learner Lab. Lancez votre environnement en cliquant sur Start Lab. Une fois le cercle passé au vert, cliquez sur AWS Details et AWS CLI. Ces clés permettent un accÚs programmatique à votre compte.

Ouvrez un terminal et exĂ©cutez la commande aws configure. Un prompt va vous demander votre AWS Access Key ID, collez la valeur de aws_access_key_id. Faites de mĂȘme pour la Secret Access Key. Pour la rĂ©gion par dĂ©faut, entrez “us-east-1”. Validez le dernier prompt. Allez ensuite modifier le fichier credentials qui se trouve dans le dossier .aws (attention ce dossier est cachĂ©).

CrĂ©ez un dossier cloud-computing avec la commande mkdir "cloud-computing". DĂ©placez-vous dans le dossier avec la commande cd "cloud computing". Clonez le dĂ©pĂŽt git du TP avec un git clone https://github.com/HealerMikado/Ensai-CloudComputingLab2.git. Au dĂ©but de chaque exercice, vous allez devoir exĂ©cuter un pipenv sync dans le dossier de l’exercice. Cela va crĂ©er un environnement virtuel Python et y installer toutes les dĂ©pendances de l’exercice. Pour que VScode reconnaisse les modules que vous allez utiliser, il faut lui spĂ©cifier l’interprĂ©teur qu’il doit utiliser. Faites un Ctrl+Shift+P, tapez Select Interpreter et prenez l’interprĂ©teur pipenv ex X cdktf ....

📩 À cause du fonctionnement de Python, cela va multiplier les environnements virtuels et le stockage qui leur est associĂ©. Une solution moins gourmande en espace disque mais moins Ă©lĂ©gante est de rĂ©utiliser toujours le mĂȘme espace.

Mon premier script avec le CDK Terraform

Une instance de base

Ouvrez le fichier main.py. Il contient l’architecture minimale du code nĂ©cessaire pour que vous puissiez rĂ©aliser le TP.

from constructs import Construct
from cdktf import App, TerraformStack, TerraformOutput
from cdktf_cdktf_provider_aws.provider import AwsProvider
from cdktf_cdktf_provider_aws.instance import Instance, InstanceEbsBlockDevice
from cdktf_cdktf_provider_aws.security_group import SecurityGroup, SecurityGroupIngress, SecurityGroupEgress
from user_data import user_data

class MyStack(TerraformStack):
    def __init__(self, scope: Construct, id: str):
        super().__init__(scope, id)

        AwsProvider(self, "AWS", region="us-east-1")
        TerraformOutput(
            self, "public_ip",
            value=instance.public_ip,
            )


app = App()
MyStack(app, "cloud_commputing")

app.synth()

Voici tous les imports dont vous aurez besoin. Ils ne sont pas toujours Ă©vidents Ă  trouver, c’est pourquoi je vous les fournis.

La classe MyStack va contenir toute votre architecture. Pour associer les services que vous allez créer à votre stack, vous allez passer en paramÚtre la stack courante à tous vos objets. Ainsi, tous les objets AWS que vous allez créer vont avoir en premier argument self.

Maintenant, vous allez dĂ©finir votre premiĂšre ressource. La classe du cdktf associĂ©e Ă  une instance EC2 d’AWS est la classe Instance. Les deux premiers arguments Ă  passer au constructeur de la classe Instance sont la stack courante et un id sous la forme d’une chaĂźne de caractĂšres.

đŸ§™â€â™‚ïž Sauf rare exception, tous les objets que vous allez crĂ©er vont avoir comme premiers arguments la stack courante et un id.

instance = Instance(
    self, "webservice")

Ensuite, via des paramĂštres nommĂ©s, vous allez dĂ©finir un peu plus en dĂ©tail votre instance. Rappelez-vous, pour une instance EC2, il vous faut dĂ©finir son OS (appelĂ© AMI chez AWS) et le type d’instance.

Ajoutez Ă  votre instance son AMI avec le paramĂštre ami qui prendra comme valeur ami-04b4f1a9cf54c11d0 (c’est l’identifiant de l’AMI Ubuntu dans la rĂ©gion us-east-1), et pour le type d’instance, vous prendrez une t2.micro. ExĂ©cutez votre architecture avec la commande cdktf deploy dans le terminal. Connectez-vous au tableau de bord EC2 et vĂ©rifiez que votre instance est bien dĂ©marrĂ©e. NĂ©anmoins, si vous essayez de vous connecter en SSH Ă  votre instance, vous allez voir que c’est impossible. En effet, lors de la dĂ©finition de l’instance, nous n’avons pas dĂ©fini la clĂ© SSH Ă  utiliser et le security group de l’instance. Tout cela fait que, pour le moment, l’instance n’est pas accessible.

Configuration de la partie réseau

Vu que ce n’est pas intĂ©ressant Ă  trouver seul, voici le code pour dĂ©finir le security group de l’instance :

security_group = SecurityGroup(
    self, "sg-tp",
    ingress=[
        SecurityGroupIngress(
            from_port=22,
            to_port=22,
            cidr_blocks=["0.0.0.0/0"],
            protocol="TCP",
            description="Accept incoming SSH connection"
        ),
        SecurityGroupIngress(
            from_port=80,
            to_port=80,
            cidr_blocks=["0.0.0.0/0"],
            protocol="TCP",
            description="Accept incoming HTTP connection"
        )
    ],
    egress=[
        SecurityGroupEgress(
            from_port=0,
            to_port=0,
            cidr_blocks=["0.0.0.0/0"],
            protocol="-1",
            description="allow all egresse connection"
        )
    ]
)

Ce Security Group n’accepte que les connexions HTTP et SSH en entrĂ©e et permet tout le trafic en sortie. Pour associer ce Security Group Ă  votre instance, vous allez devoir ajouter un paramĂštre security_groups lors de la crĂ©ation de l’objet. Attention, ce paramĂštre attend une liste de Security Groups. Pour dĂ©finir la clĂ©, ajoutez le paramĂštre key_name avec comme valeur le nom de la clĂ© (vockey). Vous pouvez maintenant relancer votre instance avec un nouveau cdktf deploy. Cela va rĂ©silier l’instance prĂ©cĂ©dente et en crĂ©er une nouvelle.

Configuration des user data

Pour le moment, nous n’avons pas dĂ©fini les user data de l’instance. Pour les ajouter, il faut simplement ajouter le paramĂštre user_data_base64 avec comme valeur la variable contenue dans user_data.py (la valeur est dĂ©jĂ  importĂ©e). Relancez votre stack et, aprĂšs quelques instants, vous pourrez vous connecter au webservice de l’instance. Utilisez l’IP qui s’affiche dans votre terminal aprĂšs un cdktf deploy.

Configuration des disques (bonus)

Actuellement, l’instance créée n’a qu’un disque de 8 Go. C’est suffisant, mais il est possible de changer cela via Terraform. Par exemple, ajoutez ce bout de code Ă  votre instance.

ebs_block_device= [InstanceEbsBlockDevice(
    device_name="/dev/sda1",
    delete_on_termination=True,
    encrypted=False,
    volume_size=20,
    volume_type="gp2"
),
InstanceEbsBlockDevice(
    device_name="/dev/sdb",
    delete_on_termination=True,
    encrypted=False,
    volume_size=100,
    volume_type="gp2"
)]

Le premier disque de l’instance aura ainsi un volume de 20 Go, et un second disque sera attachĂ© avec un volume de 100 Go. Les deux disques seront supprimĂ©s en mĂȘme temps que l’instance. Vous pouvez voir les deux disques en vous connectant Ă  l’instance en SSH et en exĂ©cutant la commande df -h (disk free).

Mise en place d’un Auto Scaling Group et d’un Load Balancer

Ci-dessous, vous trouverez l’architecture finale que vous allez mettre en place pour ce TP. Elle est un peu plus dĂ©taillĂ©e que lors du prĂ©cĂ©dent TP pour faire apparaĂźtre chaque Ă©lĂ©ment que vous allez devoir dĂ©finir. Se dĂ©tacher de l’interface graphique pour utiliser un outil IaC fait rĂ©aliser Ă  quel point la console AWS masque de nombreux dĂ©tails. Tout implĂ©menter n’est pas difficile, mais est laborieux quand on n’est pas guidĂ©. Toutes les Ă©tapes sont dĂ©coupĂ©es pour ĂȘtre unitaires et simples. Elles consistent toutes Ă  dĂ©finir un objet Python avec la bonne classe et les bons paramĂštres. Ce n’est pas simple de trouver cela seul, alors je vous donne tout. Il suffit de suivre le TP Ă  votre rythme.

Vous trouverez le code de cet exercice dans le dossier ex 2 cdktf haute dispo.

Launch Template

La premiĂšre Ă©tape va ĂȘtre de dĂ©finir le template des instances de l’Auto Scaling Group. Pour cela, vous allez utiliser la classe LaunchTemplate. Comme un template est quasiment la mĂȘme chose qu’une instance, l’objet LaunchTemplate va fortement ressembler Ă  une instance, seuls les noms des paramĂštres vont changer (oui, il n’y a pas de cohĂ©rence sur les noms). Ainsi, votre objet LaunchTemplate va avoir comme paramĂštres :

  • la stack courante,
  • un id sous la forme d’un string,
  • image_id qui va dĂ©finir son image AMI,
  • instance_type qui va dĂ©finir le type d’instance,
  • user_data qui va dĂ©finir les user data. Attention, mĂȘme si ce n’est pas prĂ©cisĂ©, elles doivent bien ĂȘtre encodĂ©es en base 64,
  • vpc_security_group_ids au lieu de security_groups pour la liste des security groups,
  • key_name pour la clĂ© SSH Ă  utiliser.

Auto Scaling Group

Maintenant que le template est dĂ©fini, c’est le moment de l’utiliser avec un Auto Scaling Group. Souvenez-vous, un Auto Scaling Group va maintenir un nombre d’instances compris entre le min et le max dĂ©fini. La classe qui reprĂ©sente un ASG est simplement AutoscalingGroup. Elle prend en paramĂštres :

  • la stack courante,
  • un id sous la forme d’un string,
  • min_size, max_size et desired_capacity pour la limite infĂ©rieure, supĂ©rieure, et la valeur initiale,
  • launch_template qui permet de spĂ©cifier le template Ă  utiliser. Vous pouvez passer un dictionnaire contenant uniquement la clĂ© id avec comme valeur l’id du launch template que vous obtiendrez avec l’attribut id du launch template,
  • vpc_zone_identifier pour spĂ©cifier les sous-rĂ©seaux Ă  utiliser pour l’Auto Scaling Group. Utilisez la variable subnets prĂ©sente dans le fichier.

Il ne vous reste plus qu’à lancer votre code. Il va crĂ©er les sous-rĂ©seaux nĂ©cessaires, un Launch Template et un ASG selon vos spĂ©cifications. Attendez quelques instants puis allez sur le tableau de bord EC2, vous devriez voir apparaĂźtre 3 instances.

Elastic Load Balancer

DerniĂšre piĂšce Ă  dĂ©finir, le Load Balancer va avoir la charge de rĂ©partir les requĂȘtes entre les instances. La crĂ©ation via l’interface a cachĂ© pas mal de choses et, au lieu de crĂ©er un simple objet, il faut en crĂ©er 3 :

  • le Load Balancer en tant que tel,
  • le Target Group qui va permettre de considĂ©rer l’ASG comme une cible possible pour le Load Balancer,
  • et un Load Balancer Listener pour relier les deux.

Load Balancer

Définir le Load Balancer est assez simple. Cela se fait avec la classe Lb. En plus des classiques stack courante et id, elle prend en paramÚtre :

  • son type avec le paramĂštre load_balancer_type. Dans le cas prĂ©sent, cela sera “application”,
  • les sous-rĂ©seaux avec lesquels il communique avec le paramĂštre subnets. Prenez la valeur subnets dĂ©jĂ  dĂ©finie,
  • et les groupes de sĂ©curitĂ© qui lui sont associĂ©s avec le paramĂštre security_groups. Le security group dĂ©jĂ  dĂ©fini convient trĂšs bien. Attention, ce paramĂštre attend une liste.

Target Group

Le Target Group est également facile. Utilisez la classe LbTargetGroup et passez la stack et un id. Il vous faut ensuite définir les paramÚtres :

  • port en spĂ©cifiant le port 80 et protocol en spĂ©cifiant HTTP car nous voulons que le TG soit accessible uniquement en HTTP sur le port 80,
  • vpc_id avec l’id du VPC dĂ©jĂ  dĂ©fini. Cela est nĂ©cessaire car cela permet Ă  AWS de savoir que les machines du Target Group seront dans le rĂ©seau.

Il faut maintenant associer votre Target Group à votre ASG. Cela passe par l’ajout d’un attribut target_group_arns dans l’ASG. Cet attribut attend la liste des ARN (Amazon Resource Names) des Target Groups. Votre Target Group expose son ARN via l’attribut arn.

Load Balancer Listener

Il ne nous reste plus qu’à dire au Load Balancer de forwarder les requĂȘtes HTTP vers notre Target Group. Il faut utiliser l’objet LbListener pour ça. Il prend, en plus des paramĂštres habituels :

  • load_balancer_arn qui est l’arn du Load Balancer. Pour rĂ©cupĂ©rer l’arn de votre Load Balancer, utilisez l’attribut arn,
  • port qui va prendre la valeur 80 car nous allons forwarder les requĂȘtes faites sur le port 80,
  • protocol qui va prendre la valeur HTTP car nous allons forwarder les requĂȘtes HTTP,
  • default_action oĂč nous allons dire ce que nous voulons faire, ici forwarder les requĂȘtes vers notre Target Group. Comme un Load Balancer Listener peut avoir plusieurs actions, ce paramĂštre attend une liste. Ensuite, notre action de forward va se dĂ©finir via un autre objet dont voici le code LbListenerDefaultAction(type="forward", target_group_arn=target_group.arn).

Vous pouvez maintenant relancer votre code avec un cdktf deploy, allez sur la page du load balancer, obtenir son adresse dns et accĂ©der au endpoint /instance. RafraĂźchissez la page et l’ID affichez devrait changer rĂ©guliĂšrement.

Conclusion

Vous venez lors de ce TP de crĂ©er via du code toute une infrastructure informatique. MĂȘme si cela n’est pas simple Ă  faire, le code que vous avez Ă©crit peut ĂȘtre maintenant rĂ©utiliser Ă  volontĂ© et versionnĂ© via git. Il est ainsi partageable, et vous pouvez voir son Ă©volution. Il peut Ă©galement ĂȘtre utilisĂ© dans un pipeline de CI/CD pour que l’architecture soit dĂ©ployĂ©e automatiquement.

MĂȘme si les solutions IaC ont des avantages, je ne vous les recommande pas pour dĂ©couvrir un service. Explorer l’interface dans un premier temps pour voir les options disponibles permet de mieux comprendre le service. Automatiser la crĂ©ation de services via du code par la suite si c’est nĂ©cessaire.