Docker + Laravel + RoadRunner = ❤

foto


Esta publicación está escrita a solicitud de los trabajadores que periódicamente preguntan sobre "Cómo ejecutar la aplicación Illuminate / Symfony / MyOwn Psr7 en la ventana acoplable". No tengo ganas de dar un enlace a una publicación escrita anteriormente , porque mis puntos de vista sobre cómo resolver el problema han cambiado bastante.


Todo lo que se escribirá a continuación es una experiencia subjetiva, que (como siempre) no pretende considerarse la única decisión correcta, pero algunos enfoques y soluciones pueden parecerle interesantes y útiles.


También usaré Laravel como una aplicación, ya que me es más familiar y bastante extendido. La adaptación a otros marcos / componentes basados ​​en PSR-7 es posible, pero esta historia no se trata de eso.

Manejo de errores


Me gustaría comenzar con lo que resultó ser "no las mejores prácticas" en el contexto del artículo anterior :


  • La necesidad de cambiar la estructura de los archivos en el repositorio
  • Usando FPM. Si queremos el rendimiento de nuestras aplicaciones, quizás una de las mejores soluciones, incluso en la etapa de selección de tecnología, es abandonarlo en favor de algo más rápido y "adaptado" al hecho de que la memoria puede perder. RoadRunner by lachezis está aquí como nunca antes
  • Una imagen separada con fuente y activos. A pesar del hecho de que con este enfoque, podemos reutilizar la misma imagen para generar un enrutamiento más complejo de las solicitudes entrantes (nginx en la parte delantera para devolver estática; las solicitudes de dinámica son atendidas por otro contenedor en el que el volumen se lanza con las mismas fuentes, para mejor escala): este esquema ha resultado ser bastante complicado en la operación del producto. Y lo que es más, RR en sí mismo representa perfectamente las estadísticas, y si hay muchas estadísticas (o el recurso puede cargar y mostrar contenido generado por el usuario) , lo llevamos a CDN (el paquete S3 + CloudFront + CloudFlare funciona bien) y, en principio, nos olvidamos de este problema
  • CI complejo. Esto se convirtió en un problema real cuando el período de "construcción de carne" activa comenzó en las etapas de montaje y pruebas automáticas. Un tipo que no apoyó este CI antes, se hace muy difícil hacer cambios sin temor a romper nada.

Ahora, sabiendo qué problemas deben corregirse y con una comprensión de cómo hacer esto, propongo proceder con su eliminación. El conjunto de "herramientas de desarrollo" no ha cambiado: es el mismo docker-ce , docker-compose y el poderoso Makefile .


Como resultado, obtenemos:


  • Un contenedor independiente con una aplicación sin la necesidad de montar un volumen adicional.
  • Un ejemplo de uso de git-hooks: colocaremos las dependencias necesarias después de que git pull automáticamente y prohibiremos presionar el código si las pruebas fallan (los ganchos se almacenarán debajo del git, naturalmente)
  • RoadRunner manejará las solicitudes HTTP (s)
  • Los desarrolladores aún podrán ejecutar dd(..) y dump(..) para la depuración, mientras que nada se bloqueará en su navegador
  • Las pruebas se pueden ejecutar directamente desde el IDE de PHPStorm, mientras que se ejecutarán en el contenedor con la aplicación
  • CI recopilará imágenes para nosotros cuando publique una nueva etiqueta de versión de la aplicación
  • Tomemos la estricta regla de mantener los archivos CHANGELOG.md y ENVIRONMENT.md

Introducción visual de un nuevo enfoque.


Para una demostración visual, dividiré todo el proceso en varias etapas, los cambios dentro de los cuales se realizarán como MR separadas (después de la fusión, todos los brunches permanecerán en sus lugares; referencias a MR en los títulos de los "pasos"). El punto de partida es el esqueleto de Laravel de una aplicación creada usando 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.*" 

Paso 1 - Acoplamiento + RR


El primer paso es enseñarle a la aplicación a ejecutarse en el contenedor. Para hacer esto, necesitamos un Dockerfile , docker-compose.yml para la descripción de "cómo levantar y vincular contenedores", y un Makefile para reducir un proceso ya simplificado a uno o dos comandos.


Dockerfile


La imagen básica que uso php:XXX-alpine es la más fácil y contiene lo que necesitas para ejecutar. Además, todas las actualizaciones posteriores al intérprete se reducen a simplemente cambiar el valor en esta línea (actualizar PHP ahora es más fácil que nunca).


Composer y el archivo binario RoadRunner se entregan en el contenedor usando varias etapas y COPY --from=... - esto es muy conveniente, y todos los valores asociados con las versiones no están "dispersos", sino que están al comienzo del archivo. Esto funciona rápido y sin dependencias de curl / git clone / make build . Las imágenes de 512k / roadrunner son compatibles conmigo, si lo desea, puede ensamblar el archivo binario usted mismo.


Una historia interesante sucedió con la variable de entorno PS1 (responsable de la solicitud en el shell): resulta que puedes usar emoji y todo funciona localmente, pero si intentas iniciar la imagen con una variable que contiene emoji en, digamos, ranchero, se bloqueará (enjambre todo funciona sin problemas).

En Dockerfile comienzo a generar un certificado SSL autofirmado para usarlo para las solicitudes HTTPS entrantes. Naturalmente, nada impide el uso de un certificado "normal".


También me gustaría decir sobre:


 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 

Aquí el significado es el siguiente: los archivos composer.lock y composer.json se entregan en una capa separada de la imagen, después de lo cual se instalan todas las dependencias descritas en ellos. Esto se hace para que durante las compilaciones posteriores de la imagen usando --cache-from , si la composición y las versiones de las dependencias instaladas no hayan cambiado, la composer install no se ejecute, tomando esta capa del caché, ahorrando así el tiempo de compilación y el tráfico (gracias por la idea jetexe ).


composer install se ejecuta dos veces (la segunda vez con --no-dev ) para "calentar" el caché de las dependencias de desarrollo, de modo que cuando colocamos todas las dependencias en el CI para ejecutar las pruebas, se colocan desde el caché del compositor, que ya está en la imagen, y no se extendía desde galaxias distantes.


Con la última instrucción RUN , mostramos las versiones del software instalado y la composición de los módulos PHP tanto para el historial en los registros de compilación como para asegurarnos de que "al menos existe y de alguna manera comienza".


También utilizo mi Entrypoint, porque antes de iniciar la aplicación en algún lugar del clúster realmente quiero verificar la disponibilidad de servicios dependientes: DB, redis, rabbit y otros.


Correcaminos


Para integrar RoadRunner con una aplicación Laravel, se escribió un paquete que reduce toda la integración a un par de comandos en el shell (ejecutando docker-compose run app sh ):


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

Agregue APP_FORCE_HTTPS=true al archivo ./docker/docker-compose.env y especifique la ruta al certificado SSL en el contenedor en los .rr*.yaml .


Para poder usar dump(..) y dd(..) y todo funcionaría, hay otro paquete: avto-dev/stacked-dumper-laravel . Todo lo que se requiere es agregar un pefix a estos ayudantes, a saber, \dev\dd(..) y \dev\dump(..) respectivamente. Sin esto, observará un error de la forma:
 worker error: invalid data found in the buffer (possible echo) 

Después de todas las manipulaciones, haga docker-compose up -d y listo:


captura de pantalla


La base de datos PostgeSQL, redis y los trabajadores de RoadRunner se ejecutaron con éxito en contenedores.


Paso 2 - Makefile y pruebas


Como escribí anteriormente, un Makefile es un elemento muy subestimado. Los objetivos dependientes, su propio azúcar sintáctico, el 99% de posibilidades de que ya se encuentre en la máquina de desarrollo de Linux / mac, autocompletar "fuera de la caja", una pequeña lista de sus ventajas.


Agregándolo a nuestro proyecto y haciendo make sin parámetros, podemos observar:


captura de pantalla


Para ejecutar pruebas unitarias, podemos hacer una make test , u obtener un shell dentro del contenedor con la aplicación ( make shell ), ejecutar composer phpunit . Para obtener el informe de cobertura, solo haga make test-cover , y antes de ejecutar las pruebas, xdebug con sus dependencias se entregará al contenedor y se iniciarán las pruebas (ya que este procedimiento no se realiza a menudo y no por CI; esta solución parece ser mejor que mantener una imagen separada con todo dev-lociones).


Ganchos Git


Los ganchos en nuestro caso cumplirán 2 roles importantes: no permitir que el código ingrese al origen cuyas pruebas no tienen éxito; y coloca automáticamente todas las dependencias necesarias si, al realizar los cambios en su máquina, resulta que composer.lock ha cambiado. En el Makefile , hay un objetivo separado para esto:


 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" 

Hacer make git-hooks simplemente quita los ganchos existentes y los coloca en el .gitlab/git-hooks en su lugar. Su fuente se puede ver en este enlace .


Ejecución de pruebas desde PhpStorm


A pesar de que es bastante simple y conveniente, lo usé durante mucho tiempo ./vendor/bin/phpunit --group=foo lugar de simplemente presionar la tecla de ./vendor/bin/phpunit --group=foo directamente mientras escribía una prueba o código asociado con él.


Haga clic en File > Settings > Languages & Frameworks > PHP > CLI interpreter > [...] > [+] > From Docker, Vargant, VM, Remote . Seleccione Docker compose y el nombre del servicio de la aplicación .


captura de pantalla


El segundo paso es decirle a phpunit que use el intérprete desde el contenedor: File > Settings > Test frameworks > [+] > PHPUnit by remote interpreter Marcos de File > Settings > Test frameworks > [+] > PHPUnit by remote interpreter y seleccione el intérprete remoto creado previamente. En el /app/vendor/autoload.php Path to script /app/vendor/autoload.php Path to script , especifique /app/vendor/autoload.php , y en las Path mappings especificamos el directorio raíz del proyecto como está montado en /app .


captura de pantalla


Y ahora podemos ejecutar pruebas directamente desde el IDE usando el intérprete dentro de la imagen con la aplicación, presionando (por defecto, Linux) Ctrl + Shift + F10.


Paso 3 - Automatización


Todo lo que nos queda por hacer es automatizar el proceso de ejecutar pruebas y ensamblar la imagen. Para hacer esto, cree el .gitlab-ci.yml en el directorio raíz de la aplicación, rellenándolo con los siguientes contenidos . La idea principal de esta configuración es ser lo más simple posible, pero no perder funcionalidad al mismo tiempo.


La imagen se ensambla en cada brunch, en cada commit. Usando --cache-from ensamblar una imagen al volver a confirmar es muy rápido. La necesidad de reconstrucción se debe al hecho de que en cada brunch tenemos una imagen con los cambios que se realizaron como parte de este brunch y, como resultado, nada nos impide extenderlo a enjambre / k8s / etc. para asegurarnos de "vivir" que todo funciona, y funciona como debería incluso antes de la fusión con la luz master .


Después del ensamblaje, ejecutamos pruebas unitarias y verificamos el inicio de la aplicación en el contenedor, enviando solicitudes de curvatura al punto final de verificación de estado (esta acción es opcional, pero varias veces esta prueba me ayudó mucho).


Para el "lanzamiento de la versión", solo publique una etiqueta del formulario vX.XX (si aún se vX.XX a las versiones semánticas, será genial): CI recopilará la imagen, ejecutará las pruebas y realizará las acciones que especifique en la deploy to somewhere .


No olvide en la configuración del proyecto (si es posible) limitar la capacidad de publicar etiquetas solo a las personas a las que se les permite "liberar versiones".

CHANGELOG.md y ENVIRONMENT.md


Antes de aceptar uno u otro MR, el inspector debe, sin falta, verificar el ENVIRONMENT.md archivos CHANGELOG.md y ENVIRONMENT.md . Si el primero es más y menos claro , entonces el segundo relativo daré una explicación. Este archivo se utiliza para describir todas las variables de entorno a las que responde el contenedor con la aplicación. Es decir Si el desarrollador agrega o elimina el soporte para una u otra variable de entorno, esto debe reflejarse en este archivo. Y en el momento en que surge la pregunta "Necesitamos redefinir urgentemente esto y aquello", nadie comienza a profundizar frenéticamente en la documentación o los códigos fuente, sino que busca en un solo archivo. Muy comodo


Conclusión


En este artículo, examinamos el proceso bastante sencillo de transferir el desarrollo y el lanzamiento de aplicaciones al entorno Docker, RoadRunner integrado y el uso de un simple script CI automatizó el ensamblaje y la prueba de la imagen con nuestra aplicación.


Después de clonar el repositorio, los desarrolladores tienen que make git-hooks && make install && make up y comenzar a escribir código útil. Camaradas * ops-am: tome la imagen con la etiqueta deseada y enróllela en sus grupos.


Naturalmente, este esquema también se simplifica, y en los proyectos de "combate" estoy terminando mucho más, pero si el enfoque descrito en el artículo ayuda a alguien, sabré que perdí mi tiempo.

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


All Articles