
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 natifqueue
- 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 conteneursnginx
( 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
:
Fonctionnement | Commande 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 tag | Destination |
---|
latest | Images 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-name | Images 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.XX | En fait, la sortie de l'application (à utiliser pour déployer une version spécifique) |
stable | Alias, 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Ăšne | Destination |
---|
prepare | La phase préparatoire - l'assemblage d'images de tous les services sauf l' image avec la source |
test | Test de l'application (exécution de phpunit , analyseurs de code statique) à l'aide d'images collectées au stade de la préparation |
build | Installation 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 ) |

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.