Skip to main content

Déploiement et Deployer

Le déploiement est l'action d'envoyer son code sur sa destination d'exécution, c'est-à-dire dans notre cas le serveur.

Cette étape est essentielle dans la mise en ligne d'une application quelle qu'elle soit. Cependant, elle amène ses propres défis :

  • Gérer les versions du code déployées sur différents serveurs
  • Avoir la possibilité de revenir en arrière
  • Gérer les migrations de base de données
  • Gérer le cache des applications
  • Gérer la sécurité et les droits d'accès aux fichiers
  • ...

Pratiques de déploiement

Certains d'entre vous ont probablement effectué des déploiements par le passé.

Listons ensemble quelques techniques de déploiement, pour en identifier les avantages et inconvénients.

(S)FTP "classique"

La technique historique, malheureusement encore très répandue, notamment dans les hébergements mutualisés, est d'envoyer les fichiers de code par FTP (ou déjà un peu mieux, par SFTP).

Bien que cette pratique soit très rapide à mettre en place, elle pose un certain nombre de problèmes :

  • Gestion de versions difficile : quelle est la version actuellement déployée ?
  • Gestion des fichiers : les fichiers renommés ou déplacés se retrouvent dupliqués, les fichiers supprimés sont encore présents et peuvent être exécutés par un appel HTTP
  • Implique potentiellement un certain nombre d'opérations manuelles
    • Envoi des fichiers
    • Effacement du cache
    • Migrations des bases de données
    • Installation des dépendances
    • Gestion manuelle des droits des dossiers
  • Rend le service inopérant pendant la mise à jour

Git

La technique précédente peut facilement être améliorée en se connectant en SSH au serveur et en utilisant Git pour tirer les nouvelles sources. Cette pratique nous permet de connaître précisément la version déployée et d'éviter de dupliquer des fichiers et de laisser les fichiers supprimés sur le serveur.

Toutefois, les autres problèmes se posent encore, avec l'addition d'une manœuvre supplémentaire pour tirer les sources.

Liens symboliques

Pour améliorer encore la pratique de déploiement précédente, nous pouvons utiliser des liens symboliques. Pour rappel (ou pas), les liens symboliques sont une technique Unix pour créer un "raccourci" vers un fichier ou un dossier situé à un autre endroit sur le disque.

On pourrait par exemple imaginer le système suivant :

|-- current -> releases/release-2
`-- releases
|-- release-1
|-- release-2

Ici, current est le dossier configuré dans notre serveur WEB pour être exposé à internet et à PHP. Mais ce dossier est en fat un lien symbolique, qui référence le dossier releases/release-2.

À chaque déploiement, nous créons un dossier dans le dossier releases, nous y téléchargeons le code avec Git, créons les droits d'accès à l'intérieur, installons les dépendances, etc. Une fois notre release prête, nous n'avons plus qu'à changer notre lien symbolique current pour qu'il pointe vers la nouvelle release. Ainsi, plus de versions incohérentes, pas de soucis de dépendances, ni de droits de fichiers, la mise à jour n'est effectuée que quand tout est prêt, sans jamais que le service soit stoppé.

De plus, si on se rend compte qu'il y a un problème avec notre code, nous pouvons facilement revenir à la release précédente en changeant la cible du lien symbolique current.

Cette technique permet de répondre à un bon nombre de problèmes, mais ajoute encore d'assez nombreuses étapes manuelles à effectuer à chaque déploiement.

Scripts de déploiement

Afin d'automatiser un peu les différentes étapes d'un déploiement plus complexe comme celui présenté ci-dessus, on pourrait imaginer écrire un script (bash par exemple) pour automatiser ces étapes.

Pour votre culture, voici le script simpliste que j'utilise pour mettre à jour ce site internet :

#!/bin/bash

# Je récupère le chemin de la release actuelle dans une variable
current_release="$(ssh ${REMOTE_USER}@${REMOTE_HOST} readlink ${WWW_PATH}/${CURRENT_PATH})"
# Je crée un nouveau nom de release à partir du timestamp de la date actuelle
next_release="release-$(date +%s)"
# Je renomme mon dossier "build" contenant le site packagé en son nouveau nom
cp -r build $next_release
# Je copie le dossier de release sur mon serveur dans le dossier "releases"
rsync -r $next_release "${REMOTE_USER}@${REMOTE_HOST}:${WWW_PATH}/${RELEASE_PATH}"
# Je met à jour mon lien symbolique "current" pour qu'il pointe sur cette nouvelle release
ssh ${REMOTE_USER}@${REMOTE_HOST} "ln -s -n -f ${RELEASE_PATH}$next_release ${WWW_PATH}/${CURRENT_PATH} \
&& rm -rf $current_release"
# Je supprime la release précédente

Comme vous pouvez le voir, ce script ne fait pas grand-chose, mais n'est pas simple à écrire pour un novice.

Heureusement, pour des déploiements plus complexes, des scripts ou programmes tout faits et faciles à configurer existent pour automatiser ce genre de tâches. Je vous propose pour aujourd'hui d'en utiliser un nommé Deployer, écrit en PHP.

Images Docker

Autre technique méritant d'être mentionnée, mais que nous n'allons pas utiliser ensemble : construire des images Docker contenant le code de notre application. Ainsi, il suffit de télécharger la nouvelle image Docker sur notre serveur, et re-créer le conteneur de l'application.

Cette technique présente de très nombreux avantages, et c'est celle que je recommande généralement. Cependant, utilisée telle quelle, elle présente dans notre cas deux inconvénients majeurs :

  • Elle impose une coupure de service
  • Nous serions obligé de créer deux images avec notre code, une pour le serveur Web et une autre pour le conteneur PHP, et de les mettre à jour simultanément.

Déployez votre code

Assez de théorie pour aujourd'hui, c'est maintenant à vous : vous allez devoir déployer votre code sur votre serveur avec un script de déploiement nommé Deployer.

Deployer

Deployer est un outil spécialisé dans l'automatisation de déploiement de projets PHP.

Cet outil permet de déployer une application, tout en gérant les différentes problématiques citées précédemment assez simplement.

Deployer est constitué d'un ensemble de "recettes" pour les déploiements PHP les plus utilisées, dont vous pouvez trouver la liste ici.

Comme vous vous en doutez, il dispose d'une "recette" permettant de déployer des applications Symfony, comme la vôtre. Cette "recette" permet notamment de :

  • gérer les fichiers communs entre les déploiements, comme les logs ou les uploads
  • Effectuer les migrations de BDD
  • Installer automatiquement les dépendances de votre projet avec Composer
  • Effacer le cache de l'application après un déploiement
caution

Deployer permet aussi d'effectuer ce qu'ils appellent du provisioning, c'est-à-dire de faire l'installation d'un serveur Web et de PHP sur votre serveur.

Ce n'est pas ce que nous souhaitons, dans la mesure où nous avons déjà déployé des conteneurs correspondant sur notre serveur. Cette section de l'outil ne nous intéresse donc pas.

Installation de Deployer

Comme Deployer est un outil PHP, vous pouvez tout simplement l'installer dans votre projet avec Composer comme dépendance de développement :

composer require --dev deployer/deployer
tip

N'oubliez pas de faire un commit Git de vos fichiers composer.json et composer.lock !

Création du fichier de configuration

La configuration de cet outil s'effectue en PHP, dans un fichier nommé deployer.php, que vous pouvez demander à Deployer de créer avec la commande suivante (à exécuter dans votre conteneur PHP local):

vendor/bin/dep init

Rentrez les informations demandées par le script interactif.

Ouvrez ensuite le fichier deploy.php généré dans votre éditeur de code.

Comme vous pouvez le voir, la configuration contient déjà un certain nombre d'éléments.

Regardez la documentation de Deployer ainsi que la "recette" pour Symfony, et essayez de modifier la configuration pour votre besoin.

Connexion au serveur

Maintenant que vous avez configuré Deployer pour votre projet, essayez de lancer votre premier déploiement avec la commande dédiée :

vendor/bin/dep deploy

Comme vous pouvez le constater, Deployer n'arrive pas à se connecter au serveur. Effectivement, vous ne pouvez pas faire de SSH vers le serveur pour la simple et bonne raison que votre clé SSH n'est pas chargée dans le conteneur PHP que vous utilisez actuellement.

Plutôt que d'utiliser votre clé SSH, nous allons en créer une exprès pour Deployer.

Exécutez la commande suivante dans le répertoire courant sur votre hôte (ou WSL) :

ssh-keygen -t ed25519 -f ./id_deployer

Ne mettez pas de passphrase et attendez que la commande génère les deux fichiers attendus dans votre répertoire courant : id_deployer et id_deployer.pub. Maintenant que votre clé pour Deployer est générée, il faut l'installer sur le serveur. Reprenez la section prise en main du serveur pour trouver les options adéquates de la commande ssh-copy-id afin d'installer la clé générée sur votre serveur.

Depuis votre conteneur, pour permettre à Deployer d'utiliser la clé générée, effectuez les commandes suivantes :

eval $(ssh-agent -s)
ssh-add id_deployer

Vous pouvez valider le bon fonctionnement de la clé en essayant depuis votre conteneur les commandes suivantes :

# Liste les clés disponibles
ssh-add -l

# Se connecte à votre serveur !
ssh lpmiaw@vpsX.lpmiaw-lr.fr

Deploy keys

Essayez à nouveau de faire votre déploiement avec la commande deploy. Une nouvelle erreur survient.

Maintenant que Deployer peut se connecter au serveur, il essaie d'y installer du code en le tirant depuis Gitlab. Cependant, le serveur ne dispose pas d'un accès SSH à Gitlab, et échoue donc lors de cette étape.

Pour commencer, connectez-vous à votre serveur, puis générez une clé SSH pour votre utilisateur :

ssh-keygen -t ed25519

Laissez les paramètres par défaut : la clé sera générée dans /home/lpmiaw/.ssh/id_ed25519.

Maintenant que votre serveur dispose de sa propre clé SSH, il nous faut donner à cette clé un accès à Gitlab. Nous pourrions le faire par l'un de vos utilisateurs, mais cette clé disposerait alors de l'accès à tous vos projets, en lecture, mais aussi en écriture.

Heureusement, Gitlab dispose d'une fonctionnalité pour répondre à cette problématique : les deploy keys, ou clés de déploiement. Ajouter une clé SSH comme deploy key permet de lui donner un accès en lecture sur un projet donné uniquement.

Pour ajouter la clé de votre serveur comme deploy key, copiez le contenu de la clé (publique) depuis votre serveur, puis rendez-vous dans le menu Settings > Repository, dans la section Deploy keys. Vous disposez dans cette section de champs pour ajouter une clé publique, ainsi que la possibilité de lui donner un titre. Une fois ceci fait, vous devriez la voir apparaître dans la liste des clés.

Configuration des commandes

Deployer arrive maintenant à mettre à jour le code sur le serveur. Cependant, il n'arrive pas à utiliser la commande composer pour mettre à jour les dépendances. Ce n'est pas étonnant : Deployer se connecte à notre serveur en SSH, sur notre utilisateur, mais pas dans le conteneur. Il ne dispose donc pas de l'environnement PHP.

Pour régler ce problème, il faut spécifier à Deployer la commande à utiliser pour exécuter composer dans le conteneur.

À l'aide de vos connaissances et de la documentation de Docker ainsi que celle de Deployer, configurez Deployer pour exécuter composer dans le conteneur. Au passage, il pourrait être utile de lui spécifier aussi comment exécuter une commande bin/console pour qu'il puisse effacer le cache par exemple...

tip

Pour vous lancer sur la bonne voie :

  • Docker dispose d'une commande exec, qui peut vous être utile (en particulier avec certaines options)
  • Vous pouvez trouver des informations intéressantes dans les options des "recettes" de Deployer, comme ici.

Migrations

La base de données sur votre serveur est fonctionnelle, mais les tables nécessaires au bon fonctionnement de votre application ne sont pas encore créées : il faut lancer les migrations.

Deployer dispose déjà de la configuration nécessaire pour effectuer ce genre de tâches, et propose une commande pour le faire pour vous, depuis votre poste :

vendor/bin/dep migrate

Si tout est correctement configuré, les migrations devraient être effectuées sur votre base de données. Cependant, il n'y a aucune données de départ : nous n'avons pas lancé les fixtures.

info

Les fixtures sont des outils de développement, et il n'est par conséquent généralement pas conseillé de les utiliser en production. Dans notre cas, nous allons le faire quand même pour nous faire gagner du temps.

Connectez-vous sur votre serveur, ouvrez un terminal dans le conteneur PHP et lancez y la commande suivante :

APP_ENV=dev bin/console doctrine:fixtures:load

Configuration sur le serveur

Si vous êtes parvenus à faire fonctionner les étapes précédentes, félicitations ! Votre code est correctement déployé sur votre serveur. Cependant, nos conteneurs ne sont pas bien configurés pour l'exécuter : le code de la version en cours n'est plus situé exactement au même endroit.

Pour faire en sorte que Caddy utilise la "release" courante de l'application, modifiez la directive root de votre Caddyfile.

tip

Pour que le code se mette correctement à jour en suivant les liens symboliques, la documentation de Caddy donne une astuce ici...

Si tout s'est bien déroulé, vous devriez avoir accès à votre application depuis le nom de domaine de votre serveur !

Vérifiez que tout fonctionne correctement :

  • la page d'accueil
  • la connexion
  • l'ajout d'artistes et de concerts

Si ce n'est pas le cas, certaines choses sont peut-être à modifier dans votre configuration Deployer !

Correction

Correction
deploy.php
<?php
namespace Deployer;

require 'recipe/symfony.php';

// Config

set('repository', getenv('CI_REPOSITORY_URL'));

add('shared_files', []);
add('shared_dirs', [
'public/assets/imgGroupes'
]);
add('writable_dirs', [
'public/assets/imgGroupes'
]);

// Hosts

host('production')
->set('hostname', getenv('REMOTE_HOST'))
->set('remote_user', getenv('REMOTE_USER'))
->set('remote_user_id', getenv('REMOTE_USER_ID'))
->set('port', getenv('REMOTE_PORT'))
->set('container_name', getenv('PHP_CONTAINER_NAME'))
->set('deploy_path', '/srv/lpmiaw_devops_app')
->set('bin/console', 'docker exec -t -w /var/www/app/current {{container_name}} php bin/console')
->set('bin/composer', 'docker exec -t -u {{remote_user_id}} -w /var/www/app/release {{container_name}} composer')
->set('http_user', getenv('PHP_USER_ID'));

// Hooks
after('deploy:failed', 'deploy:unlock');