Docker + Laravel = ❀

laravel-in-docker


Dans cet article, je vais parler de mon expérience de «wrapper» une application Laravel dans un conteneur Docker afin que les développeurs frontend et backend puissent travailler localement avec elle, et la lancer en production était aussi simple que possible. De plus, CI exécutera automatiquement des analyseurs de code statique, des tests de phpunit et construira des images.


"Et qu'est-ce que la complexité en fait?" - vous pouvez dire, et vous aurez en partie raison. Le fait est que beaucoup de discussions dans les communautés russophones et anglophones sont consacrées à ce sujet, et je diviserais conditionnellement presque tous les fils étudiés dans les catégories suivantes:


  • "J'utilise docker pour le dĂ©veloppement local. J'ai mis laradock et je ne connais pas les problĂšmes." Cool, mais qu'en est-il du lancement de l'automatisation et de la production?
  • "Je rĂ©cupĂšre un conteneur (monolith) basĂ© sur fedora:latest (~ 230 Mo), y mets tous les services (nginx, db, cache, etc.), exĂ©cute tout Ă  l'intĂ©rieur du superviseur." Également excellent, facile Ă  dĂ©marrer, mais qu'en est-il de l'idĂ©ologie «un conteneur - un processus»? Qu'en est-il de l'Ă©quilibrage et de la gestion des processus? Quelle est la taille de l'image?
  • "Voici des morceaux de configs, assaisonnez avec des extraits de sh-scripts, ajoutez des valeurs env magiques, utilisez-le." Merci, mais qu'en est-il d'au moins un exemple vivant que je pourrais bifurquer et jouer pleinement?

Tout ce que vous lisez ci-dessous est une expĂ©rience subjective qui ne prĂ©tend pas ĂȘtre la vĂ©ritĂ© ultime. Si vous avez des ajouts ou des indications d'inexactitudes - bienvenue aux commentaires.


Pour les impatients - un lien vers le rĂ©fĂ©rentiel , clonez que vous pouvez dĂ©marrer l'application Laravel avec une seule commande. Il ne sera pas non plus difficile de l'exĂ©cuter sur le mĂȘme Ă©leveur , de «lier» correctement les conteneurs ou d'utiliser la version d'Ă©picerie docker-compose.yml comme point de dĂ©part.

Partie théorique


Quels outils utiliserons-nous dans notre travail et sur quoi nous concentrerons-nous? Tout d'abord, nous devons installer sur l'hĂŽte:


  • docker - au moment de la rĂ©daction, j'ai utilisĂ© la version 18.06.1-ce
  • docker-compose - il gĂšre la liaison des conteneurs et le stockage des valeurs d'environnement nĂ©cessaires; version 1.22.0
  • make - vous pourriez ĂȘtre surpris, mais cela s'intĂšgre parfaitement dans le contexte de travail avec docker

Vous pouvez curl -fsSL get.docker.com | sudo sh docker sur des systÚmes de type debian avec la commande curl -fsSL get.docker.com | sudo sh curl -fsSL get.docker.com | sudo sh , mais docker-compose préférable d'installer à l'aide de pip , car les versions les plus récentes vivent dans ses référentiels ( apt loin derriÚre, en rÚgle générale).

Ceci complÚte la liste des dépendances. Ce que vous utiliserez pour travailler avec le code source - phpstorm , phpstorm ou le vim mort - c'est à vous.


Ensuite, un contrÎle qualité impromptu dans le contexte de la conception d'image (je n'ai pas peur de ce mot) :


  • Q: Image de base - quel est le meilleur choix?


  • R: Celui qui est "plus mince", sans fioritures. Sur la base de l' alpine (~ 5 Mo), vous pouvez collecter tout ce que votre cƓur dĂ©sire, mais vous devrez probablement jouer avec l'ensemble des services de la source. Comme alternative - jessie-slim (~ 30 Mo) . Ou utilisez celui qui est le plus souvent utilisĂ© dans vos projets.


  • Q: Pourquoi le poids de l'image est-il important?


  • R: Diminution du volume de trafic, diminution de la probabilitĂ© d'une erreur lors du tĂ©lĂ©chargement (moins de donnĂ©es - moins de probabilitĂ©), diminution de la place consommĂ©e. La rĂšgle "La gravitĂ© est fiable" (© "Snatch") ne fonctionne pas trĂšs bien ici.


  • Q: Mais mon ami %friend_name% dit qu'une image "monolithique" avec toutes les dĂ©pendances est le meilleur moyen.


  • R: Disons simplement compter. L'application a 3 dĂ©pendances - PG, Redis, PHP. Et vous vouliez tester son comportement dans des bundles de diffĂ©rentes versions de ces dĂ©pendances. PG - versions 9.6 et 10, Redis - 3.2 et 4.0, PHP - 7.0 et 7.2. Dans le cas oĂč chaque dĂ©pendance est une image distincte - vous en avez besoin de 6, que vous n'avez mĂȘme pas besoin de collecter - tout est prĂȘt et se trouve sur hub.docker.com . Si, pour des raisons idĂ©ologiques, toutes les dĂ©pendances sont "emballĂ©es" dans un seul conteneur, vous devez le remonter avec des stylos ... 8 fois? Ajoutez maintenant la condition que vous voulez toujours jouer avec opcache . Dans le cas d'une dĂ©composition, il s'agit simplement d'un changement dans les balises des images utilisĂ©es. Un monolithe est plus facile Ă  gĂ©rer et Ă  entretenir, mais c'est la route vers nulle part.


  • Q: Pourquoi le superviseur du conteneur est-il mauvais?


  • R: Parce que PID 1 . Si vous ne voulez pas une multitude de problĂšmes avec les processus zombies et que vous avez la possibilitĂ© d '«ajouter de la capacité» de maniĂšre flexible si nĂ©cessaire - essayez d'exĂ©cuter un processus par conteneur. Une exception particuliĂšre est nginx avec ses employĂ©s et php-fpm , qui ont la capacitĂ© de produire des processus, mais doivent le supporter (en outre, ils ne sont pas mauvais pour rĂ©agir Ă  SIGTERM , "tuant" Ă  juste titre leurs employĂ©s). En lançant tous les dĂ©mons en tant que superviseur, vous vous condamnez certainement Ă  des problĂšmes. Bien que, dans certains cas, il soit difficile de s'en passer, ce sont dĂ©jĂ  des exceptions.



AprÚs avoir décidé des principales approches, passons à notre application. Il doit pouvoir:


  • web|api - donnez statique avec nginx , et gĂ©nĂ©rez du contenu dynamique avec fpm
  • scheduler - exĂ©cuter le planificateur de tĂąches natif
  • queue - traiter les travaux des files d'attente

Un ensemble de base qui peut ĂȘtre Ă©tendu si nĂ©cessaire. Passons maintenant aux images que nous devons collecter pour que notre application «dĂ©colle» (leurs noms de code sont indiquĂ©s entre parenthĂšses):


  • PHP + PHP-FPM ( app ) - l'environnement dans lequel notre code sera exĂ©cutĂ©. Étant donnĂ© que les versions de PHP et de FPM seront les mĂȘmes pour nous - nous les collectons dans une seule image. Il est donc plus facile Ă  gĂ©rer avec les configurations, et la composition des packages sera identique. Bien sĂ»r - FPM et les processus d'application s'exĂ©cuteront dans diffĂ©rents conteneurs
  • nginx ( nginx ) - cela ne dĂ©rangerait pas la livraison de configs et de modules optionnels pour nginx - nous collecterons une image sĂ©parĂ©e avec. Puisqu'il s'agit d'un service distinct, il a son propre fichier docker et son contexte
  • Sources de l'application ( sources ) - la source sera livrĂ©e Ă  l'aide d'une image distincte, montant le volume avec eux dans un conteneur avec l'application. L'image de base est alpine , Ă  l'intĂ©rieur, il n'y a que des sources avec des dĂ©pendances installĂ©es et collectĂ©es Ă  l'aide des actifs du webpack (gĂ©nĂ©ration d'artefacts)

D'autres services de développement sont lancés dans des conteneurs, les tirant de hub.docker.com ; sur la production, d'autre part - ils fonctionnent sur des serveurs distincts, en cluster. Il ne nous reste plus qu'à indiquer à l'application (via l'environnement) à quelles adresses / ports et avec quels détails il faut y toucher. Encore plus cool est d'utiliser la découverte de services à cette fin, mais pas à cette époque.


AprÚs avoir décidé de la partie théorique, je propose de passer à la partie suivante.


La partie pratique


Je suggÚre d'organiser les fichiers dans le référentiel comme suit:


 . ├── docker #    -   │  ├── app │  │  ├── Dockerfile │  │  └── ... │  ├── nginx │  │  ├── Dockerfile │  │  └── ... │  └── sources │    ├── Dockerfile │    └── ... ├── src #   │ ├── app │ ├── bootstrap │ ├── config │ ├── artisan │ └── ... ├── docker-compose.yml # Compose-    ├── Makefile ├── CHANGELOG.md └── README.md 

Vous pouvez vous familiariser avec la structure et les fichiers en cliquant sur ce lien .

Pour créer un service, vous pouvez utiliser la commande:


 $ docker build \ --tag %local_image_name% \ -f ./docker/%service_directory%/Dockerfile ./docker/%service_directory% 

La seule diffĂ©rence sera l'assemblage de l'image avec les sources - pour cela, le contexte d'assemblage (argument extrĂȘme) ./src ĂȘtre dĂ©fini sur ./src .


Les rÚgles de dénomination des images dans le registre local recommandent d'utiliser celles que docker-compose utilise par défaut, à savoir: %root_directory_name%_%service_name% . Si le répertoire du projet est appelé my-awesome-project et que le service est nommé redis , le nom de l'image (local) est préférable de choisir my-awesome-project_redis respectivement.


Pour accélérer le processus de construction, vous pouvez dire au docker d'utiliser le cache de l'image précédemment assemblée, et pour cela, l' --cache-from %full_registry_name% lancement --cache-from %full_registry_name% . Ainsi, le démon docker va regarder avant de démarrer une instruction particuliÚre dans le Dockerfile - a-t-elle changé? Et sinon (le hachage converge) - il sautera l'instruction, en utilisant le calque déjà préparé de l'image, que vous lui direz d'utiliser comme cache. Cette chose n'est pas mauvaise donc elle va reconstruire le processus, surtout si rien n'a changé :)

Faites attention aux scripts ENTRYPOINT pour lancer les conteneurs d'applications.

L'image de l'environnement de lancement de l'application (app) a Ă©tĂ© collectĂ©e en tenant compte du fait que cela fonctionnera non seulement en production, mais aussi localement, les dĂ©veloppeurs doivent interagir efficacement avec elle. L'installation et la suppression des dĂ©pendances du composer , l'exĂ©cution de tests unit , les journaux de fin et l'utilisation d'alias familiers ( php /app/artisan → art , composer → c ) devraient ĂȘtre sans aucun inconfort. De plus - il sera Ă©galement utilisĂ© pour exĂ©cuter des tests unit et des analyseurs de code statique ( phpstan dans notre cas) sur CI. C'est pourquoi son Dockerfile, par exemple, contient la xdebug installation de xdebug , mais le module lui-mĂȘme n'est pas activĂ© (il est activĂ© uniquement Ă  l'aide de CI).


De plus, pour le composer le hirak/prestissimo est hirak/prestissimo , ce qui augmente considérablement l'installation de toutes les dépendances.

En production, nous montons le contenu du répertoire /src partir de l'image avec les sources (sources) à l'intérieur dans le répertoire /app . Pour le développement, nous «survolons» le répertoire local avec les sources d'application ( -v "$(pwd)/src:/app:rw" ).


Et ici réside une complexité - ce sont les droits d'accÚs aux fichiers créés à partir du conteneur. Le fait est que par défaut les processus en cours d'exécution à l'intérieur du conteneur démarrent à partir de la racine ( root:root ), les fichiers créés par ces processus (cache, logs, sessions, etc.) - aussi, et par conséquent - vous n'avez rien «localement» avec eux vous pouvez le faire sans exécuter sudo chown -R $(id -u):$(id -g) /path/to/sources .


Comme solution, utilisez fixuid , mais cette solution est simple. Le meilleur moyen m'a semblé de USER_ID local et son GROUP_ID à l'intérieur du conteneur, et de démarrer les processus avec ces valeurs . Par défaut, la substitution des valeurs 1000:1000 (les valeurs par défaut pour le premier utilisateur local) s'est débarrassée de l'appel $(id -u):$(id -g) , et si nécessaire, vous pouvez toujours les remplacer ( $ USER_ID=666 docker-compose up -d ) ou placez le fichier docker-compose dans le fichier .env .


De plus, lorsque php-fpm lancé localement php-fpm pas d'en désactiver opcache - sinon il y a beaucoup de "oui quoi diable!" vous sera fourni.


Pour une connexion "directe" à redis et postgres, j'ai jeté des ports supplémentaires "out" ( 15432 et 15432 respectivement), donc il n'y a aucun problÚme avec "se connecter et voir ce que c'est vraiment" et en principe ".


Je garde le conteneur avec l' app nom de code en cours d'exécution ( --command keep-alive.sh ) dans le but d'un accÚs pratique à l'application.


Voici quelques exemples de résolution de problÚmes quotidiens avec docker-compose :


FonctionnementCommande en cours d'exécution
Installer le package du composer$ docker-compose exec app composer require package/name
Exécuter phpunit$ docker-compose exec app php ./vendor/bin/phpunit --no-coverage
Installer toutes les dépendances de noeud$ docker-compose run --rm node npm install
Installer le package de noeud$ docker-compose run --rm node npm i package_name
Lancement d'une reconstruction en direct des actifs$ docker-compose run --rm node npm run watch

Vous pouvez trouver tous les détails de lancement dans le fichier docker-compose.yml .


Choi make vivant!


Taper les mĂȘmes commandes Ă  chaque fois devient ennuyeux aprĂšs la deuxiĂšme fois, et puisque les programmeurs sont des crĂ©atures paresseuses par nature, faisons leur «automatisation». Conserver un ensemble de scripts sh est une option, mais pas aussi attrayante qu'un Makefile , d'autant plus que son applicabilitĂ© dans le dĂ©veloppement moderne est largement sous-estimĂ©e.


Vous trouverez le manuel complet en russe Ă  ce lien .

Voyons à quoi ressemble le make run à la racine du référentiel:


 [user@host ~/projects/app] $ make help Show this help app-pull Application - pull latest Docker image (from remote registry) app Application - build Docker image locally app-push Application - tag and push Docker image into remote registry sources-pull Sources - pull latest Docker image (from remote registry) sources Sources - build Docker image locally sources-push Sources - tag and push Docker image into remote registry nginx-pull Nginx - pull latest Docker image (from remote registry) nginx Nginx - build Docker image locally nginx-push Nginx - tag and push Docker image into remote registry pull Pull all Docker images (from remote registry) build Build all Docker images push Tag and push all Docker images into remote registry login Log in to a remote Docker registry clean Remove images from local registry --------------- --------------- up Start all containers (in background) for development down Stop all started for development containers restart Restart all started for development containers shell Start shell into application container install Install application dependencies into application container watch Start watching assets for changes (node) init Make full application initialization (install, seed, build assets) test Execute application tests Allowed for overriding next properties: PULL_TAG - Tag for pulling images before building own ('latest' by default) PUBLISH_TAGS - Tags list for building and pushing into remote registry (delimiter - single space, 'latest' by default) Usage example: make PULL_TAG='v1.2.3' PUBLISH_TAGS='latest v1.2.3 test-tag' app-push 

Il est trĂšs bon pour les objectifs addictifs. Par exemple, pour exĂ©cuter la watch ( docker-compose run --rm node npm run watch ), vous avez besoin que l'application soit «augmentĂ©e» - il vous suffit de spĂ©cifier la cible up comme dĂ©pendante - et vous n'avez pas Ă  vous soucier d'oublier de le faire avant d'appeler watch - make lui-mĂȘme fera tout pour vous. La mĂȘme chose s'applique Ă  l'exĂ©cution de tests et d'analyseurs statiques, par exemple, avant de valider des modifications - exĂ©cutez un make test et toute la magie se produira pour vous!


Inutile de dire que vous n'avez pas à vous soucier de l'assemblage d'images, de les télécharger, de spécifier --cache-from et à peu prÚs tout?


Vous pouvez voir le contenu du Makefile sur ce lien .


PiĂšce auto


Commençons la derniÚre partie de cet article - il s'agit de l'automatisation du processus de mise à jour des images dans le Docker Registry. Bien que dans mon exemple, GitLab CI soit utilisé - pour transférer l'idée vers un autre service d'intégration, je pense que ce sera tout à fait possible.


Tout d'abord, nous déterminerons la dénomination des balises d'image utilisées:


Nom du tagDestination
latestImages recueillies auprĂšs de la branche principale.
L'Ă©tat du code est le plus rĂ©cent, mais pas encore prĂȘt Ă  entrer dans la version
some-branch-nameImages collectées sur le brunch du nom d' some-branch-name .
Ainsi, nous pouvons «déployer» les changements dans n'importe quel environnement qui ont été implémentés uniquement dans le cadre d'un brunch spécifique avant de les fusionner avec le master -light - il suffit de «étirer» les images avec cette balise.
Et - oui, les changements peuvent concerner à la fois le code et les images de tous les services en général!
vX.XXEn fait, la sortie de l'application (à utiliser pour déployer une version spécifique)
stableAlias, pour la balise avec la derniÚre version (à utiliser pour déployer la derniÚre version stable)

La sortie a lieu en publiant une balise dans un vX.XX format vX.XX


Pour accélérer la construction, la mise en cache des répertoires ./src/vendor et ./src/node_modules + --cache-from pour la docker build et comprend les étapes suivantes:


Nom de la scĂšneDestination
prepareLa phase préparatoire - l'assemblage d'images de tous les services sauf l' image avec la source
testTest de l'application (exécution de phpunit , analyseurs de code statique) à l'aide d'images collectées au stade de la préparation
buildInstallation de toutes les dépendances du composer ( --no-dev ), assemblage des assets webpack et assemblage de l'image avec le code source, y compris les artefacts reçus ( vendor/* , app.js , app.css )

capture d'écran de pipelines


L'assemblage sur la branche principale produisant un push avec les latest balises et les balises principales

En moyenne, toutes les étapes de l'assemblage prennent 4 minutes , ce qui est un trÚs bon résultat (l'exécution parallÚle des tùches est notre tout).


Vous pouvez vous familiariser avec le contenu de la configuration ( .gitlab-ci.yml ) du collecteur Ă  ce lien .


Au lieu d'une conclusion


Comme vous pouvez le voir, organiser le travail avec une application php (en utilisant Laravel comme exemple) en utilisant Docker n'est pas si difficile. À titre de test, vous pouvez bifurquer le rĂ©fĂ©rentiel , et en remplaçant toutes les occurrences de tarampampam/laravel-in-docker par les vĂŽtres, essayez tout en direct par vous-mĂȘme.


Pour un lancement local - exécutez seulement 2 commandes:


 $ git clone https://gitlab.com/tarampampam/laravel-in-docker.git ./laravel-in-docker && cd $_ $ make init 

Ouvrez ensuite http://127.0.0.1:9999 dans votre navigateur préféré.


... saisissant l'occasion


En ce moment je travaille sur le projet TL "autocode", et nous recherchons des dĂ©veloppeurs php et des administrateurs systĂšme talentueux (le bureau de dĂ©veloppement est situĂ© Ă  Iekaterinbourg). Si vous vous considĂ©rez comme le premier ou le deuxiĂšme - Ă©crivez notre lettre RH avec le texte "Je veux ĂȘtre une Ă©quipe de dĂ©veloppement, reprenez:% link_on_summary%" Ă  l'e-mail hr@avtocod.ru , nous aidons Ă  la relocalisation.

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


All Articles