Infrastructure as Code

 

Master Expert Technologie de l'information EPITECH 2020.

Co-fondateur et CTO d'une startup dans l'Edtech 2019 - fin 2022. (+3 ans)

Formation PSPO-1 Agile Scrum 2022.

Co-fondateur et CTO d'une startup dans la Deeptech fin 2022 - aujourd'hui.

Valentin MONTAGNE

1

1. Découverte de Terraform

3

3. Configurer avec Ansible

2

2. Déployer avec Terraform sur un Cloud Provider

Déroulement du cours

Chaque séance débutera par la présentation d'un concept et de l'intérêt d'utilisation de celui-ci.

1

Théorie

Après la théorie, nous verrons alors la pratique en réalisant des exercices sur un repository gitlab.

2

Pratique

Nous verrons ensemble la correction des travaux pratiques. N'hésitez pas à poser vos questions.

3

Correction

Déroulement des journées

Connaissez-vous l'IaC / Terraform ?

Rendu TP et exercices

Pour faciliter le rendu final des TPs et exercices, vous allez créer un dépôt sur Github avec le nom IaC en publique et le cloner sur votre machine.

 

Si vous préférez le garder en privé, vous devez m'ajouter avec les droits pour voir votre dépôt avec le nom d'utilisateur : ValentinMontagne

Découverte de Terraform

1.

Qu'est-ce que l'Infrastructure as Code ?

Définir toute son infrastructure via des fichiers de configuration.

Automatiser la création, l'édition et la suppression des ressources de l'infrastructure dans le cloud.

Suivre les différentes versions de l'infrastructure en fonction de la solution.

Qu'est-ce que Terraform ?

Automatise le déploiement sur tous les grands Clouds.

Est capable de gérer des grands cluster avec Kubernetes.

S'intègre facilement dans les pipelines CI / CD.

Pourquoi utiliser Terraform ?

Est devenu une référence comme outil d'IaC.

Des fonctionnalités internes pour gérer des situations complexes.

Plusieurs outils CI / CD ont déjà des intégrations.

Configuration as Code vs IaC

CaC et IaC sont deux moyens de gérer les ressources de l'infrastructure, mais ils se concentrent sur des choses différentes :

Le CaC gère la configuration, les logiciels et les paramètres au sein des serveurs, comme les paramètres des utilisateurs et les configurations des applications. Ansible et Puppet sont des exemples d'outils CaC.

 

Conclusion : alors que l'IaC met en place l'environnement, le CaC s'assure que le logiciel au sein de cet environnement fonctionne correctement.

Alternative à Terraform - OpenTofu

Terraform a été mis en open-source en 2014 sous la Mozilla Public License (v2.0). Puis, le 10 août 2023, avec peu ou pas de préavis, HashiCorp a changé la licence pour Terraform de la MPL à la Business Source License (v1.1), une licence non open source.

 

OpenTofu est un fork de la version Open source de Terraform et est géré par la fondation Linux. C'est donc une bonne alternative à Terraform aujourd'hui.

La migration à OpenTofu est extrêmement simple car il n'y a pas de différence de fonctionnement avec Terraform.

Découverte de HCL

HCL, ou HashiCorp Configuration Language, est un langage lisible par l'homme pour les outils DevOps. Il est utilisé pour coder la gestion de l'infrastructure et l'orchestration des services de manière claire et gérable.

HCL est conçu pour trouver un équilibre entre un langage de configuration générique comme JSON ou YAML et un langage de script de haut niveau.

Plusieurs produits HashiCorp, dont Terraform, utilisent HCL comme langage de configuration principal. Sa syntaxe et sa structure claires permettent de créer des modules de ressources et des configurations.

La syntaxe de HCL

La syntaxe de base du langage de configuration HCL comprend la définition de blocs, d'attributs et d'expressions.

Les blocs sont des unités fondamentales telles que la ressource, le module et le provider, identifiées par des mots-clés et placées entre accolades.

Les attributs sont des paires clé-valeur à l'intérieur des blocs, où les clés sont des chaînes et les valeurs peuvent être des chaînes, des nombres ou d'autres types de données.

Les expressions permettent d'intégrer des variables, des fonctions et des références à d'autres ressources, ce qui permet des configurations dynamiques.

Exemple de la syntaxe de HCL

resource "aws_instance" "ec2" {     # <-- Bloc ressource : type "aws_instance", nom "ec2"
  ami           = data.aws_ami.amazon_linux_2.id  # <-- Expression : référence à une autre ressource
  instance_type = var.instance_type               # <-- Expression : variable d'entrée
  key_name      = "staging"                       # <-- Attribut string

  user_data = <<-EOF                # <-- Bloc d'attribut multi-lignes (heredoc)
              #!/bin/bash
              yum update -y
              yum install -y awscli jq docker
              service docker start

              export GITLAB_PASSWORD=$(echo $(aws secretsmanager get-secret-value --secret-id GITLAB_CONTAINER_REGISTRY --query SecretString --output text --region eu-west-3) | jq -r '.password')
              echo $(aws secretsmanager get-secret-value --secret-id workspace/staging --query SecretString --output text --region eu-west-3) | jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" > .env

              docker login registry.gitlab.com -u vm-singularity -p $GITLAB_PASSWORD
              docker run --env-file .env -d -p 80:80 registry.gitlab.com/marvelab/workspace:${var.hash}
           EOF

  iam_instance_profile   = aws_iam_instance_profile.ec2_profile.name  # <-- Référence à une autre ressource
  vpc_security_group_ids = [aws_security_group.ec2.id]                # <-- Liste avec référence

  # Bloc d'attributs imbriqué pour les tags
  tags = {
    Name = "${var.namespace}_EC2_${var.environment}"  # <-- Interpolation de variables
  }

  user_data_replace_on_change = true  # <-- Attribut booléen
}

Providers

Qu'est-ce qu'un Provider ?

Les Providers sont des plugins qui permettent d'interagir avec diverses API externes. Ils gèrent le cycle de vie des ressources en définissant les types de ressources et les sources de données.

Chaque Provider nécessite une configuration, qui comprend généralement des détails d'authentification et des URLs.

Les Providers sont spécifiés dans le bloc provider, et plusieurs Providers peuvent être utilisés dans un seul projet Terraform pour gérer les ressources sur différentes plateformes.

Terraform Registry

Permet de découvrir, de partager et d'utiliser les modules et Providers Terraform.

 

Lien vers le Registry

Configurer un provider

La configuration est réalisée dans le bloc provider de vos fichiers de configuration Terraform. Ce bloc comprend des paramètres tels que les identifiants d'authentification, la région et d'autres paramètres spécifiques au provider.

Les providers doivent être initialisés à l'aide de terraform init pour télécharger et installer les plugins nécessaires.

Des configurations multiples peuvent être gérées en créant des alias de provider, ce qui permet de gérer les ressources dans différents environnements ou comptes au sein d'un même provider.

Version d'un provider

La spécification des versions des Providers dans Terraform garantit un comportement cohérent et prévisible dans différents environnements. La version doit être définie dans le bloc required_providers.

Cette approche permet d'éviter les changements inattendus ou les problèmes de compatibilité dus aux mises à jour des providers, améliorant ainsi la stabilité et la fiabilité de la gestion de l'infrastructure.

Exemple de configuration

# Bloc principal de configuration de Terraform
terraform {
  # Déclaration des providers nécessaires pour ce projet
  required_providers {
    aws = {
      # Le provider AWS est développé par HashiCorp (source officielle)
      source  = "hashicorp/aws"

      # Spécifie la version du provider AWS compatible
      version = "~> 4.16"
    }
  }

  # Version minimale de Terraform requise pour exécuter ce projet
  required_version = ">= 1.2.0"
}

# Configuration du provider AWS
# Ce bloc précise comment Terraform va interagir avec AWS
provider "aws" {
  # Région AWS cible : ici, eu-west-3 correspond à la région "Paris"
  region = "eu-west-3"

  # Optionnel : vous pouvez ajouter un profil si vous utilisez ~/.aws/credentials
  # profile = "mon-profil"

  # Terraform utilisera automatiquement vos identifiants AWS si :
  # - vous les avez définis via les variables d’environnement AWS_ACCESS_KEY_ID et AWS_SECRET_ACCESS_KEY
  # - ou vous avez un fichier ~/.aws/credentials correctement configuré
}

Resources

Qu'est-ce qu'une Resource ?

Représente des composants de l'infra comme les VMs, les buckets, les bases de données ou les VPS. 

Chaque déclaration génère l'élément côté Provider après execution de Terraform.

Chaque resource est configurable en fonction du Provider.

Comportement d'une Resource

Lorsque Terraform crée un nouvel objet d'infrastructure représenté par un bloc de ressources, l'identifiant de cet objet réel est enregistré dans le State de Terraform, ce qui permet de le mettre à jour et de le détruire en réponse à des changements futurs.

Pour les blocs de ressources qui ont déjà un objet d'infrastructure associé dans le State, Terraform compare la configuration réelle de l'objet avec les arguments donnés dans la configuration et, si nécessaire, met à jour l'objet pour qu'il corresponde à la configuration.

Comportement d'une Resource

L'application d'une configuration Terraform va :

  1. Créer des ressources qui existent dans la configuration mais qui ne sont pas associées à un objet d'infrastructure réel dans l'état.
  2. Détruire les ressources qui existent dans l'état mais qui n'existent plus dans la configuration.
  3. Mettre à jour les ressources en place dont les arguments ont changé.
  4. Détruire et recréer les ressources dont les arguments ont changé mais qui ne peuvent pas être mises à jour sur place en raison des limitations de l'API distante.

Accès à une Resource et Data sources

Pour accéder à une ressource dans un fichier de configuration, on utilise une Expression qui a la syntaxe suivante : <RESOURCE TYPE>.<NAME>.<ATTRIBUTE>

On peut accéder à une ressource pour ses attributs en lecture seule obtenues à partir de l'API distante ; il s'agit souvent d'éléments qui ne peuvent être connus avant la création de la ressource, comme l'id de la ressource.

De nombreux providers incluent également des Data sources, qui sont un type spécial de ressources utilisées uniquement pour rechercher des informations.

Exemple d'accès et de Data source

## Get most recent AMI for an ECS-optimized Amazon Linux 2 instance
data "aws_ami" "amazon_linux_2" {
  most_recent = true

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-ecs-hvm-*-x86_64-ebs"]
  }

  owners = ["amazon"]
}

resource "aws_instance" "ec2" {
  ami                    = data.aws_ami.amazon_linux_2.id # <-- Expression d'accès
  instance_type          = var.instance_type
  ...

Meta arguments

Les méta-arguments dans les ressources Terraform fournissent un contrôle supplémentaire sur la façon dont les ressources sont gérées et interagissent dans la configuration :

  • count
  • for_each
  • depends_on
  • provider
  • lifecycle

Count

Permet de spécifier le nombre d'instances d'une ressource particulière à créer. Terraform génère dynamiquement plusieurs instances de la ressource, indexées de 0 à count-1.

Cette fonction est utile pour gérer des infrastructures qui nécessitent plusieurs ressources identiques ou similaires, telles que la création de plusieurs machines virtuelles ou de plusieurs buckets. Vous pouvez créer des ressources de manière conditionnelle en définissant la valeur en fonction de variables ou d'expressions.

Chaque instance de la ressource peut être référencée de manière unique à l'aide de la valeur count.index.

Depends_on

Déclare explicitement les dépendances entre les ressources, en s'assurant qu'une ou plusieurs ressources sont créées ou détruites seulement après que les ressources dépendantes spécifiées ont été appliquées avec succès.

Ceci est crucial pour gérer les dépendances de ressources qui ne sont pas automatiquement détectées par l'analyse implicite des dépendances de Terraform.

For_each

Permet de créer plusieurs instances d'une ressource en fonction d'un ensemble ou d'une map. Contrairement à count, qui utilise un simple entier, for_each permet une création de ressources plus granulaire et dynamique, puisque chaque instance est associée à une paire clé-valeur spécifique d'un objet ou d'une map.

Ce méta-argument est particulièrement utile pour créer des ressources avec des configurations uniques dérivées d'une map obtenu par un Data source par exemple.

Provider

Spécifie la configuration du provider à utiliser pour une ressource, en remplaçant la sélection du provider par défaut basée sur le nom du type de ressource.

Ceci est utile dans les scénarios où plusieurs configurations du même provider sont nécessaires, comme la gestion des ressources dans différentes régions ou environnements. En définissant l'argument provider, vous pouvez vous assurer que la ressource utilise la configuration spécifiée du fournisseur, identifiée par son alias.

Lifecycle

Personnalise le comportement des ressources lors de leur création, de leur mise à jour et de leur suppression. Il comprend des paramètres tels que create_before_destroy, qui garantit qu'une nouvelle ressource est créée avant que l'ancienne ne soit détruite, ce qui évite les temps d'arrêt.

prevent_destroy protège les ressources contre les suppressions accidentelles, et ignore_changes spécifie les attributs à ignorer lors des mises à jour, ce qui permet d'apporter des modifications externes sans déclencher de changements dans Terraform.

Variables

Qu'est-ce qu'une Variable ?

Terraform utilise des variables pour rendre les configurations plus flexibles et réutilisables. Les variables peuvent être déclarées dans des fichiers .tf et se voir attribuer des valeurs par différentes méthodes, notamment des valeurs par défaut, des flags de ligne de commande, des variables d'environnement ou des fichiers .tfvars distincts. Elles prennent en charge plusieurs types de données tels que les chaînes de caractères, les nombres, les bools, les listes et les maps. Les variables peuvent être référencées dans toute la configuration à l'aide du préfixe var.<myVariable>.

Ce système permet à l'infrastructure en tant que code d'être plus dynamique et de s'adapter à différents environnements ou cas d'utilisation.

Input Variable

Les variables d'entrée Terraform sont des paramètres pour les modules, déclarés à l'aide de blocs de variables. Elles prennent en charge plusieurs types de données, des valeurs par défaut et des descriptions. Les utilisateurs fournissent des valeurs lorsqu'ils invoquent des modules ou exécutent Terraform.

Elles peuvent être marquées comme sensibles pour des raisons de sécurité et sont généralement définies dans un fichier variables.tf.

 

Exemple : Définir lors l'exécution que les instances EC2 seront de type T3.micro.

Contraintes de type

Les contraintes de type de variable Terraform spécifient les types de données autorisés pour les variables d'entrée. Elles incluent les types primitifs (string, number, bool), les types complexes (list, set, map, object), et any pour les types non spécifiés.

Les contraintes peuvent imposer des structures spécifiques, des types imbriqués ou des plages de valeurs. Elles sont définies dans l'argument de type du bloc de variables, ce qui permet de détecter rapidement les erreurs et de garantir une utilisation correcte des variables dans toutes les configurations.

Contraintes de type - Exemple

# Déclaration d'une variable nommée "buckets"
variable "buckets" {
  # On impose une contrainte de type stricte à cette variable :
  # il s'agit d'une LISTE d'OBJETS, chacun représentant un bucket à créer
  type = list(object({

    # Chaque objet de la liste doit obligatoirement avoir une clé "name"
    # de type chaîne de caractères (string)
    name = string

    # Clé facultative : "enabled"
    # de type booléen, qui indique si le bucket est actif ou non
    # Valeur par défaut : true (si non précisée dans l’appel)
    enabled = optional(bool, true)

    # Clé facultative : "website", qui elle-même est un objet complexe
    website = optional(object({
      index_document = optional(string, "index.html")
      error_document = optional(string, "error.html")
      routing_rules = optional(string)
    # Valeur par défaut pour l’objet website : objet vide {}
    }), {})

  }))
}

Local Values

Les valeurs locales peuvent être considérées comme un nom attribué à toute expression afin de pouvoir l'utiliser plusieurs fois directement par le nom dans votre module terraform. Les valeurs locales sont appelées locals et peuvent être déclarées à l'aide du bloc locals. Les valeurs locales peuvent être des constantes littérales, des attributs de ressources, des variables ou d'autres valeurs locales. Les valeurs locales sont utiles pour définir des expressions ou des valeurs que vous devez utiliser plusieurs fois dans le module, car elles permettent d'actualiser facilement la valeur en mettant simplement à jour la valeur locale. On peut accéder à une valeur locale en utilisant l'argument local comme local.<nom_de_la_valeur>.

Local Values - Exemple

locals {
  # Ids for multiple sets of EC2 instances, merged together
  instance_ids = concat(aws_instance.blue.*.id, aws_instance.green.*.id)
}

locals {
  # Common tags to be assigned to all resources
  common_tags = {
    Service = local.service_name
    Owner   = local.owner
  }
}

# Utilisation :
resource "aws_instance" "blue" {
  # ...

  tags = local.common_tags
}

Variables d'environnement

Les variables d'environnement peuvent être utilisées pour modifier le comportement par défaut de terraform, par exemple augmenter la verbosité, mettre à jour le chemin du fichier journal, définir l'espace de travail, etc.

TF_VAR_name permet de donner une valeur à certaines variables comme par exemple : 

export TF_VAR_region=us-west-1

export TF_VAR_ami=ami-049d8641

export TF_VAR_alist='[1,2,3]'

export TF_VAR_amap='{ foo = "bar", baz = "qux" }'

Validation Rules

Les règles de validation peuvent être utilisées pour spécifier des validations personnalisées pour une variable. L'ajout de règles de validation a pour but de rendre la variable conforme aux règles. Les règles de validation peuvent être ajoutées à l'aide d'un bloc de validation dans un block variable.

variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."

  validation {
    condition     = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
    error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
  }
}

Outputs

Qu'est-ce qu'un Output ?

Un output expose les valeurs sélectionnées d'une configuration ou d'un module, les rendant accessibles aux utilisateurs ou à d'autres modules.

Définies dans des blocs de sortie, généralement dans un fichier outputs.tf, ils peuvent faire référence à des attributs de ressources ou à d'autres valeurs calculées.

Les sorties sont affichées après les opérations d'application, peuvent être interrogées à l'aide de commande Terraform et sont essentielles pour transmettre des informations entre les modules ou à des systèmes externes.

Syntaxe d'un Output

# Déclaration d'une sortie (output) dans Terraform
# Les outputs permettent d'exposer certaines valeurs après l'exécution.
output "name" {
  # La valeur que Terraform affichera en sortie
  # Il peut s’agir d’un nom de ressource, d’un attribut, d’une variable, etc.
  # Exemple concret : aws_s3_bucket.mon_bucket.bucket
  value = expression

  # (Facultatif) Une description textuelle de la sortie
  # Utile pour la documentation et la compréhension dans les grands projets
  description = "Optional description"

  # (Facultatif) Marque cette sortie comme sensible
  # Cela empêche Terraform d'afficher la valeur dans la console ou les logs
  # Exemple d'utilisation : mots de passe, clés d’API, tokens
  sensitive = bool
}

Sensitive Output

L'attribut sensitive est une fonctionnalité utilisée pour protéger les informations sensibles dans les configurations Terraform. Lorsqu'une sortie est marquée comme sensible, Terraform obscurcit sa valeur dans la sortie de la console, en l'affichant sous la forme <sensitive> au lieu de la valeur réelle.

 

Cette fonction est essentielle pour protéger les données sensibles telles que les mots de passe ou les clés d'API.

Le CLI Terraform

Les commandes principales

Terraform possède un CLI permettant d'exécuter des commandes.

Voici les commandes principales à connaître pour vérifier, planifier, lancer et détruire votre infrastructure :

  • terraform fmt
  • terraform validate
  • terraform plan
  • terraform apply
  • terraform destroy

Format

Formate automatiquement les fichiers de configuration dans un style cohérent. Elle ajuste l'indentation, aligne les arguments et trie les blocs et les arguments par ordre alphabétique. La commande réécrit les fichiers de configuration Terraform (.tf et .tfvars) dans le répertoire courant et ses sous-répertoires.

Elle est utilisée pour maintenir un style cohérent entre les projets et les équipes, améliorant la lisibilité et réduisant les conflits de fusion.

 

Bonne pratique : utiliser un git hook pour automatiser le lancement de la commande avant chaque commit.

Validate

Permet de vous assurer que votre code Terraform est syntaxiquement correct avant de le déployer.

Vous évitez ainsi les erreurs de configuration dues à des attributs manquants ou à des dépendances incorrectes, ce qui vous permet de gagner du temps, d'améliorer l'efficacité et de réduire les coûts.

 

Bonne pratique : utiliser un git hook pour automatiser le lancement de la commande avant chaque commit.

Plan

Crée un plan d'exécution, montrant les changements que Terraform va apporter à votre infrastructure.

Il compare l'état actuel avec l'état souhaité défini dans les fichiers de configuration et produit une liste détaillée des ressources à créer, modifier ou supprimer. Il est important de noter qu'il n'apporte aucun changement réel à l'infrastructure, mais qu'il aide à identifier les problèmes potentiels avant d'appliquer les changements. Le plan peut être enregistré dans un fichier en vue d'une exécution ou d'une révision ultérieure.

Bonne pratique : Lancer la commande dans un job de votre CI pour valider les modifications d'infrastructure proposées.

Apply

Mets en œuvre les changements définis dans vos fichiers de configuration Terraform. Elle crée, met à jour ou supprime les ressources d'infrastructure spécifiées afin qu'elles correspondent à l'état souhaité.

Avant d'effectuer les changements, elle affiche un plan similaire à terraform plan et demande une confirmation, sauf si l'option -auto-approve est utilisée.

Apply met à jour le fichier d'état pour refléter l'état actuel de l'infrastructure, ce qui permet à Terraform de suivre et de gérer les ressources au fil du temps. Il gère les dépendances entre les ressources, en les créant dans le bon ordre.

Destroy

Supprime toutes les ressources gérées par une configuration Terraform. Elle crée un plan de suppression de toutes les ressources et demande une confirmation avant l'exécution. Cette commande est utile pour nettoyer des environnements temporaires ou mettre hors service des infrastructures entières.

Elle supprime les ressources dans l'ordre inverse de leurs dépendances pour garantir un démantèlement correct.

Après la destruction, Terraform met à jour le fichier state pour refléter les changements, mais il est important de gérer ou de supprimer ce fichier si le projet est complètement déclassé.

Qu'est-ce qu'un State ?

Permet de suivre l'état actuel de votre infrastructure gérée. Il est généralement stocké dans un fichier nommé terraform.tfstate, qui associe les ressources du monde réel à votre configuration.

Cet état permet à Terraform de déterminer quels changements sont nécessaires pour obtenir la configuration souhaitée.

Il contient des informations sensibles et doit être stocké de manière sécurisée, souvent dans des backends distants comme S3 ou Terraform Cloud.

Les bonnes pratiques

  • Stockez les fichiers d'état à distance dans des backends cryptés et contrôlés par version, tels que S3 ou Terraform Cloud, pour permettre l'accès de l'équipe et renforcer la sécurité.
  • Mettez en œuvre le verrouillage des états pour empêcher les modifications simultanées. Utiliser des espaces de travail ou des fichiers d'état distincts pour différents environnements.
  • Sauvegardez régulièrement les fichiers d'état et activez le contrôle de version pour les capacités de retour en arrière.
  • Évitez de stocker des données sensibles directement dans l'état ; utilisez plutôt des outils de gestion des secrets.
  • Conservez les fichiers d'état séparément de votre configuration Terraform dans le contrôle de version.

Intégration à une CI / CD

Terraform est un très bon outil pour réaliser du CI / CD dans un projet. Il permet d'automatiser le déploiement de l'infrastructure à chaque nouvelle version de votre application.

Pour cela, je vous propose de consulter un exemple simple d'un déploiement d'une image Docker sur AWS via Github Actions :

Lien vers le projet exemple

 

Et la slide suivante pour voir le job permettant le déploiement.

Déployer l'image Docker avec Terraform sur un Cloud Provider

deploy-image:
    runs-on: ubuntu-latest
    needs: build-image
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      AWS_DEFAULT_REGION: eu-west-3
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v1
        with:
          cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
      - name: Terraform Init
        run: terraform init
      - name: Terraform Format
        run: terraform fmt -check
      - name: Terraform Plan
        run: terraform plan -input=false
      - name: Terraform Apply
        run: terraform apply -auto-approve -input=false
        # Add condition here to avoid to deploy when checking a PR.

TP - Prise en main de Terraform en local

Installation de Terraform

En premier lieu, nous allons maintenant installer Terraform sur votre machine : https://developer.hashicorp.com/terraform/install

Vérifiez que vous Terraform est bien installé avec la commande suivante : terraform -v

Ensuite, créez le dossier "TP-local" et créez à l'intérieur le "main.tf" qui sera le fichier principal de votre infrastructure :

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

provider "local" {}

resource "local_file" "test_file" {
  content  = "Hello depuis Terraform !"
  filename = "${path.module}/hello.txt"
}

Initialisation du projet

Pour l'instant, nous allons utiliser le provider "local" qui permet de réaliser des changements en local sur votre machine.

Dans le fichier main.tf, la resource local_file permet de générer un nouveau fichier.

Initialisez le projet avec la commande :

terraform init

Lancez les commandes suivantes :

  1. terraform fmt
  2. terraform validate
  3. terraform plan
  4. terraform apply

 

Vérifiez que les changements, que remarquez-vous ?

Modification du projet

Nous allons maintenant modifier notre "infrastructure", changer le contenu de votre fichier hello.txt qui a pour nom "test_file" dans votre configuration.

Relancez les différentes commandes précédentes.

 

Que remarquez-vous ?

Utilisation de commande

Nous allons maintenant ajouter une commande qui va s'exécuter automatiquement.

Ajoutez dans votre main.tf le code suivant :

 

 

 

Ajoutez les dépendances nécessaire avec la commande :

terraform init -upgrade

 

Exécutez à nouveau la configuration, qu'observez-vous ?

resource "null_resource" "example" {
  provisioner "local-exec" {
    command = "echo 'Commande exécutée !'"
  }
}

Détruire l'infrastructure

Maintenant que nous avons terminé avec cette configuration, vous allez pouvoir la détruire, pour cela exécutez la commande :

terraform destroy

 

Qu'observez-vous ?

 

Oui, le fichier terraform.tfstate est toujours présent avec sa sauvegarde, nous allons les supprimer car nous n'allons plus utiliser cette configuration.

Utilisation de Docker

Nous allons maintenant créer une infrastructure en utilisant Docker pour simuler ce que l'on va pouvoir réaliser avec un Cloud Provider. Remplacez le contenu de main.tf par celui-ci :

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "3.5.0"
    }
  }
}

provider "docker" {}

resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = true
}

resource "docker_container" "nginx" {
  name  = "nginx-terraform"
  image = docker_image.nginx.image_id
  ports {
    internal = 80
    external = 8080
  }
}

Utilisation de Docker

Attention, pour MacOS et Windows, cela peut ne pas fonctionner sans plus de configuration.

Pour Windows, vous devez allez via General > Expose Daemon on tcp://localhost:2375 et ajouter dans le block provider "docker" la ligne :

host = "tcp://localhost:2375".

Pour MacOS, vous devez mettre le bon chemin :

host = "unix://${pathexpand("~/.docker/run/docker.sock")}"

Exécutez cette configuration.

 

Que remarquez-vous ?

Appelez-moi pour que valider ensemble.

Clean up

Bravo, vous avez terminé ce premier TP !

Je vous invite maintenant à supprimer l'infrastructure actuelle et de passer aux exercices.

Exercice 1 : Variables

Ajoutez des variables Terraform à la place des valeurs codées en dur suivantes :

  1. Le nom de l’image Docker (nginx:latest)

  2. Le nom du conteneur

  3. Le port externe exposé

  4. Le port interne du conteneur

Vous devez :

  • Déclarer chaque variable dans le fichier variables.tf

  • Fournir des valeurs par défaut

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 2 : Output

Ajoutez un output nommé nginx_container_id à votre configuration Terraform.

Celui-ci doit afficher l’identifiant (id) du conteneur nginx créé avec la ressource docker_container.nginx.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 3 : Créer une commande

Ajoutez une commande à votre configuration Terraform qui permet de tester automatiquement que le serveur Nginx déployé sur le port spécifié répond avec une page d’index.

La commande doit tester l’URL :

http://localhost:<external_port> 

Et vérifier que la réponse contient le mot Welcome (présent dans l’index par défaut de Nginx).

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 4 : Créer un autre container Docker

Ajoutez un deuxième conteneur Docker nommé client, basé sur l’image appropriate/curl, qui exécutera une commande pour appeler le serveur nginx (déjà déployé).

Pour cela, vous devez :

  1. Créer un réseau Docker dédié.

  2. Connecter les deux conteneurs à ce réseau.

  3. Faire en sorte que le conteneur client utilise curl pour interroger http://nginx:80 et sleep ensuite.

  4. Vérifier que la communication fonctionne dans le conteneur client.

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 5 : Utilisation de count

Reprenez l'exercice précédent, puis :

  1. Modifiez le conteneur client pour qu’il soit déployé en plusieurs exemplaires (ex. 3) à l’aide de count.

  2. Assurez-vous que chaque conteneur :

    • ait un nom unique (client-0, client-1, etc.),

    • soit connecté au même réseau Docker que nginx,

    • exécute un curl http://nginx suivi d’un sleep de 30 secondes.

  3. Le nombre de clients doit être paramétrable via une variable.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 6 : Utilisation de for_each

Reprenez l'exercice précédent, transformez le count par un for_each qui doit boucler sur une liste de nom pour vos serveurs.

 

Chaque serveur doit posséder le bon nom :

server-<nom>

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 7 : Utilisation des types de variables

Vous êtes en train de créer un module Terraform qui déploie des machines virtuelles. Chaque machine doit être définie avec : un nom, un nombre de vCPU (min. 2, max. 64), une taille de disque (en Go, min. 20), une région (parmi : "eu-west-1", "us-east-1", "ap-southeast-1").

Créez une variable machines de type liste d'objets contenant les 4 attributs cités.

Ajoutez une validation personnalisée sur : les vcpu (entre 2 et 64),

le disk_size (>= 20), la region.

 

Appelez-moi pour que l'on puisse valider ensemble.

Déployer avec Terraform sur un Cloud Provider

2.

Configurer le Provider AWS

Pour utiliser un Provider comme AWS, il faut le configurer pour qu'il puisse se connecter à notre compte AWS.

Pour cela, la documentation du Provider indique plusieurs manières de faire :

  1. Paramètres dans la configuration du fournisseur
  2. Variables d'environnement
  3. Fichiers d'identification partagés
  4. Fichiers de configuration partagés
  5. Informations d'identification du conteneur
  6. Informations d'identification du profil d'instance et région

 

Je recommande d'utiliser la méthode 2 qui est pour moi plus sécurisé.

Utiliser les variables d'environnement

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "us-east-1"
}

# Create a VPC
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

# terminal
% export AWS_ACCESS_KEY_ID="anaccesskey"
% export AWS_SECRET_ACCESS_KEY="asecretkey"
% terraform plan

Exemple de configuration :

Les ressources avec AWS

Le Provider AWS possède plus de 1499 ressources, c'est pour cela que nous n'allons voir que les plus simples durant cet atelier.

Débutons par la ressource de base, l'instance AWS qui est une instance EC2 :

Lien vers la documentation complète

(Le panel de gauche permet de rechercher à l'intérieur)

 

Un exemple est disponible à la slide suivante.

Instance EC2 - Exemple

resource "aws_instance" "ec2" {
  # ID de l'AMI Amazon Linux 2, récupéré dynamiquement via un data source
  ami = data.aws_ami.amazon_linux_2.id
  # Type d'instance défini via une variable (ex: t2.micro, t3.medium, etc.)
  instance_type = var.instance_type
  # Nom de la clé SSH permettant d'accéder à l'instance
  key_name = "staging"

  # Script shell exécuté au démarrage de l'instance (cloud-init)
  user_data = <<-EOF
              #!/bin/bash
              yum update -y                           # Mise à jour des paquets
              yum install -y awscli jq docker         # Installation de l’AWS CLI, jq (JSON parser) et Docker
              service docker start                    # Démarrage du service Docker
           EOF

  # Profil IAM attaché à l’instance pour lui donner des permissions AWS (ex: accès à S3)
  iam_instance_profile = aws_iam_instance_profile.ec2_profile.name

  # Liste des IDs de groupes de sécurité associés à l’instance
  vpc_security_group_ids = [aws_security_group.ec2.id]

  # Étiquettes (tags) appliquées à l’instance pour l'organisation et le suivi
  tags = {
    Name = "${var.namespace}_EC2_${var.environment}"  # Exemple : "dev_EC2_staging"
  }

  # Forcer le remplacement de l'instance si le user_data change
  user_data_replace_on_change = true
}

Les clés SSH avec AWS

Pour nous connecter à nos instances, nous allons avoir besoin de clés SSH. Voici comment générer et configurer une paire de clé :

ssh-keygen -t rsa -b 4096 -f ~/.ssh/my-ec2-key

Ne jamais partager la clé privée.

Exemple de configuration avec AWS :

provider "aws" {
  region = "eu-west-3"
}

resource "aws_key_pair" "my_key" {
  key_name   = "my-ec2-key"
  public_key = file("~/.ssh/my-ec2-key.pub")
}

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0" # à adapter à la région
  instance_type = "t2.micro"
  key_name      = aws_key_pair.my_key.key_name

  tags = {
    Name = "Terraform-Instance"
  }
}

TP - Déployer en local avec AWS

Installation de Localstack

Pour éviter d'utiliser directement un vrai compte AWS qui pourrait être coûteux ou laborieux quand vous devez réaliser des tests, nous allons utiliser un outil très puissant : Localstack.

Installez Localstack en suivant les instructions dans le Readme du projet.

Vérifiez l'installation de Localstack avec la commande :

localstack -v

Puis démarrez l'environnement local :

localstack start

 

Nous allons maintenant installer le CLI d'AWS.

Installation de AWS CLI

Pour MacOS :

brew install awscli

Pour Windows :

https://awscli.amazonaws.com/AWSCLIV2.msi

Pour Linux :

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

 

Vérifiez la bonne installation du CLI :

aws --version

Configurer le provider AWS

Nous allons devoir ajouter des configurations non nécessaire normalement vu que nous allons être sur un faux environnement d'AWS dans le main.tf :

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  access_key                  = "test"
  secret_key                  = "test"
  region                      = "us-east-1"
  
  # LocalStack endpoint configuration
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true
  
  endpoints {
    s3       = "http://localhost:4566"
    ec2 	 = "http://localhost:4566"
  }
}

Créer un bucket

Nous allons créer une ressource emblématique d'AWS, un bucket S3 pour pouvoir stocker des fichiers.

Pour cela, nous allons créer le fichier s3.tf :

# Create an S3 bucket
resource "aws_s3_bucket" "demo_bucket" {
  bucket = "my-bucket"
}

# Enable versioning for the bucket
resource "aws_s3_bucket_versioning" "demo_bucket_versioning" {
  bucket = aws_s3_bucket.demo_bucket.id

  versioning_configuration {
    status = "Enabled"
  }
}

# Upload a file to the bucket
resource "aws_s3_object" "demo_object" {
  bucket = aws_s3_bucket.demo_bucket.id
  key    = "hello-world.txt"
  source = "./test-file.txt"
  etag   = filemd5("./test-file.txt")
}

Créer un bucket - 2

Dans la slide précédente, on peut voir que l'on upload un fichier sur notre Bucket, nous allons donc créer le fichier test-file.txt :

 

Lancez l'initialisation du projet Terraform, planifiez et appliquez les changements. Vérifiez vos changements avec les commandes suivantes pour Linux et MacOS :

AWS_ACCESS_KEY_ID="test" AWS_SECRET_ACCESS_KEY="test" AWS_DEFAULT_REGION="us-east-1" aws --endpoint-url=http://localhost:4566 s3 ls

AWS_ACCESS_KEY_ID="test" AWS_SECRET_ACCESS_KEY="test" AWS_DEFAULT_REGION="us-east-1" aws --endpoint-url=http://localhost:4566 s3 ls s3://my-bucket

Hello, World! This is a test file for my Terraform and LocalStack demo.

Créer un bucket - 3

Pour Windows :

& { $env:AWS_ACCESS_KEY_ID = "test"; $env:AWS_SECRET_ACCESS_KEY = "test"; $env:AWS_DEFAULT_REGION = "us-east-1"; aws --endpoint-url=http://localhost:4566 s3 ls }

& { $env:AWS_ACCESS_KEY_ID = "test"; $env:AWS_SECRET_ACCESS_KEY = "test"; $env:AWS_DEFAULT_REGION = "us-east-1"; aws --endpoint-url=http://localhost:4566 s3 ls s3://my-bucket }

Créer une instance EC2

Maintenant que notre bucket est fonctionnel, nous allons créer une instance EC2 qui lancera un Nginx, créez le fichier ec2.tf :

# Generate SSH key
resource "tls_private_key" "key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

# Create key pair
resource "aws_key_pair" "deployer" {
  key_name   = "deployer-key"
  public_key = tls_private_key.key.public_key_openssh
}

# Store private key locally
resource "local_file" "private_key" {
  content         = tls_private_key.key.private_key_pem
  filename        = "${path.module}/deployer-key.pem"
  file_permission = "0600"
}

# Create EC2 instance with Nginx
resource "aws_instance" "web" {
  ami             = "ami-12345678"
  instance_type   = "t2.micro"
  security_groups = [aws_security_group.web.name]
  key_name        = aws_key_pair.deployer.key_name

  user_data = <<-EOF
              #!/bin/bash
              # Install and configure Nginx
              yum update -y
              amazon-linux-extras install -y nginx1
              systemctl start nginx
              systemctl enable nginx
              
              # Create a simple webpage
              echo "<h1>Hello from Terraform and LocalStack!</h1>" > /usr/share/nginx/html/index.html
              EOF

  tags = {
    Name = "nginx-server"
  }
}

Créer un groupe de sécurité

Avec AWS, la connexion à une instance doit être géré par des ressources réseau comme un groupe de sécurité, créez sg.tf :

# Create a security group for the EC2 instance
resource "aws_security_group" "web" {
  name        = "nginx-sg"
  description = "Allow web and SSH traffic"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow HTTP traffic"
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow SSH traffic"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow all outbound traffic"
  }

  tags = {
    Name = "nginx-sg"
  }
}

Créer les Outputs

Pour vérifier que notre instance EC2 fonctionne correctement, nous allons récupérer des informations, malheureusement localstack ne va pas jusqu'à simuler une vraie VM capable d'exécuter nginx, nous allons juste vérifier si elle est bien lancé.

Créez outputs.tf :

output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web.id
}

output "instance_public_ip" {
  description = "Public IP of the EC2 instance"
  value       = aws_instance.web.public_ip
}

output "ssh_command" {
  description = "SSH command to connect to the instance"
  value       = "ssh -i deployer-key.pem ec2-user@${aws_instance.web.public_ip}"
}

Modification et vérification

Lancez l'application de la nouvelle configuration et vérifiez que l'instance EC2 est bien présente :

AWS_ACCESS_KEY_ID="test" AWS_SECRET_ACCESS_KEY="test" AWS_DEFAULT_REGION="us-east-1" aws --endpoint-url=http://localhost:4566 ec2 describe-instances --instance-ids <Instance ID>

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 1 - Variables

Ajoutez des variables Terraform à la place des valeurs codées en dur suivantes :

  1. Le type d'instance EC2

  2. Le nom de l'instance EC2

  3. Le nom du bucket S3

  4. Le port par défaut pour le groupe de sécurité

Vous devez :

  • Déclarer chaque variable dans le fichier variables.tf

  • Fournir des valeurs par défaut

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 2 - Output

Ajoutez un output nommé bucket_id à votre configuration Terraform.

Celui-ci doit afficher l’identifiant (id) du bucket créé avec la ressource aws_s3_bucket.demo_bucket.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 3 - Création d'une nouvelle instance EC2

Grâce aux exercices précédents, une application web statique est déployée sur une instance EC2 avec Nginx. Nous allons maintenant déployer une nouvelle instance EC2 qui contiendra une base de donnée.

Vous devez :

  1. Créer une nouvelle instance EC2

  2. Changer la configuration pour qu'on puisse facilement l'identifier comme le serveur de la base de donnée. 

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 4 - Déployer sur AWS

Maintenant que vous avez validé plusieurs configurations grâce à Localstack, le but est de mettre en place sur un vrai compte AWS la dernière configuration et de vérifier son bon fonctionnement.

Arrêtez et retirer la configuration en lien avec Localstack et les buckets et déployez directement sur le compte AWS fournit pour l'atelier. (N'hésitez pas à consulter AWS details)

Créez une paire de clé et liez les à votre instance EC2 pour vous permettre de vous connecter via SSH à l'instance.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 5 - Déployer un bucket

Ajoutez un S3 bucket à votre infrastructure.

Créez un fichier en local "test-file.txt" avec comme contenu "Hello World" et ajoutez dans la configuration l'upload du fichier sur le bucket S3.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 6 Bonus - Rendre le bucket public

Maintenant que vous avez un bucket S3, ajoutez les permissions pour permettre la lecture de tous les fichiers à l'intérieur par n'importe qui.

 

On doit pouvoir accéder à l'objet S3 via son URL.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 7 Bonus - Déploiement d'une application

Récupérez un projet que vous avez déjà réalisé et réalisez une image Docker de ce projet.

Déployez l'image Docker et les images nécessaires pour votre projet en utilisant Terraform sur le compte AWS fournit pour cet atelier.

 

Appelez-moi pour que l'on puisse valider ensemble.

Configurer avec Ansible

3.

Qu'est-ce qu'Ansible ?

Logiciel open-source pour automatiser le déploiement, la configuration et la gestion d’infrastructures.

Contrairement à d’autres outils, n’utilise pas d'agent mais SSH pour se connecter et exécuter les tâches.

Les tâches à exécuter sont décrites de manière déclarative dans des fichiers au format YAML.

Pourquoi utiliser Ansible avec Terraform ?

  1. Complémentarité entre provisionnement et configuration : Terraform est idéal pour créer l’infrastructure, tandis qu’Ansible excelle dans la configuration des machines (installation de paquets, déploiement d’applications).

  2. Séparation des responsabilités : Utiliser Terraform pour gérer l’état de l’infrastructure et Ansible pour appliquer des configurations cela sépare "ce qui est" (l’infra) de "ce qu’elle fait" (les services qu’elle héberge).

  3. Flexibilité post-provisionnement : Ansible peut être relancé facilement et indépendamment pour appliquer des mises à jour, corriger une configuration ou réagir à un changement, sans toucher à l’infrastructure sous-jacente.

Les inventaires

L’inventaire (ou inventory) est un fichier ou une source dynamique qui liste tous les hôtes (machines) que Ansible peut gérer.


Il permet aussi de regrouper ces hôtes, de leur affecter des variables, et de cibler précisément les machines sur lesquelles exécuter des tâches.

 

Par défaut, l’inventaire est un fichier texte situé dans /etc/ansible/hosts ou défini via -i dans la ligne de commande.

Format d'un inventaire

[webservers]
web1.example.com ansible_host=192.168.1.100
web2.example.com ansible_host=192.168.1.101

[dbservers]
db1.example.com ansible_host=192.168.1.200 mysql_port=3306
db2.example.com ansible_host=192.168.1.201 mysql_port=3306

# Production Environment Group using another group
[production:children]
webservers
dbservers

# Specific Server Variables
[webservers:vars]
http_port=80
https_port=443

# Group with Connection Variables
[remote_servers]
remote1.example.com ansible_host=203.0.113.10 ansible_user=admin ansible_ssh_private_key_file=/path/to/private/key

# Global SSH Connection Defaults
[all:vars]
ansible_connection=ssh
ansible_user=ansible
ansible_become=yes
ansible_become_method=sudo

Les Playbooks

Un Playbook est un fichier YAML qui décrit une ou plusieurs séquences de tâches à exécuter sur un ou plusieurs hôtes.
Il permet d’automatiser des configurations, des déploiements, ou de l’orchestration de services de manière déclarative.

 

On parle de scénario, contenant des tasks, exécutées sur des hosts.

Le format d'un Playbook

---
- hosts: webservers # <-- Means the tasks will run on all servers in the webservers group
  tasks:
    - name: Ensure nginx is installed
      apt: # <-- Use APT to install packages on Debian / Ubuntu
        name: nginx
        state: present
    
    - name: Configure web server
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify:
        - Restart Nginx # <-- Call handler Restart Nginx

  handlers:
    - name: Restart Nginx
      service: # <-- Use Debian / Ubuntu's service system
        name: nginx
        state: restarted

Exemple d'un Playbook

---
- hosts: webservers
  become: yes  # Use sudo for elevated privileges
  vars:
    # Centralized variables for easy management
    web_package: nginx
    web_port: 80
    application_name: myapp
    app_root_directory: "/var/www/{{ application_name }}"
    
  # Pre-tasks run before roles and tasks
  pre_tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600  # Only update if older than 1 hour
      when: ansible_os_family == 'Debian'

  # Roles allow for modular, reusable configuration
  roles:
    - role: security
      tags: 
        - security
    
    - role: webserver
      tags:
        - web

  # Individual tasks for specific configurations
  tasks:
    - name: Ensure web server is installed
      apt:
        name: "{{ web_package }}"
        state: present
      tags:
        - packages

    - name: Create application directory
      file:
        path: "{{ app_root_directory }}"
        state: directory
        owner: www-data
        group: www-data
        mode: '0755'
      tags:
        - app-setup

    - name: Deploy application code
      git:
        repo: 'https://github.com/yourorg/yourapp.git'
        dest: "{{ app_root_directory }}"
        version: main  # Specify branch/tag
      notify: 
        - Restart Nginx
      tags:
        - deployment

    - name: Configure Nginx virtual host
      template:
        src: templates/nginx-site.conf.j2
        dest: "/etc/nginx/sites-available/{{ application_name }}"
      notify: 
        - Validate Nginx Configuration
        - Restart Nginx
      tags:
        - configuration

    - name: Enable Nginx site
      file:
        src: "/etc/nginx/sites-available/{{ application_name }}"
        dest: "/etc/nginx/sites-enabled/{{ application_name }}"
        state: link
      notify: 
        - Restart Nginx
      tags:
        - configuration

    - name: Set up application environment
      copy:
        content: |
          DB_HOST={{ database_host }}
          DB_USER={{ database_user }}
          SECRET_KEY={{ secret_key }}
        dest: "{{ app_root_directory }}/.env"
        owner: www-data
        group: www-data
        mode: '0600'
      no_log: true  # Prevents logging sensitive information
      tags:
        - secrets

  # Handlers for service management
  handlers:
    - name: Validate Nginx Configuration
      command: nginx -t
      register: nginx_test
      failed_when: nginx_test.rc != 0
      changed_when: false

    - name: Restart Nginx
      service:
        name: nginx
        state: restarted

  # Post-tasks run after roles and tasks
  post_tasks:
    - name: Verify application is responding
      uri:
        url: "http://localhost:{{ web_port }}"
        status_code: 200
      register: webapp_response
      retries: 3
      delay: 5
      until: webapp_response.status == 200
      tags:
        - validation

# Example of a separate play for database servers
- hosts: dbservers
  become: yes
  roles:
    - role: database
      tags:
        - database

# Conditional play for monitoring servers
- hosts: monitoring
  become: yes
  tasks:
    - name: Install monitoring tools
      apt:
        name: 
          - prometheus
          - grafana
        state: present
      when: monitoring_enabled | default(false)

TP - Configurer des serveurs

Installation de Ansible et multipass

Installez Ansible avec la commande suivante :

pipx install --include-deps ansible

 

Puis, installez Multipass, qui nous permettra de simuler des serveurs.

 

Vérifiez que tout est bien installé :

multipass -v

ansible -v

Créer les serveurs

Créez les serveurs nécessaires pour la suite du TPs :

 

 

Ensuite, générez la paire de clé SSH qui permettra à Ansible de se connecter à nos serveurs :

multipass launch -n web-server && multipass launch -n db-server
# Generate SSH key if you don't have one
ssh-keygen -t rsa -b 4096 -f ./ansible_test_key

# Add SSH key to instances
multipass exec web-server -- mkdir -p /home/ubuntu/.ssh
multipass exec db-server -- mkdir -p /home/ubuntu/.ssh

# Copy public key to instances
multipass exec web-server -- sh -c "echo '$(cat ./ansible_test_key.pub)' >> /home/ubuntu/.ssh/authorized_keys"
multipass exec db-server -- sh -c "echo '$(cat ./ansible_test_key.pub)' >> /home/ubuntu/.ssh/authorized_keys"

Créer l'inventaire

Créez ensuite le fichier inventory avec les informations des serveurs :

[webservers]
web-server ansible_host=<web-server IP> # <-- multipass info web-server | grep IPv4

[dbservers]
db-server ansible_host=<db-server IP> # <-- multipass info db-server | grep IPv4

[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=./ansible_test_key
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

Créer le Playbook

Créez ensuite le fichier playbook.yml avec les tâches à réaliser pour Ansible :

---
- hosts: all
  become: yes
  tasks:
    - name: Update apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Install basic packages
      apt:
        name:
          - htop
          - vim
          - curl
        state: present

- hosts: webservers
  become: yes
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: present

- hosts: dbservers
  become: yes
  tasks:
    - name: Install MySQL
      apt:
        name: 
          - mysql-server
          - python3-pymysql
        state: present

Appliquer la configuration

Vérifiez que Nginx et Mysql ne sont pas déjà présent sur les serveurs :

 

 

 

Vous devriez avoir une erreur pour les deux commandes.

Maintenant, lancez Ansible pour configurer les serveurs :

 

 

Réessayez les commandes pour vérifier que Nginx et Mysql sont bien présents.

 

Appelez-moi pour que l'on puisse valider ensemble.

# Run the playbook
ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i inventory playbook.yml
# Connectez-vous à web-server pour lancer la commande dans son terminal
curl localhost

# Connectez-vous à db-server pour lancer la commande dans son terminal
mysql -v

Exercice 1 - Configurer Docker

Ajoutez dans le Playbook Ansible tout ce qui est nécessaire pour ajouter au web-server le service Docker.

Retirez Nginx et lancez l'image traefik/whoami via Ansible.

Vérifiez que votre web-server affiche quelque chose sur le port 80.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 2 - Ansible et Terraform

Déployez avec Terraform, 3 instances EC2 et créez automatiquement un inventory avec toutes les informations nécessaires pour que Ansible puisse configurer ces instances.

(IP, Clés SSH, etc)

Appliquez sur les 3 instances une configuration via un Playbook qui installe et lance sur toutes les instances Nginx.

Vérifiez sur vos instances EC2 que Nginx réagit au port 80.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 3 Bonus - AWS et Docker

Déployez avec Terraform une instance EC2 "webserver" et une instance EC2 "dbserver" et créez automatiquement un inventory avec toutes les informations nécessaires pour que Ansible puisse configurer ces instances.

(IP, Clés SSH, etc)

Appliquez sur l'instance "webserver" une configuration via un Playbook qui installe et lance l'image Docker Nginx et sur l'instance "dbserver" une base de donnée MySQL.

Vérifiez sur vos instances EC2 que Nginx et MySQL soient bien présents.

 

Appelez-moi pour que l'on puisse valider ensemble.

Exercice 4 Bonus - Déployer et configurer une application

Déployez avec Terraform une infrastructure pour mettre en ligne une application que vous avez déjà réalisé précédemment.

Créez une image Docker de votre projet pour pouvoir ensuite utiliser Ansible pour configurer l'ajout et l'utilisation de l'image Docker et d'autres images nécessaires pour votre projet sur vos instances déployées par Terraform.

 

Appelez-moi pour que l'on puisse valider ensemble.

Soutenance

QCM

30 minutes

4.

N'hésitez pas à me donner votre avis.

Merci!