Étude de cas 2 : Déploiement d'une application LAMP avec Ansible
Objectifs
- Mettre en place une infrastructure LAMP (Linux, Apache, MySQL, PHP)
- Utiliser Ansible pour automatiser le déploiement et la configuration
- Gérer les fichiers de configuration et les services
Les quatres lettres LAMP signifient :
- L pour Linux : le système d'exploitation
- A pour Apache : le serveur web
- M pour MySQL : le système de gestion de base de données
- P pour PHP : le langage de programmation côté serveur
Cette architecture est couramment utilisée pour héberger des sites web dynamiques et des applications web.

Prérequis
- Avoir une machine de contrôle avec Ansible installé
- Avoir une machine cible accessible via SSH
Vous pouvez utiliser les .ova fournis dans le cours de la semaine 10 (ansible-controller et node1 - nous n'avons pas besoin d'un deuxième noeud géré).
Étape 1 : Préparation de l'environnement
Structure à créer
lamp-project/
├── inventory.yml # Liste des serveurs cibles
├── ansible.cfg # Configuration locale d'Ansible
└── lamp_stack.yml # Notre playbook (créé à l'étape suivante)
Créez ce dossier et les fichiers suivants :
Fichier : ansible.cfg
[defaults]
inventory = inventory.ini
host_key_checking = False
Explications :
- inventory : indique où trouver la liste des serveurs
- host_key_checking = False : évite la confirmation manuelle à la première connexion (pratique en lab, à éviter en prod)
Fichier : inventory.ini
Remplacez l'adresse IP par celle de votre machine cible.
[webservers]
web01 ansible_host=ADDRESS_WEB1
[all:vars]
ansible_user=ansible
ansible_ssh_pass=Ansible123!
ansible_become_pass=Ansible123!
Vérification
Testez la connectivité :
# Voir l'inventaire parsé
ansible-inventory --list
# Tester la connexion SSH
ansible webservers -m ping
# Vérifier qu'on peut devenir root
ansible webservers -m command -a "whoami" --become
Résultat attendu du ping :
web01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Étape 2 : Installation d'Apache
Le A de LAMP est pour Apache, le serveur web. Le serveur web est responsable de la gestion des requêtes HTTP des clients (navigateurs web) et de la livraison des pages web. Ainsi, lorsqu'une requête est faite pour une page web, Apache traite cette requête et renvoie le contenu approprié au client.
Nous allons créer un playbook Ansible pour installer et configurer Apache sur notre serveur web. Nous allons créer une application web que nous appelerons "monapp". Nous allons également utiliser le concept de VirtualHost, qui permet d'héberger plusieurs sites web sur un même serveur Apache en utilisant des configurations distinctes.
Fichier : lamp_stack.yml
---
- name: Déploiement stack LAMP
hosts: webservers
become: true
vars:
apache_document_root: "/var/www/monapp"
tasks:
# ==========================================
# APACHE - Installation de base
# ==========================================
- name: Installer Apache
ansible.builtin.apt:
name: apache2
state: present
update_cache: true
cache_valid_time: 3600
Explications :
- name: Déploiement stack LAMP
Nom du play. Un playbook peut contenir plusieurs plays.
hosts: webservers
Cible le groupe défini dans l'inventaire.
become: true
Exécute les tâches avec sudo (nécessaire pour apt).
vars:
apache_document_root: "/var/www/monapp"
Définit des variables réutilisables. Centralise les valeurs = maintenance facile.
- name: Installer Apache
Nom de la tâche. Apparaît dans la sortie d'exécution. Soyez descriptif !
ansible.builtin.apt:
Module Ansible pour gérer les paquets Debian/Ubuntu. Ici, nous utilisons le FQCN (Fully Qualified Collection Name) pour plus de clarté :
ansible.builtin.= FQCN (Fully Qualified Collection Name)- Bonne pratique depuis Ansible 2.10+, bien que l'ancienne notation
apt:fonctionne toujours.
name: apache2
state: present
- state: present → installe si absent, ne fait rien si présent
- state: absent → désinstalle
- state: latest → installe ou met à jour vers la dernière version
update_cache: true
cache_valid_time: 3600
- Équivalent de
apt-get updateavant l'installation cache_valid_time: 3600→ ne rafraîchit que si le cache a plus d'1 heure
Exécution
# Lancer le playbook
ansible-playbook lamp_stack.yml
# Mode verbose pour voir les détails
ansible-playbook lamp_stack.yml -v
# Mode check (dry-run) - simule sans appliquer
ansible-playbook lamp_stack.yml --check
Sortie attendue (première exécution) :
TASK [Installer Apache] ************************************
changed: [web01]
PLAY RECAP *************************************************
web01 : ok=1 changed=1 unreachable=0 failed=0
Sortie attendue (deuxième exécution) :
TASK [Installer Apache] ************************************
ok: [web01]
PLAY RECAP *************************************************
web01 : ok=1 changed=0 unreachable=0 failed=0
Remarquez : changed=1 devient changed=0 → c'est l'idempotence !
Vérification sur le serveur
# Connexion SSH
ssh ansible@192.168.1.10 # Remplacez par l'IP de votre serveur
# Vérifier qu'Apache est installé
apache2 -v
# Vérifier que le service tourne
systemctl status apache2
Questions de compréhension
- Que signifie "changed" vs "ok" dans le résultat ?
- Quel est l'avantage d'utiliser
cache_valid_timeplutôt queupdate_cache: trueseul ? - Quel est l'avantage d'utiliser
state: latestplutôt questate: presenten production ?
Étape 3 : Configuration d'Apache
Ajoutez ces tâches à lamp_stack.yml
tasks:
# [...tâche "Installer Apache" de l'étape précédente...]
# ==========================================
# APACHE - Configuration
# ==========================================
- name: Activer les modules Apache nécessaires
ansible.builtin.apache2_module:
name: "{{ item }}"
state: present
loop:
- rewrite
- ssl
notify: Redémarrer Apache
- name: Créer le répertoire de l'application
ansible.builtin.file:
path: "{{ apache_document_root }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Configurer le VirtualHost
ansible.builtin.copy:
dest: /etc/apache2/sites-available/monapp.conf
content: |
<VirtualHost *:80>
ServerName {{ ansible_fqdn }}
DocumentRoot {{ apache_document_root }}
<Directory {{ apache_document_root }}>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/monapp_error.log
CustomLog ${APACHE_LOG_DIR}/monapp_access.log combined
</VirtualHost>
owner: root
group: root
mode: '0644'
notify: Redémarrer Apache
- name: Activer le site monapp
ansible.builtin.command:
cmd: a2ensite monapp.conf
creates: /etc/apache2/sites-enabled/monapp.conf
notify: Redémarrer Apache
- name: Désactiver le site par défaut
ansible.builtin.file:
path: /etc/apache2/sites-enabled/000-default.conf
state: absent
notify: Redémarrer Apache
- name: S'assurer qu'Apache est démarré et activé
ansible.builtin.service:
name: apache2
state: started
enabled: true
# ==========================================
# HANDLERS - À ajouter À LA FIN du playbook
# Au même niveau d'indentation que 'tasks:'
# ==========================================
handlers:
- name: Redémarrer Apache
ansible.builtin.service:
name: apache2
state: restarted
Explications des nouveaux concepts
La boucle loop
- name: Activer les modules Apache nécessaires
ansible.builtin.apache2_module:
name: "{{ item }}"
state: present
loop:
- rewrite
- ssl
loopitère sur une liste{{ item }}contient la valeur courante- Équivalent à écrire 2 tâches séparées, mais plus maintenable
Le module file
- name: Créer le répertoire de l'application
ansible.builtin.file:
path: "{{ apache_document_root }}"
state: directory # directory, file, link, absent
owner: www-data
group: www-data
mode: '0755' # Toujours en string avec quotes !
Attention! : mode: 0755 (sans quotes) est interprété en octal → résultat inattendu !
Le module copy avec contenu inline
- name: Configurer le VirtualHost
ansible.builtin.copy:
dest: /etc/apache2/sites-available/monapp.conf
content: |
<VirtualHost *:80>
[...]
content: |permet d'écrire du contenu multi-lignes directement- Alternative :
src: fichier.confpour copier un fichier local {{ ansible_fqdn }}→ fact automatique (nom complet de la machine)
Le module command avec idempotence
- name: Activer le site monapp
ansible.builtin.command:
cmd: a2ensite monapp.conf
creates: /etc/apache2/sites-enabled/monapp.conf
commandexécute une commande shell- Problème : pas idempotent par défaut
- Solution :
creates:→ n'exécute que si le fichier n'existe pas - Alternative :
removes:→ n'exécute que si le fichier existe
Le handler
handlers:
- name: Redémarrer Apache
ansible.builtin.service:
name: apache2
state: restarted
- Déclenché par
notify: Redémarrer Apache - S'exécute une seule fois à la fin, même si notifié 4 fois
- Le nom doit correspondre exactement au notify
Ordre d'exécution
1. Tâche "Activer modules" → changed → notify handler
2. Tâche "Créer répertoire" → changed (pas de notify)
3. Tâche "Configurer VHost" → changed → notify handler
4. Tâche "Activer site" → changed → notify handler
5. Tâche "Désactiver default" → changed → notify handler
6. Tâche "Service started" → ok
7. HANDLER "Redémarrer Apache" → s'exécute UNE SEULE FOIS
Vérification
# Exécuter le playbook
ansible-playbook lamp_stack.yml
# Vérifier sur le serveur
curl http://192.168.1.10 # Remplacez par l'IP de votre serveur
# Vérifier la config Apache
ssh ansible@192.168.1.10 "apache2ctl -S" # Remplacez par l'IP de votre serveur
Questions de compréhension
- Pourquoi utiliser un handler plutôt que
state: restarteddirectement ? - Comment forcer l'exécution des handlers immédiatement (avant la fin) ?
Étape 4 : Installation et sécurisation de MariaDB
Le M de LAMP est pour MySQL/MariaDB, le système de gestion de base de données relationnelle. MariaDB est un fork de MySQL, souvent utilisé comme alternative open-source. Le rôle de la base de donnée dans la structure LAMP est de stocker, gérer et récupérer les données nécessaires pour les applications web. Les applications web interagissent avec la base de données pour effectuer des opérations telles que la création, la lecture, la mise à jour et la suppression de données.
Ici, nous allons utiliser la collection Ansible community.mysql pour gérer MariaDB/MySQL. Nous verrons les collections plus en détail dans les semaines suivantes, mais pour l'instant, on peut comprendre que les collections sont des ensembles de modules et plugins Ansible supplémentaires, souvent maintenus par la communauté. On peut les installer via la commande ansible-galaxy collection install.
Prérequis : Installer la collection MySQL
# Installer la collection community.mysql
ansible-galaxy collection install community.mysql
# Vérifier l'installation
ansible-galaxy collection list | grep mysql
Ajoutez ces variables dans la section vars:
vars:
apache_document_root: "/var/www/monapp"
# Nouvelles variables pour MySQL
mysql_root_password: "ChangezMoiEnProd123!"
app_db_name: "monapp"
app_db_user: "appuser"
app_db_password: "AppPassword456!"
En production : ne jamais mettre de mots de passe en clair ! Nous pouvons utiliser Ansible Vault ou des solutions de gestion de secrets.
Ajoutez ces tâches après la section Apache
# ==========================================
# MARIADB - Installation
# ==========================================
- name: Installer MariaDB et les dépendances Python
ansible.builtin.apt:
name:
- mariadb-server
- mariadb-client
- python3-mysqldb
state: present
- name: S'assurer que MariaDB est démarré et activé
ansible.builtin.service:
name: mariadb
state: started
enabled: true
# ==========================================
# MARIADB - Sécurisation (équivalent mysql_secure_installation)
# ==========================================
- name: Définir le mot de passe root MySQL
community.mysql.mysql_user:
name: root
password: "{{ mysql_root_password }}"
host: localhost
login_unix_socket: /var/run/mysqld/mysqld.sock
state: present
- name: Créer le fichier .my.cnf pour root
ansible.builtin.copy:
dest: /root/.my.cnf
content: |
[client]
user=root
password={{ mysql_root_password }}
owner: root
group: root
mode: '0600'
- name: Supprimer les utilisateurs anonymes
community.mysql.mysql_user:
name: ''
host_all: true
state: absent
- name: Supprimer la base de données test
community.mysql.mysql_db:
name: test
state: absent
# ==========================================
# MARIADB - Création de la base applicative
# ==========================================
- name: Créer la base de données de l'application
community.mysql.mysql_db:
name: "{{ app_db_name }}"
encoding: utf8mb4
collation: utf8mb4_unicode_ci
state: present
- name: Créer l'utilisateur de l'application
community.mysql.mysql_user:
name: "{{ app_db_user }}"
password: "{{ app_db_password }}"
priv: "{{ app_db_name }}.*:ALL"
host: localhost
state: present
Explications des nouveaux concepts
Installation de plusieurs paquets
- name: Installer MariaDB et les dépendances Python
ansible.builtin.apt:
name:
- mariadb-server
- mariadb-client
- python3-mysqldb # Nécessaire pour les modules mysql_*
state: present
- On peut passer une liste à
name: python3-mysqldbest obligatoire sur la machine cible pour que les modulescommunity.mysql.*fonctionnent
Module d'une collection externe
- name: Définir le mot de passe root MySQL
community.mysql.mysql_user:
community.mysql→ nom de la collectionmysql_user→ nom du module- FQCN complet =
community.mysql.mysql_user - Ce module gère les utilisateurs MySQL/MariaDB. Ici, nous définissons le mot de passe root.
name: root
password: "{{ mysql_root_password }}"
host: localhost
login_unix_socket: /var/run/mysqld/mysqld.sock
state: present
- Le paramètre
namespécifie l'utilisateur (ici root) - Le paramètre
passworddéfinit le nouveau mot de passe pour l'utilisateur spécifié (root). - Le paramètre
hostindique l'hôte (ici localhost) à partir duquel cet utilisateur est autorisé à se connecter. Ici, nous limitons l'accès à root uniquement depuis localhost, soit la machine locale. - Le paramètre
login_unix_socketspécifie le socket Unix à utiliser pour l'authentification. C'est le point clé qui rend cette tâche possible juste après l'installation de MariaDB, avant même que le mot de passe root soit défini. Ce paramètre indique au module Ansible de se connecter à la base de données en utilisant le socket Unix local (et non via le réseau avec port TCP/IP). Ce socket n'est accessible que par les utilisateurs locaux, ce qui permet à Ansible de s'authentifier en tant que root sans avoir besoin du mot de passe. - Le paramètre
state: presentindique que l'utilisateur doit exister avec les paramètres spécifiés. Si l'utilisateur n'existe pas, il sera créé ; s'il existe déjà, ses paramètres seront mis à jour.
Le fichier .my.cnf
- name: Créer le fichier .my.cnf pour root
ansible.builtin.copy:
dest: /root/.my.cnf
content: |
[client]
user=root
password={{ mysql_root_password }}
mode: '0600' # Lecture/écriture uniquement pour root !
- MySQL/MariaDB lit automatiquement ce fichier pour les credentials
- Permet aux tâches suivantes de s'authentifier sans spécifier le mot de passe
- mode '0600' est critique pour la sécurité !
Gestion des privilèges
priv: "{{ app_db_name }}.*:ALL"
- Format :
base.table:PRIVILEGES monapp.*:ALL→ tous les privilèges sur toutes les tables demonapp- Exemples :
*.*:ALL(super admin),monapp.users:SELECT,INSERT
Vérification
# Exécuter le playbook
ansible-playbook lamp_stack.yml
# Tester la connexion MySQL sur le serveur
ssh ansible@192.168.1.10
# En tant que root (utilise .my.cnf)
sudo mysql -e "SHOW DATABASES;"
# En tant qu'utilisateur applicatif
mysql -u appuser -p'AppPassword456!' -e "SHOW DATABASES;"
Résultat attendu :
+--------------------+
| Database |
+--------------------+
| information_schema |
| monapp |
+--------------------+
Questions de compréhension
- À quoi sert la collection
community.mysqlque nous avons installée ? - Que se passe-t-il si on relance le playbook ? Le mot de passe root est-il réinitialisé ?
- Pourquoi le fichier
.my.cnfdoit avoir les permissions0600?
Étape 5 : Installation et configuration de PHP
Finalement, le P de LAMP est pour PHP, le langage de programmation côté serveur. PHP est utilisé pour créer des pages web dynamiques qui interagissent avec la base de données et génèrent du contenu en fonction des requêtes des utilisateurs. Nous allons installer PHP et les extensions nécessaires pour que notre application web puisse fonctionner correctement avec Apache et MariaDB. D'autres options du P seraient Python (LAMP devient LAMPy) ou Perl (LAMP devient LAMPe), mais PHP reste le plus courant.
Ajoutez cette variable dans la section vars:
vars:
# [...variables précédentes...]
php_packages:
- php
- php-mysql
- php-cli
- php-curl
- php-gd
- php-mbstring
- php-xml
- libapache2-mod-php
Ajoutez ces tâches après la section MariaDB
# ==========================================
# PHP - Installation
# ==========================================
- name: Installer PHP et les extensions
ansible.builtin.apt:
name: "{{ php_packages }}"
state: present
notify: Redémarrer Apache
# ==========================================
# PHP - Configuration pour la production
# ==========================================
- name: Configurer PHP pour la production
ansible.builtin.lineinfile:
path: /etc/php/8.3/apache2/php.ini
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
backup: true
loop:
- regexp: '^;?display_errors'
line: 'display_errors = Off'
- regexp: '^;?expose_php'
line: 'expose_php = Off'
- regexp: '^;?upload_max_filesize'
line: 'upload_max_filesize = 64M'
- regexp: '^;?post_max_size'
line: 'post_max_size = 64M'
- regexp: '^;?max_execution_time'
line: 'max_execution_time = 300'
- regexp: '^;?date.timezone'
line: 'date.timezone = America/Montreal'
notify: Redémarrer Apache
Explications détaillées
Variable de type liste
php_packages:
- php
- php-mysql
[...]
Définir la liste dans vars: plutôt qu'inline permet :
- De réutiliser la liste ailleurs
- De la surcharger facilement via l'inventaire ou la ligne de commande
- Une meilleure lisibilité
Utilisation d'une variable liste
name: "{{ php_packages }}"
Le module apt accepte une liste pour name:. Ansible installe tous les paquets en une seule transaction APT (plus efficace).
Le module lineinfile
- name: Configurer PHP pour la production
ansible.builtin.lineinfile:
path: /etc/php/8.3/apache2/php.ini
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
backup: true
Paramètres clés :
| Paramètre | Description |
|---|---|
path |
Fichier à modifier |
regexp |
Expression régulière pour trouver la ligne |
line |
Nouvelle ligne à écrire |
backup |
Crée une copie .bak avant modification |
Comportement :
- Cherche une ligne correspondant à
regexp - Si trouvée → la remplace par
line - Si non trouvée → ajoute
lineà la fin du fichier
L'expression régulière expliquée
regexp: '^;?display_errors'
^→ début de ligne;?→ point-virgule optionnel (0 ou 1 fois)display_errors→ texte littéral
Pourquoi ;? : Dans php.ini, les options commentées commencent par ;
;display_errors = On ← Option commentée (désactivée)
display_errors = Off ← Option active
Notre regexp matche les deux cas !
Boucle avec dictionnaires
loop:
- regexp: '^;?display_errors'
line: 'display_errors = Off'
- regexp: '^;?expose_php'
line: 'expose_php = Off'
- Chaque élément est un dictionnaire avec clés
regexpetline - Accès via
{{ item.regexp }}et{{ item.line }}
Pourquoi ces paramètres PHP ?
| Paramètre | Valeur | Raison |
|---|---|---|
display_errors = Off |
Sécurité | Ne pas exposer les erreurs aux utilisateurs |
expose_php = Off |
Sécurité | Cache la version PHP dans les headers HTTP |
upload_max_filesize = 64M |
Fonctionnel | Permet l'upload de fichiers volumineux |
post_max_size = 64M |
Fonctionnel | Doit être ≥ upload_max_filesize |
max_execution_time = 300 |
Fonctionnel | Scripts longs (imports, exports) |
date.timezone |
Fonctionnel | Évite les warnings sur les fonctions date |
Attention à la version PHP
path: /etc/php/8.3/apache2/php.ini
Le chemin contient la version PHP (8.3). Si vous obtenez une erreur "Fichier non trouvé", c'est probablement dû à une différence de version.
Sur votre système, vérifiez :
# Trouver la version PHP installée
php -v
# Lister les php.ini disponibles
ls /etc/php/*/apache2/php.ini
Adaptez le chemin dans le playbook si nécessaire.
Vérification
# Exécuter le playbook
ansible-playbook lamp_stack.yml
# Vérifier PHP
ssh ansible@192.168.1.10 "php -v"
# Vérifier les modules PHP
ssh ansible@192.168.1.10 "php -m"
# Vérifier une config modifiée
ssh ansible@192.168.1.10 "grep display_errors /etc/php/8.3/apache2/php.ini"
# Vérifier le backup
ssh ansible@192.168.1.10 "ls -la /etc/php/8.3/apache2/php.ini*"
Étape 6 : Déploiement d'une application de test
Nous allons créer une simple page PHP qui affiche des informations sur le serveur, la version de PHP, les modules chargés, et teste la connexion à la base de données MariaDB. Cela nous permettra de vérifier que toute la stack LAMP fonctionne correctement.
Ajoutez cette tâche après la section PHP
# ==========================================
# APPLICATION - Page de test
# ==========================================
- name: Déployer une page PHP de test
ansible.builtin.copy:
dest: "{{ apache_document_root }}/index.php"
content: |
<?php
$host = 'localhost';
$dbname = '{{ app_db_name }}';
$user = '{{ app_db_user }}';
$pass = '{{ app_db_password }}';
echo "<h1>Stack LAMP déployée avec Ansible pour le cours 5R3!</h1>";
echo "<h2>Informations système</h2>";
echo "<p><strong>Serveur:</strong> " . gethostname() . "</p>";
echo "<p><strong>PHP Version:</strong> " . phpversion() . "</p>";
echo "<p><strong>Système:</strong> {{ ansible_distribution }} {{ ansible_distribution_version }}</p>";
echo "<p><strong>Adresse IP:</strong> {{ ansible_default_ipv4.address }}</p>";
echo "<h2>Test de connexion MySQL</h2>";
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "<p style='color:green'>✔ Connexion à la base de données réussie!</p>";
$stmt = $pdo->query("SELECT VERSION() as version");
$row = $stmt->fetch();
echo "<p><strong>Version MariaDB:</strong> " . $row['version'] . "</p>";
} catch(PDOException $e) {
echo "<p style='color:red'>✘ Erreur: " . htmlspecialchars($e->getMessage()) . "</p>";
}
echo "<h2>Modules PHP chargés</h2>";
$modules = get_loaded_extensions();
sort($modules);
echo "<p>" . implode(", ", $modules) . "</p>";
?>
owner: www-data
group: www-data
mode: '0644'
Les Facts Ansible utilisés
Dans le code PHP, nous utilisons des facts collectés automatiquement :
echo "<p>Système: {{ ansible_distribution }} {{ ansible_distribution_version }}</p>";
echo "<p>Adresse IP: {{ ansible_default_ipv4.address }}</p>";
Facts courants et utiles
| Fact | Description | Exemple de valeur |
|---|---|---|
ansible_hostname |
Nom court de la machine | web01 |
ansible_fqdn |
Nom complet (FQDN) | web01.example.com |
ansible_distribution |
Distribution Linux | Ubuntu, Debian |
ansible_distribution_version |
Version de la distro | 22.04, 12 |
ansible_os_family |
Famille d'OS | Debian, RedHat |
ansible_default_ipv4.address |
IP principale | 192.168.1.10 |
ansible_processor_vcpus |
Nombre de vCPUs | 4 |
ansible_memtotal_mb |
RAM totale en Mo | 8192 |
Explorer tous les facts
# Voir tous les facts d'une machine
ansible web01 -m ansible.builtin.setup
# Filtrer les facts
ansible web01 -m ansible.builtin.setup -a "filter=ansible_distribution*"
# Sauvegarder dans un fichier
ansible web01 -m ansible.builtin.setup > facts_web01.json
Comment Ansible remplace les variables dans le fichier
Attention : le fichier index.php contient un mélange de :
Variables Ansible (remplacées au déploiement) :
$dbname = '{{ app_db_name }}'; // Devient: $dbname = 'monapp'; echo "{{ ansible_distribution }}"; // Devient: echo "Ubuntu";Code PHP (exécuté par le serveur web) :
echo phpversion(); // Exécuté à chaque requête HTTP echo gethostname(); // Exécuté à chaque requête HTTP
Résultat sur le serveur après déploiement :
$dbname = 'monapp'; // Variable Ansible → valeur fixe
echo phpversion(); // PHP → valeur dynamique
Vérification complète de la stack
# Exécuter le playbook
ansible-playbook lamp_stack.yml
# Tester dans le navigateur
curl http://192.168.1.10
# Ou ouvrir dans un navigateur :
# http://192.168.1.10
Résultat attendu :
Stack LAMP déployée avec Ansible pour le cours 5R3!
Informations système
Serveur: web01
PHP Version: 8.3.x
Système: Ubuntu 22.04
Adresse IP: 192.168.1.10
Test de connexion MySQL
✔ Connexion à la base de données réussie!
Version MariaDB: 10.x.x-MariaDB
Modules PHP chargés
Core, curl, date, gd, mbstring, mysql, mysqli, ...
Questions de compréhension
- Quelle est la différence entre
{{ ansible_hostname }}dans le playbook etgethostname()en PHP ?
Étape 7 : Configuration du Firewall (UFW)
Prérequis : Installer la collection
ansible-galaxy collection install community.general
Ajoutez ces tâches à la fin (avant les handlers)
# ==========================================
# FIREWALL - Sécurisation
# ==========================================
- name: Installer UFW
ansible.builtin.apt:
name: ufw
state: present
- name: Autoriser SSH (TOUJOURS EN PREMIER !)
community.general.ufw:
rule: allow
port: '22'
proto: tcp
- name: Autoriser HTTP
community.general.ufw:
rule: allow
port: '80'
proto: tcp
- name: Autoriser HTTPS
community.general.ufw:
rule: allow
port: '443'
proto: tcp
- name: Activer UFW avec politique deny par défaut
community.general.ufw:
state: enabled
default: deny
direction: incoming
Attention à l'ordre des tâches !
- name: Autoriser SSH (TOUJOURS EN PREMIER !)
community.general.ufw:
rule: allow
port: '22'
Cette tâche DOIT être exécutée AVANT l'activation du firewall !
Si vous activez UFW avant d'autoriser SSH :
- Le firewall bloque tout le trafic entrant
- Y compris votre connexion SSH
- Vous perdez l'accès au serveur car Ansible se connecte via SSH
Ansible exécute les tâches dans l'ordre → c'est pourquoi notre playbook est sûr.
Explications du module UFW
Autoriser un port
- name: Autoriser HTTP
community.general.ufw:
rule: allow # allow, deny, reject, limit
port: '80' # Numéro de port (en string)
proto: tcp # tcp, udp, any
Politique par défaut et activation
- name: Activer UFW avec politique deny par défaut
community.general.ufw:
state: enabled # enabled, disabled, reset
default: deny # allow, deny, reject
direction: incoming # incoming, outgoing, routed
Politique deny sur incoming signifie :
- Tout trafic entrant est bloqué par défaut
- Seuls les ports explicitement autorisés sont ouverts
- Le trafic sortant reste autorisé
Vérification
# Exécuter le playbook
ansible-playbook lamp_stack.yml
# Vérifier le statut UFW
ssh ansible@192.168.1.10 "sudo ufw status verbose"
Résultat attendu :
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)
443/tcp (v6) ALLOW IN Anywhere (v6)
Playbook complet - Récapitulatif
À ce stade, votre playbook contient :
- Installation Apache
- Configuration VirtualHost
- Installation MariaDB
- Sécurisation MySQL
- Création base de données
- Installation PHP
- Configuration php.ini
- Application de test
- Firewall UFW
Nombre de tâches : environ 20 Lignes de code : environ 200
Prochaine étape : Refactoring !
Votre playbook fonctionne, mais il a des problèmes :
- Trop long (200+ lignes dans un seul fichier)
- Difficile à réutiliser sur d'autres projets
Solution : transformer ce playbook en utilisant :
import_tasksetinclude_tasks- Les rôles Ansible