C’est quoi une stack LEMP ?

LEMP est un acronyme pour

  • Linux
  • Nginx (se prononce Engine X)
  • MariaDB ou MySQL
  • PHP

Une stack est un ensemble de logiciels ‘empilés’ les uns sur les autres. Ici, le logiciel le plus proche de la machine, le système d’exploitation GNU/Linux est le premier élément de cette pile. Vient ensuite Nginx qui est un serveur web. Nginx se base sur le réseau et le système de fichiers gérés par Linux pour répondre aux requêtes HTTP du client (le visiteur de votre site). Quand il rencontre un script PHP, Nginx passe la main à l’interpréteur PHP via le protocole FastCGI. PHP lui même fait appel à MariaDB pour le stockage de données structurées. Cet ensemble de logiciels se empilés forme la stack LEMP.

Dans ce tutoriel, nous allons utiliser Ansible pour installer et configurer cette stack automatiquement sur Debian 11.

Installation de Nginx

Comme l’installation de la stack LEMP est succinte, nous allons utiliser un playbook Ansible qui contiendra tout le code. Dans un article suivant, nous verrons découper ce playbook en rôles quand sa complexité devient trop grande.

---
- name: Installation et configuration d'une stack LEMP
  hosts: serveur_debian_test
  become: yes

  tasks:
    - name: Installation du serveur web Nginx
      apt:
        name: nginx
	    update_cache: yes

La première tâche que nous venons d’ajouter au playbook installe Nginx avec apt. Nous n’avons pas encore configuré PHP sur notre serveur, car il dépend de la configuration PHP-FastCGI que nous allons réaliser. Voyons la configuration du serveur par défaut /etc/nginx/sites-enabled/default installée avec le paquet Nginx:

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	index index.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		try_files $uri $uri/ =404;
	}

	# pass PHP scripts to FastCGI server
	#
	#location ~ \.php$ {
	#	include snippets/fastcgi-php.conf;
	#
	#	# With php-fpm (or other unix sockets):
	#	fastcgi_pass unix:/run/php/php7.4-fpm.sock;
	#	# With php-cgi (or other tcp sockets):
	#	fastcgi_pass 127.0.0.1:9000;
	#}
}

La racine de ce site par défaut est située /var/www/html. Nous allons utiliser cette configuration par la suite. La configuration par défaut comporte aussi une prise en charge des urls finissant par .php. Nous allons décommenter ces lignes une fois que le serveur PHP FastCGI sera installé et disponible pour que Nginx sous-traite l’exécution des scripts PHP par le serveur PHP-FastCGI.

Installation de PHP et du serveur FastCGI

---
- name: Installation et configuration d'une stack LEMP
  hosts: conteneur_test
  become: yes

  tasks:
    - name: Installation du serveur web Nginx
      apt:
        name: nginx
	    update_cache: yes

    - name: Installation de l'interpréteur PHP et de ses modules
      apt:
        name:
          - php # interpréteur PHP
          - php-fpm # serveur FastCGI pour PHP
          - php-mysql # Paquet pour utiliser MySQL ou MariaDB avec PHP

De la même façon, une configuration par défaut du serveur FastCGI a été installée avec le paquet php-fpm dans le fichier /etc/php/7.4/fpm/pool.d/www.conf. Le serveur par défaut est configuré pour lancer PHP en tant qu’utilisateur www-data et écoutera les requêtes sur le socket unix situé /run/php/php7.4-fpm.sock d’après les lignes suivantes de la configuration:

listen = /run/php/php7.4-fpm.sock
user = www-data
group = www-data

On va maintenant lancer le serveur PHP FastCGI:

    - name: Lancement du serveur PHP FastCGI
      service:
        name: php7.4-fpm
        state: started

Configuration de Nginx pour utiliser PHP

Le serveur FastCGI qui va nous permettre de lancer notre script PHP est maintenant prêt. On peut décommenter la configuration de PHP-FastCGI dans le serveur Nginx par défaut, et activer l’utilisation de PHP-FastCGI pour le traitement des scripts PHP. On obtient la configuration suivante:

server {
	listen 80 default_server;
	listen [::]:80 default_server;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.php index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}

	# pass PHP scripts to FastCGI server
	#
	location ~ \.php$ {
		include snippets/fastcgi-php.conf;

		# With php-fpm (or other unix sockets):
		fastcgi_pass unix:/run/php/php7.4-fpm.sock;
		# With php-cgi (or other tcp sockets):
		# fastcgi_pass 127.0.0.1:9000;
	}
}

On stocke cette configuration dans le fichier default dans le même répertoire que le playbook Ansible.

On ajoute le démarrage du serveur Nginx ainsi que la copie de la configuration du serveur par défaut au playbook:

    - name: Configuration du serveur Nginx
      copy:
        src: default
        dest: /etc/nginx/sites-available/default

    - name: Lancement du serveur Nginx
      service:
        name: nginx
        state: started

Installation de MariaDB

Il ne reste plus qu’à installer la base de données MariaDB pour compléter notre stack.

Nous allons également créer un superutilisateur dont le nom et le mot de passe seront stockés dans les variables mariadb.root_user et mariadb.root_password pour pouvoir accéder à la base de données via PHP.

---
- name: Installation et configuration d'une stack LEMP
  hosts: conteneur_test
  become: yes
  vars:
    mariadb:
      root_user: tutoriel
      root_password: tutoriel
      database: tutoriel_lemp

En plus de l’installation de la base, on va installer le SDK Python de MySQL/MariaDB. Cette librairie permet de communiquer avec ces bases de données en utilisant Python. Dans notre cas, on va avec cette librairie et le rôle community.mysql.mysql_user créer un utilisateur MariaDB. Ensuite, on créera la base de données utilisée lors de ce tutoriel.

La collection de rôles Ansible community.mysql n’est pas installée par défaut. Nous allons l’ajouter à l’aide d’Ansible galaxy:

ansible-galaxy collection install community.mysql

Voici le code à ajouter au playbook:

    - name: Installation de la base de données MariaDB
      apt:
        name: mariadb-server

    - name: Lancement de la base de données
      service:
        name: mariadb
        state: started
        enabled: yes

    - name: Installation du SDK de la base de données
      pip:
        name: PyMySQL

    - name: Création d'un utilisateur avec tous les privilèges
      community.mysql.mysql_user:
        name: "{{ mariadb.root_user }}"
        password: "{{ mariadb.root_password }}"
        plugin: mysql_native_password
        priv: '*.*:ALL,GRANT'
        state: present
        login_unix_socket: /var/run/mysqld/mysqld.sock

    - name: Sauvegarde de l'identifiant de cet utilisateur
      template:
        src: my.cnf.j2
        dest: /root/.my.cnf
        owner: root
        group: root
        mode: '0600'

    - name: Création d'une base de données
      community.mysql.mysql_db:
        name: "{{ mariadb.database }}"
        state: present

Script PHP de démo

On va ajouter un script PHP qui va se connecter à la base et compter le nombre de visites de ce script pour tester la stack. Voici le script que nous allons utiliser:

<?php
// Connection à la base de données
$pdo = new PDO(
    'mysql:host=localhost;dbname={{ mariadb.database }}',
    '{{ mariadb.root_user }}',
    '{{ mariadb.root_password }}'
);

// Incrémentation du compteur de visites
$pdo->query("CREATE TABLE IF NOT EXISTS `visites`(id int PRIMARY KEY AUTO_INCREMENT, at DATETIME DEFAULT CURRENT_TIMESTAMP())");
$pdo->query("INSERT INTO `visites` VALUES ()");

// Récupération du nombre total de visites
$statement = $pdo->query("SELECT COUNT(*) as total_visites FROM `visites`");
$row = $statement->fetch(PDO::FETCH_ASSOC);
echo 'Nombre de visites total de /test.php: '. $row['total_visites'];
?>

Ce script PHP contient des variables Jinja2 comme {{ mariadb.database }} qui vont permettre de paramétriser l’utilisateur/mot de passe ainsi que la base de données utilisée par PHP. Pour refléter cela, nous allons mettre deux extensions au fichier PHP contenant des variables Jinja: test.php.j2. Le module template permet à Ansible de transformer la template de script PHP en script PHP puis de le copier vers le serveur distant (ici dans le fichier /var/www/html/test.php).

On place ce script à la racine de notre serveur de test, et on ajoute le programme cURL qui va nous permettre de faire des requêtes HTTP au serveur

    - name: Ajout du script PHP de test à la racine
      template:
        src: test.php.j2
        dest: /var/www/html/test.php
        owner: www-data
        group: www-data
        mode: '0644'

    - name: Installation de cURL
      apt:
        name: curl

Il est maintenant possible d’utiliser cURL à l’intérieur du conteneur de test afin de récupérer la page /test.php en HTTP, voici le résultat:

docker exec conteneur_test curl -s http://localhost/test.php
# Résultat (ici, 17 visites au total):
# Nombre de visites total de /test.php: 17

Conclusion

Vous pouvez retrouver les sources du projet complet sur ce site. Lancez la construction du conteneur de test avec ./conteneur-test.sh. Puis exécutez le playbook avec ansible-playbook -i hosts playbook.yml. Ansible devra bien sûr être préalablement installé sur votre machine.

La configuration de la stack LEMP de cette article devra être améliorée pour correspondre à vos besoins. J’espère que cette première approche vous a permis d’avoir un aperçu de la conception d’une stack LEMP.