Infrastructure Production-Ready
sur AWS

Guide pas-à-pas pour déployer, sécuriser et monitorer une application web complète. Chaque étape via la console AWS.

VPC RDS ALB ASG CloudWatch S3 CloudFront WAF KMS
00

Setup — Connexion PC ↔ AWS Prérequis avant de commencer

~15 min

1 Installer AWS CLI sur votre PC

Télécharger et installer depuis le site officiel :

# Windows (PowerShell admin)
msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi

# macOS
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

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

# Vérifier
aws --version

2 Créer les Access Keys (console AWS)

  1. Console AWS → IAMUsers → votre utilisateur
  2. Onglet Security credentials
  3. Create access key → Use case: CLI
  4. Copier Access Key ID et Secret Access Key
⚠️ SÉCURITÉ : Ne jamais partager ces clés, ne jamais les mettre dans du code, ne jamais les commit sur Git.

3 Configurer AWS CLI sur votre PC

# Configurer les credentials
aws configure

# Il demande 4 choses :
# AWS Access Key ID : AKIA...........
# AWS Secret Access Key : wJalr..........
# Default region : us-east-1 (ou votre région)
# Default output format : json

# Vérifier que ça marche
aws sts get-caller-identity
Alternative : Vous pouvez aussi utiliser AWS CloudShell directement dans le navigateur (icône terminal en haut à droite de la console). Pas besoin de configurer quoi que ce soit.

4 Créer une Key Pair SSH

Pour se connecter aux instances en SSH :

# Option A : via la console
# EC2 → Key Pairs → Create key pair
# Name : ma-cle-production
# Type : RSA, Format : .pem
# Le fichier .pem se télécharge automatiquement

# Option B : via CLI
aws ec2 create-key-pair --key-name ma-cle-production \
  --query 'KeyMaterial' --output text > ma-cle-production.pem

# Sécuriser la clé (Linux/Mac)
chmod 400 ma-cle-production.pem

# Connexion SSH à une instance
ssh -i ma-cle-production.pem ec2-user@IP_PUBLIQUE

5 Installer OpenTofu (pour l'IaC)

# Windows (avec Chocolatey)
choco install opentofu

# macOS (avec Homebrew)
brew install opentofu

# Linux
curl -fsSL https://get.opentofu.org/install-opentofu.sh | sh

# Vérifier
tofu --version

OpenTofu utilise automatiquement vos credentials AWS CLI. Si aws sts get-caller-identity marche, tofu marchera aussi.

6 Structure de projet OpenTofu

# Créer le dossier du projet
mkdir grand-tp && cd grand-tp

# Créer les fichiers de base
touch provider.tf main.tf variables.tf outputs.tf

# Créer le .gitignore (OBLIGATOIRE)
cat > .gitignore <<'EOF'
*.tfstate
*.tfstate.backup
.terraform/
terraform.tfvars
crash.log
EOF

provider.tf — Toujours commencer par ça :

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

provider "aws" {
  region = "us-east-1"  # Adapter à votre région

  default_tags {
    tags = {
      Project     = "GrandTP"
      Environment = "production"
      ManagedBy   = "OpenTofu"
    }
  }
}

7 Workflow OpenTofu

# 1. Initialiser (télécharge le provider AWS)
tofu init

# 2. Formater le code
tofu fmt

# 3. Valider la syntaxe
tofu validate

# 4. Prévisualiser les changements
tofu plan

# 5. Appliquer (déployer)
tofu apply

# 6. À LA FIN — OBLIGATOIRE — Détruire
tofu destroy
⚠️ OBLIGATOIRE : Toujours exécuter tofu destroy à la fin ! Oublier = ressources facturées = pénalité.
🔍 Vérification Setup
# Tout doit marcher avant de commencer
aws --version                    # AWS CLI installé
aws sts get-caller-identity      # Credentials OK
aws configure get region         # Bonne région
tofu --version                   # OpenTofu installé
ls ~/.ssh/ | grep pem            # Clé SSH présente
❌ Erreurs fréquentes Setup
ErreurCauseSolution
Unable to locate credentialsaws configure pas faitLancer aws configure
Invalid regionMauvaise région configuréeaws configure set region us-east-1
Permission denied (publickey)Clé SSH pas trouvée ou mauvais chmodchmod 400 ma-cle.pem
tofu: command not foundOpenTofu pas installéInstaller via le script officiel
Error: No valid credential sourcesOpenTofu ne trouve pas les credentialsVérifier aws sts get-caller-identity d'abord
01

Fondation Réseau — VPC Multi-AZ Créer l'infrastructure réseau isolée

~30 min

Console VPC › Create VPC

ParamètreValeur
Nameproduction-vpc
CIDR10.0.0.0/16
TenancyDefault

Console VPC › Subnets › Create (4 d'un coup)

NomAZCIDRType
public-aus-east-1a10.0.1.0/24Public
public-bus-east-1b10.0.2.0/24Public
private-aus-east-1a10.0.3.0/24Privé
private-bus-east-1b10.0.4.0/24Privé
⚠️ CRITIQUE : Pour chaque subnet public → Actions › Edit subnet settings › cocher "Enable auto-assign public IPv4 address"

Console Internet Gateway + Route Tables

  1. VPC › Internet Gateways › Create › Name : production-igw
  2. Actions › Attach to VPC › production-vpc
  3. VPC › Route Tables › Create : public-rt
  4. Routes › Edit › Add : 0.0.0.0/0production-igw
  5. Subnet Associations › Edit › cocher les 2 subnets publics

Console Security Groups (3 à créer)

SGInbound RulesOutbound
alb-sgHTTP (80) + HTTPS (443) depuis 0.0.0.0/0All traffic → 0.0.0.0/0
web-sgHTTP (80) depuis alb-sg
SSH (22) depuis votre IP/32
All traffic → 0.0.0.0/0
db-sgMySQL (3306) depuis web-sgAll traffic → 0.0.0.0/0
⚠️ FATAL : Jamais 0.0.0.0/0 sur SSH ou sur db-sg. Source = toujours le SG de la couche précédente.
🔍 Commandes de vérification
aws ec2 describe-vpcs --query 'Vpcs[].{ID:VpcId,CIDR:CidrBlock,Name:Tags[?Key==`Name`].Value|[0]}' --output table
aws ec2 describe-subnets --filters "Name=vpc-id,Values=VPC_ID" --query 'Subnets[].{Name:Tags[?Key==`Name`].Value|[0],CIDR:CidrBlock,AutoIP:MapPublicIpOnLaunch}' --output table
aws ec2 describe-security-groups --filters "Name=vpc-id,Values=VPC_ID" --query 'SecurityGroups[].{Name:GroupName,Inbound:IpPermissions}' --output json
❌ Erreurs fréquentes Phase 1
ErreurConséquenceVérification
Auto-assign IP non activéInstances sans IP publiqueConsole › Subnets
Route 0.0.0.0/0 manquantePas d'accès InternetConsole › Route Tables
Route table non associéeSubnets utilisent la table par défautSubnet Associations
Outbound vide sur SGRien ne se téléchargeSG › Outbound rules
db-sg ouvert à 0.0.0.0/0ÉCHEC EXAMENSource doit être web-sg
02

BDD Sécurisée — RDS + KMS + Secrets Manager Chiffrement et gestion automatique des credentials

~20 min

Console KMS › Create key

ParamètreValeur
TypeSymmetric
Aliascle-production
RotationActivée

Console RDS › Subnet Groups › Create

Name: production-dbnet, VPC: production-vpc, Subnets: private-a + private-b (les 2 privés)

Console RDS › Create database

ParamètreValeur
EngineMariaDB
TemplateFree tier
DB Identifierproduction-db
CredentialsManage in AWS Secrets Manager
Encryptioncle-production
Public accessNo ⚠️
Security Groupdb-sg
Initial DBguestbook

Console IAM › Roles › Create role

Trusted entity: AWS service › EC2

Policies à attacher :

  • AmazonSSMManagedInstanceCore
  • CloudWatchAgentServerPolicy
  • SecretsManagerReadWrite
  • AWSKeyManagementServicePowerUser

Role name: ec2-production-role

03

Application Web — ALB + Auto Scaling Load Balancer + scaling automatique

~30 min

1 Target Group

EC2 › Target Groups › Create : production-tg, HTTP/80, VPC production, Health check: /health

Info : Ne pas enregistrer d'instances — l'ASG s'en charge.

2 Application Load Balancer

EC2 › Load Balancers › Create ALB : production-alb, Internet-facing, 2 subnets publics, SG: alb-sg, Listener HTTP:80 → production-tg

3 Launch Template

ParamètreValeur
Nameguestbook-template
AMIAmazon Linux 2023
Typet3.micro
Security Groupweb-sg ⚠️
IAM Instance Profileec2-production-role
User dataScript ci-dessous
⚠️ 3 pièges du Launch Template :
1. Vérifier que le SG est web-sg (pas default !)
2. Vérifier que le IAM Role est attaché
3. Vérifier le nom exact de la RDS dans le user_data
📋 User Data complet (Amazon Linux 2023)
#!/bin/bash
yum update -y
yum install -y python3-pip jq

TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/placement/region)

SECRET_ARN=$(aws rds describe-db-instances \
  --db-instance-identifier production-db \
  --query 'DBInstances[0].MasterUserSecret.SecretArn' \
  --output text --region $REGION)

SECRET_JSON=$(aws secretsmanager get-secret-value \
  --secret-id "$SECRET_ARN" \
  --query SecretString --output text --region $REGION)

DB_USER=$(echo "$SECRET_JSON" | jq -r '.username')
DB_PASS=$(echo "$SECRET_JSON" | jq -r '.password')
DB_HOST=$(aws rds describe-db-instances \
  --db-instance-identifier production-db \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text --region $REGION)

mkdir -p /opt/guestbook
python3 -m venv /opt/guestbook/venv
source /opt/guestbook/venv/bin/activate
pip install flask pymysql

cat > /opt/guestbook/app.py <<'PYEOF'
from flask import Flask, request
import pymysql, os
app = Flask(__name__)

def get_db():
    return pymysql.connect(
        host=os.environ['DB_HOST'],
        user=os.environ['DB_USER'],
        password=os.environ['DB_PASS'],
        database='guestbook', autocommit=True)

@app.route('/health')
def health():
    return 'OK', 200

@app.route('/')
def index():
    db = get_db()
    cur = db.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTS messages (id INT AUTO_INCREMENT PRIMARY KEY, msg TEXT, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
    cur.execute("SELECT msg, ts FROM messages ORDER BY ts DESC LIMIT 20")
    msgs = cur.fetchall()
    html = "<h1>Guestbook</h1><form method=POST><input name=msg><button>Envoyer</button></form>"
    for m in msgs:
        html += f"<p><b>{m[1]}</b>: {m[0]}</p>"
    return html

@app.route('/', methods=['POST'])
def post():
    db = get_db()
    cur = db.cursor()
    cur.execute("CREATE TABLE IF NOT EXISTS messages (id INT AUTO_INCREMENT PRIMARY KEY, msg TEXT, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP)")
    cur.execute("INSERT INTO messages (msg) VALUES (%s)", (request.form['msg'],))
    return index()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)
PYEOF

export DB_HOST="$DB_HOST" DB_USER="$DB_USER" DB_PASS="$DB_PASS"
cd /opt/guestbook && nohup /opt/guestbook/venv/bin/python app.py &

4 Auto Scaling Group

production-asg : Launch Template guestbook-template, 2 subnets publics, Attach to production-tg, Health check: ELB, Min=1, Desired=2, Max=4, CPU target 50%

04

Monitoring — CloudWatch Dashboard et métriques

~15 min

1 Activer le monitoring détaillé

EC2 › Instance › Actions › Monitor › Enable detailed monitoring (sur chaque instance)

2 Créer le Dashboard

CloudWatch › Dashboards › Create : production-dashboard

WidgetMétriqueNamespace
Line chartCPUUtilizationAWS/EC2
NumberHealthyHostCountAWS/ApplicationELB
Line chartDatabaseConnectionsAWS/RDS
Line chartNetworkIn/OutAWS/EC2
05

S3 + CloudFront + WAF Site statique sécurisé avec CDN

~30 min

1 S3 › Create bucket

ParamètreValeur
Nameproduction-static-ACCOUNT-ID
Block all public accessON
VersioningEnabled
EncryptionSSE-KMS avec cle-production

Management › Lifecycle rule : Standard-IA (30j) → Glacier (90j) → Suppression (365j)

2 CloudFront › Create distribution

ParamètreValeur
OriginBucket S3
Origin accessOAC (Origin Access Control)
Viewer protocolRedirect HTTP to HTTPS
WAFEnable security protections
Default root objectindex.html
Après création : CloudFront fournit une bucket policy à copier dans S3 › Permissions › Bucket Policy
06

Sauvegardes — Snapshots EBS DLM et Disaster Recovery

~15 min

1 Taguer les instances

EC2 › Instances › Tags › Add : snapshot = yes

2 EC2 › Lifecycle Manager › Create policy

EBS snapshot policy, Target tags: snapshot = yes, Daily à 00:20, Retention: 7

Chaîne DR : EBS Snapshot → AMI → Launch Template → EC2
🔧

Guide de Debug Rapide Chaque erreur et sa solution

504 Gateway Timeout Trafic n'arrive pas aux instances. Vérifier : IP publique ? SG outbound ? Route vers IGW ? SG = web-sg (pas default) ?
502 Bad Gateway L'ALB contacte l'instance mais rien n'écoute sur le port 80. Vérifier : Flask tourne ? Port = 80 dans app.py ?
503 Service Unavail. Toutes les instances unhealthy. Vérifier : /health retourne 200 ?
Target.Timeout SG bloque le trafic. Vérifier que le Launch Template a web-sg, pas le SG default.
AccessDenied KMS Ajouter AWSKeyManagementServicePowerUser au rôle IAM.
apt-get not found Vous êtes sur Amazon Linux (utiliser yum). Vérifier l'AMI dans le Launch Template.
SSM Agent Offline Pas d'IP publique, pas d'accès Internet, ou rôle IAM manquant. Utiliser EC2 Instance Connect.
EC2 Connect échoue Ajouter SSH (22) depuis 3.112.23.0/29 (Tokyo) dans web-sg inbound.

Procédure debug standard

  1. EC2 › Instances › vérifier IP publique + SG = web-sg
  2. Connect › EC2 Instance Connect › username ec2-user
  3. sudo cat /var/log/cloud-init-output.log | tail -50
  4. curl http://localhost/health
📝

Aide-Mémoire Concepts Tout retenir pour l'examen

Chaîne de sécurité

Internet WAF (L7) CloudFront ALB (alb-sg) EC2 (web-sg) RDS (db-sg, subnet privé)

SG vs NACL

  • SG = instance, stateful, allow only
  • NACL = subnet, stateless, allow + deny
  • NACL : ports éphémères 1024-65535 requis

Classes S3

Standard → Standard-IA (30j) → Glacier (90j) → Deep Archive (180j)

Intelligent-Tiering = pattern d'accès inconnu

ALB vs NLB

  • ALB = Layer 7, HTTP, routage URL → web
  • NLB = Layer 4, TCP, faible latence → gaming

Modèles de service

  • IaaS = EC2, VPC (vous gérez OS)
  • PaaS = RDS (vous gérez apps)
  • FaaS = Lambda (vous gérez code)
  • SaaS = WorkMail (vous gérez config)

6 Principes Sécurité

  1. Moindre privilège
  2. Segmentation
  3. Défense en profondeur
  4. Automatisation
  5. Monitoring continu
  6. Chiffrement partout

Workflow IaC

init → fmt → validate → plan → apply → destroy

State file = ne jamais commit
.gitignore : *.tfstate, .terraform/

Chiffrement

  • At-rest : KMS (EBS, RDS), SSE-S3/KMS (S3)
  • In-transit : TLS/HTTPS
  • Symétrique = données, Asymétrique = TLS

CLI Obligatoire

aws sts get-caller-identity
aws configure get region

Toujours en premier !

EC2 Instance Connect — IPs par région

RégionIP à autoriser en SSH
us-east-118.206.107.24/29
eu-west-118.202.216.48/29
ap-northeast-13.112.23.0/29

Nettoyage — Ordre de suppression

  1. ASG → ALB → Target Group → Launch Template
  2. RDS (skip final snapshot) → DB Subnet Group
  3. Secrets Manager → KMS (schedule deletion)
  4. CloudFront → S3 (vider d'abord)
  5. CloudWatch dashboard → DLM policy
  6. VPC (supprime tout : subnets, SG, RT, IGW)
  7. IAM Role