Docker + Laravel + RoadRunner = ❤

image


Ce message est écrit à la demande de travailleurs qui demandent périodiquement «Comment exécuter l'application Illuminate / Symfony / MyOwn Psr7 dans Docker ». Je n'ai pas envie de donner un lien vers un article précédemment écrit , car mes opinions sur la façon de résoudre le problème ont beaucoup changé.


Tout ce qui sera écrit ci-dessous est une expérience subjective qui (comme toujours) ne prétend pas être considérée comme la seule bonne décision, mais certaines approches et solutions peuvent vous sembler intéressantes et utiles.


J'utiliserai également Laravel comme application, car elle m'est plus familière et assez répandue. L'adaptation à d'autres cadres / composants basés sur PSR-7 est possible, mais cette histoire n'est pas à ce sujet.

Gestion des erreurs


Je voudrais commencer par ce qui s'est avéré être "pas les meilleures pratiques" dans le contexte de l' article précédent :


  • La nécessité de changer la structure des fichiers dans le référentiel
  • Utilisation de FPM. Si nous voulons des performances de nos applications, alors peut-être qu'une des meilleures solutions même au stade de la sélection de la technologie sera de l'abandonner au profit de quelque chose de plus rapide et «adapté» au fait que la mémoire peut fuir. RoadRunner de lachezis est là comme jamais auparavant
  • Une image séparée avec la source et les actifs. Malgré le fait qu'en utilisant cette approche, nous pouvons réutiliser la même image pour créer un routage plus complexe des demandes entrantes (nginx à l'avant pour retourner la statique; les demandes de dynamique sont servies par un autre conteneur dans lequel le volume est jeté avec les mêmes sources - pour meilleure mise à l'échelle) - ce schéma s'est révélé assez compliqué dans le fonctionnement du produit. Et en plus, RR lui-même rend parfaitement la statique, et s'il y a beaucoup de statique (ou la ressource peut charger et afficher le contenu généré par l'utilisateur) - nous le transférons vers CDN (le bundle S3 + CloudFront + CloudFlare fonctionne bien) et oublions ce problème en principe
  • CI complexe. Cela est devenu un véritable problème lorsque la période de "construction de viande" active a commencé aux étapes de l'assemblage et des tests automatiques. Un mec qui ne supportait pas ce CI auparavant, il devient très difficile d'y apporter des modifications sans craindre de casser quoi que ce soit.

Maintenant, sachant quels problèmes doivent être résolus et sachant comment procéder, je propose de procéder à leur élimination. L'ensemble des "outils de développement" n'a pas changé - c'est le même docker-ce , docker-compose et le puissant Makefile .


En conséquence, nous obtenons:


  • Un conteneur autonome avec une application sans avoir besoin de monter un volume supplémentaire
  • Un exemple d'utilisation de git-hooks - nous mettrons automatiquement les dépendances nécessaires après git pull et interdirons de pousser le code si les tests échouent (les hooks seront stockés sous git, naturellement)
  • RoadRunner traitera les demandes HTTP (s)
  • Les développeurs pourront toujours exécuter dd(..) et dump(..) pour le débogage, tandis que rien ne plantera dans leur navigateur
  • Les tests peuvent être exécutés directement à partir de l'IDE PHPStorm, tandis qu'ils seront exécutés dans le conteneur avec l'application
  • CI collectera des images pour nous lors de la publication d'une nouvelle balise de version d'application
  • Prenons la règle stricte de maintien des fichiers CHANGELOG.md et ENVIRONMENT.md

Introduction visuelle d'une nouvelle approche


Pour une démonstration visuelle, je décompose l'ensemble du processus en plusieurs étapes dont les changements se feront en MR séparés (après la fusion, tous les brunchs resteront à leur place; références au MR dans les en-têtes des «étapes»). Le point de départ est le squelette Laravel d'une application créée à l'aide du composer create-project laravel/laravel :


 $ docker run \ --rm -i \ -v "$(pwd):/src" \ -u "$(id -u):$(id -g)" \ composer composer create-project --prefer-dist laravel/laravel \ /src/laravel-in-docker-with-rr "5.8.*" 

Étape 1 - Docking + RR


La première étape consiste à apprendre à exécuter l'application dans le conteneur. Pour ce faire, nous avons besoin d'un Dockerfile , docker-compose.yml pour la description de «comment soulever et lier des conteneurs», et d'un Makefile afin de réduire le processus déjà simplifié à une ou deux commandes.


Dockerfile


L'image de base que j'utilise php:XXX-alpine est la plus simple et contient ce dont vous avez besoin pour exécuter. De plus, toutes les mises à jour ultérieures de l'interpréteur sont réduites à une simple modification de la valeur de cette ligne (la mise à jour de PHP est maintenant plus facile que jamais).


Composer et le fichier binaire RoadRunner sont livrés au conteneur en utilisant plusieurs étapes et COPY --from=... - c'est très pratique, et toutes les valeurs associées aux versions ne sont pas "dispersées", mais se trouvent au début du fichier. Cela fonctionne rapidement et sans dépendances sur curl / git clone / make build . Les images 512k / roadrunner sont prises en charge par moi, si vous le souhaitez - vous pouvez assembler le fichier binaire vous-même.


Une histoire intéressante s'est produite avec la variable d'environnement PS1 (responsable de l'invite dans le shell) - il s'avère que vous pouvez utiliser des emoji dedans, et tout fonctionne localement, mais si vous essayez de démarrer l'image avec une variable contenant des emoji dans, disons, rancher, elle plantera (dans tout essaim) fonctionne sans problème).

Dans Dockerfile je commence à générer un certificat SSL auto-signé afin de l'utiliser pour les demandes HTTPS entrantes. Naturellement - rien n'empêche l'utilisation d'un certificat "normal".


Je voudrais également dire:


 COPY ./composer.* /app/ RUN set -xe \ && composer install --no-interaction --no-ansi --no-suggest --prefer-dist \ --no-autoloader --no-scripts \ && composer install --no-dev --no-interaction --no-ansi --no-suggest \ --prefer-dist --no-autoloader --no-scripts 

Ici, la signification est la suivante - les fichiers composer.lock et composer.json sont livrés dans une couche distincte à l'image, après quoi toutes les dépendances qui y sont décrites sont installées. Cela est fait de sorte que lors des --cache-from suivantes de l'image à l'aide de --cache-from , si la composition et les versions des dépendances installées n'ont pas changé, l' composer install ne composer install pas exécutée, en prenant cette couche du cache, économisant ainsi du temps de construction et du trafic (merci pour l'idée jetexe ).


composer install est exécutée deux fois (la deuxième fois avec --no-dev ) pour «réchauffer» le cache des dépendances de dev, de sorte que lorsque nous mettons toutes les dépendances sur le CI pour exécuter les tests, elles sont placées à partir du cache de compositeur, qui est déjà dans l'image, non étiré de galaxies lointaines.


Avec la dernière instruction RUN , nous affichons les versions du logiciel installé et la composition des modules PHP à la fois pour l'historique dans les journaux de build et pour nous assurer qu '"il existe au moins et démarre en quelque sorte".


J'utilise également mon Entrypoint, car avant de démarrer l'application quelque part dans le cluster, je veux vraiment vérifier la disponibilité des services dépendants - DB, redis, rabbit et autres.


Roadrunner


Pour intégrer RoadRunner avec une application Laravel, un package a été écrit qui réduit toute l'intégration à quelques commandes dans le shell (en exécutant docker-compose run app sh ):


 $ composer require avto-dev/roadrunner-laravel "^2.0" $ ./artisan vendor:publish --provider='AvtoDev\RoadRunnerLaravel\ServiceProvider' --tag=rr-config 

Ajoutez APP_FORCE_HTTPS=true au fichier ./docker/docker-compose.env et spécifiez le chemin d'accès au certificat SSL dans le conteneur dans les .rr*.yaml .


Afin de pouvoir utiliser dump(..) et dd(..) et tout fonctionnerait, il existe un autre package - avto-dev/stacked-dumper-laravel . Il suffit d'ajouter un pefix à ces assistants, à savoir \dev\dd(..) et \dev\dump(..) respectivement. Sans cela, vous constaterez une erreur de forme:
 worker error: invalid data found in the buffer (possible echo) 

Après toutes les manipulations, faites docker-compose up -d et le tour est joué:


capture d'écran


La base de données PostgeSQL, redis et les employés de RoadRunner ont réussi à s'exécuter dans des conteneurs.


Étape 2 - Makefile et tests


Comme je l'ai écrit plus tôt, un Makefile est un élément très sous-estimé. Objectifs dépendants, votre propre sucre syntaxique, 99% de chances qu'il se trouve déjà sur la machine de développement Linux / mac, saisie semi-automatique "prête à l'emploi" - une petite liste de ses avantages.


En l'ajoutant à notre projet et en faisant make without parameters, nous pouvons observer:


capture d'écran


Pour exécuter des tests unitaires, nous pouvons soit faire un make test , soit obtenir un shell à l'intérieur du conteneur avec l'application ( make shell ), exécuter le composer phpunit . Pour obtenir le rapport de couverture, faites simplement make make test-cover , et avant d'exécuter les tests, xdebug avec ses dépendances sera livré dans le conteneur et les tests seront lancés (car cette procédure n'est pas souvent effectuée et pas par CI - cette solution semble être meilleure que de garder une image séparée avec tous dev-lotions).


Crochets Git


Les crochets dans notre cas rempliront 2 rôles importants - ne permettant pas de pousser du code dans l'origine dont les tests n'ont pas réussi; et mettez automatiquement toutes les dépendances nécessaires si, en tirant les modifications sur votre machine, il s'avère que composer.lock a changé. Dans le Makefile , il existe une cible distincte pour cela:


 cwd = $(shell pwd) git-hooks: ## Install (reinstall) git hooks (required after repository cloning) -rm -f "$(cwd)/.git/hooks/pre-push" "$(cwd)/.git/hooks/pre-commit" "$(cwd)/.git/hooks/post-merge" ln -s "$(cwd)/.gitlab/git-hooks/pre-push.sh" "$(cwd)/.git/hooks/pre-push" ln -s "$(cwd)/.gitlab/git-hooks/pre-commit.sh" "$(cwd)/.git/hooks/pre-commit" ln -s "$(cwd)/.gitlab/git-hooks/post-merge.sh" "$(cwd)/.git/hooks/post-merge" 

Faire make git-hooks supprime simplement les hooks existants et les place dans le .gitlab/git-hooks à leur place. Leur source peut être consultée sur ce lien .


Exécution de tests à partir de PhpStorm


Malgré le fait qu'il soit assez simple et pratique - je l'ai utilisé pendant longtemps ./vendor/bin/phpunit --group=foo au lieu d'appuyer simplement sur le raccourci clavier directement lors de l'écriture d'un test ou d'un code qui lui est associé.


Cliquez sur File > Settings > Languages & Frameworks > PHP > CLI interpreter > [...] > [+] > From Docker, Vargant, VM, Remote . Sélectionnez Docker compose et le nom du service d' application .


capture d'écran


La deuxième étape consiste à dire à phpunit d'utiliser l'interpréteur à partir du conteneur: File > Settings > Test frameworks > [+] > PHPUnit by remote interpreter et sélectionnez l'interpréteur distant créé précédemment. Dans le /app/vendor/autoload.php Path to script , spécifiez /app/vendor/autoload.php , et dans Path mappings /app/vendor/autoload.php le répertoire racine du projet tel qu'il est monté dans /app .


capture d'écran


Et maintenant, nous pouvons exécuter des tests directement à partir de l'IDE en utilisant l'interpréteur à l'intérieur de l'image avec l'application, en appuyant (par défaut, Linux) Ctrl + Maj + F10.


Étape 3 - Automatisation


Il ne nous reste plus qu'à automatiser le processus d'exécution des tests et d'assemblage de l'image. Pour ce faire, créez le .gitlab-ci.yml dans le répertoire racine de l'application, en le remplissant avec le contenu suivant . L'idée principale de cette configuration est d'être aussi simple que possible, mais de ne pas perdre de fonctionnalité en le faisant.


L'image est assemblée à chaque brunch, à chaque commit. En utilisant --cache-from assemblage d'une image lors d'une nouvelle validation est très rapide. La nécessité de la reconstruction est due au fait que sur chaque brunch, nous avons une image avec les modifications qui ont été apportées dans le cadre de ce brunch, et en conséquence, rien ne nous empêche de le déployer en essaim / k8s / etc afin de nous assurer que "live" que tout fonctionne et fonctionne comme il se doit avant même la fusion avec la lumière principale.


Après l'assemblage, nous exécutons des tests unitaires et vérifions le lancement de l'application dans le conteneur, en envoyant des demandes curl-e au point de terminaison de vérification de l'état (cette action est facultative, mais plusieurs fois ce test m'a beaucoup aidé).


Pour la "version de la version" - il suffit de publier une balise de la forme vX.XX (si vous vous en tenez toujours au versioning sémantique - ce sera très cool) - CI collectera l'image, exécutera les tests et effectuera les actions que vous spécifiez dans le deploy to somewhere .


N'oubliez pas dans les paramètres du projet (si possible) de limiter la possibilité de publier des tags uniquement aux personnes autorisées à "publier des versions".

CHANGELOG.md et ENVIRONMENT.md


Avant d'accepter l'un ou l'autre MR, l'inspecteur doit, sans faute, vérifier la CHANGELOG.md fichiers CHANGELOG.md et ENVIRONMENT.md . Si la première est de plus en plus claire , alors la seconde relative, je donnerai une explication. Ce fichier est utilisé pour décrire toutes les variables d'environnement auxquelles le conteneur avec l'application répond. C'est-à-dire si le développeur ajoute ou supprime la prise en charge de l'une ou l'autre variable d'environnement, cela doit se refléter dans ce fichier. Et au moment où la question «Nous devons redéfinir de toute urgence ceci et cela» se pose - personne ne commence frénétiquement à fouiller dans la documentation ou les codes sources - mais regarde dans un seul fichier. Très confortable.


Conclusion


Dans cet article, nous avons examiné le processus plutôt indolore de transfert du développement et du lancement d'applications vers l'environnement Docker, RoadRunner intégré et l'utilisation d'un simple script CI automatisé l'assemblage et le test de l'image avec notre application.


Après avoir cloné le référentiel, les développeurs doivent faire make git-hooks && make install && make up et commencer à écrire du code utile. Camarades * ops-am - prenez l'image avec la balise souhaitée et faites-la rouler sur leurs clusters.


Naturellement - ce schéma est également simplifié, et sur les projets de "combat", je termine beaucoup plus, mais si l'approche décrite dans l'article aide quelqu'un, je sais que j'ai perdu mon temps.

Source: https://habr.com/ru/post/fr461687/


All Articles