Étude de cas : Gestion d'une infrastructure d'application web
Contexte
Vous devez déployer et configurer une stack applicative comprenant :
- Un serveur web (Nginx)
- Une base de données (PostgreSQL)
- Un serveur de cache (Redis)
- Une application Python
Étape 0: Préparation de l'environnement
Pour cette étude de cas, vous aurez besoin d'une machine de contrôle Ansible avec Ansible installé, ainsi que de trois hôtes cibles pour simuler les serveurs web, bases de données et applications. Vous pouvez utiliser la machine de contrôle Ansible créée dans la partie pratique du cours de la semaine 10, ainsi que les 2 machines virtuelles supplémentaires pour les serveurs web et base de données.
Pour créer la machine virtuelle supplémentaire pour le serveur d'application, vous pouvez cloner l'une des machines existantes (node1 ou node2) en effectuant un "full clone".
Changez ensuite son nom d'hôte en node3 en effectuant les commandes suivantes sur la machine clonée :
sudo hostnamectl set-hostname node3
Ensuite, modifiez le fichier /etc/hosts pour ajouter une entrée pour node3 :
sudo nano /etc/hosts
Finalement, redémarrez la machine pour que les changements prennent effet :
sudo reboot
Étape 1: Création des fichiers de configuration Ansible
Voici la structure des fichiers et le contenu nécessaire pour configurer votre infrastructure sur votre machine de contrôle Ansible.
lab_semaine11/
├── ansible.cfg
├── inventory/
│ └── hosts.ini
├── group_vars/
│ ├── all.yml
│ ├── webservers.yml
│ └── databases.yml
├── host_vars/
│ └── app1.yml
├── vars/
│ └── app_config.yml
└── deploy_stack.yml
Utilisez la commande suivante pour créer les répertoires nécessaires :
mkdir -p lab_semaine11/inventory lab_semaine11/group_vars lab_semaine11/host_vars lab_semaine11/vars
Et la commande suivante pour créer les fichiers vides :
touch lab_semaine11/inventory/hosts.ini lab_semaine11/group_vars/all.yml lab_semaine11/group_vars/webservers.yml lab_semaine11/group_vars/databases.yml lab_semaine11/host_vars/app-server.yml lab_semaine11/vars/app_config.yml lab_semaine11/deploy_stack.yml lab_semaine11/ansible.cfg
Fichier d'inventaire inventory/hosts.ini
Modifiez votre fichier d'inventaire pour définir les groupes d'hôtes et leurs adresses, en remplacant les adresses par celles de vos hôtes cibles.
[webservers]
web1 ansible_host=ADDRESS_WEB1
[databases]
db1 ansible_host=ADDRESS_DB1
[appservers]
app1 ansible_host=ADDRESS_APP1
[all:vars]
ansible_user=ansible
ansible_ssh_pass=Ansible123!
ansible_become_pass=Ansible123!
Maintenant, modifiez le fichier de configuration Ansible ansible.cfg dans le répertoire lab_semaine11/ avec le contenu suivant :
[defaults]
inventory = inventory/hosts.ini
host_key_checking = False
Étape 2: Test des connexions
Avant de commencer le déploiement, assurez-vous que vous pouvez vous connecter à tous les hôtes cibles en utilisant Ansible :
ansible all -m ping
Étape 3 : Définition des variables
Nous allons définir les variables nécessaires pour chaque groupe d'hôtes et pour l'application.
Variables communes à tous les serveurs
Pour définir des variables communes à tous les serveurs, modifiez le fichier group_vars/all.yml comme suit :
---
# Variables communes à tous les serveurs
app_environment: "production"
company_name: "TechCorp"
admin_email: "admin@techcorp.com"
# Configuration de base
base_packages:
- curl
- wget
- vim
- htop
# Configuration de sécurité
firewall_enabled: true
selinux_enabled: false
Variables spécifiques aux groupes d'hôtes
Nous allons maintenant définir des variables spécifiques pour chaque groupe d'hôtes: le groupe des serveurs web et le groupe des bases de données.
Variables pour les serveurs web
Modifiez le fichier group_vars/webservers.yml comme suit :
---
# Configuration Nginx
nginx_config:
worker_processes: 4
worker_connections: 1024
keepalive_timeout: 65
# Virtual hosts
virtual_hosts:
- name: "app.techcorp.com"
port: 80
ssl_port: 443
document_root: "/var/www/app"
ssl_enabled: true
- name: "api.techcorp.com"
port: 80
ssl_port: 443
document_root: "/var/www/api"
ssl_enabled: true
# Modules Nginx à activer
nginx_modules:
- ssl
- gzip
- headers
Variables pour les bases de données
Modifiez le fichier group_vars/databases.yml comme suit :
---
# Configuration PostgreSQL
postgresql_version: "14"
postgresql_port: 5432
postgresql_max_connections: 100
postgresql_shared_buffers: "256MB"
# Bases de données à créer
databases:
- name: "app_production"
owner: "app_user"
encoding: "UTF8"
- name: "analytics"
owner: "analytics_user"
encoding: "UTF8"
# Configuration Redis
redis_port: 6379
redis_maxmemory: "512mb"
redis_maxmemory_policy: "allkeys-lru"
Note: Pour simplifier, la base de données PostgreSQL et Redis seront installés sur le même serveur (db1).
Variables spécifiques à un hôte
Finalement, nous allons définir des variables spécifiques pour un hôte particulier, soit le serveur d'application, dans le fichier host_vars/app-server.yml.
---
# Surcharge pour le serveur d'application principal
nginx_config:
worker_processes: 8 # Plus de workers pour le serveur principal
worker_connections: 2048
# Configuration spécifique de l'application
app_settings:
debug_mode: false
log_level: "INFO"
max_upload_size: "50M"
session_timeout: 3600
Variables de l'application
Finalement, nous allons définir les variables qui seront utilisés dans le playbook dans le fichier vars/app_config.yml.
---
# Configuration de l'application Python
python_app:
name: "TechCorp WebApp"
version: "2.1.0"
port: 8000
workers: 4
dependencies:
- django==4.2.0
- psycopg2-binary==2.9.6
- redis==4.5.5
- celery==5.2.7
- gunicorn==20.1.0
environment_vars:
DATABASE_URL: "postgresql://app_user@db1:5432/app_production"
REDIS_URL: "redis://localhost:6379/0"
SECRET_KEY: "change-me-in-production"
ALLOWED_HOSTS: "app.techcorp.com,api.techcorp.com"
# Configuration de monitoring
monitoring:
enabled: true
check_interval: 60
alert_email: "{{ admin_email }}"
services_to_monitor:
- nginx
- postgresql
- redis
- gunicorn
Étape 4: Le déploiement avec le playbook principal
Maintenant que toutes les variables sont définies, nous allons créer le playbook principal deploy_stack.yml pour déployer et configurer l'infrastructure complète.
---
# Play 1 : Préparation commune sur tous les serveurs
- name: Préparation commune des serveurs
hosts: all
become: true
gather_facts: true
vars_files:
- vars/app_config.yml
tasks:
- name: Afficher le contexte du serveur
debug:
msg: "Serveur {{ inventory_hostname }} dans l'environnement {{ app_environment }} (company={{ company_name }})"
- name: Installer les paquets de base (Debian/Ubuntu seulement)
apt:
name: "{{ item }}"
state: present
update_cache: yes
loop: "{{ base_packages }}"
when: ansible_facts['os_family'] == "Debian"
- name: Simulation de configuration du pare-feu
debug:
msg: "Le pare-feu serait configuré ici."
when: firewall_enabled
- name: Vérifier l'état attendu de SELinux (simulation)
debug:
msg: "SELinux doit être désactivé (selinux_enabled={{ selinux_enabled }})"
- name: Exemple de condition basée sur une variable
debug:
msg: "Configuration spécifique pour la production"
when: app_environment == "production"
# Play 2 : Configuration des serveurs web (Nginx)
- name: Configuration des serveurs web (Nginx)
hosts: webservers
become: true
gather_facts: true
vars_files:
- vars/app_config.yml
tasks:
- name: Installer Nginx (simulation)
debug:
msg: "Installation de Nginx sur {{ inventory_hostname }}"
changed_when: true
notify: manage_nginx
- name: Afficher la configuration Nginx utilisée
debug:
msg: "Nginx config: {{ nginx_config }}"
- name: Afficher les modules Nginx à activer
debug:
msg: "Modules Nginx à activer: {{ nginx_modules | join(', ') }}"
- name: Générer les virtual hosts (simulation)
debug:
msg: "VHost {{ item.name }} -> root {{ item.document_root }} (SSL={{ item.ssl_enabled }})"
loop: "{{ virtual_hosts }}"
changed_when: true
notify: manage_nginx
- name: N'appliquer une action que si le port applicatif est supérieur à 8000
debug:
msg: "Le port applicatif {{ python_app.port }} est supérieur à 8000."
when: python_app.port > 8000
- name: Forcer l'exécution des handlers tout de suite
meta: flush_handlers
- name: Tâche qui s'exécute après le flush des handlers
debug:
msg: "Cette tâche s'exécute après le redémarrage / rechargement de Nginx."
handlers:
- name: Redémarrer Nginx
debug:
msg: ">>> Redémarrage complet de Nginx sur {{ inventory_hostname }} <<<"
listen: manage_nginx
- name: Recharger la configuration Nginx
debug:
msg: ">>> Rechargement de la configuration Nginx sur {{ inventory_hostname }} <<<"
listen: manage_nginx
# Play 3 : Configuration de la base de données et de Redis
- name: Configuration des bases de données et du cache
hosts: databases
become: true
gather_facts: true
vars_files:
- vars/app_config.yml
tasks:
- name: Afficher la configuration PostgreSQL
debug:
msg: "PostgreSQL {{ postgresql_version }} sur le port {{ postgresql_port }} (max_connections={{ postgresql_max_connections }})"
- name: Afficher la configuration Redis
debug:
msg: "Redis sur le port {{ redis_port }} (maxmemory={{ redis_maxmemory }}, policy={{ redis_maxmemory_policy }})"
- name: Créer les bases de données (simulation)
debug:
msg: "Création de la base {{ item.name }} pour l'utilisateur {{ item.owner }} (encoding={{ item.encoding }})"
loop: "{{ databases }}"
changed_when: true
notify: restart_db_services
- name: Vérifier la disponibilité de PostgreSQL (simulation)
command: "echo 'pg_isready sur le port {{ postgresql_port }}'"
register: pg_check
changed_when: false
- name: Afficher le résultat du check PostgreSQL
debug:
msg: "Check PostgreSQL rc={{ pg_check.rc }} stdout={{ pg_check.stdout }}"
- name: Exemple de failed_when personnalisé
command: "/bin/false"
register: pg_fake
failed_when: "'CRITICAL' in pg_fake.stderr"
ignore_errors: true
- name: Réagir à l'échec simulé
debug:
msg: "Commande en erreur (rc={{ pg_fake.rc }}), mais playbook non interrompu."
when: pg_fake is defined and pg_fake.rc != 0
handlers:
- name: Redémarrer les services de base de données
debug:
msg: ">>> Redémarrage de PostgreSQL et Redis sur {{ inventory_hostname }} <<<"
listen: restart_db_services
# Play 4 : Déploiement de l'application Python
- name: Déploiement de l'application Python
hosts: appservers
become: true
gather_facts: true
vars_files:
- vars/app_config.yml
tasks:
- name: Afficher la configuration de l'application
debug:
msg: "Déploiement de {{ python_app.name }} v{{ python_app.version }} sur le port {{ python_app.port }}"
- name: Déterminer dynamiquement le nombre de workers
set_fact:
effective_workers: "{{ python_app.workers if app_environment == 'production' else 1 }}"
- name: Afficher le nombre de workers effectif
debug:
msg: "Workers utilisés pour {{ python_app.name }} : {{ effective_workers }}"
- name: Afficher les variables d'environnement de l'application
debug:
msg: "Env vars: {{ python_app.environment_vars }}"
- name: Installer les dépendances Python (simulation)
debug:
msg: "Installation de la dépendance Python {{ item }}."
loop: "{{ python_app.dependencies }}"
changed_when: true
- name: Simulation de création du service Gunicorn
debug:
msg: "Création / mise à jour du service Gunicorn pour {{ python_app.name }} (port {{ python_app.port }})"
changed_when: true
notify: restart_app
- name: Exemple de commande avec changed_when = false
command: "echo 'Healthcheck applicatif'"
register: healthcheck
changed_when: false
- name: Afficher le résultat du healthcheck
debug:
msg: "Healthcheck rc={{ healthcheck.rc }} stdout={{ healthcheck.stdout }}"
- name: Configurer le monitoring si activé
debug:
msg: "Configuration du monitoring (intervalle={{ monitoring.check_interval }}s) pour services: {{ monitoring.services_to_monitor | join(', ') }}"
when: monitoring.enabled
handlers:
- name: Redémarrer l'application
debug:
msg: ">>> Redémarrage de l'application {{ python_app.name }} sur {{ inventory_hostname }} <<<"
listen: restart_app
À remarquer::
- Pour le play 1:
- Utilisation de variables communes avec
vars/app_config.yml. - Le play s'applique à tous les hôtes.
- Utilisation implicite des variables définies dans
group_vars/all.yml. - Installation d'une liste de paquets avec une boucle
loop. La liste de paquets est définie dans les variables communes (variablebase_packagesdu fichiergroup_vars/all.yml). - Utilisation de conditions
whenpour exécuter des tâches spécifiques selon les variables définies.ansible_factsest une variable prédéfinie par Ansible qui contient des informations sur le système cible.firewall_enabled,selinux_enabledetapp_environmentsont des variables définies dansgroup_vars/all.yml.
- Les tâches de configuration du pare-feu et de SELinux sont simulées avec des messages de débogage afin de simplifier l'exemple.
- Utilisation de variables communes avec
- Pour le play 2:
- Le play ne s'applique que sur les hôtes du groupe
webservers. - Utilisation de variables spécifiques aux serveurs web définies dans
group_vars/webservers.yml(de façon implicite via l'inventaire). - Utilisation de handlers pour redémarrer ou recharger Nginx lorsque des changements sont effectués. Les deux handlers sont liés à la même notification
manage_nginx. - La mention
changed_when: truedans les tâches simulant l'installation de Nginx et la génération des virtual hosts force Ansible à considérer ces tâches comme ayant effectué des changements, déclenchant ainsi les handlers associés. - Utilisation d'une boucle
looppour créer plusieurs virtual hosts définis dans la variablevirtual_hostsdu fichiergroup_vars/webservers.yml. - Utilisation de la variable
python_app.portdéfinie dansvars/app_config.ymlpour une conditionwhen. - Utilisation de la commande
meta: flush_handlerspour forcer l'exécution immédiate des handlers.
- Le play ne s'applique que sur les hôtes du groupe
- Pour le play 3:
- Le play ne s'applique que sur les hôtes du groupe
databases. - Utilisation de variables spécifiques aux bases de données définies dans
group_vars/databases.yml(de façon implicite via l'inventaire). - Utilisation d'une boucle
looppour créer plusieurs bases de données définies dans la variabledatabasesdu fichiergroup_vars/databases.yml. - Simulation de la vérification de la disponibilité de PostgreSQL avec la commande
pg_isreadyet affichage du résultat avecregistersuivi d'undebugqui affichera le code de retour (.rc) et la sortie standard (.stdout). - Utilisation d'une condition
failed_whenpersonnalisée pour simuler une gestion d'erreur avancée: la tâche échouera uniquement si le mot "CRITICAL" est présent dans la sortie d'erreur standard de la variable enregistréepg_fake, qui représente le résultat d'une commande simulée qui échoue toujours (/bin/false). L'optionignore_errors: truepermet de continuer l'exécution du playbook même si cette tâche échoue. - Utilisation d'une condition
whenpour réagir à l'échec simulé en affichant un message de débogage.
- Le play ne s'applique que sur les hôtes du groupe
- Pour le play 4:
- Le play ne s'applique que sur les hôtes du groupe
appservers. - Utilisation de variables spécifiques au serveur d'application définies dans
host_vars/app1.yml(de façon implicite via l'inventaire). - Utilisation de la commande
set_factpour définir dynamiquement une variableeffective_workersbasée sur la variableapp_environmentdéfinie dansgroup_vars/all.yml. - Utilisation de la variable
effective_workersdans une tâche de débogage. - Utilisation d'une boucle
looppour installer plusieurs dépendances Python définies dans la variablepython_app.dependenciesdu fichiervars/app_config.yml. - Simulation de la création ou mise à jour du service Gunicorn avec une tâche de débogage qui notifie un handler
restart_appen cas de changement... qui sera toujours le cas grâce àchanged_when: true. - Utilisation de la commande
echopour simuler un healthcheck applicatif, avecchanged_when: falsepour indiquer qu'aucun changement n'est effectué par cette tâche. - Utilisation d'une condition
whenpour configurer le monitoring uniquement si la variablemonitoring.enabledest définie surtrue(définie dansvars/app_config.ymlgrâce à un dictionnaire).
- Le play ne s'applique que sur les hôtes du groupe