Projet d'Infrastructure as Code (IaC) Déploiement automatisé d'une application web full-stack sur Microsoft Azure
- Vue d'ensemble
- Architecture
- Prérequis
- Configuration pour un nouveau compte Azure
- Structure du projet
- Déploiement étape par étape
- Gestion des Archives ZIP
- Initialisation de la base de données
- Variables et configuration
- Sécurité
- Surveillance et logs
- Dépannage
- Nettoyage des ressources
Ce projet déploie une stack web complète comprenant :
- Frontend : Application web statique (HTML/CSS/JavaScript)
- Backend : API REST en PHP avec gestion CORS
- Base de données : MySQL Azure Flexible Server
- Infrastructure : Azure App Services avec SSL/TLS
-Déploiement Infrastructure as Code (IaC) avec Terraform
-Génération automatique des mots de passe sécurisés
-Configuration SSL pour MySQL Azure
-Gestion CORS pour les requêtes cross-origin
-Déploiement automatique via Azure CLI
-Variables d'environnement sécurisées
graph TB
A[Utilisateur] --> B[Frontend App Service]
B --> C[API Backend App Service]
C --> D[MySQL Flexible Server]
E[Terraform] --> F[Resource Group]
F --> G[Service Plan]
G --> B
G --> C
F --> D
H[Firewall Rules] --> D
I[Random Password] --> D
| Composant | Type Azure | SKU | Coût estimé/mois |
|---|---|---|---|
| Resource Group | azurerm_resource_group |
- | Gratuit |
| Service Plan | azurerm_service_plan |
S1 Standard | ~15€ |
| Frontend App | azurerm_linux_web_app |
- | Inclus dans Service Plan |
| Backend App | azurerm_linux_web_app |
- | Inclus dans Service Plan |
| MySQL Server | azurerm_mysql_flexible_server |
B_Standard_B1ms | ~8€ |
| Total | ~23€/mois |
# Terraform
terraform --version # >= 1.0
# Azure CLI
az --version # >= 2.0
# Utilitaires système
zip --version
curl --versionLinux (Ubuntu/Debian)
# Terraform
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
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
sudo apt update && sudo apt install terraform
# Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bashmacOS
# Terraform
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# Azure CLI
brew install azure-cliWindows
# Terraform (via Chocolatey)
choco install terraform
# Azure CLI
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet'# Connexion à Azure
az login
# Lister les souscriptions disponibles
az account list --output table
# Sélectionner la bonne souscription
az account set --subscription "YOUR_SUBSCRIPTION_ID"
# Créer un Service Principal
az ad sp create-for-rbac --name "terraform-sp" --role="Contributor" --scopes="/subscriptions/YOUR_SUBSCRIPTION_ID"Sortie attendue :
{
"appId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"displayName": "terraform-sp",
"password": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tenant": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Créez un fichier .env (à ne jamais commiter) :
# .env (ne pas commiter ce fichier !)
export ARM_SUBSCRIPTION_ID="your-subscription-id"
export ARM_TENANT_ID="your-tenant-id"
export ARM_CLIENT_ID="your-service-principal-app-id"
export ARM_CLIENT_SECRET="your-service-principal-password"Chargez les variables :
source .envModifiez la section provider dans main.tf :
provider "azurerm" {
features {}
subscription_id = "your-subscription-id" # Remplacez par votre subscription ID
tenant_id = "your-tenant-id" # Remplacez par votre tenant ID
client_id = "your-client-id" # Remplacez par votre client ID
client_secret = "your-client-secret" # Remplacez par votre client secret
}Note de sécurité : Cette approche avec des valeurs hardcodées est uniquement pour les tests rapides. Pour un environnement de production, utilisez plutôt les variables d'environnement ou un fichier terraform.tfvars non versionné.
terraform-azure-webapp/
├── README.md # Ce fichier
├── main.tf # Configuration Terraform principale
├── init.sql # Script d'initialisation base de données
├── dummy-app/ # Code source de l'application
│ ├── frontend/ # Application frontend
│ │ ├── index.html # Page d'accueil
│ │ └── app.html # Interface principale
│ └── backend/ # API backend
│ ├── index.php # Point d'entrée
│ ├── api/ # Endpoints API
│ │ ├── products.php # GET /api/products
│ │ └── add_product.php # POST /api/add_product
│ └── config/ # Configuration
│ ├── database.php # Classe de connexion MySQL
│ └── BaltimoreCyberTrustRoot.crt.pem # Certificat SSL Azure
├── dummy-backend.zip # Archive backend (généré)
├── dummy-frontend.zip # Archive frontend (généré)
# Vérifier la configuration Azure
az account show# Initialiser Terraform (télécharge les providers)
terraform init
# Valider la configuration
terraform validate
# Voir ce qui va être créé
terraform plan
# Sauvegarder le plan (optionnel)
terraform plan -out=tfplanExemple de sortie :
Plan: 10 to add, 0 to change, 0 to destroy.
+ azurerm_resource_group.rg
+ azurerm_service_plan.app_service_plan
+ azurerm_mysql_flexible_server.mysql_server
+ azurerm_mysql_flexible_database.mysql_db
+ azurerm_linux_web_app.dummy_backend_app
+ azurerm_linux_web_app.dummy_frontend_app
+ null_resource.deploy_backend
+ null_resource.deploy_frontend
+ random_password.mysql_admin_password
# Déployer l'infrastructure
terraform apply
# Ou utiliser le plan sauvegardé
terraform apply tfplanTemps de déploiement estimé : 5-8 minutes
# Afficher les outputs
terraform output
# Tester les endpoints
curl "https://$(terraform output -raw backend_url)/api/products.php"
curl "https://$(terraform output -raw frontend_url)/"Terraform génère automatiquement les archives ZIP lors du déploiement :
# Archive du backend
data "archive_file" "dummy_backend" {
type = "zip"
source_dir = "${path.root}/dummy-app/backend"
output_path = local.backend_zip_path
}
# Archive du frontend
data "archive_file" "dummy_frontend" {
type = "zip"
source_dir = "${path.root}/dummy-app/frontend"
output_path = local.frontend_zip_path
}Les applications sont redéployées automatiquement quand le code change :
resource "null_resource" "deploy_backend" {
triggers = {
# Se redéclenche si le contenu du zip change
zip_hash = data.archive_file.dummy_backend.output_sha
}
provisioner "local-exec" {
command = "az webapp deploy ..."
}
}Si nécessaire, vous pouvez créer les archives manuellement :
# Backend
cd dummy-app/backend
zip -r ../../dummy-backend.zip .
# Frontend
cd ../frontend
zip -r ../../dummy-frontend.zip .
# Revenir au répertoire racine
cd ../../Important : Les fichiers doivent être à la racine du ZIP, pas dans un sous-dossier.
Correct:
dummy-backend.zip
├── index.php
├── api/
│ ├── products.php
│ └── add_product.php
└── config/
└── database.php
Incorrect:
dummy-backend.zip
└── dummy-app/
└── backend/
├── index.php
└── api/
Le fichier init.sql contient la structure et les données initiales :
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO products (name, description, price) VALUES
('Ordinateur Portable', 'Portable haute performance pour le travail et les loisirs', 1250.00),
('Clavier Mécanique', 'Clavier mécanique RGB avec switches Cherry MX', 180.50),
('Souris Gamer', 'Souris gaming haute précision avec capteur optique', 75.00);# Récupérer les informations de connexion
MYSQL_HOST=$(terraform output -raw mysql_server_fqdn)
MYSQL_PASSWORD=$(terraform output -raw mysql_server_admin_password)
MYSQL_DATABASE=$(terraform output -raw mysql_database_name)
# Exécuter le script
mysql -h $MYSQL_HOST \
-u mysqladmin \
-p$MYSQL_PASSWORD \
--ssl-ca=dummy-app/backend/config/BaltimoreCyberTrustRoot.crt.pem \
--ssl-mode=REQUIRED \
$MYSQL_DATABASE < init.sql# Upload du script vers un blob storage temporaire
az storage blob upload --file init.sql --name init.sql --container-name scripts
# Exécution via Azure CLI
az mysql flexible-server execute \
--name $(terraform output -raw mysql_server_fqdn | cut -d'.' -f1) \
--admin-user mysqladmin \
--admin-password $(terraform output -raw mysql_server_admin_password) \
--database-name $(terraform output -raw mysql_database_name) \
--file-path init.sql| Variable | Description | Type | Défaut | Obligatoire |
|---|---|---|---|---|
subscription_id |
ID de souscription Azure | string |
- | |
tenant_id |
ID du tenant Azure | string |
- | |
client_id |
ID du Service Principal | string |
- | |
client_secret |
Secret du Service Principal | string |
- | |
location |
Région Azure | string |
"France Central" |
|
environment |
Nom de l'environnement | string |
"dev" |
|
resource_group_name |
Nom du Resource Group | string |
"DummyApp-RG" |
Ces variables sont automatiquement injectées dans l'App Service backend :
app_settings = {
"MYSQL_HOST" = azurerm_mysql_flexible_server.mysql_server.fqdn
"MYSQL_USERNAME" = azurerm_mysql_flexible_server.mysql_server.administrator_login
"MYSQL_PASSWORD" = random_password.mysql_admin_password.result
"MYSQL_DATABASE" = azurerm_mysql_flexible_database.mysql_db.name
}- Génération automatique des mots de passe via
random_password - Stockage sécurisé dans Azure Key Vault (optionnel)
- Variables sensibles marquées
sensitive = true - Jamais de secrets hardcodés dans le code
- Connexions chiffrées à MySQL via SSL
- Certificat Baltimore inclus pour Azure
- HTTPS par défaut sur App Services
# Règle pour l'IP locale (initialisation)
resource "azurerm_mysql_flexible_server_firewall_rule" "allow_my_ip" {
start_ip_address = chomp(data.http.myip.response_body)
end_ip_address = chomp(data.http.myip.response_body)
}
# Règle pour les services Azure
resource "azurerm_mysql_flexible_server_firewall_rule" "allow_azure_services" {
start_ip_address = "0.0.0.0"
end_ip_address = "0.0.0.0"
}# Activer les logs
az webapp log config --name $(terraform output -raw backend_url | cut -d'.' -f1) \
--resource-group $(terraform output -raw resource_group_name) \
--web-server-logging filesystem
# Voir les logs en temps réel
az webapp log tail --name $(terraform output -raw backend_url | cut -d'.' -f1) \
--resource-group $(terraform output -raw resource_group_name)Error: building AzureRM Client: obtain subscription() from Azure CLI:
parsing json result from the Azure CLI: waiting for the Azure CLI: exit status 1
Solution :
az login
az account set --subscription "YOUR_SUBSCRIPTION_ID"Error: Error waiting for deployment: context deadline exceeded
Solution : Le déploiement se fait via Azure CLI pour éviter les timeouts Terraform.
SQLSTATE[HY000] [2002] Connection refused
Solutions :
- Vérifier les règles de pare-feu
- Vérifier les variables d'environnement
- Tester la connexion depuis votre machine
# Test de connexion
mysql -h $(terraform output -raw mysql_server_fqdn) \
-u mysqladmin \
-p$(terraform output -raw mysql_server_admin_password) \
--ssl-mode=REQUIRED \
-e "SELECT 1"Access to fetch at 'https://backend.azurewebsites.net/api/products.php'
from origin 'https://frontend.azurewebsites.net' has been blocked by CORS policySolution : Vérifier les headers CORS dans le backend PHP.
# État des ressources
terraform state list
# Détails d'une ressource
terraform state show azurerm_linux_web_app.dummy_backend_app
# Import d'une ressource existante
terraform import azurerm_resource_group.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example
# Récréer une ressource
terraform taint azurerm_linux_web_app.dummy_backend_app
terraform apply# Détruire toute l'infrastructure
terraform destroy
# Confirmation requise - tapez "yes"# Détruire seulement une ressource
terraform destroy -target=azurerm_linux_web_app.dummy_backend_app
# Détruire plusieurs ressources
terraform destroy -target=azurerm_linux_web_app.dummy_backend_app -target=azurerm_linux_web_app.dummy_frontend_app# Supprimer les fichiers temporaires
rm -f dummy-backend.zip dummy-frontend.zip
rm -rf .terraform/
rm -f terraform.tfstate*
rm -f tfplanAuteur : Lucas - Master 1 Architecture des Logiciels
Date : Juillet 2025
Contexte : Projet DevOps - Infrastructure as Code avec Terraform