---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.0
kernelspec:
  name: python3
  display_name: Python 3
---

# 07 — Terraform avec les providers cloud

Terraform tire toute sa puissance de ses providers : chaque fournisseur cloud expose des centaines de ressources gérables via HCL. Ce chapitre couvre les trois grands clouds (AWS, GCP, Azure), les patterns d'infrastructure moderne, la stratégie multi-cloud, Terragrunt et Pulumi comme alternatives complémentaires.

## Les trois grands clouds — équivalences de services

| Domaine | AWS | GCP | Azure |
| --- | --- | --- | --- |
| Réseau virtuel | VPC | VPC (Global) | VNet |
| Machine virtuelle | EC2 | Compute Engine (GCE) | Virtual Machine |
| Kubernetes managé | EKS | GKE | AKS |
| Base de données relationnelle | RDS | Cloud SQL | Azure SQL |
| Stockage objet | S3 | GCS | Blob Storage |
| Identité & accès | IAM | IAM | Azure AD / RBAC |
| DNS | Route 53 | Cloud DNS | Azure DNS |
| Fonctions serverless | Lambda | Cloud Functions | Azure Functions |
| Registre de conteneurs | ECR | Artifact Registry | Azure Container Registry |
| Secrets | Secrets Manager | Secret Manager | Key Vault |

## Provider AWS

### Configuration du provider

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

provider "aws" {
  region = var.aws_region

  assume_role {
    role_arn = "arn:aws:iam::${var.account_id}:role/TerraformDeployRole"
  }

  default_tags {
    tags = local.common_tags
  }
}
```

### VPC et réseau

```hcl
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  tags = { Name = "${local.name_prefix}-vpc" }
}

resource "aws_subnet" "private" {
  for_each = {
    "eu-west-1a" = "10.0.1.0/24"
    "eu-west-1b" = "10.0.2.0/24"
    "eu-west-1c" = "10.0.3.0/24"
  }
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value
  availability_zone = each.key
  tags = { Name = "private-${each.key}" }
}

resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public["eu-west-1a"].id
  depends_on    = [aws_internet_gateway.main]
}
```

### EKS

```hcl
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 20.0"

  cluster_name    = "${local.name_prefix}-eks"
  cluster_version = "1.29"
  vpc_id          = aws_vpc.main.id
  subnet_ids      = [for s in aws_subnet.private : s.id]

  cluster_endpoint_public_access  = false
  cluster_endpoint_private_access = true

  eks_managed_node_groups = {
    system = {
      instance_types = ["t3.medium"]
      min_size       = 2
      max_size       = 4
      desired_size   = 2
    }
    app = {
      instance_types = ["c5.xlarge"]
      min_size       = 2
      max_size       = 20
      desired_size   = 4
      capacity_type  = "SPOT"
    }
  }
}
```

### RDS

```hcl
resource "aws_db_instance" "main" {
  identifier             = "${local.name_prefix}-postgres"
  engine                 = "postgres"
  engine_version         = "15.4"
  instance_class         = var.rds_instance_class
  allocated_storage      = 100
  max_allocated_storage  = 1000   # autoscaling storage

  db_name  = var.db_name
  username = var.db_username
  password = var.db_password   # sensitive

  multi_az               = local.is_production
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]

  backup_retention_period = 7
  deletion_protection     = local.is_production
  skip_final_snapshot     = !local.is_production

  performance_insights_enabled = true

  lifecycle {
    ignore_changes = [password]
  }
}
```

## Provider GCP

### Configuration du provider GCP

```hcl
provider "google" {
  project = var.gcp_project_id
  region  = var.gcp_region
}

provider "google-beta" {
  project = var.gcp_project_id
  region  = var.gcp_region
}
```

### VPC et GKE

```hcl
resource "google_compute_network" "main" {
  name                    = "${local.name_prefix}-vpc"
  auto_create_subnetworks = false
}

resource "google_compute_subnetwork" "private" {
  name          = "${local.name_prefix}-subnet-private"
  ip_cidr_range = "10.0.1.0/24"
  region        = var.gcp_region
  network       = google_compute_network.main.id

  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = "10.1.0.0/16"
  }
  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = "10.2.0.0/20"
  }
}

resource "google_container_cluster" "main" {
  name     = "${local.name_prefix}-gke"
  location = var.gcp_region

  # Cluster autopilot (recommandé)
  enable_autopilot = true

  network    = google_compute_network.main.id
  subnetwork = google_compute_subnetwork.private.id

  ip_allocation_policy {
    cluster_secondary_range_name  = "pods"
    services_secondary_range_name = "services"
  }

  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = false
    master_ipv4_cidr_block  = "172.16.0.0/28"
  }
}
```

## Provider Azure

### Configuration du provider Azure

```hcl
provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = true
    }
    key_vault {
      purge_soft_delete_on_destroy = false
    }
  }
  subscription_id = var.azure_subscription_id
}
```

### VNet, AKS et Azure SQL

```hcl
resource "azurerm_resource_group" "main" {
  name     = "${local.name_prefix}-rg"
  location = var.azure_location
  tags     = local.common_tags
}

resource "azurerm_virtual_network" "main" {
  name                = "${local.name_prefix}-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_kubernetes_cluster" "main" {
  name                = "${local.name_prefix}-aks"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  dns_prefix          = local.name_prefix
  kubernetes_version  = "1.29"

  default_node_pool {
    name                = "system"
    node_count          = 2
    vm_size             = "Standard_D4s_v5"
    vnet_subnet_id      = azurerm_subnet.aks.id
    enable_auto_scaling = true
    min_count           = 2
    max_count           = 10
  }

  identity {
    type = "SystemAssigned"
  }

  network_profile {
    network_plugin    = "azure"
    load_balancer_sku = "standard"
  }
}

resource "azurerm_mssql_server" "main" {
  name                         = "${local.name_prefix}-sqlserver"
  resource_group_name          = azurerm_resource_group.main.name
  location                     = azurerm_resource_group.main.location
  version                      = "12.0"
  administrator_login          = var.db_username
  administrator_login_password = var.db_password
}
```

## Infrastructure cloud moderne — pattern de référence

Une infrastructure cloud moderne réunit typiquement :

- **Réseau** : VPC/VNet avec subnets privés/publics, NAT gateway, peering, VPN ou Direct Connect
- **Compute** : Kubernetes managé (EKS/GKE/AKS) avec node groups hétérogènes (on-demand + spot)
- **Données** : Base relationnelle managée avec réplicas en lecture, cache Redis, stockage objet
- **DNS et trafic** : Load balancer + ingress controller, CDN, certificats TLS automatisés
- **Sécurité** : IAM avec moindre privilège, secrets managés, chiffrement at-rest et in-transit
- **Observabilité** : métriques, logs, traces corrélées (voir chapitres dédiés)

## Stratégie multi-cloud

Terraform permet de gérer plusieurs clouds dans la même configuration grâce à l'instanciation de providers distincts.

```hcl
# Abstraction cross-cloud : déploiement d'un bucket de logs sur AWS et GCP
module "logs_aws" {
  source      = "./modules/object-storage/aws"
  bucket_name = "logs-${var.environment}"
  region      = "eu-west-1"
  versioning  = true
}

module "logs_gcp" {
  source      = "./modules/object-storage/gcp"
  bucket_name = "logs-${var.project}-${var.environment}"
  location    = "EU"
  versioning  = true
}
```

:::{admonition} Complexité du multi-cloud
:class: warning
Le multi-cloud augmente la surface de gestion. Terraform abstrait le provisioning mais pas les différences sémantiques entre providers (politiques IAM, modèles réseau, types de nœuds Kubernetes). Justifiez le multi-cloud par des exigences métier réelles (résilience, réglementation) et non par le seul fait d'éviter le vendor lock-in.
:::

## Terragrunt : DRY pour Terraform

Terragrunt est un wrapper Terraform qui résout deux problèmes récurrents : la duplication de configuration backend et la gestion des dépendances entre stacks.

```hcl
# terragrunt.hcl (à la racine du dépôt)
locals {
  account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl"))
  env_vars     = read_terragrunt_config(find_in_parent_folders("env.hcl"))

  account_id  = local.account_vars.locals.account_id
  environment = local.env_vars.locals.environment
}

remote_state {
  backend = "s3"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
  config = {
    bucket         = "tf-states-${local.account_id}"
    key            = "${path_relative_to_include()}/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# envs/prod/eks/terragrunt.hcl
dependency "vpc" {
  config_path = "../vpc"

  mock_outputs = {
    vpc_id         = "vpc-00000000"
    private_subnet_ids = ["subnet-00000000"]
  }
}

inputs = {
  vpc_id     = dependency.vpc.outputs.vpc_id
  subnet_ids = dependency.vpc.outputs.private_subnet_ids
}
```

```bash
# Déployer toutes les stacks d'un environnement dans l'ordre des dépendances
terragrunt run-all apply --terragrunt-working-dir envs/prod

# Planifier en parallèle
terragrunt run-all plan --terragrunt-parallelism 4
```

## Pulumi : positionnement vs Terraform

Pulumi prend une approche différente : l'infrastructure est décrite dans un **langage de programmation généraliste** (TypeScript, Python, Go, Java, C#) plutôt qu'un DSL déclaratif.

```python
# Exemple Pulumi Python — équivalent du module EKS Terraform
import pulumi
import pulumi_aws as aws
import pulumi_eks as eks

# Les ressources sont des objets Python
vpc = aws.ec2.Vpc("main-vpc",
    cidr_block="10.0.0.0/16",
    enable_dns_hostnames=True)

cluster = eks.Cluster("prod-eks",
    vpc_id=vpc.id,
    instance_type="t3.medium",
    desired_capacity=3,
    min_size=2,
    max_size=10)

pulumi.export("kubeconfig", cluster.kubeconfig)
```

| Critère | Terraform / OpenTofu | Pulumi |
| --- | --- | --- |
| Langage | HCL (DSL déclaratif) | Python, TypeScript, Go, Java… |
| Courbe d'apprentissage | Modérée (HCL spécifique) | Variable (dépend du langage connu) |
| Logique conditionnelle | Limitée (ternaire, for) | Complète (boucles, classes, libs) |
| State management | Fichier tfstate / backends | Pulumi Cloud ou backends S3/GCS |
| Écosystème providers | 3000+ providers | Basé sur les providers Terraform |
| Tests unitaires | Faibles (terratest externe) | Natifs (pytest, jest…) |

## Coûts et optimisation

```hcl
# Utiliser des instances Spot/Preemptible pour les workloads tolerant les interruptions
resource "aws_eks_node_group" "spot" {
  # ...
  capacity_type  = "SPOT"
  instance_types = ["c5.xlarge", "c5a.xlarge", "c5d.xlarge"]  # plusieurs types pour disponibilité

  scaling_config {
    desired_size = 4
    min_size     = 2
    max_size     = 20
  }
}

# Rightsizing : utiliser des locals pour centraliser les tailles par environnement
locals {
  instance_sizes = {
    dev     = { db = "db.t3.micro",   eks = "t3.small" }
    staging = { db = "db.t3.medium",  eks = "t3.medium" }
    prod    = { db = "db.r6g.large",  eks = "c5.xlarge" }
  }
  sizes = local.instance_sizes[var.environment]
}
```

:::{admonition} Infracost — estimation de coût dans la CI
:class: tip
L'outil [Infracost](https://www.infracost.io/) s'intègre dans le pipeline CI et affiche l'impact financier de chaque `terraform plan` avant l'apply. Il permet de détecter les augmentations de coût involontaires (ex : changement de type d'instance, ajout d'un NAT gateway).
:::

---

## Visualisations

```{code-cell} python3
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)
```

```{code-cell} python3
# Visualisation 1 : Radar chart comparatif AWS / GCP / Azure

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

categories = ["Kubernetes\nmanagé", "Base de\ndonnées", "Serverless", "Prix relatif\n(inversé)", "Maturité\nIaC", "Écosystème\noutils"]
N = len(categories)

# Scores sur 10 (subjectifs, à des fins pédagogiques)
scores = {
    "AWS":   [9, 9, 9, 6, 9, 9],
    "GCP":   [9, 8, 8, 7, 8, 7],
    "Azure": [8, 8, 7, 7, 8, 8],
}

angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]

fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
ax.set_theta_offset(np.pi / 2)
ax.set_theta_direction(-1)
ax.set_xticks(angles[:-1])
ax.set_xticklabels(categories, size=10)
ax.set_ylim(0, 10)
ax.set_yticks([2, 4, 6, 8, 10])
ax.set_yticklabels(["2", "4", "6", "8", "10"], size=8, color="#868e96")
ax.set_title("Comparaison AWS / GCP / Azure\n(services cloud pour infrastructure moderne)", fontsize=13, fontweight="bold", pad=25)

colors_radar = {"AWS": "#ff922b", "GCP": "#4dabf7", "Azure": "#74c0fc"}
for provider, vals in scores.items():
    vals_closed = vals + vals[:1]
    ax.plot(angles, vals_closed, linewidth=2.2, label=provider, color=colors_radar[provider])
    ax.fill(angles, vals_closed, alpha=0.12, color=colors_radar[provider])

ax.legend(loc="upper right", bbox_to_anchor=(1.35, 1.15), fontsize=10)

plt.savefig("cloud_radar.png", dpi=120, bbox_inches="tight")
plt.show()
```

```{code-cell} python3
# Visualisation 2 : Simulation de coût selon la stratégie multi-régions

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

strategies = ["Single region", "Active-passive\n(2 régions)", "Active-active\n(3 régions)"]
composants = ["Compute (EKS)", "Base de données", "Réseau (data transfer)", "Load balancers", "Stockage"]

# Coûts mensuels simulés en €
costs = np.array([
    [1200, 2400, 3800],   # Compute
    [ 400,  800, 1200],   # BDD
    [ 100,  400,  900],   # Réseau
    [ 150,  350,  600],   # LB
    [ 200,  400,  700],   # Stockage
])

x = np.arange(len(strategies))
width = 0.14
palette = ["#4dabf7", "#51cf66", "#ff922b", "#cc5de8", "#ffd43b"]

fig, ax = plt.subplots(figsize=(11, 6))
bottoms = np.zeros(len(strategies))

for i, (comp, color) in enumerate(zip(composants, palette)):
    bars = ax.bar(x, costs[i], width=0.55, bottom=bottoms,
                  label=comp, color=color, alpha=0.88, edgecolor="white", linewidth=0.8)
    bottoms += costs[i]

# Totaux
for i, total in enumerate(bottoms):
    ax.text(i, total + 40, f"{total:,.0f} €/mois", ha="center", va="bottom",
            fontsize=10, fontweight="bold", color="#333")

ax.set_xticks(x)
ax.set_xticklabels(strategies, fontsize=11)
ax.set_ylabel("Coût mensuel estimé (€)")
ax.set_title("Simulation de coût d'infrastructure multi-régions\nselon la stratégie de déploiement", fontsize=13, fontweight="bold")
ax.legend(title="Composant", loc="upper left", fontsize=9)
ax.set_ylim(0, 8500)

plt.savefig("multiregion_costs.png", dpi=120, bbox_inches="tight")
plt.show()
```

```{code-cell} python3
# Visualisation 3 : Heatmap comparative Terraform vs Pulumi vs CDK

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

criteres = [
    "Flexibilité du\nlangage",
    "Courbe d'appren-\ntissage IaC",
    "Écosystème\nproviders",
    "Gestion\ndu state",
    "Tests\nunitaires",
    "Maturité /\ncommunauté",
    "Multi-cloud\nnatif",
    "Intégration\nCI/CD",
]

# Scores /10 pour chaque outil
scores_matrix = np.array([
    # Terraform  Pulumi   CDK (AWS)
    [4,          9,       7       ],  # Flexibilité langage
    [7,          6,       5       ],  # Courbe apprentissage (élevé = facile)
    [9,          8,       5       ],  # Écosystème providers
    [8,          7,       6       ],  # Gestion state
    [4,          9,       8       ],  # Tests unitaires
    [9,          7,       7       ],  # Maturité
    [9,          8,       3       ],  # Multi-cloud
    [9,          8,       7       ],  # CI/CD
])

fig, ax = plt.subplots(figsize=(9, 8))
im = ax.imshow(scores_matrix, cmap="YlGn", aspect="auto", vmin=0, vmax=10)

outils = ["Terraform /\nOpenTofu", "Pulumi", "AWS CDK"]
ax.set_xticks(range(len(outils)))
ax.set_yticks(range(len(criteres)))
ax.set_xticklabels(outils, fontsize=11, fontweight="bold")
ax.set_yticklabels(criteres, fontsize=9.5)
ax.set_title("Comparaison Terraform / Pulumi / AWS CDK\n(score /10)", fontsize=13, fontweight="bold", pad=15)

for i in range(len(criteres)):
    for j in range(len(outils)):
        val = scores_matrix[i, j]
        color = "white" if val > 7 else "#333"
        ax.text(j, i, str(val), ha="center", va="center", fontsize=12, fontweight="bold", color=color)

plt.colorbar(im, ax=ax, label="Score (10 = meilleur)", shrink=0.8)

plt.savefig("iac_tools_heatmap.png", dpi=120, bbox_inches="tight")
plt.show()
```

## Résumé

1. Les trois grands clouds partagent les mêmes catégories de services (réseau, compute, K8s managé, BDD, IAM) mais avec des modèles et des APIs suffisamment différents pour que chaque provider Terraform soit distinct.
2. Le provider **AWS** est le plus mature et le plus riche en ressources gérées par Terraform ; les modules `terraform-aws-modules` couvrent l'essentiel des besoins courants.
3. Le provider **GCP** se distingue par son réseau global (pas de régional par défaut) et GKE Autopilot, qui délègue entièrement la gestion des nœuds à Google.
4. Le provider **Azure** impose la notion de `resource_group` comme conteneur de toutes les ressources, ce qui structure naturellement le découpage par domaine ou environnement.
5. Une **infrastructure cloud moderne** combine réseau privé, Kubernetes managé pour le compute applicatif, base de données managée, et une couche IAM fine — tous ces éléments s'orchestrent naturellement avec Terraform.
6. Le **multi-cloud** avec Terraform est techniquement possible mais doit être justifié par des exigences métier réelles ; la complexité opérationnelle augmente significativement.
7. **Terragrunt** résout les limitations de Terraform en matière de DRY pour la configuration backend et les dépendances inter-stacks, sans imposer un nouveau langage.
8. **Pulumi** offre la puissance d'un vrai langage de programmation (boucles, classes, tests) au prix d'une courbe d'apprentissage plus longue et d'un écosystème moins étendu que Terraform.
9. L'optimisation des coûts doit être intégrée dès la conception IaC : instances Spot pour les workloads résilients, rightsizing par environnement via des locals, et Infracost dans la CI.
10. Quel que soit le cloud ou l'outil, les principes restent constants : modules réutilisables, state distant verrouillé, pipeline CI/CD pour chaque changement, et révision de code systématique pour l'infrastructure.
