> ## Documentation Index
> Fetch the complete documentation index at: https://ai-kb.automationanywhere.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Guide de déploiement Terragrunt

> Commandes étape par étape pour déployer l'infrastructure complète EKB EKS à l'aide de Terragrunt, incluant la validation par exécution à blanc et le déploiement réel.

Ce guide décrit le déploiement complet de l'infrastructure EKB EKS sur AWS à l'aide de Terragrunt. Il couvre l'installation des outils, la configuration de l'environnement et une séquence de déploiement par phases conçue pour garantir un ordre de dépendance correct entre tous les composants d'infrastructure.

Les déploiements sont organisés en neuf phases :

1. **Gestion de l'état** — Prépare le compartiment S3 utilisé pour stocker l'état Terraform de l'environnement.
2. **Infrastructure EKS** — Provisionne le VPC, les sous-réseaux, les passerelles NAT, les rôles IAM et le cluster EKS avec les groupes de nœuds gérés.
3. **Stockage et équilibrage de charge** — Déploie le pilote EBS CSI pour les volumes persistants et le contrôleur de équilibrage de charge AWS pour l'entrée ALB.
4. **Mise à l'échelle Karpenter** — Configure le provisionnement dynamique des nœuds avec support des instances Spot et gestion des interruptions via SQS et EventBridge.
5. **Mise à l'échelle KEDA** — Déploie KEDA pour la mise à l'échelle au niveau des pods basée sur les seuils CPU et mémoire.
6. **Services de données** — Provisionne Supabase (auto-hébergé ou Cloud), ElastiCache Redis et Amazon MQ RabbitMQ.
7. **Services Odin** — Déploie la pile d'application EKB (Web, FastAPI, Celery, Automator) via Helm.
8. **Observabilité SigNoz** — Déploie la traçabilité distribuée, les métriques et l'agrégation des journaux via SigNoz et l'agent k8s-infra.
9. **Déploiement final** — Exécute un `terragrunt apply` complet pour réconcilier les ressources restantes.

Avant de commencer, complétez la liste de prérequis avec le client et assurez-vous que tous les espaces réservés `<YOUR_*>` dans le modèle d'environnement sont remplis. Certaines valeurs — notamment l'ID VPC, le point de terminaison du cluster EKS et les points de terminaison Redis et RabbitMQ — ne sont disponibles qu'après l'achèvement de certaines phases, c'est pourquoi le guide indique exactement quand les capturer et les appliquer.

***

## Prérequis

* AWS CLI configuré avec les permissions appropriées
* Terraform (>= 1.0)
* Terragrunt (dernière version)
* `kubectl` pour la gestion Kubernetes
* `helm` pour la gestion des graphiques Helm

***

## Guide d'installation

### Installation de Terragrunt

**macOS (Homebrew)**

```bash theme={null}
brew install terragrunt
```

**Linux (apt)**

```bash theme={null}
# Add HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg

# Add HashiCorp repository
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Update and install
sudo apt update
sudo apt install terragrunt
```

**Windows (Chocolatey)**

```bash theme={null}
choco install terragrunt
```

### Installation de kubectl

**macOS (Homebrew)**

```bash theme={null}
brew install kubectl
```

**Linux**

```bash theme={null}
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
```

**Windows (Chocolatey)**

```bash theme={null}
choco install kubernetes-cli
```

### Installation de Helm

**macOS (Homebrew)**

```bash theme={null}
brew install helm
```

**Linux**

```bash theme={null}
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
```

**Windows (Chocolatey)**

```bash theme={null}
choco install kubernetes-helm
```

### Vérification de l'installation

```bash theme={null}
terragrunt --version
terraform --version
kubectl version --client
helm version
```

### Configuration de l'AWS CLI

```bash theme={null}
# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Configure AWS credentials
aws configure

# Verify configuration
aws sts get-caller-identity
```

***

## Créer un nouvel environnement

### Étape 1 : Copier le modèle d'environnement

Le dossier `env-template-folder` contient des fichiers pré-structurés avec des espaces réservés `<YOUR_*>` prêts à être remplis. Copiez-le entièrement pour créer votre nouveau dossier d'environnement.

```bash theme={null}
# Navigate to the terragrunt environments directory
cd terragrunt/environments

# Copy the full template folder to a new environment (replace 'your-env-name')
cp -r env-template-folder your-env-name

# The folder structure is ready:
# your-env-name/
# ├── terragrunt.hcl               # Core cluster configuration
# ├── state/
# │   └── terragrunt.hcl           # S3 state bucket configuration
# └── values/
#     ├── infrastructure.yaml      # AWS Load Balancer Controller
#     ├── karpenter-values.yaml    # Karpenter controller settings
#     ├── karpenter-nodeclasses.yaml  # EC2NodeClass definitions
#     ├── karpenter.yaml           # Karpenter NodePool definitions
#     ├── keda.yaml                # KEDA autoscaler
#     ├── aws-ebs-csi-driver.yaml  # EBS CSI driver
#     ├── odin-services.yaml       # Odin application services
#     ├── supabase.yaml            # Supabase (if self-hosting)
#     ├── ha-supabase-db.yaml      # Supabase HA DB (if self-hosting)
#     ├── cloudnative-pg.yaml      # CloudNativePG operator (if self-hosting)
#     ├── signoz.yaml              # SigNoz observability (optional)
#     └── signoz-k8s-infra.yaml    # SigNoz k8s metrics (optional)
```

### Étape 2 : Vérifier que tous les espaces réservés sont présents

```bash theme={null}
cd your-env-name

# List all placeholders that need to be filled in
grep -r "<YOUR_" . --include="*.hcl" --include="*.yaml" | sort
```

Tous les espaces réservés suivent la convention `<YOUR_*>`. Les étapes suivantes guident leur remplissage fichier par fichier.

### Étape 3 : Provisionner les certificats SSL (AWS ACM)

Avant de définir les variables d'environnement, vous avez besoin des ARN des certificats. Utilisez la console AWS pour demander des certificats SSL dans AWS Certificate Manager (ACM) pour tous les domaines que votre environnement desservira.

**Option A : Un seul certificat wildcard (recommandé)**

Un seul certificat wildcard couvre tous les sous-domaines avec un ARN. Par exemple, si votre domaine de base est `app.example.com`, un seul certificat `*.app.example.com` couvre :

| Service   | Domaine                       |
| --------- | ----------------------------- |
| Web       | `app.example.com`             |
| FastAPI   | `api-app.example.com`         |
| Automator | `automations-app.example.com` |
| Supabase  | `supabase-app.example.com`    |
| SigNoz    | `signoz-app.example.com`      |

**Option B : Certificats par service**

Demandez un certificat par domaine si vous ne pouvez pas utiliser de wildcard. Répétez les étapes ci-dessous pour chaque domaine : `<YOUR_WEB_DOMAIN>`, `<YOUR_API_DOMAIN>`, `<YOUR_AUTOMATOR_DOMAIN>`, `<YOUR_SUPABASE_DOMAIN>` (uniquement si `ENABLE_SUPABASE=true`), `<YOUR_SIGNOZ_DOMAIN>` (uniquement si `ENABLE_SIGNOZ=true`).

**Demander un certificat dans la console AWS**

1. Ouvrez la console AWS Certificate Manager
2. Passez à la bonne région (en haut à droite) — elle doit correspondre à `<YOUR_AWS_REGION>`
3. Cliquez sur **Demander un certificat** → **Demander un certificat public** → **Suivant**
4. Sous **Nom de domaine pleinement qualifié**, saisissez le wildcard (par ex. `*.app.example.com`) ou un domaine spécifique
5. Définissez la **Méthode de validation** sur **Validation DNS**
6. Cliquez sur **Demander** — le certificat est créé dans l'état `En attente de validation`

**Ajouter l'enregistrement CNAME DNS de validation**

ACM génère un enregistrement CNAME que vous devez ajouter à votre fournisseur DNS pour prouver la propriété du domaine. Obtenez les valeurs depuis la console ACM en ouvrant le certificat et en développant le domaine sous **Domaines**.

| Champ DNS             | Valeur                                                                      |
| --------------------- | --------------------------------------------------------------------------- |
| Type d'enregistrement | `CNAME`                                                                     |
| Nom / Hôte            | par ex. `_fa187f22ac17bce6f508bf3c56439c61.signoz-app.example.com.`         |
| Valeur / Pointe vers  | par ex. `_c7c97325fe38061e168e232d122c7ff3.jkddzztszm.acm-validations.aws.` |

<Info>
  Incluez le point final (`.`) à la fin des valeurs CNAME si votre fournisseur DNS l'exige.
</Info>

**Cloudflare**

1. Connectez-vous à Cloudflare → sélectionnez votre domaine → accédez à **DNS** → **Enregistrements** → **Ajouter un enregistrement**
2. Définissez le **Type** sur `CNAME`
3. Collez le nom CNAME d'ACM dans **Nom** et la valeur CNAME d'ACM dans **Cible**
4. Définissez l'**État du proxy** sur **DNS uniquement** (icône nuage gris) — le certificat ne sera pas validé via le proxy Cloudflare
5. Cliquez sur **Enregistrer**

**Route 53**

1. Ouvrez la console Route 53 → **Zones hébergées** → sélectionnez votre zone → **Créer un enregistrement**
2. Définissez le **Type d'enregistrement** sur `CNAME`
3. Collez le nom CNAME d'ACM dans **Nom de l'enregistrement** (partie sous-domaine uniquement) et la valeur dans **Valeur**
4. Définissez le TTL sur `300` et cliquez sur **Créer les enregistrements**

<Tip>
  Dans ACM, vous pouvez également cliquer sur **Créer des enregistrements dans Route 53** pour qu'ACM ajoute l'enregistrement automatiquement si la zone hébergée se trouve dans le même compte.
</Tip>

Une fois la propagation DNS effectuée (généralement 1 à 5 minutes), le statut du certificat passe à **Émis**. Copiez l'ARN depuis le haut du certificat — il ressemble à `arn:aws:acm:<region>:<account-id>:certificate/<uuid>`. Conservez le(s) ARN pour l'étape suivante.

### Étape 4 : Définir les variables d'environnement

Définissez ces variables d'environnement shell avant d'exécuter les commandes Terragrunt. Elles sont lues directement par `terragrunt.hcl` via `get_env()`.

```bash theme={null}
export AWS_REGION="<YOUR_AWS_REGION>"          # e.g., "eu-west-2", "us-east-2"
export CLUSTER_NAME="<env_folder_name>"          # e.g., "env-template-folder"

# Domain configuration
export WEB_DOMAIN="<YOUR_WEB_DOMAIN>"                    # e.g., "app.example.com"
export FASTAPI_DOMAIN="<YOUR_API_DOMAIN>"                # e.g., "api-app.example.com"
export AUTOMATOR_DOMAIN="<YOUR_AUTOMATOR_DOMAIN>"        # e.g., "automations-app.example.com"
export SUPABASE_DOMAIN="<YOUR_SUPABASE_DOMAIN>"          # e.g., "supabase-app.example.com"
export SIGNOZ_DOMAIN="<YOUR_SIGNOZ_DOMAIN>"              # e.g., "signoz-app.example.com"

# SSL Certificate ARNs — Option A: Single wildcard certificate (recommended)
export WILDCARD_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_WILDCARD_CERT_ID>"

# SSL Certificate ARNs — Option B: Per-service certificates
export WEB_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_WEB_CERT_ID>"
export FASTAPI_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_API_CERT_ID>"
export AUTOMATOR_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_AUTOMATOR_CERT_ID>"
export SUPABASE_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_SUPABASE_CERT_ID>"
export SIGNOZ_CERTIFICATE_ARN="arn:aws:acm:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:certificate/<YOUR_SIGNOZ_CERT_ID>"

# Service enablement flags
export ENABLE_ALB_CONTROLLER="true"
export ENABLE_AWS_SERVICES="true"      # Set to "true" to enable ElastiCache and AmazonMQ

# Supabase stack (self-hosted) — enable all three together if self-hosting Supabase
export ENABLE_CNPG="true"             # CloudNativePG operator  (namespace: cnpg-system)
export ENABLE_HA_SUPABASE_DB="true"   # Supabase HA database    (namespace: ha-supabase-db)
export ENABLE_SUPABASE="true"         # Supabase application    (namespace: supabase)

export ENABLE_SIGNOZ="true"           # Set to "true" to enable SigNoz observability
export SSL_TERMINATION="alb"
```

#### Instances Spot et charges de travail persistantes

Les instances Spot sont configurées par NodePool dans `values/karpenter.yaml`, pas via les variables d'environnement. Chaque NodePool déclare sa propre stratégie de capacité :

| NodePool            | Libellé `workload-type`                      | Type de capacité          | Justification                                                                   |
| ------------------- | -------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------- |
| `general`           | `general`                                    | Spot → On-Demand de repli | Optimisé pour les charges de travail batch/arrière-plan sans état               |
| `compute-intensive` | `compute-intensive`                          | Spot → On-Demand de repli | Optimisé pour les charges de travail CPU-bound                                  |
| `memory-intensive`  | `memory-intensive`                           | On-Demand → Spot de repli | Stabilité prioritaire pour les pods à haute mémoire                             |
| `gpu`               | `gpu`                                        | Spot → On-Demand de repli | Optimisé pour les charges de travail batch IA/ML                                |
| `application`       | `application`                                | On-Demand uniquement      | Services stables面向utilisateur (Supabase, Kong, etc.) — pas d'interruptions Spot |
| `database`          | `database` / `node-type: database-dedicated` | On-Demand uniquement      | Persistant — l'interruption Spot est dangereuse pour les bases de données       |

Le NodePool `application` utilise les familles d'instances m/c (génération 5+) avec On-Demand uniquement. Les pods de service Supabase sont épinglés ici via `nodeSelector: workload-type: "application"` pour garantir qu'ils ne sont jamais interrompus par un événement de récupération Spot.

Le NodePool `database-dedicated` n'utilise jamais Spot. Il utilise `consolidationPolicy: WhenEmpty` afin que Karpenter n'évince pas un nœud qui a encore un pod en cours d'exécution, ce qui le rend sûr pour les charges de travail persistantes telles que PostgreSQL et les réplicas CloudNativePG.

**Directives pour les charges de travail persistantes sur Spot :**

* N'exécutez pas de bases de données, de files d'attente persistantes ou de pod avec un `PersistentVolumeClaim` sur les NodePools Spot.
* Utilisez un `nodeSelector` ciblant `node-type: database-dedicated` avec la tolérance correspondante `database-workload: "true"` pour les pods de base de données.
* Utilisez `nodeSelector: workload-type: "application"` pour les services sans état面向utilisateur qui doivent rester disponibles sans interruption.
* Pour les charges de travail en arrière-plan (Web, API, Celery, Automator), le NodePool Spot `general` est approprié — le gestionnaire d'interruptions SQS de Karpenter évacue gracieusement les nœuds Spot avant qu'AWS ne les récupère, et le nombre minimum de réplicas KEDA (≥ 2) garantit la disponibilité pendant le remplacement des nœuds.
* Pour désactiver Spot globalement, supprimez `"spot"` de la liste des valeurs dans chaque NodePool dans `values/karpenter.yaml`.

**Comment Karpenter gère les avertissements d'interruption Spot :**

AWS donne un préavis d'interruption de 2 minutes avant de résilier une instance Spot. Karpenter utilise EventBridge et SQS pour agir automatiquement :

```
AWS Spot Interruption Event
        │
        ▼
Amazon EventBridge (CloudWatch Events)
  Rule: EC2 Spot Instance Interruption Warning
        │
        ▼
   SQS Queue (Karpenter interruption queue)
        │
        ▼
  Karpenter Controller (polls SQS continuously)
        │
        ├── Cordons the node (no new pods scheduled)
        ├── Drains existing pods (respects PodDisruptionBudgets)
        ├── Provisions a replacement node in parallel
        └── Pods reschedule onto the new node before the 2-min window closes
```

Ceci est configuré dans le bloc `karpenter` de `terragrunt.hcl` :

```hcl theme={null}
karpenter = {
  spot_interruption_handling = true   # creates the SQS queue and EventBridge rule
  enable_spot_instances       = true   # allows Spot in NodePool capacity requirements
}
```

### Étape 5 : Mettre à jour les valeurs spécifiques à l'environnement

Effectuez un rechercher-remplacer dans tous les fichiers de votre nouveau dossier d'environnement pour les espaces réservés suivants :

| Espace réservé          | Description                           | Exemple                  |
| ----------------------- | ------------------------------------- | ------------------------ |
| `<YOUR_ENV_NAME>`       | Identifiant unique de l'environnement | `app-eks-prod`           |
| `<YOUR_AWS_REGION>`     | Région AWS du cluster                 | `eu-west-2`, `us-east-2` |
| `<YOUR_AWS_ACCOUNT_ID>` | ID de compte AWS à 12 chiffres        | `123456789012`           |
| `<YOUR_ENVIRONMENT>`    | Valeur du tag d'environnement         | `prod`, `staging`, `dev` |
| `<YOUR_PROJECT>`        | Valeur du tag projet                  | `odin`, `ekb`            |

```bash theme={null}
# Run from your new env folder to find all remaining placeholders
grep -r "<YOUR_" .
```

#### 5.1 `terragrunt.hcl` — Configuration principale du cluster

```bash theme={null}
nano terragrunt.hcl
```

| Champ                                      | Espace réservé             | Notes                                                                                                    |
| ------------------------------------------ | -------------------------- | -------------------------------------------------------------------------------------------------------- |
| `cluster_name`                             | `<YOUR_ENV_NAME>`          | Doit correspondre au nom du cluster EKS                                                                  |
| `cluster_region`                           | `<YOUR_AWS_REGION>`        | Région AWS                                                                                               |
| `aws_account_id`                           | `<YOUR_AWS_ACCOUNT_ID>`    | ID de compte à 12 chiffres                                                                               |
| `vpc_cidr`                                 | `<YOUR_VPC_CIDR>`          | par ex. `192.168.0.0/16`                                                                                 |
| `availability_zones`                       | `<YOUR_REGION>a/b/c`       | 3 AZ dans votre région                                                                                   |
| `tags.Environment`                         | `<YOUR_ENVIRONMENT>`       | par ex. `prod`                                                                                           |
| `tags.Project`                             | `<YOUR_PROJECT>`           | par ex. `odin`                                                                                           |
| `aws_services.amazon_mq.rabbitmq.username` | `<YOUR_RABBITMQ_USERNAME>` | Nom d'utilisateur admin RabbitMQ (uniquement quand `ENABLE_AWS_SERVICES=true`)                           |
| `aws_services.amazon_mq.rabbitmq.password` | `<YOUR_RABBITMQ_PASSWORD>` | Min 12 caractères ; doit inclure des majuscules, des minuscules, des chiffres et des caractères spéciaux |

#### 5.2 `state/terragrunt.hcl` — Compartiment d'état S3

```bash theme={null}
nano state/terragrunt.hcl
```

| Champ         | Espace réservé                         | Notes                                 |
| ------------- | -------------------------------------- | ------------------------------------- |
| `bucket_name` | `odin-terraform-state-<YOUR_ENV_NAME>` | Doit être unique à l'échelle mondiale |
| `region`      | `<YOUR_AWS_REGION>`                    | Même région que le cluster            |

#### 5.3 `values/infrastructure.yaml` — Contrôleur de équilibrage de charge AWS

<Warning>
  Obtenez l'ID VPC **après** la création du cluster EKS avant de déployer le contrôleur de équilibrage de charge AWS.
</Warning>

```bash theme={null}
# Get VPC ID after EKS cluster is created
aws eks describe-cluster --name <YOUR_ENV_NAME> \
  --query "cluster.resourcesVpcConfig.vpcId" --output text
```

```bash theme={null}
nano values/infrastructure.yaml
```

| Champ                                                   | Espace réservé                             | Notes                           |
| ------------------------------------------------------- | ------------------------------------------ | ------------------------------- |
| `clusterName`                                           | `<YOUR_ENV_NAME>`                          | Nom du cluster EKS              |
| `region`                                                | `<YOUR_AWS_REGION>`                        | Région AWS                      |
| `vpcId`                                                 | `<YOUR_VPC_ID>`                            | Requis avant le déploiement ALB |
| `serviceAccount.annotations.eks.amazonaws.com/role-arn` | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rôle IAM pour le contrôleur ALB |

#### 5.4 `values/karpenter-values.yaml` — Contrôleur Karpenter

<Warning>
  Obtenez le point de terminaison du cluster EKS **après** la création du cluster EKS et avant de déployer Karpenter.
</Warning>

```bash theme={null}
# Get cluster endpoint after EKS cluster is created
aws eks describe-cluster --name <YOUR_ENV_NAME> \
  --query "cluster.endpoint" --output text
```

```bash theme={null}
nano values/karpenter-values.yaml
```

| Champ                                                   | Espace réservé                             | Notes                                 |
| ------------------------------------------------------- | ------------------------------------------ | ------------------------------------- |
| `serviceAccount.annotations.eks.amazonaws.com/role-arn` | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rôle IAM pour Karpenter               |
| `env.CLUSTER_NAME`                                      | `<YOUR_ENV_NAME>`                          | Nom du cluster EKS                    |
| `env.CLUSTER_ENDPOINT`                                  | `<YOUR_EKS_CLUSTER_ENDPOINT>`              | Requis avant le déploiement Karpenter |
| `settings.aws.defaultInstanceProfile`                   | `<YOUR_ENV_NAME>`                          | Profil d'instance nœud Karpenter      |

#### 5.5 `values/karpenter-nodeclasses.yaml` — Classes de nœuds Karpenter

```bash theme={null}
nano values/karpenter-nodeclasses.yaml
```

| Champ                                                      | Espace réservé       | Notes                                        |
| ---------------------------------------------------------- | -------------------- | -------------------------------------------- |
| Toutes les balises `kubernetes.io/cluster/<YOUR_ENV_NAME>` | `<YOUR_ENV_NAME>`    | Balise cluster pour les sélecteurs subnet/SG |
| `user_data` nom de cluster d'amorçage                      | `<YOUR_ENV_NAME>`    | Script d'amorçage du nœud                    |
| `tags.Environment`                                         | `<YOUR_ENVIRONMENT>` | par ex. `prod`                               |
| `tags.Project`                                             | `<YOUR_PROJECT>`     | par ex. `odin`                               |

#### 5.6 `values/aws-ebs-csi-driver.yaml` — Pilote EBS CSI

```bash theme={null}
nano values/aws-ebs-csi-driver.yaml
```

| Champ                                                              | Espace réservé                             | Notes                               |
| ------------------------------------------------------------------ | ------------------------------------------ | ----------------------------------- |
| `controller.serviceAccount.annotations.eks.amazonaws.com/role-arn` | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rôle IAM pour le contrôleur EBS CSI |
| `node.serviceAccount.annotations.eks.amazonaws.com/role-arn`       | `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENV_NAME>` | Rôle IAM pour le nœud EBS CSI       |
| `controller.env.AWS_DEFAULT_REGION`                                | `<YOUR_AWS_REGION>`                        | Région AWS                          |
| `controller.env.AWS_REGION`                                        | `<YOUR_AWS_REGION>`                        | Région AWS                          |
| `node.env.AWS_DEFAULT_REGION`                                      | `<YOUR_AWS_REGION>`                        | Région AWS                          |
| `node.env.AWS_REGION`                                              | `<YOUR_AWS_REGION>`                        | Région AWS                          |

#### 5.7 `values/karpenter.yaml` — NodePools Karpenter

```bash theme={null}
nano values/karpenter.yaml
```

| Champ                                        | Espace réservé                                           | Notes                                     |
| -------------------------------------------- | -------------------------------------------------------- | ----------------------------------------- |
| `*.labels.Environment`                       | `<YOUR_ENVIRONMENT>`                                     | Appliqué à toutes les balises de NodePool |
| `*.requirements topology.kubernetes.io/zone` | `["<YOUR_REGION>a", "<YOUR_REGION>b", "<YOUR_REGION>c"]` | AZ pour tous les NodePools                |

Les noms de classes de nœuds (`general`, `compute-intensive`, `memory-intensive`, `gpu`, `database`) doivent correspondre aux entrées de `karpenter-nodeclasses.yaml`.

#### 5.8 `values/keda.yaml` — Mise à l'échelle KEDA

Aucun espace réservé spécifique à l'environnement requis. Les limites de ressources et les nombres de réplicas sont préconfigurés avec des valeurs par défaut raisonnables. Examinez et ajustez si nécessaire.

#### 5.9 `values/supabase.yaml` — Application Supabase (uniquement si `ENABLE_SUPABASE=true`)

<Warning>
  Toutes les clés ci-dessous doivent être générées de manière cohérente et partagées avec `ha-supabase-db.yaml`. Générez-les une seule fois et utilisez les mêmes valeurs dans les deux fichiers.
</Warning>

```bash theme={null}
# Generate JWT secret
openssl rand -hex 32

# Generate anon/service role JWTs (requires Supabase CLI)
brew install supabase/tap/supabase
supabase gen-keys

# Generate passwords and tokens
openssl rand -hex 24       # for passwords
openssl rand -base64 64    # for secretKeyBase
```

```bash theme={null}
nano values/supabase.yaml
```

| Champ                                 | Espace réservé                             | Notes                                                                |
| ------------------------------------- | ------------------------------------------ | -------------------------------------------------------------------- |
| `secret.jwt.anonKey`                  | `<YOUR_SUPABASE_ANON_KEY>`                 | Doit correspondre à `anonKey` de `ha-supabase-db.yaml`               |
| `secret.jwt.serviceKey`               | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>`         | Doit correspondre à `serviceRoleKey` de `ha-supabase-db.yaml`        |
| `secret.jwt.secret`                   | `<YOUR_SUPABASE_JWT_SECRET>`               | Doit correspondre à `jwtSecret` de `ha-supabase-db.yaml`             |
| `secret.db.password`                  | `<YOUR_SUPABASE_DB_PASSWORD>`              | Doit correspondre à `postgresPassword` de `ha-supabase-db.yaml`      |
| `secret.analytics.publicAccessToken`  | `<YOUR_SUPABASE_ANALYTICS_PUBLIC_TOKEN>`   | Token interne Logflare                                               |
| `secret.analytics.privateAccessToken` | `<YOUR_SUPABASE_ANALYTICS_PRIVATE_TOKEN>`  | Token interne Logflare                                               |
| `secret.dashboard.username`           | `<YOUR_SUPABASE_DASHBOARD_USERNAME>`       | Connexion à l'interface Studio                                       |
| `secret.dashboard.password`           | `<YOUR_SUPABASE_DASHBOARD_PASSWORD>`       | Connexion à l'interface Studio                                       |
| `secret.realtime.secretKeyBase`       | `<YOUR_SUPABASE_REALTIME_SECRET_KEY_BASE>` | Clé secrète Phoenix                                                  |
| `secret.meta.cryptoKey`               | `<YOUR_SUPABASE_META_CRYPTO_KEY>`          | `openssl rand -hex 32`                                               |
| `secret.s3.keyId`                     | `<YOUR_MINIO_KEY_ID>`                      | Doit correspondre à `secret.minio.user` (`openssl rand -hex 16`)     |
| `secret.s3.accessKey`                 | `<YOUR_MINIO_ACCESS_KEY>`                  | Doit correspondre à `secret.minio.password` (`openssl rand -hex 32`) |
| `secret.minio.user`                   | `<YOUR_MINIO_KEY_ID>`                      | Même valeur que `secret.s3.keyId`                                    |
| `secret.minio.password`               | `<YOUR_MINIO_ACCESS_KEY>`                  | Même valeur que `secret.s3.accessKey`                                |

#### 5.10 `values/ha-supabase-db.yaml` — Base de données HA Supabase (uniquement si `ENABLE_HA_SUPABASE_DB=true`)

<Warning>
  Les secrets ici doivent correspondre à `supabase.yaml`. Utilisez les mêmes valeurs générées pour `postgresPassword`, `jwtSecret`, `anonKey` et `serviceRoleKey`.
</Warning>

```bash theme={null}
nano values/ha-supabase-db.yaml
```

| Champ                                  | Espace réservé                     | Notes                                                          |
| -------------------------------------- | ---------------------------------- | -------------------------------------------------------------- |
| `secrets.inline.postgresPassword`      | `<YOUR_SUPABASE_DB_PASSWORD>`      | Doit correspondre à `secret.db.password` de `supabase.yaml`    |
| `secrets.inline.authenticatorPassword` | `<YOUR_SUPABASE_DB_PASSWORD>`      | Doit être identique à `postgresPassword`                       |
| `secrets.inline.pgbouncerPassword`     | `<YOUR_SUPABASE_DB_PASSWORD>`      | Doit être identique à `postgresPassword`                       |
| `secrets.inline.jwtSecret`             | `<YOUR_SUPABASE_JWT_SECRET>`       | Doit correspondre à `secret.jwt.secret` de `supabase.yaml`     |
| `secrets.inline.anonKey`               | `<YOUR_SUPABASE_ANON_KEY>`         | Doit correspondre à `secret.jwt.anonKey` de `supabase.yaml`    |
| `secrets.inline.serviceRoleKey`        | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Doit correspondre à `secret.jwt.serviceKey` de `supabase.yaml` |

La classe de stockage (`ebs-csi-gp2`), le nombre d'instances et les limites de ressources sont préconfigurés. Ajustez `postgres.storage.size` et `postgres.walStorage.size` en fonction du volume de données attendu.

#### 5.11 `values/cloudnative-pg.yaml` — Opérateur CloudNativePG (uniquement si `ENABLE_CNPG=true`)

Aucun espace réservé spécifique à l'environnement requis. Ceci déploie uniquement le contrôleur de l'opérateur CNPG. Les paramètres par défaut (3 réplicas, limites de ressources) conviennent à la plupart des environnements.

#### 5.12 `values/odin-services.yaml` — Services d'application Odin

<Warning>
  Les points de terminaison Redis et RabbitMQ ne sont disponibles qu'après que Terraform a créé ces ressources AWS. Les ARN de certificat doivent être provisionnés dans ACM avant le déploiement.
</Warning>

```bash theme={null}
nano values/odin-services.yaml
```

**Paramètres généraux :**

| Champ                  | Espace réservé                  | Notes                                                                                                 |
| ---------------------- | ------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `server`               | `<YOUR_WEB_DOMAIN>`             | Domaine web principal                                                                                 |
| `toolkitEncryptionKey` | `<YOUR_TOOLKIT_ENCRYPTION_KEY>` | Générer : `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` |

**Supabase (`dataServiceConfig`) — auto-hébergé (`ENABLE_SUPABASE=true`) :**

| Champ                        | Espace réservé                                                       | Source                                                |
| ---------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------- |
| `supabase.projectUrl`        | `http://supabase-kong:8000`                                          | Fixe — Kong Supabase interne                          |
| `supabase.key`               | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>`                                   | Même que `secret.jwt.serviceKey` dans `supabase.yaml` |
| `supabase.postgres.user`     | `postgres`                                                           | Fixe pour l'auto-hébergement                          |
| `supabase.postgres.host`     | `ha-supabase-db-postgres-pooler-rw.ha-supabase-db.svc.cluster.local` | Fixe — service DB Pool dans le cluster                |
| `supabase.postgres.password` | `<YOUR_SUPABASE_DB_PASSWORD>`                                        | Même que `secret.db.password` dans `supabase.yaml`    |
| `supabase.projectId`         | *(laisser vide)*                                                     | Non utilisé en mode auto-hébergé                      |

**Supabase (`dataServiceConfig`) — Supabase Cloud (`ENABLE_SUPABASE=false`) :**

| Champ                        | Espace réservé                     | Source                                                                               |
| ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------ |
| `supabase.projectUrl`        | `<YOUR_SUPABASE_PROJECT_URL>`      | Dashboard Supabase → Paramètres du projet → API                                      |
| `supabase.key`               | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Dashboard Supabase → API → clé `service_role`                                        |
| `supabase.postgres.user`     | `<YOUR_SUPABASE_DB_USER>`          | Dashboard Supabase → Paramètres du projet → Base de données                          |
| `supabase.postgres.host`     | `<YOUR_SUPABASE_DB_HOST>`          | Dashboard Supabase → Base de données (par ex. `aws-0-eu-west-2.pooler.supabase.com`) |
| `supabase.postgres.password` | `<YOUR_SUPABASE_DB_PASSWORD>`      | Dashboard Supabase → Paramètres du projet → Base de données                          |
| `supabase.projectId`         | `<YOUR_SUPABASE_PROJECT_ID>`       | Depuis l'URL de votre projet Supabase                                                |

**Redis :**

| Champ        | Espace réservé                                       | Notes                                      |
| ------------ | ---------------------------------------------------- | ------------------------------------------ |
| `redis.url`  | `rediss://<YOUR_REDIS_HOST>:6379?ssl_cert_reqs=none` | Après que Terraform a créé ElastiCache     |
| `redis.host` | `<YOUR_REDIS_HOST>`                                  | Point de terminaison principal ElastiCache |

```bash theme={null}
# Get Redis endpoint after Terraform apply
aws elasticache describe-cache-clusters \
  --show-cache-node-info \
  --query "CacheClusters[?starts_with(CacheClusterId,'<YOUR_ENV_NAME>')].CacheNodes[0].Endpoint.Address" \
  --output text
```

**RabbitMQ :**

| Champ               | Espace réservé                                                                        | Notes                                   |
| ------------------- | ------------------------------------------------------------------------------------- | --------------------------------------- |
| `rabbitmq.url`      | `amqps://<YOUR_RABBITMQ_USERNAME>:<YOUR_RABBITMQ_PASSWORD>@<YOUR_RABBITMQ_HOST>:5671` | Après que Terraform a créé AmazonMQ     |
| `rabbitmq.host`     | `<YOUR_RABBITMQ_HOST>`                                                                | Point de terminaison du broker AmazonMQ |
| `rabbitmq.username` | `<YOUR_RABBITMQ_USERNAME>`                                                            | Défini dans `terragrunt.hcl`            |
| `rabbitmq.password` | `<YOUR_RABBITMQ_PASSWORD>`                                                            | Défini dans `terragrunt.hcl`            |

```bash theme={null}
# Get RabbitMQ endpoint after Terraform apply
aws mq list-brokers \
  --query "BrokerSummaries[?BrokerName=='odin-rabbitmq'].BrokerId" --output text | \
  xargs -I{} aws mq describe-broker --broker-id {} \
  --query "BrokerInstances[0].Endpoints[0]" --output text
```

**SSL / ARN de certificat :**

| Champ                                        | Espace réservé                     | Notes                                 |
| -------------------------------------------- | ---------------------------------- | ------------------------------------- |
| `ssl.services.web.domain`                    | `<YOUR_WEB_DOMAIN>`                | par ex. `app.example.com`             |
| `ssl.services.web.certificateArn`            | `<YOUR_WEB_CERTIFICATE_ARN>`       | ARN de certificat ACM                 |
| `ssl.services.fastapiBackend.domain`         | `<YOUR_API_DOMAIN>`                | par ex. `api-app.example.com`         |
| `ssl.services.fastapiBackend.certificateArn` | `<YOUR_API_CERTIFICATE_ARN>`       | ARN de certificat ACM                 |
| `ssl.services.automator.domain`              | `<YOUR_AUTOMATOR_DOMAIN>`          | par ex. `automations-app.example.com` |
| `ssl.services.automator.certificateArn`      | `<YOUR_AUTOMATOR_CERTIFICATE_ARN>` | ARN de certificat ACM                 |
| `ssl.services.supabase.domain`               | `<YOUR_SUPABASE_DOMAIN>`           | par ex. `supabase-app.example.com`    |
| `ssl.services.supabase.certificateArn`       | `<YOUR_SUPABASE_CERTIFICATE_ARN>`  | ARN de certificat ACM                 |

```bash theme={null}
# List ACM certificates in your region
aws acm list-certificates --region <YOUR_AWS_REGION> \
  --query "CertificateSummaryList[*].[DomainName,CertificateArn]" --output table
```

**Clés Supabase du frontend Web — auto-hébergé (`ENABLE_SUPABASE=true`) :**

| Champ                         | Espace réservé                     | Source                                                |
| ----------------------------- | ---------------------------------- | ----------------------------------------------------- |
| `web.supabase.url`            | `https://<YOUR_SUPABASE_DOMAIN>`   | URL externe acheminée via l'entrée ALB                |
| `web.supabase.anonKey`        | `<YOUR_SUPABASE_ANON_KEY>`         | Même que `secret.jwt.anonKey` dans `supabase.yaml`    |
| `web.supabase.serviceRoleKey` | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Même que `secret.jwt.serviceKey` dans `supabase.yaml` |
| `web.supabase.clientanonKey`  | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Même que `secret.jwt.serviceKey` dans `supabase.yaml` |

**Clés Supabase du frontend Web — Supabase Cloud (`ENABLE_SUPABASE=false`) :**

| Champ                         | Espace réservé                     | Source                                          |
| ----------------------------- | ---------------------------------- | ----------------------------------------------- |
| `web.supabase.url`            | `<YOUR_SUPABASE_PROJECT_URL>`      | Dashboard Supabase → Paramètres du projet → API |
| `web.supabase.anonKey`        | `<YOUR_SUPABASE_ANON_KEY>`         | Dashboard Supabase → API → clé `anon`           |
| `web.supabase.serviceRoleKey` | `<YOUR_SUPABASE_SERVICE_ROLE_KEY>` | Dashboard Supabase → API → clé `service_role`   |
| `web.supabase.clientanonKey`  | `<YOUR_SUPABASE_CLIENT_ANON_KEY>`  | Même que la clé `service_role`                  |

#### 5.13 `values/signoz.yaml` — Observabilité SigNoz (uniquement si `ENABLE_SIGNOZ=true`)

```bash theme={null}
nano values/signoz.yaml
```

| Champ                                                                  | Espace réservé                                                               | Notes                            |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------------------------------- |
| `global.clusterName`                                                   | `<YOUR_ENV_NAME>`                                                            | Nom du cluster EKS               |
| `signoz.ingress.annotations.alb.ingress.kubernetes.io/certificate-arn` | `<YOUR_AWS_REGION>`, `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_SIGNOZ_CERTIFICATE_ID>` | Certificat ACM pour SigNoz       |
| `signoz.ingress.hosts[0].host`                                         | `<YOUR_SIGNOZ_DOMAIN>`                                                       | par ex. `signoz-app.example.com` |

#### 5.14 `values/signoz-k8s-infra.yaml` — Métriques K8s SigNoz (uniquement si `ENABLE_SIGNOZ=true`)

```bash theme={null}
nano values/signoz-k8s-infra.yaml
```

| Champ                | Espace réservé    | Notes                                              |
| -------------------- | ----------------- | -------------------------------------------------- |
| `global.clusterName` | `<YOUR_ENV_NAME>` | Nom du cluster EKS pour l'étiquetage des métriques |

Le point de terminaison du collecteur OTel (`signoz-otel-collector.monitoring.svc.cluster.local:4317`) est préconfiguré en supposant que SigNoz et k8s-infra sont déployés dans le namespace `monitoring`. Aucune modification n'est nécessaire sauf si vous utilisez un nom de release personnalisé.

#### Rappel de l'ordre de déploiement

Certaines valeurs ne sont disponibles qu'après le déploiement de certaines infrastructures. Suivez cet ordre :

1. **Avant tout déploiement** — Définir : `<YOUR_ENV_NAME>`, `<YOUR_AWS_REGION>`, `<YOUR_AWS_ACCOUNT_ID>`, `<YOUR_ENVIRONMENT>`, `<YOUR_PROJECT>`, `<YOUR_VPC_CIDR>`, tous les noms de domaine, tous les ARN de certificat, toutes les valeurs Supabase, `<YOUR_TOOLKIT_ENCRYPTION_KEY>`, nom d'utilisateur/mot de passe RabbitMQ
2. **Après la création du cluster EKS** — Définir : `<YOUR_VPC_ID>` (`infrastructure.yaml`), `<YOUR_EKS_CLUSTER_ENDPOINT>` (`karpenter-values.yaml`)
3. **Après `terraform apply` pour les services AWS** — Définir : `<YOUR_REDIS_HOST>`, `<YOUR_RABBITMQ_HOST>` (`odin-services.yaml`)

### Étape 6 : Vérifier qu'aucun espace réservé ne subsiste

```bash theme={null}
grep -r "<YOUR_" . --include="*.hcl" --include="*.yaml"
```

La sortie attendue doit être vide ou ne contenir que des références à des ressources sur le point d'être créées (VPC, Redis, MQ, EKS). Si des espaces réservés subsistent, reportez-vous aux sous-sections de l'étape 5 ci-dessus.

**Liste de contrôle des fichiers :**

| Fichier                             | Étape | Requis                                     |
| ----------------------------------- | ----- | ------------------------------------------ |
| `terragrunt.hcl`                    | 5.1   | Toujours                                   |
| `state/terragrunt.hcl`              | 5.2   | Toujours                                   |
| `values/infrastructure.yaml`        | 5.3   | Toujours                                   |
| `values/karpenter-values.yaml`      | 5.4   | Toujours                                   |
| `values/karpenter-nodeclasses.yaml` | 5.5   | Toujours                                   |
| `values/karpenter.yaml`             | 5.7   | Toujours                                   |
| `values/keda.yaml`                  | 5.8   | Toujours                                   |
| `values/aws-ebs-csi-driver.yaml`    | 5.6   | Toujours                                   |
| `values/odin-services.yaml`         | 5.12  | Toujours                                   |
| `values/cloudnative-pg.yaml`        | 5.11  | Uniquement si `ENABLE_CNPG=true`           |
| `values/ha-supabase-db.yaml`        | 5.10  | Uniquement si `ENABLE_HA_SUPABASE_DB=true` |
| `values/supabase.yaml`              | 5.9   | Uniquement si `ENABLE_SUPABASE=true`       |
| `values/signoz.yaml`                | 5.13  | Uniquement si `ENABLE_SIGNOZ=true`         |
| `values/signoz-k8s-infra.yaml`      | 5.14  | Uniquement si `ENABLE_SIGNOZ=true`         |

***

## Phase 1 : Configuration de la gestion de l'état

**Objectif :** Création du compartiment S3 pour l'état Terraform.

Le module de gestion de l'état de chaque environnement crée un compartiment S3 avec le motif `odin-terraform-state-{environment-name}`, configure le chiffrement, le versionnage et le blocage d'accès public, et utilise l'état local pour le module d'état lui-même (modèle d'amorçage).

```bash theme={null}
cd terragrunt/environments/{your-env-name}/state
terragrunt init
terragrunt plan
terragrunt apply
```

***

## Phase 2 : Déploiement de l'infrastructure EKS

**Objectif :** Réseau principal (VPC, sous-réseaux, passerelle NAT), rôles et politiques IAM, cluster EKS et groupes de nœuds gérés.

### 2.1 Exécution à blanc — Infrastructure EKS

**Infrastructure principale**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_vpc.main" \
  -target="aws_internet_gateway.main" \
  -target="aws_subnet.public" \
  -target="aws_subnet.private" \
  -target="aws_eip.nat" \
  -target="aws_nat_gateway.main" \
  -target="aws_route_table.public" \
  -target="aws_route_table.private" \
  -target="aws_route_table_association.public" \
  -target="aws_route_table_association.private"
```

**Rôles et politiques IAM**

```bash theme={null}
terragrunt plan -target="aws_iam_role.cluster" \
  -target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
  -target="aws_iam_openid_connect_provider.eks" \
  -target="aws_iam_role.node" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly"
```

**Cluster EKS et groupes de nœuds**

```bash theme={null}
terragrunt plan -target="aws_eks_cluster.main" \
  -target="aws_eks_node_group.main" \
  -target="kubernetes_secret.regcred"
```

#### Utiliser un registre Docker personnalisé / privé

Par défaut, les images EKB sont tirées de Docker Hub à l'aide d'un secret nommé `regcred`. Si le client héberge les images dans un autre registre, suivez ces étapes avant de déployer `odin-services`.

**Étape 1 — Créer le `imagePullSecret` dans le namespace cible**

```bash theme={null}
# Generic private registry (Docker Hub, Quay, self-hosted, etc.)
kubectl create secret docker-registry regcred \
  --namespace default \
  --docker-server=<YOUR_REGISTRY_HOST> \
  --docker-username=<YOUR_REGISTRY_USERNAME> \
  --docker-password=<YOUR_REGISTRY_PASSWORD> \
  --docker-email=<YOUR_EMAIL>

# AWS ECR — token expires every 12h; refresh via a CronJob or use ECR pull-through cache
aws ecr get-login-password --region <YOUR_AWS_REGION> | \
  kubectl create secret docker-registry regcred \
    --namespace default \
    --docker-server=<YOUR_AWS_ACCOUNT_ID>.dkr.ecr.<YOUR_AWS_REGION>.amazonaws.com \
    --docker-username=AWS \
    --docker-password-stdin
```

**Étape 2 — Définir le nom du secret dans `values/odin-services.yaml`**

```yaml theme={null}
# values/odin-services.yaml
imagePullSecrets:
  - name: regcred          # must match the secret name created above
  # - name: customer-registry-secret  # add additional registries if needed
```

**Étape 3 — Mettre à jour les références d'images**

```yaml theme={null}
web:
  image: <YOUR_REGISTRY_HOST>/<YOUR_ORG>/web:<TAG>

fastapiBackend:
  image: <YOUR_REGISTRY_HOST>/<YOUR_ORG>/server:<TAG>
```

**Étape 4 — Vérifier l'accès de tirage avant le déploiement complet**

```bash theme={null}
kubectl run registry-test \
  --image=<YOUR_REGISTRY_HOST>/<YOUR_ORG>/web:<TAG> \
  --overrides='{"spec":{"imagePullSecrets":[{"name":"regcred"}]}}' \
  --restart=Never --rm -it -- echo "Pull successful"
```

### 2.2 Déployer l'infrastructure EKS

**Étape 1 : Infrastructure principale**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_vpc.main" \
  -target="aws_internet_gateway.main" \
  -target="aws_subnet.public" \
  -target="aws_subnet.private" \
  -target="aws_eip.nat" \
  -target="aws_nat_gateway.main" \
  -target="aws_route_table.public" \
  -target="aws_route_table.private" \
  -target="aws_route_table_association.public" \
  -target="aws_route_table_association.private"
```

<Warning>
  Après cette étape, mettez à jour `vpcId` dans `values/infrastructure.yaml` avant de déployer le contrôleur de équilibrage de charge AWS.
</Warning>

**Étape 2 : Cluster EKS et rôles et politiques IAM**

```bash theme={null}
terragrunt apply -target="aws_iam_role.cluster" \
  -target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
  -target="aws_iam_openid_connect_provider.eks" \
  -target="aws_iam_role.node" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly"
```

**Étape 3 : Groupes de nœuds et add-ons**

```bash theme={null}
terragrunt apply -target="aws_eks_cluster.main" \
  -target="aws_eks_node_group.main" \
  -target="kubernetes_secret.regcred"
```

<Warning>
  Après cette étape, mettez à jour `CLUSTER_ENDPOINT` dans `values/karpenter-values.yaml` avant de déployer Karpenter.
</Warning>

**Vérifier la connectivité du cluster EKS**

```bash theme={null}
aws eks update-kubeconfig --region $AWS_REGION --name $CLUSTER_NAME

kubectl cluster-info
kubectl get nodes
kubectl get secret regcred -n default
```

***

## Phase 3 : Stockage et équilibrage de charge

**Objectif :** Pilote EBS CSI pour les volumes persistants, contrôleur de équilibrage de charge AWS fonctionnant sur le groupe de nœuds géré.

### 3.1 Exécution à blanc — Stockage et équilibrage de charge

**Pilote EBS CSI**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_iam_role.ebs_csi_driver" \
  -target="aws_iam_role_policy_attachment.ebs_csi_driver" \
  -target="helm_release.ebs_csi_driver"
```

**Contrôleur de équilibrage de charge AWS**

```bash theme={null}
terragrunt plan -target="aws_iam_role.aws_load_balancer_controller" \
  -target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
  -target="aws_iam_policy.aws_load_balancer_controller" \
  -target="helm_release.infrastructure"
```

### 3.2 Déployer le stockage et l'équilibrage de charge

**Étape 1 : Pilote EBS CSI**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_iam_role.ebs_csi_driver" \
  -target="aws_iam_role_policy_attachment.ebs_csi_driver" \
  -target="helm_release.ebs_csi_driver"
```

**Vérification**

```bash theme={null}
helm list -n kube-system | grep ebs
kubectl get pods -n kube-system | grep ebs-csi
kubectl get storageclass
aws iam get-role --role-name $CLUSTER_NAME-ebs-csi-driver-role --region $AWS_REGION
kubectl get sa -n kube-system | grep ebs-csi
kubectl describe sa ebs-csi-controller-sa -n kube-system
```

**Étape 2 : Contrôleur de équilibrage de charge AWS**

```bash theme={null}
terragrunt apply -target="aws_iam_role.aws_load_balancer_controller" \
  -target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
  -target="aws_iam_policy.aws_load_balancer_controller" \
  -target="helm_release.infrastructure"
```

**Vérification**

```bash theme={null}
helm list -n infrastructure
kubectl get pods -n infrastructure | grep aws-load-balancer-controller
kubectl get sa -n infrastructure
kubectl describe sa aws-load-balancer-controller -n infrastructure
aws iam get-role --role-name $CLUSTER_NAME-aws-load-balancer-controller --region $AWS_REGION
kubectl logs -n infrastructure -l app.kubernetes.io/name=aws-load-balancer-controller
kubectl get ingressclass
```

***

## Phase 4 : Mise à l'échelle Karpenter

**Objectif :** Rôles IAM pour Karpenter, gestion des interruptions Spot, contrôleur Karpenter et pools de nœuds.

### 4.1 Exécution à blanc — Karpenter

**Ressources IAM Karpenter**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="aws_iam_role.karpenter_controller" \
  -target="aws_iam_policy.karpenter_controller" \
  -target="aws_iam_role_policy_attachment.karpenter_controller" \
  -target="aws_iam_role.karpenter_node" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
  -target="aws_iam_instance_profile.karpenter_node"
```

**Rôle lié de service EC2 Spot (si les instances Spot sont activées)**

```bash theme={null}
terragrunt plan -target="aws_iam_service_linked_role.ec2_spot[0]"
```

**Interruption Spot Karpenter (si activée dans `terragrunt.hcl`)**

```bash theme={null}
terragrunt plan -target="aws_sqs_queue.karpenter_interruption_queue" \
  -target="aws_sqs_queue_policy.karpenter_interruption_queue" \
  -target="aws_cloudwatch_event_rule.karpenter_interruption" \
  -target="aws_cloudwatch_event_target.karpenter_interruption"
```

**Graphiques Helm Karpenter**

```bash theme={null}
terragrunt plan -target="helm_release.karpenter"
```

**NodePools et EC2NodeClasses Karpenter**

```bash theme={null}
terragrunt plan -target="kubernetes_manifest.karpenter_nodepool" \
  -target="kubernetes_manifest.karpenter_nodeclass" \
  -target="kubernetes_config_map.aws_auth"
```

<Info>
  Une erreur attendue peut apparaître lors du plan : `API did not recognize GroupVersionKind from manifest (CRD may not be installed)`. C'est normal et peut être ignoré — Kubernetes valide les ressources par rapport à l'API en direct au moment du plan, avant l'installation des CRD.
</Info>

### 4.2 Déployer Karpenter

**Étape 1 : Ressources IAM Karpenter**

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="aws_iam_role.karpenter_controller" \
  -target="aws_iam_policy.karpenter_controller" \
  -target="aws_iam_role_policy_attachment.karpenter_controller" \
  -target="aws_iam_role.karpenter_node" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
  -target="aws_iam_instance_profile.karpenter_node"
```

**Vérification**

```bash theme={null}
aws iam get-role --role-name $CLUSTER_NAME-karpenter-controller --region $AWS_REGION
aws iam get-role --role-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
aws iam get-instance-profile --instance-profile-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
aws iam list-attached-role-policies --role-name $CLUSTER_NAME-karpenter-node --region $AWS_REGION
```

**Étape 2 : Rôle lié de service EC2 Spot (si les instances Spot sont activées)**

<Warning>
  Le rôle lié de service EC2 Spot est au niveau du compte (un seul par compte AWS) et doit exister avant que Karpenter puisse lancer des instances Spot.
</Warning>

**Option A : Laisser Terraform le créer (recommandé pour les nouveaux déploiements)**

```bash theme={null}
terragrunt apply -target="aws_iam_service_linked_role.ec2_spot[0]"
```

**Option B : Importer si le rôle existe déjà**

```bash theme={null}
# Check if the role exists
aws iam get-role --role-name AWSServiceRoleForEC2Spot --region $AWS_REGION

# If it doesn't exist, create it manually
aws iam create-service-linked-role --aws-service-name spot.amazonaws.com --region $AWS_REGION

# Import into Terraform (replace ACCOUNT_ID with your 12-digit AWS account ID)
terragrunt import 'aws_iam_service_linked_role.ec2_spot[0]' \
  arn:aws:iam::ACCOUNT_ID:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot
```

<details>
  <summary>Dépannage des problèmes de création d'instances Spot</summary>

  **Vérifier les journaux Karpenter pour les erreurs liées à Spot :**

  ```bash theme={null}
  kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter -f | grep -i spot
  ```

  Erreurs courantes : `AuthFailure.ServiceLinkedRoleCreationNotPermitted`, `UnfulfillableCapacity`, `InsufficientInstanceCapacity`.

  **Vérifier que le rôle lié de service existe :**

  ```bash theme={null}
  aws iam get-role --role-name AWSServiceRoleForEC2Spot --region $AWS_REGION
  ```

  **Vérifier que la politique IAM inclut la permission Spot :**

  ```bash theme={null}
  aws iam get-policy-version \
    --policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`YOUR_CLUSTER_NAME-karpenter-controller`].Arn' --output text) \
    --version-id $(aws iam get-policy --policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`YOUR_CLUSTER_NAME-karpenter-controller`].Arn' --output text) --query 'Policy.DefaultVersionId' --output text) \
    --region $AWS_REGION | grep -i "CreateServiceLinkedRole"
  ```

  **Vérifier la disponibilité des instances Spot :**

  ```bash theme={null}
  aws ec2 describe-spot-price-history \
    --instance-types r6a.4xlarge r6a.large \
    --product-descriptions "Linux/UNIX" \
    --region $AWS_REGION \
    --max-items 10
  ```

  **Vérifier les types de capacité des NodePool :**

  ```bash theme={null}
  kubectl get nodepool -o yaml | grep -A 5 "capacity-type"
  ```

  **Vérifier les nœuds Spot vs On-Demand :**

  ```bash theme={null}
  kubectl get nodes -o json | jq -r '.items[] | "\(.metadata.name)\t\(.metadata.labels."karpenter.sh/capacity-type")\t\(.metadata.labels."node.kubernetes.io/instance-type")"'
  ```
</details>

**Étape 3 : Interruption Spot Karpenter (si activée dans `terragrunt.hcl`)**

```bash theme={null}
terragrunt apply -target="aws_sqs_queue.karpenter_interruption_queue" \
  -target="aws_sqs_queue_policy.karpenter_interruption_queue" \
  -target="aws_cloudwatch_event_rule.karpenter_interruption" \
  -target="aws_cloudwatch_event_target.karpenter_interruption"
```

**Vérification**

```bash theme={null}
aws events describe-rule --name $CLUSTER_NAME-karpenter-interruption --region $AWS_REGION
aws events list-targets-by-rule --rule $CLUSTER_NAME-karpenter-interruption --region $AWS_REGION
aws sqs get-queue-url --queue-name $CLUSTER_NAME-karpenter-interruption-queue --region $AWS_REGION
```

**Étape 4 : Graphique Helm Karpenter**

```bash theme={null}
terragrunt apply -target="helm_release.karpenter"
```

**Vérification**

```bash theme={null}
helm list -n kube-system | grep karpenter
kubectl get pods -n kube-system | grep karpenter
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
kubectl describe sa karpenter -n kube-system
```

**Étape 5 : Manifestes Kubernetes Karpenter**

```bash theme={null}
terragrunt apply -target="kubernetes_manifest.karpenter_nodepool" \
  -target="kubernetes_manifest.karpenter_nodeclass"

# Import the existing aws-auth ConfigMap
# Note: Use quotes to prevent zsh from interpreting brackets as glob patterns
terragrunt import 'kubernetes_config_map.aws_auth[0]' kube-system/aws-auth

# Then apply
terragrunt apply -target='kubernetes_config_map.aws_auth[0]'
```

**Vérification**

```bash theme={null}
kubectl get nodepools -o wide
kubectl describe nodepool general
kubectl describe nodepool application
kubectl describe nodepool database
kubectl get ec2nodeclasses -o wide
kubectl get configmap aws-auth -n kube-system -o jsonpath='{.data.mapRoles}' | grep karpenter-node
kubectl get nodepools -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.conditions[?(@.type=="Ready")].status}{"\n"}{end}'
kubectl get nodes -l karpenter.sh/nodepool --show-labels
kubectl get events -n kube-system --field-selector involvedObject.name=karpenter --sort-by='.lastTimestamp'
```

***

## Phase 5 : Mise à l'échelle KEDA

**Objectif :** KEDA pour la mise à l'échelle au niveau de l'application.

### 5.1 Exécution à blanc — KEDA

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.keda"
```

### 5.2 Déployer KEDA

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.keda"
```

**Vérification**

```bash theme={null}
helm list -n keda
kubectl get pods -n keda
kubectl get deployment -n keda
kubectl get crd | grep keda
kubectl get validatingwebhookconfigurations | grep keda
kubectl get svc -n keda
```

***

## Phase 6 : Services de données

**Objectif :** Supabase (base de données), ElastiCache (Redis), RabbitMQ (file d'attente de messages).

<Info>
  Déployez d'abord l'opérateur CloudNativePG, puis le cluster de base de données HA Supabase, puis l'application Supabase. Le cluster de base de données doit être prêt avant le démarrage de Supabase.
</Info>

### 6.1 Exécution à blanc — Services de données

**Étape 1 : Opérateur CloudNativePG (si activé)**

```bash theme={null}
cd terragrunt/environments/your-env-name
ENABLE_CNPG=true terragrunt plan \
  --target='helm_release.additional_charts["cloudnative-pg"]'
```

**Étape 2 : Base de données HA Supabase (si activée)**

```bash theme={null}
ENABLE_HA_SUPABASE_DB=true terragrunt plan \
  --target='helm_release.additional_charts["ha-supabase-db"]'
```

**Étape 3 : Application Supabase (si activée)**

```bash theme={null}
if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
  ENABLE_SUPABASE=true terragrunt plan \
    --target='helm_release.supabase[0]'
fi
```

**Étape 4 : Services AWS — ElastiCache et RabbitMQ (si activés)**

```bash theme={null}
if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
  terragrunt plan -target="aws_elasticache_subnet_group.redis" \
    -target="aws_security_group.redis" \
    -target="aws_elasticache_replication_group.redis" \
    -target="aws_security_group.rabbitmq" \
    -target="aws_mq_broker.rabbitmq"
fi
```

### 6.2 Déployer les services de données

**Étape 1 : Opérateur CloudNativePG (si activé)**

```bash theme={null}
cd terragrunt/environments/your-env-name
ENABLE_CNPG=true terragrunt apply --auto-approve \
  --target='helm_release.additional_charts["cloudnative-pg"]'
```

**Étape 2 : Base de données HA Supabase (si activée)**

```bash theme={null}
ENABLE_HA_SUPABASE_DB=true terragrunt apply --auto-approve \
  --target='helm_release.additional_charts["ha-supabase-db"]'
```

**Vérifier le pooler PgBouncer et les identifiants après le déploiement :**

```bash theme={null}
kubectl get svc -n ha-supabase-db | grep pooler
kubectl get secrets -n ha-supabase-db
kubectl get secret ha-supabase-db-authenticator-credentials -n ha-supabase-db \
  -o jsonpath='{.data.username}' | base64 -d && echo ""
```

Utilisez le ClusterIP du pooler (ou `EXTERNAL-IP` si LoadBalancer) comme valeur `SUPABASE_POSTGRES_HOST` dans `values/odin-services.yaml` et comme `secret.db.postgresHost` dans `values/supabase.yaml`.

**Étape 3 : Application Supabase (si activée)**

Tous les pods de service Supabase s'exécutent exclusivement sur le NodePool `application` Karpenter (On-Demand uniquement) pour prévenir les interruptions Spot.

```bash theme={null}
if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
  ENABLE_SUPABASE=true terragrunt apply --auto-approve \
    --target='helm_release.supabase[0]'
fi
```

**Étape 4 : Services AWS — ElastiCache et RabbitMQ (si activés)**

```bash theme={null}
if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
  terragrunt apply \
    -target="aws_elasticache_subnet_group.redis" \
    -target="aws_security_group.redis" \
    -target="aws_elasticache_replication_group.redis" \
    -target="aws_security_group.rabbitmq" \
    -target="aws_mq_broker.rabbitmq"
fi
```

**Vérification**

```bash theme={null}
# Get connection details from Terraform outputs
terragrunt output elasticache_endpoint
terragrunt output elasticache_port
terragrunt output rabbitmq_endpoint
terragrunt output rabbitmq_port

# Test Redis connectivity from EKS cluster
kubectl run redis-test --image=redis:7-alpine --restart=Never -- \
  sh -c "redis-cli -h <redis-endpoint> -p 6379 --tls --insecure ping && echo 'Redis connection successful'"
kubectl logs redis-test
kubectl delete pod redis-test

# Check Redis encryption status
aws elasticache describe-replication-groups \
  --replication-group-id $CLUSTER_NAME-redis \
  --region $AWS_REGION \
  --query 'ReplicationGroups[0].{AtRestEncryption:AtRestEncryptionEnabled,TransitEncryption:TransitEncryptionEnabled}'
```

<Warning>
  Avant de déployer les services Odin, mettez à jour `values/odin-services.yaml` avec le point de terminaison Redis, le point de terminaison RabbitMQ et tous les ARN de certificat obtenus dans cette phase.
</Warning>

***

## Phase 7 : Services Odin

**Objectif :** Déploiement de l'application via Helm.

<Info>
  Avant le déploiement, réduisez temporairement le nombre de réplicas de `fastapiBackend` à un seul pour l'exécution initiale de la migration de base de données — définissez `replicaCount: 1`, `workers: 1` et `keda.minReplicas: 1`. Une fois la migration terminée avec succès, rétablissez ces valeurs à leurs valeurs par défaut de production avant de redéployer.
</Info>

### 7.1 Exécution à blanc — Services Odin

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target="helm_release.odin_services"
```

### 7.2 Déployer les services Odin

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target="helm_release.odin_services"
```

**Vérification**

```bash theme={null}
kubectl get pods
kubectl get ingress  # Add the ALB endpoints to your DNS provider
```

***

## Phase 8 : Observabilité SigNoz

**Objectif :** Surveillance des journaux et des métriques.

### 8.1 Exécution à blanc — Graphiques SigNoz

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt plan -target='helm_release.additional_charts["signoz"]'
terragrunt plan -target='helm_release.additional_charts["k8s-infra"]'
```

### 8.2 Déployer les graphiques SigNoz

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply -target='helm_release.additional_charts["signoz"]'
terragrunt apply -target='helm_release.additional_charts["k8s-infra"]'
```

**Vérification**

```bash theme={null}
kubectl get pods -n monitoring
kubectl get ingress -n monitoring  # Add the ALB endpoints to your DNS provider
```

***

## Phase 9 : Déploiement final

### 9.1 Déploiement complet

```bash theme={null}
cd terragrunt/environments/your-env-name
terragrunt apply
```

Cet apply final gère les ressources restantes qui n'ont pas été explicitement ciblées dans les phases précédentes.

### 9.2 Vérifier le déploiement

```bash theme={null}
# Update kubeconfig
aws eks update-kubeconfig --region us-east-2 --name your-env-name

# Check cluster status
kubectl get nodes
kubectl get pods --all-namespaces

# Check Karpenter
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter

# Check AWS Load Balancer Controller
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller

# Check KEDA
kubectl get pods -n keda

# Check Odin Services
kubectl get pods -n default
kubectl get services -n default
kubectl get ingress -n default

# Check all Helm releases
helm list --all-namespaces
```

***

## Dépannage

**Problèmes de verrouillage d'état**

```bash theme={null}
terragrunt force-unlock <lock-id>
```

**Karpenter ne fonctionne pas**

```bash theme={null}
kubectl describe nodes
kubectl logs -n kube-system -l app.kubernetes.io/name=karpenter
```

**Problèmes de équilibrage de charge**

```bash theme={null}
kubectl describe ingress -n default
kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
```

**Problèmes de graphique Helm**

```bash theme={null}
helm status <release-name> -n <namespace>
helm rollback <release-name> <revision> -n <namespace>
```

***

## Nettoyage

```bash theme={null}
# Destroy infrastructure
cd terragrunt/environments/your-env-name
terragrunt destroy -auto-approve

# Destroy state bucket (use with caution)
cd terragrunt/environments/your-env-name/state
terragrunt destroy -auto-approve
```

***

## Surveillance et journalisation

```bash theme={null}
# AWS Resources
aws eks describe-cluster --name your-env-name --region us-east-2
aws ec2 describe-instances --filters "Name=tag:kubernetes.io/cluster/your-env-name,Values=owned"

# Kubernetes Resources
kubectl top nodes
kubectl top pods --all-namespaces
kubectl get events --sort-by=.metadata.creationTimestamp
```

***

## Référence rapide — Toutes les commandes de déploiement

```bash theme={null}
# Phase 1: State Management
cd terragrunt/environments/your-env-name/state
terragrunt apply

# Phase 2: EKS Infrastructure
cd terragrunt/environments/your-env-name

terragrunt apply -target="aws_vpc.main" -target="aws_internet_gateway.main" \
  -target="aws_subnet.public" -target="aws_subnet.private" -target="aws_eip.nat" \
  -target="aws_nat_gateway.main" -target="aws_route_table.public" \
  -target="aws_route_table.private" -target="aws_route_table_association.public" \
  -target="aws_route_table_association.private" -auto-approve

terragrunt apply -target="aws_iam_role.cluster" \
  -target="aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy" \
  -target="aws_iam_openid_connect_provider.eks" -target="aws_iam_role.node" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.node_AmazonEC2ContainerRegistryReadOnly" \
  -auto-approve

terragrunt apply -target="aws_eks_cluster.main" \
  -target="aws_eks_node_group.main" -target="kubernetes_secret.regcred" -auto-approve

# Phase 3: Storage and Load Balancing
terragrunt apply -target="aws_iam_role.ebs_csi_driver" \
  -target="aws_iam_role_policy_attachment.ebs_csi_driver" \
  -target="helm_release.ebs_csi_driver" -auto-approve

terragrunt apply -target="aws_iam_role.aws_load_balancer_controller" \
  -target="aws_iam_role_policy_attachment.aws_load_balancer_controller" \
  -target="aws_iam_policy.aws_load_balancer_controller" \
  -target="helm_release.infrastructure" -auto-approve

# Phase 4: Karpenter Autoscaling
terragrunt apply -target="aws_iam_role.karpenter_controller" \
  -target="aws_iam_policy.karpenter_controller" \
  -target="aws_iam_role_policy_attachment.karpenter_controller" \
  -target="aws_iam_role.karpenter_node" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKSWorkerNodePolicy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEKS_CNI_Policy" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEC2ContainerRegistryReadOnly" \
  -target="aws_iam_role_policy_attachment.karpenter_node_AmazonEBSCSIDriverPolicy" \
  -target="aws_iam_instance_profile.karpenter_node" -auto-approve

# Spot interruption handling (if spot_interruption_handling = true)
terragrunt apply -target="aws_sqs_queue.karpenter_interruption_queue" \
  -target="aws_sqs_queue_policy.karpenter_interruption_queue" \
  -target="aws_cloudwatch_event_rule.karpenter_interruption" \
  -target="aws_cloudwatch_event_target.karpenter_interruption" -auto-approve

terragrunt apply -target="helm_release.karpenter" -auto-approve

terragrunt apply -target="kubernetes_manifest.karpenter_nodepool" \
  -target="kubernetes_manifest.karpenter_nodeclass" \
  -target='kubernetes_config_map.aws_auth[0]' -auto-approve

# Phase 5: KEDA Autoscaling
terragrunt apply -target="helm_release.keda" -auto-approve

# Phase 6: Data Services
ENABLE_CNPG=true terragrunt apply --target='helm_release.additional_charts["cloudnative-pg"]' -auto-approve
ENABLE_HA_SUPABASE_DB=true terragrunt apply --target='helm_release.additional_charts["ha-supabase-db"]' -auto-approve

if [ "${ENABLE_SUPABASE:-false}" = "true" ]; then
  ENABLE_SUPABASE=true terragrunt apply --target='helm_release.supabase[0]' -auto-approve
fi

if [ "${ENABLE_AWS_SERVICES:-false}" = "true" ]; then
  terragrunt apply -target="aws_elasticache_subnet_group.redis" \
    -target="aws_security_group.redis" \
    -target="aws_elasticache_replication_group.redis" \
    -target="aws_security_group.rabbitmq" \
    -target="aws_mq_broker.rabbitmq" -auto-approve
fi

# Phase 7: Odin Services
terragrunt apply -target="helm_release.odin_services" -auto-approve

# Phase 8: SigNoz (if enabled)
terragrunt apply -target='helm_release.additional_charts["signoz"]' -auto-approve
terragrunt apply -target='helm_release.additional_charts["k8s-infra"]' -auto-approve

# Phase 9: Final Deployment
terragrunt apply -auto-approve
```

<Info>
  Remplacez `your-env-name` par le nom réel de votre environnement. Exécutez toujours des exécutions à blanc (`terragrunt plan`) pour valider votre configuration avant d'appliquer les modifications.
</Info>
