Docker + Laravel = ❤

    Laravel-in-Docker


    In diesem Artikel werde ich über meine Erfahrungen beim Verpacken einer Laravel-Anwendung in einen Docker-Container sprechen, so dass die Frontend- und Backend-Entwickler lokal damit arbeiten können. Der Start in der Produktion war so einfach wie möglich. Außerdem führt CI automatisch statische Code-Analysatoren aus, phpunittestet und stellt Bilder zusammen.


    "Was ist die Schwierigkeit?" - Sie können sagen, und Sie werden teilweise recht haben. Tatsache ist, dass sich ziemlich viele Diskussionen in der russisch- und englischsprachigen Gemeinschaft diesem Thema widmen, und ich würde fast alle untersuchten Fäden bedingt in die folgenden Kategorien einteilen:


    • "Ich verwende Docker für lokale Entwicklung. Ich lege Laradock an und ich kenne die Probleme nicht." Cool, aber wie sieht es mit dem Automatisierungs- und Produktionsstart aus?
    • "Ich sammle einen Container (Monolith) auf der Basis fedora:latest(~ 230 MB), stelle alle Dienste (nginx, bd, cache usw.) in den Container und starte alles mit dem Supervisor." Zu groß, einfach zu laufen, aber wie sieht es mit der Ideologie von "one container - one process" aus? Wie läuft es mit Balancing und Prozesskontrolle? Wie groß ist das Bild?
    • "Hier haben Sie die Stücke von Konfigurationen, wir würzen mit Auszügen aus Sh-Skripten, fügen magische Env-Werte hinzu, verwenden sie." Danke, aber wie wäre es mit mindestens einem lebenden Beispiel, dass ich ein komplettes Spiel spielen könnte?

    Alles, was Sie unten lesen, ist eine subjektive Erfahrung, die nicht die ultimative Wahrheit darstellt. Wenn Sie Ergänzungen oder Hinweise auf Ungenauigkeiten haben - willkommen zu Kommentaren.


    Für Ungeduldige - ein Link zum Repository , mit dem Sie die Laravel-Anwendung mit einem Befehl ausführen können. Es ist auch nicht schwierig, es auf demselben Rancher auszuführen , die Container korrekt zu verknüpfen oder die Lebensmitteloption docker-compose.ymlals Ausgangspunkt zu verwenden.

    Teil theoretisch


    Welche Werkzeuge werden wir in unserer Arbeit einsetzen und was machen wir mit Akzenten? Zunächst benötigen wir die auf dem Host installierten:


    • docker - Zum Zeitpunkt dieses Schreibens verwendete die Version 18.06.1-ce
    • docker-compose- Es eignet sich hervorragend zum Verknüpfen von Containern und zum Speichern der erforderlichen Umgebungswerte. Version von1.22.0
    • make - Sie sind vielleicht überrascht, aber er passt perfekt in den Kontext der Arbeit mit dem Docker

    Setzen Sie dockerauf einen debianmöglichen Team -ähnlichen System curl -fsSL get.docker.com | sudo sh, aber docker-composebesser mit Hilfe zu setzen pip, wie die neueste Version bewohnen (in seinen Repositories aptweit dahinter, in der Regel sind).

    Diese Liste der Abhängigkeiten kann abgeschlossen werden. Was werden Sie mit der Quelle verwenden arbeiten - phpstorm, netbeansoder trushny vim- nur für Sie zu entscheiden.


    Als nächstes kommt eine improvisierte QS im Kontext des Image-Designs (ich habe keine Angst vor dem Wort) :


    • F: Grundbild - welches ist besser zu wählen?


    • A: Diejenige, die "dünner" ist, ohne Exzesse. Auf der Grundlage von (~ 5 Mb) können Sie alles sammeln, was Ihr Herz begehrt, aber höchstwahrscheinlich müssen Sie mit dem Zusammenstellen der Dienste aus den Quellen herumspielen. Als Alternative - (~ 30 Mb) . Oder verwenden Sie die, die am häufigsten für Ihre Projekte verwendet wird.alpinejessie-slim


    • F: Warum ist das Bildgewicht wichtig?


    • A: Verringerung des Verkehrsaufkommens, Verringerung der Wahrscheinlichkeit eines Fehlers beim Herunterladen (weniger Daten - geringere Wahrscheinlichkeit), Verringerung des belegten Speicherplatzes. Die Regel "Zuverlässigkeit ist zuverlässig" (© "Snatch") funktioniert hier nicht sehr gut.


    • F: Mein Freund %friend_name%sagt jedoch, dass ein "monolithisches" Bild mit allen Abhängigkeiten der beste Weg ist.


    • A: Lass uns einfach zählen. Die Anwendung hat 3 Abhängigkeiten - PG, Redis, PHP. Sie wollten testen, wie es sich in Paketen verschiedener Versionen dieser Abhängigkeiten verhält. PG - Versionen 9.6 und 10, Redis - 3.2 und 4.0, PHP - 7.0 und 7.2. Wenn es sich bei jeder Abhängigkeit um ein separates Bild handelt, benötigen Sie 6 Teile, die Sie nicht einmal sammeln müssen. Alles ist fertig und liegt auf hub.docker.com. Wenn alle Abhängigkeiten aus ideologischen Gründen in einem Behälter "verpackt" sind, müssen Sie ihn dann mit Stiften wieder zusammenbauen ... 8-mal? Fügen Sie nun eine Bedingung hinzu, mit der Sie noch spielen möchten opcache. Bei der Zerlegung handelt es sich lediglich um eine Änderung der Tags der verwendeten Bilder. Monolith ist einfacher zu betreiben und zu warten, aber dies ist ein Weg ins Nirgendwo.


    • F: Warum ist ein Containerüberwacher böse?


    • A: Weil PID 1. Sie möchten keine Fülle von Problemen mit Zombie-Prozessen und können dort flexibel "Kapazität hinzufügen", wo sie benötigt wird. Versuchen Sie, einen Prozess pro Container auszuführen. Besondere Ausnahmen gibt es nginxbei ihren Arbeitern und php-fpmdie neigen dazu, Prozesse zu erzeugen, aber dies muss toleriert werden (außerdem ist es nicht schlecht, wenn sie auf sie reagieren SIGTERMund ihre Mitarbeiter ganz richtig "töten"). Nachdem Sie alle Dämonen vom Vorgesetzten ausgeführt haben, verurteilen Sie sich wahrscheinlich selbst zu Problemen. In manchen Fällen ist es zwar schwierig, darauf zu verzichten, aber dies sind Ausnahmen.



    Nachdem wir uns für die Hauptansätze entschieden haben, gehen wir zu unserer Anwendung über. Es sollte in der Lage sein:


    • web|api- Statik durch Kräfte geben nginxund dynamischen Inhalt durch Kräfte erzeugenfpm
    • scheduler - nativen Taskplaner ausführen
    • queue - Verarbeiten von Aufgaben aus Warteschlangen

    Das Grundset, das bei Bedarf erweitert werden kann. Nun zu den Bildern, die wir sammeln müssen, damit unsere Anwendung „abheben“ kann (die Codenamen sind in Klammern angegeben):


    • PHP + PHP-FPM( App ) - die Umgebung, in der unser Code ausgeführt wird. Da die PHP- und FPM-Versionen für uns gleich sind, sammeln wir sie in einem Bild. Mit configs ist es also einfacher zu verwalten und die Zusammensetzung der Pakete ist identisch. Natürlich laufen FPM- und Anwendungsprozesse in verschiedenen Containern.
    • nginx( nginx ) - das würde bei der Auslieferung von Configs und optionalen Modulen nichts ausmachen nginx- wir sammeln ein eigenes Image damit. Da es sich um einen separaten Dienst handelt, verfügt er über eine eigene Docker-Datei und deren Kontext.
    • Anwendungsquellen ( Quellen ) - Die Quelle wird über ein separates Image bereitgestellt volumeund mit der App in den Container eingebunden. Basis-Image - alpinenur Quellcodes mit installierten Abhängigkeiten und Assets, die mithilfe von Webpacks (Build-Artefakte) erfasst wurden

    Die übrigen Entwicklungsdienste werden in Containern gestartet und ziehen sie ab hub.docker.com. In der Produktion laufen sie auf separaten Servern, die in einem Cluster zusammengefasst sind. Für uns bleibt nur noch übrig, der Anwendung (über die Umgebung) mitzuteilen, an welchen Adressen / Ports und mit welchen Details es notwendig ist, sie anzuklopfen. Noch cooler ist es, Service-Discovery für diese Zwecke zu verwenden, aber dies ist nicht die Zeit.


    Nachdem ich mich für das Theoretische entschieden habe, schlage ich vor, zum nächsten Teil überzugehen.


    Teilweise praktisch


    Ich schlage vor, die Dateien im Repository wie folgt zu organisieren:


    .
    ├── docker  # Директория для хранения докер-файлов необходимых сервисов
    │   ├── app
    │   │   ├── Dockerfile
    │   │   └── ...
    │   ├── nginx
    │   │   ├── Dockerfile
    │   │   └── ...
    │   └── sources
    │       ├── Dockerfile
    │       └── ...
    ├── src  # Исходники приложения
    │   ├── app
    │   ├── bootstrap
    │   ├── config
    │   ├── artisan
    │   └── ...
    ├── docker-compose.yml  # Compose-конфиг для локальной разработки
    ├── Makefile
    ├── CHANGELOG.md
    └── README.md

    Sie können die Struktur und die Dateien anzeigen, indem Sie auf diesen Link klicken .

    Um einen Dienst zu erstellen, können Sie den Befehl verwenden:


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

    Der einzige Unterschied besteht im Zusammenfügen des Bildes mit dem Quellcode. Es ist erforderlich, den Assemblykontext (das letzte Argument) als gleich anzugeben ./src.


    Namensgebung der Bilder in der lokalen Registrierung empfehlen diejenigen verwenden, verwenden Sie docker-composedie Standardeinstellung wie folgt: %root_directory_name%_%service_name%. Wenn das Projektverzeichnis aufgerufen my-awesome-projectwird und der Dienst einen Namen hat redis, ist es besser, den Bildnamen (lokal) my-awesome-project_redisentsprechend auszuwählen .


    Um den Erstellungsprozess zu beschleunigen, können Sie dem Andockfenster mitteilen, dass es den Cache eines zuvor kompilierten Images verwenden soll. Dazu wird die Startoption verwendet --cache-from %full_registry_name%. Der Docker-Daemon betrachtet also den Start dieser oder jener Anweisung in der Dockerfile - hat sich das geändert? Und wenn nicht (der Hash wird konvergieren), wird er die Anweisung überspringen, wobei er die bereits vorbereitete Ebene aus dem Bild verwendet, die Sie als Cache angeben. Diese Sache ist nicht so schlecht, um den Wiederherstellungsprozess zu starten, vor allem, wenn sich nichts geändert hat :)

    Beachten Sie die ENTRYPOINTStartskripts der Anwendungscontainer.

    Das Image der Umgebung für das Starten einer Anwendung (App) wurde unter Berücksichtigung der Tatsache erfasst, dass sie nicht nur in der Produktion funktioniert, sondern auch vor Ort. Entwickler müssen effektiv mit ihr interagieren. Das Installieren und Entfernen von composerAbhängigkeiten, das Ausführen von unitTests, tailProtokollen und die Verwendung bekannter Aliasnamen ( php /app/artisanart, composerc) sollte problemlos sein. Darüber hinaus werden unitTests und statische Codeanalysatoren ( phpstanin unserem Fall) auf CI ausgeführt. Aus diesem Grund enthält die Dockerfile beispielsweise eine Installationszeile xdebug, das Modul selbst ist jedoch nicht enthalten (es wird nur mithilfe von CI aktiviert).


    Auch für composerglobal setzen Sie ein Paket hirak/prestissimo, das den Installationsprozess für alle Abhängigkeiten stark startet.

    In der Produktion mounten wir in einem Verzeichnis den /appInhalt des Verzeichnisses /srcaus dem Quellabbild (Quellen). Für die Entwicklung "prokidyvayem" wird das lokale Verzeichnis mit dem Quellcode der Anwendung ( -v "$(pwd)/src:/app:rw") "prokidyvayem" .


    Und hier liegt eine Schwierigkeit - es sind die Zugriffsrechte auf Dateien , die aus dem Container erstellt werden. Tatsache ist, dass die Prozesse, die innerhalb des Containers ausgeführt werden, standardmäßig vom root ( root:root) aus ausgeführt werden. Die von diesen Prozessen erstellten Dateien (Cache, Protokolle, Sitzungen usw.) werden ebenfalls ausgeführt. Daher können Sie nichts lokal mit ihnen machen. nicht auf der Bühne sudo chown -R $(id -u):$(id -g) /path/to/sources.


    Als eine der Lösungen ist fixuid zu verwenden , aber diese Lösung ist einfach "so lala ". Der beste Weg schien mir lokal USER_IDund GROUP_IDinnerhalb des Containers zu sein und Prozesse mit diesen Werten zu starten . Standardmäßig wurde 1000:1000der Aufruf durch das Ersetzen von Werten (Standardwerte für den ersten lokalen Benutzer) aufgehoben. $(id -u):$(id -g)Sie können sie bei Bedarf jederzeit überschreiben ( $ USER_ID=666 docker-compose up -d) oder in die .envDocker-Compose-Datei einfügen .


    Starten Sie einfach die lokale an php-fpm, vergessen Sie nicht , um sie zu deaktivieren opcache- oder ein Bündel von „Was zum Teufel!“ Sie werden zur Verfügung gestellt.


    Für „direkte“ Verbindung Redis und Postgres - prokinul zusätzliche Ports „außen“ ( 16379und 15432ist), so dass die Probleme, um „zu verbinden ja ja sehen , wie es wirklich“ nicht prinzipiell nicht auftritt.


    appIch lasse den Container mit dem Codenamen ausgeführt ( --command keep-alive.sh), um bequem auf die Anwendung zuzugreifen.


    Hier einige Beispiele für die Lösung "alltäglicher" Probleme mit docker-compose:


    BedienungAusführbarer Befehl
    composerPaket installieren$ docker-compose exec app composer require package/name
    Phpunit laufen lassen$ docker-compose exec app php ./vendor/bin/phpunit --no-coverage
    Installation aller Knotenabhängigkeiten$ docker-compose run --rm node npm install
    Installieren Sie das Knotenpaket$ docker-compose run --rm node npm i package_name
    Start des Live-Asset-Wiederaufbaus$ docker-compose run --rm node npm run watch

    Alle Startdetails finden Sie in der Datei docker-compose.yml .


    Choimake am Leben


    Es ist langweilig, jedes Mal nach dem zweiten Mal die gleichen Befehle auszufüllen, und da Programmierer von Natur aus faul sind, kümmern wir uns um ihre „Automatisierung“. Das Halten einer Reihe von shSkripten ist eine Option, aber nicht so attraktiv wie eine Makefile, zumal ihre Anwendbarkeit in der modernen Entwicklung stark unterschätzt wird.


    Das vollständige russischsprachige Handbuch finden Sie unter diesem Link .

    Mal sehen, wie der Start makeim Stamm des Repositorys aussieht :


    [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

    Er ist sehr gut in der Zielabhängigkeit. Für run watch( docker-compose run --rm node npm run watch) ist es zum Beispiel erforderlich, dass die Anwendung "erhöht" wird - Sie müssen nur das Ziel upals abhängig angeben - und Sie können sich keine Gedanken darüber machen, was Sie vor dem Aufruf vergessen watch- makees wird alles für Sie tun. Gleiches gilt für die Ausführung von Tests und statischen Analysatoren, zum Beispiel vor dem Übernehmen von Änderungen - make testund der Zauber wird für Sie geschehen!


    Ob es sich lohnt zu sagen, dass für das Zusammenstellen von Bildern, deren Herunterladen, Anweisungen --cache-fromund allem - alles - Sie sich keine Sorgen mehr machen müssen?


    Sie können den Inhalt Makefileunter diesem Link anzeigen .


    Teilautomatik


    Kommen wir zum letzten Teil dieses Artikels - dies ist eine Automatisierung der Aktualisierung von Images in der Docker Registry. Obwohl in meinem Beispiel GitLab CI verwendet wird - um die Idee auf einen anderen Integrationsdienst zu übertragen, denke ich, dass dies durchaus möglich ist.


    Zunächst definieren und benennen wir die verwendeten Image-Tags:


    Tag-NameZweck
    latestBilder aus einem Zweig gesammelt master.
    Der Status des Codes ist der "frischste", aber noch nicht zur Veröffentlichung freigegeben.
    some-branch-nameBilder zum Brunch gesammelt some-branch-name.
    So können wir Änderungen in jeder Umgebung „ausrollen“, die nur im Rahmen eines bestimmten Brunchs implementiert wurden, bevor sie mit dem masterZweig zusammengeführt werden. Es reicht aus, Bilder mit diesem Tag „herauszuziehen“.
    Und - ja, die Änderungen können im Allgemeinen sowohl den Code als auch die Images aller Dienste betreffen!
    vX.X.XEigentlich die Version der Anwendung (verwenden, um eine bestimmte Version bereitzustellen)
    stableAlias ​​für das Tag mit der aktuellsten Version (zur Bereitstellung der neuesten stabilen Version verwenden)

    Die Freigabe erfolgt durch Veröffentlichung eines Tags in einem Git-Format vX.X.X.


    Um die Assembly zu beschleunigen, werden die Verzeichniszwischenspeicherung ./src/vendorund ./src/node_modules+ --cache-fromfür verwendet docker buildund besteht aus den folgenden Schritten ( stages):


    Name der Bühne        Zweck
    prepareDie Vorbereitungsphase - das Zusammenstellen von Bildern aller Dienste mit Ausnahme des Quellbildes
    testTesten der Anwendung (Start phpunit, statische Codeanalysatoren) anhand von Bildern, die in der Vorbereitungsphase gesammelt wurden
    buildInstallieren alle composerAbhängigkeiten ( --no-dev), werden die assetsMontagekräfte webpackund bauen ein Bild der Quelle , einschließlich der Artefakte erzeugt ( vendor/*, app.js, app.css)

    Pipelines-Screenshot


    Baue on-the- masterfly, produziere pushmit Tags latestundmaster

    Im Durchschnitt dauern alle Montageschritte 4 Minuten , was ein ziemlich gutes Ergebnis ist (parallele Ausführung von Aufgaben ist unser A und O).


    .gitlab-ci.ymlÜber diesen Link können Sie sich mit dem Inhalt der Konfiguration ( ) des Collectors vertraut machen .


    Anstelle des Schlusses


    Wie Sie sehen, ist es Laravelnicht so schwierig, die Arbeit mit einer PHP-Anwendung (am Beispiel ) mit Docker zu organisieren. Als Test können Sie das Repository zusammenfassen und alle Einträge tarampampam/laravel-in-dockerdurch Ihre eigenen ersetzen - probieren Sie alles "live" aus.


    Um lokal zu starten, führen Sie nur 2 Befehle aus:


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

    Dann öffnen Sie http://127.0.0.1:9999in Ihrem bevorzugten Browser.


    ... die Gelegenheit nutzen


    Momentan arbeite ich an einem TL-Autocode-Projekt und wir suchen talentierte PHP-Entwickler und Systemadministratoren (das Entwicklungsbüro befindet sich in Jekaterinburg). Wenn Sie sich für die erste oder zweite Person halten - schreiben Sie einen Brief an unsere Personalabteilung mit dem Text "Ich möchte ein Team aufbauen, Lebenslauf:% link_in_resume%" auf der Elektromail hr@avtocod.ru, wir helfen beim Umzug.


    Jetzt auch beliebt: