Déploiement sans temps d'arrêt de Laravel

Publié: 2022-03-11

Lorsqu'il s'agit de mettre à jour une application en direct, il existe deux manières fondamentalement différentes de procéder.

Dans la première approche, nous apportons des modifications incrémentielles à l'état de notre système. Par exemple, nous mettons à jour des fichiers, modifions les propriétés de l'environnement, installons des nécessités supplémentaires, etc. Dans la deuxième approche, nous démontons des machines entières et reconstruisons le système avec de nouvelles images et des configurations déclaratives (par exemple, en utilisant Kubernetes).

Le déploiement de Laravel simplifié

Cet article couvre principalement des applications relativement petites, qui peuvent ne pas être hébergées dans le cloud, bien que je mentionnerai comment Kubernetes peut grandement nous aider avec des déploiements au-delà du scénario "sans cloud". Nous discuterons également de certains problèmes généraux et de conseils pour effectuer des mises à jour réussies qui pourraient être applicables dans une gamme de situations différentes, pas seulement avec le déploiement de Laravel.

Pour les besoins de cette démonstration, j'utiliserai un exemple Laravel, mais gardez à l'esprit que toute application PHP pourrait utiliser une approche similaire.

Gestion des versions

Pour commencer, il est crucial que nous connaissions la version de code actuellement déployée en production. Il peut être inclus dans un fichier ou au moins dans le nom d'un dossier ou d'un fichier. En ce qui concerne la dénomination, si nous suivons la pratique standard du versioning sémantique, nous pouvons y inclure plus d'informations qu'un simple numéro.

En examinant deux versions différentes, ces informations supplémentaires pourraient nous aider à comprendre facilement la nature des changements introduits entre elles.

Image montrant l'explication de la gestion sémantique des versions.

La gestion des versions de la version commence par un système de contrôle de version, tel que Git. Supposons que nous ayons préparé une version pour le déploiement, par exemple, la version 1.0.3. Lorsqu'il s'agit d'organiser ces versions et flux de code, il existe différents styles de développement tels que le développement basé sur le tronc et le flux Git, que vous pouvez choisir ou mélanger en fonction des préférences de votre équipe et des spécificités de votre projet. En fin de compte, nous finirons très probablement avec nos versions étiquetées en conséquence sur notre branche principale.

Après le commit, nous pouvons créer une balise simple comme celle-ci :

git tag v1.0.3

Et puis nous incluons des balises lors de l'exécution de la commande push :

git push <origin> <branch> --tags

Nous pouvons également ajouter des balises aux anciens commits en utilisant leurs hachages.

Obtenir les fichiers de version vers leur destination

Le déploiement de Laravel prend du temps, même s'il s'agit simplement de copier des fichiers. Cependant, même si cela ne prend pas trop de temps, notre objectif est d'atteindre zéro temps d'arrêt .

Par conséquent, nous devons éviter d'installer la mise à jour sur place et ne devons pas modifier les fichiers qui sont diffusés en direct. Au lieu de cela, nous devrions déployer dans un autre répertoire et effectuer le changement uniquement une fois que l'installation est complètement prête.

En fait, il existe divers outils et services qui peuvent nous aider dans les déploiements, tels que Envoyer.io (du concepteur de Laravel.com Jack McDade), Capistrano, Deployer, etc. Je ne les ai pas encore tous utilisés en production, donc je ne peux pas faire des recommandations ou écrire une comparaison complète, mais laissez-moi vous présenter l'idée derrière ces produits. Si certains (ou tous) d'entre eux ne peuvent pas répondre à vos besoins, vous pouvez toujours créer vos scripts personnalisés pour automatiser le processus de la meilleure façon qui vous convient.

Pour les besoins de cette démonstration, supposons que notre application Laravel est servie par un serveur Nginx à partir du chemin suivant :

/var/www/demo/public

Tout d'abord, nous avons besoin d'un répertoire pour placer les fichiers de version chaque fois que nous effectuons un déploiement. De plus, nous avons besoin d'un lien symbolique qui pointera vers la version de travail actuelle. Dans ce cas, /var/www/demo servira de lien symbolique. La réaffectation du pointeur nous permettra de changer rapidement de version.

Gestion des fichiers de déploiement Laravel

Dans le cas où nous avons affaire à un serveur Apache, nous devrons peut-être autoriser les liens symboliques suivants dans la configuration :

Options +FollowSymLinks

Notre structure peut ressembler à ceci :

 /opt/demo/release/v0.1.0 /opt/demo/release/v0.1.1 /opt/demo/release/v0.1.2

Il peut y avoir des fichiers dont nous avons besoin pour persister à travers différents déploiements, par exemple, les fichiers journaux (si nous n'utilisons pas Logstash, évidemment). Dans le cas du déploiement de Laravel, nous souhaiterons peut-être conserver le répertoire de stockage et le fichier de configuration .env. Nous pouvons les garder séparés des autres fichiers et utiliser leurs liens symboliques à la place.

Afin de récupérer nos fichiers de version à partir du référentiel Git, nous pouvons utiliser les commandes clone ou archive. Certaines personnes utilisent git clone, mais vous ne pouvez pas cloner un commit ou un tag particulier. Cela signifie que l'ensemble du référentiel est récupéré, puis la balise spécifique est sélectionnée. Lorsqu'un référentiel contient de nombreuses branches ou un historique volumineux, sa taille est considérablement plus grande que l'archive de la version. Donc, si vous n'avez pas spécifiquement besoin du dépôt git en production, vous pouvez utiliser git archive . Cela nous permet de récupérer uniquement une archive de fichier par une balise spécifique. Un autre avantage de l'utilisation de ce dernier est que nous pouvons ignorer certains fichiers et dossiers qui ne devraient pas être présents dans l'environnement de production, par exemple, les tests. Pour cela, il nous suffit de définir la propriété export-ignore dans le .gitattributes file . Dans la liste de contrôle des pratiques de codage sécurisé OWASP, vous pouvez trouver la recommandation suivante : "Supprimez le code de test ou toute fonctionnalité non destinée à la production, avant le déploiement."

Si nous récupérons la version du système de contrôle de version source, git archive et export-ignore pourraient nous aider avec cette exigence.

Jetons un coup d'œil à un script simplifié (il nécessiterait une meilleure gestion des erreurs en production) :

deploy.sh

 #!/bin/bash # Terminate execution if any command fails set -e # Get tag from a script argument TAG=$1 GIT_REMOTE_URL='here should be a remote url of the repo' BASE_DIR=/opt/demo # Create folder structure for releases if necessary RELEASE_DIR=$BASE_DIR/releases/$TAG mkdir -p $RELEASE_DIR mkdir -p $BASE_DIR/storage cd $RELEASE_DIR # Fetch the release files from git as a tar archive and unzip git archive \ --remote=$GIT_REMOTE_URL \ --format=tar \ $TAG \ | tar xf - # Install laravel dependencies with composer composer install -o --no-interaction --no-dev # Create symlinks to `storage` and `.env` ln -sf $BASE_DIR/.env ./ rm -rf storage && ln -sf $BASE_DIR/storage ./ # Run database migrations php artisan migrate --no-interaction --force # Run optimization commands for laravel php artisan optimize php artisan cache:clear php artisan route:cache php artisan view:clear php artisan config:cache # Remove existing directory or symlink for the release and create a new one. NGINX_DIR=/var/www/public mkdir -p $NGINX_DIR rm -f $NGINX_DIR/demo ln -sf $RELEASE_DIR $NGINX_DIR/demo

Pour déployer notre version, nous pourrions simplement exécuter ce qui suit :

deploy.sh v1.0.3

Remarque : Dans cet exemple, v1.0.3 est la balise git de notre version.

Compositeur en production ?

Vous avez peut-être remarqué que le script appelle Composer pour installer les dépendances. Bien que vous le voyiez dans de nombreux articles, cette approche peut poser des problèmes. En règle générale, il est recommandé de créer une version complète d'une application et de faire progresser cette version dans divers environnements de test de votre infrastructure. En fin de compte, vous auriez une version soigneusement testée, qui peut être déployée en toute sécurité en production. Même si chaque version doit être reproductible à partir de zéro, cela ne signifie pas que nous devons reconstruire l'application à différentes étapes. Lorsque nous effectuons l'installation de composer en production, ce n'est pas vraiment la même version que celle testée et voici ce qui peut mal tourner :

  • Une erreur réseau peut interrompre le téléchargement des dépendances.
  • Le fournisseur de la bibliothèque peut ne pas toujours suivre le SemVer.

Une erreur de réseau peut être facilement remarquée. Notre script cesserait même de s'exécuter avec une erreur. Mais un changement radical dans une bibliothèque peut être très difficile à cerner sans exécuter de tests, ce que vous ne pouvez pas faire en production. Lors de l'installation des dépendances, Composer, npm et d'autres outils similaires s'appuient sur la gestion sémantique des versions - major.minor.patch. Si vous voyez ~1.0.2 dans le composer.json, cela signifie d'installer la version 1.0.2 ou la dernière version de correctif, telle que 1.0.4. Si vous voyez ^1.0.2, cela signifie que vous devez installer la version 1.0.2 ou la dernière version mineure ou corrective, telle que 1.1.0. Nous faisons confiance au fournisseur de la bibliothèque pour augmenter le nombre majeur lorsqu'un changement avec rupture est introduit, mais parfois cette exigence n'est pas respectée ou n'est pas respectée. Il y a eu de tels cas dans le passé. Même si vous mettez des versions corrigées dans votre composer.json, vos dépendances peuvent avoir ~ et ^ dans leur composer.json.

S'il est accessible, à mon avis, une meilleure façon serait d'utiliser un référentiel d'artefacts (Nexus, JFrog, etc.). La version de version, contenant toutes les dépendances nécessaires, serait créée une fois, initialement. Cet artefact serait stocké dans un référentiel et récupéré pour différentes étapes de test à partir de là. En outre, ce serait la version à déployer en production, au lieu de reconstruire l'application à partir de Git.

Garder le code et la base de données compatibles

La raison pour laquelle je suis tombé amoureux de Laravel à première vue était la façon dont son auteur a porté une attention particulière aux détails, a pensé à la commodité des développeurs et a également incorporé de nombreuses meilleures pratiques dans le cadre, comme les migrations de bases de données.

Les migrations de bases de données nous permettent d'avoir notre base de données et notre code synchronisés. Leurs deux modifications peuvent être incluses dans un seul commit, donc une seule version. Cependant, cela ne signifie pas que tout changement peut être déployé sans temps d'arrêt. À un moment donné du déploiement, différentes versions de l'application et de la base de données seront en cours d'exécution. En cas de problème, ce point peut même se transformer en période. Nous devrions toujours essayer de les rendre compatibles avec les versions précédentes de leurs compagnons : ancienne base de données - nouvelle application, nouvelle base de données - ancienne application.

Par exemple, supposons que nous ayons une colonne d' address et que nous devions la diviser en address1 et address2 . Pour que tout reste compatible, nous pourrions avoir besoin de plusieurs versions.

  1. Ajoutez deux nouvelles colonnes dans la base de données.
  2. Modifiez l'application pour utiliser de nouveaux champs chaque fois que possible.
  3. Migrez les données d' address vers de nouvelles colonnes et supprimez-les.

Ce cas est également un bon exemple de la façon dont de petits changements sont bien meilleurs pour le déploiement. Leur restauration est également plus facile. Si nous modifions la base de code et la base de données pendant plusieurs semaines ou mois, il peut être impossible de mettre à jour le système de production sans temps d'arrêt.

Quelques merveilles de Kubernetes

Même si l'échelle de notre application n'a peut-être pas besoin de clouds, de nœuds et de Kubernetes, je voudrais quand même mentionner à quoi ressemblent les déploiements dans les K8. Dans ce cas, nous n'apportons pas de modifications au système, mais déclarons plutôt ce que nous aimerions réaliser et ce qui devrait être exécuté sur combien de répliques. Ensuite, Kubernetes s'assure que l'état réel correspond à celui souhaité.

Chaque fois que nous avons une nouvelle version prête, nous construisons une image avec de nouveaux fichiers, étiquetons l'image avec la nouvelle version et la transmettons à K8s. Ce dernier va rapidement faire tourner notre image à l'intérieur d'un cluster. Il attendra que l'application soit prête en fonction de la vérification de l'état de préparation que nous fournissons, puis redirigera le trafic de manière imperceptible vers la nouvelle application et tuera l'ancienne. Nous pouvons très facilement avoir plusieurs versions de notre application en cours d'exécution, ce qui nous permettrait d'effectuer des déploiements bleu/vert ou canari avec seulement quelques commandes.

Si cela vous intéresse, il y a des démonstrations impressionnantes dans la conférence « 9 Steps to Awesome with Kubernetes by Burr Sutter ».

En relation: Authentification complète de l'utilisateur et contrôle d'accès - Un didacticiel sur le passeport Laravel, Pt. 1