Laravel CI con Gitlab, Docker Runner, Mysql 8.0 y PHP 7.3

Laravel CI con Gitlab Runnner and Gitlab CI Pipelines

Ir a la última, a veces no es baladí, ya que los cambios que se han producido pueden ser suficientes para optar por esta vía, si nuestro proyecto esta comenzando. Necesitaba comenzar a hacer testing, o mejor dicho CI o integración continua con Laravel, usando mi propio servidor Gitlab CE. Prefería tener Gitlab Runner en un VPS dedicado por seguridad y porque no decirlo comodidad. Pero también necesitaba que mi proyecto usará MySQL 8.0 y PHP 7.3 y aquí comenzaron los problemas

Gitlab y Gitlab Runner

Instalar Gitlab EE

Bien lo primero es instalar en un VPS Worker en mi caso prefería usar una Ubuntu Bionic 18.04 LTS porque después de 25 años, al final me he decantado por Ubuntu por que para un programador novel, puede ser infinitamente más sencillo esta distribución y sus actualizaciones que los horrores a los que nos tiene acostumbrado RedHat y sus forks, o Debian y su retardo con la modernidad.

Instalar Gitlab en Ubuntu Bionic es tan sencillo como seguir sus instrucciones. Lo importante es después es configurarlo para usarlo seguro y con certificado Let’s Encrypt

Instalar Gitlab Runner

Para instalar Gitlab Runner en otro VPS Entrepeneur opte por la misma distribución, y usar los repositorios oficiales de Gitlab, además de en lugar de usar la opción de instalar docker durante la instalación de Ubuntu Server, instalar docker desde el sistema oficial, ya que Ubuntu instala la versión con snap lo que no me gusta mucho para este tipo de paquetes.

Crear un proyecto Laravel de prueba

La mejor manera de volverse loco cuando uno es aprendiz del tema Gitlab+Docker, es no seguir a pie juntillas sus manuales, porque al final nos volvernos locos, y ni decir tiene, comenzar a buscar en Medium, y otros sitios de bloggers, pues muchas veces es copy & paste, y con errores garrafales.

Sí, hay que leer el manual de Gitlab + Gitlab Runner y si hay que tenerlos a mano, pero mejor te lo cuento que seguro que ahorras un montón de tiempo.Abdelkarim Mateos

Crear un repositorio vacio en nuestro Gitlab

Crear un repositorio público en tu Gitlab

Instalar Laravel

Yo prefiero usar laravel installer pero siente libre de usar el sistema que desees

abkrim@abkrim-nox$ laravel new laravel-test
Crafting application...
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Package operations: 80 installs, 0 updates, 0 removals
  - Installing doctrine/inflector (v1.3.0): Loading from cache
  - Installing doctrine/lexer (1.0.2): Loading from cache
  - Installing dragonmantank/cron-expression (v2.3.0): Loading from cache
...
...
...
filp/whoops suggests installing whoops/soap (Formats errors as SOAP responses)
sebastian/global-state suggests installing ext-uopz (*)
phpunit/phpunit suggests installing phpunit/php-invoker (^2.0)
Generating optimized autoload files
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
> @php artisan key:generate --ansi
Application key set successfully.
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: beyondcode/laravel-dump-server
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
Application ready! Build something amazing.
abkrim@abkrim-nox$

Usar la nueva instalación para usar el repositorio de Gitlab

cd ~/path/laravel-test
git init
git remote add origin git@gitlab.midominio.tld:root/laravel-test.git
git add .
git commit -m "Initial commit"
git push -u origin master

Preparar nuestro proyecto para unas pruebas mínimas

Javascript

Necesitamos tener instalado NPM o Yarn, para actualizar los paquetes javascript en la raíz de nuestro proyecto.

abkrim@abkrim-nox $ npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

added 1001 packages from 485 contributors and audited 17129 packages in 37.831s
found 0 vulnerabilities

Database con MySQL 8.0

Necesitamos preparar nuestra aplicación para generar algún tipo de datos con MySQL, que es lo que queremos probar, así que que mejor que el scaffolding de autenticación de Laravel.

Editamos el fichero .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=my_database
DB_USERNAME=myuser
DB_PASSWORD=mypassword
# Sustituye <> por el valor apropiado
Generamos el scaffolding
Generar el scaffolding de autenticación de Laravel
abkrim@abkrim-nox? php artisan make:auth
Authentication scaffolding generated successfully.
abkrim@abkrim-nox? php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (0.05 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (0.05 seconds)

Configurando nuestro proyecto Gitlab CI

Obtención del token en nuestro Gitlab

En nuestro proyecto tendremos que ajustar nuestro runner para configurarlo y obtener el token. 

Proyecto > Settings > CI / CD -> Runners -> Set up a specific Runner manually

Copiamos el token que ahora necesitaremos para configurar nuestro runner en la máquina de Gitlab Runner que hemos instalado.

Configurar el proyecto en Gitlab para Gitlab Runner

Registrar el runner del proyecto

Ahora debemos ir a la consola de nuestro VPS Gitlab Runner y registrar nuestro runner. En principio con el comando debería bastar pero hay opciones que no consiguo en modo interactivo, (¿alguien se anima a comentar la solución?)

? sudo gitlab-runner register \
  --non-interactive \
  --url "https://gitlab.dominio.tld/" \
  --registration-token "NuEsTrO_ToKeN_CaMbIaLo" \
  --executor "docker" \
  --docker-image alpine:latest \
  --description "laravel-test" \
  --tag-list "laravel,php,mysql,npm" \
  --run-untagged="true" \
  --locked="false" \
  --access-level="not_protected"

Runtime platform                                    arch=amd64 os=linux pid=13854 revision=a987417a version=12.2.0
Running in system-mode.

Registering runner... succeeded                     runner=bxAAbr3B
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!

Esto escribirá o añadirá el fichero /etc/gitlab-runner/config.toml donde se guarda la configuración de los dockers de gitlab runner

Vamos a editar este fichero manualmente para añadir alguna cosa que vimos muy provechosa del post de Oh Dear relativa a los procesos concurrentes

? sudo nano /etc/gitlab-runner/config.toml

concurrent = 2
check_interval = 0

[session_server]
    session_timeout = 1800

[[runners]]
    name = "laravel-test"
    url = "https://gitlab.dominio.tld"
    token = "NuEsTrO_ToKeN_CaMbIaLo"
    executor = "docker"
    [runners.custom_build_dir]
    [runners.docker]
        tls_verify = false
        image = "alpine:latest"
        privileged = false
        disable_entrypoint_overwrite = false
        oom_kill_disable = false
        disable_cache = false
        volumes = ["/cache"]
        shm_size = 0
    [runners.cache]
    [runners.cache.s3]
    [runners.cache.gcs]

Problema de resolución

Sin embargo esa configuración me dio problemas localizados en:
fatal: unable to access 'https://gitlab-ci-token:[MASKED]@gitlab.castris.com/root/laravel-test.git/':
Could not resolve host: gitlab.castris.com
Se trata de modificar el fichero de configuración añadiendo en la sección [runners.dock] según obtuvimos en el control de problemas de Gitlab
dns_search = ["gitlab.castris.com"]
extra_hosts = ["gitlab.castris.com:176.31.31.225"]
En mi repo te dejo la versión definitiva del fichero config.toml, y se hago algún cambio o mejora alli estará. Fallo de resolución de Gitlab Runner Si quieres conocer más sobre el formato del archivo TOM, puedes leer su documentación

Crear nuestro fichero .gitlab-ci.yml

Bueno hora de configurar nuestro proyecto para trabajar en el escenario que queremos.
stages:
  - preparation
  - building
  - testing
  - security

variables:
  MYSQL_ROOT_PASSWORD: root
  MYSQL_USER: myuser
  MYSQL_PASSWORD: mypassword
  MYSQL_DATABASE: mydatabase
  DB_HOST: mysql

cache:
  key: $CI_BUILD_REF_NAM

composer:
  stage: preparation
  services:
    - mysql:8.0
  image: edbizarro/gitlab-ci-pipeline-php:7.3-alpine
  script:
    - php -v
    - composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts
    - cp .env.example .env
    - php artisan key:generate
  artifacts:
    paths:
      - vendor/
      - .env
    expire_in: 5 days
    when: always
  cache:
    paths:
      - vendor/

yarn:
  stage: preparation
  image: edbizarro/gitlab-ci-pipeline-php:7.3-alpine
  script:
    - yarn --version
    - yarn install --pure-lockfile
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 days
    when: always
  cache:
    paths:
      - node_modules/

build-assets:
  stage: building
  image: edbizarro/gitlab-ci-pipeline-php:7.3-alpine
  # Download the artifacts for these jobs
  dependencies:
    - composer
    - yarn
  script:
    - yarn --version
    - yarn run production --progress false
  artifacts:
    paths:
      - public/css/
      - public/js/
      - public/fonts/
      - public/mix-manifest.json
    expire_in: 1 days
    when: always

db-seeding:
  stage: building
  services:
    - mysql:8.0
  image: edbizarro/gitlab-ci-pipeline-php:7.3-alpine
  # Download the artifacts for these jobs
  dependencies:
    - composer
    - yarn
  script:
    - php artisan migrate:fresh --seed
  artifacts:
    paths:
      - ./storage/logs # for debugging
    expire_in: 1 days
    when: on_failure

phpunit:
  stage: testing
  services:
    - mysql:8.0
  image: edbizarro/gitlab-ci-pipeline-php:7.3-alpine
  # Download the artifacts for these jobs
  dependencies:
    - build-assets
    - composer
    - db-seeding
  script:
    - php -v
    - sudo cp /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.bak
    - echo "" | sudo tee /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
    - ./vendor/phpunit/phpunit/phpunit --version
    - php -d short_open_tag=off ./vendor/phpunit/phpunit/phpunit -v --colors=never --stderr
    - sudo cp /usr/local/etc/php/conf.d/docker-php-ext-xdebug.bak /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
  artifacts:
    paths:
      - ./storage/logs # for debugging
    expire_in: 1 days
    when: on_failure

Credenciales de MySQL en Laravel en modo CI

Para esto necesitaremos pasar en el fichero .env.example los valores de conexión a MySQL en modo CI. Es importante no usar root como usuario, y usar un nombre de base de datos por proyecto, si estamos trabajando distintos proyectos.
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=mydatabase
DB_USERNAME=myuser
DB_PASSWORD=mypassword
Atención al DB_HOST que produce mas de un quebradero de cabeza Y al mismo tiempo en la sección de la etapa de preparación, en el composer definimos su copia
cp .env.example .env

Explicación

  • En la parte superior dividimos el proceso CI en varias etapas. Esto es bueno cuando trabajamos en equipo, y también para identificar potenciales problemas. No es necesario pero es una buena practica.
  • Asignamos las variables necesarias para nuestra aplicación y su uso con MySQL
  • Definimos la variable key para el cache
  • Preparamos las distintas etapas para las que vamos a usar un repositorio bastante bueno para Laravel CI, dbizarro/gitlab-ci-pipeline-php con algunas modificaciones en cuanto al su recomendación de configuración y las de OnMyDear.app
  • Preparamos el fichero .env.example, para ser usado como remisor de las variables de uso de MySQL

Probando la integración CI

Hacemos una pequeña modificación en cualquier fichero que no suponga un problema
git add 
git commit -m 'Primera prueba'
git push
Si acudimos al rato a nuestro proyecto en Gitlab, > Pipelines veremos el fallo
$ php artisan migrate:fresh --seed

   Illuminate\Database\QueryException  : SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Name does not resolve (SQL: SHOW FULL TABLES WHERE table_type = 'BASE TABLE')

  at /builds/root/laravel-test/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668|

  Exception trace:

  1   PDOException::("PDO::__construct(): php_network_getaddresses: getaddrinfo failed: Name does not resolve")
      /builds/root/laravel-test/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=mysql;port=3306;dbname=laravel", "root", "secret", [])
      /builds/root/laravel-test/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.

    Uploading artifacts...
./storage/logs: found 3 matching files
Uploading artifacts to coordinator... ok            id=346 responseStatus=201 Created token=TF3GSFJV
ERROR: Job failed: exit code 1
  • Pipeline CI Fallada db-seeding
  • Mostrar el paso de las pruebas en el repositorio
El error es debido a problemas de comunicación entre PHP y MySQL 8.0 que requieren un cambio en el arranque de mysql que permita el sistema antiguo de contraseñas en tanto y cuanto, PHP y multitud de librerias se pongan al día con el nuevo sistema de contraseñas de MySQl. Este cambio implica añadir la linea default-authentication-plugin = mysql_native_password al fichero de configuración de mysql. Como no me gustan dar muchas vueltas, y prefiero centrarme en usar las buenas herramientas de terceros, como el repositorio propuesto, la mejor opción que vi, era la de crear mi propia imagen de mysql basada en la oficial, pero con la modificación. Así que procedemos a crear el container y subirlo a nuestra cuenta de Docker.

Creación de un container de MySQL modificado

Docker Gitlab CI PHP, es un repositorio basado en imágenes oficiales, asi que no es complicado preparar nuestro container basado en una imagen oficial y modificada para tal efecto, en tanto y cuanto pase la tormenta del tema de la autenticación. Mi imagen creada esta en el Hub de Docker, abkrim /mysql8_legacy_password y en GitHub puedes ver su código que es muy simple
FROM mysql:8.0
MAINTAINER Abdelkarim Mateos 

COPY mysqld_password.cnf /etc/mysql/conf.d/mysqld_password.cnf

ENV MYSQL_ROOT_PASSWORD=123456
mysqld_password.cnf
[mysqld]
default-authentication-plugin = mysql_native_password
collation-server = utf8mb4_general_ci
character-set-server = utf8mb4

Construcción del repositorio en Gitub de nuestro container y enlazamos con Hub Docker

Creamos el repositorio en GitHUb y después lo enlazamos con nuestro repositorio dockerhub. Configurar dockerhub para hacer builds automáticos Ahora sólo falta subir a nuestro repositorio de github, nuestro proyecto de Docker.
$ mkdir mysql8-legacy-password
$ cd $_
$ git init
$ git remote add origin git@github.com:/mysql8-legacy-password.git
$ git add .
$ git commit -m "Initial commit"
$ git push -u origin master
Dockerhub, connectará con Github, y cuando detecte un cambio comenzará un despliegue del container. Si no falla, en unos minutos (a veces puede tardar una hora, si hay muchos proyectos en cola) si no hay fallos, estará disponible y recibirás los avisos por correo electronico.

Testing en local

Antes es buena práctica comprobar que tu imagen es correcta. Asi que puede construirla de forma fácil con
 docker build --no-cache -t mysql8_legacy_password .  

Actualización de nuestro .gitlab-ci.yml

La cosa es sencilla. Se trata de modificar todas las llamadas a la imagen oficial de mysql por nuestra imagen cambiando mysql:8.0 por {name: ‘abkrim/mysql8_legacy_password’, alias: ‘mysql’}
sed -i -e "s/mysql\:8\.0/\{name\:\ \'abkrim\/mysql8\_legacy\_password\'\,\ alias\:\ \'mysql\'\}/g" .gitlab-ci.yml
git add .gitlab-ci.yml
git commit -m 'Update .gitlab-ci.yml'
git push
Pasado un rato, si todo es correcto veremos el resultado Resultado correcto de los test pasados

Mostrar la información del CI en nuestro README.md

En Settings -> CI/CD -> General Pipelines se pueden obtener los códigos para que se visualice en nuestro repo o también en la página web Mostrar el paso de las pruebas en el repositorio

ToDo

  • Mejorar y comprender el sistema de cache en docker
  • Otros métodos para alterar imágenes o despliegues

Agradecimientos

Como siempre gracias Unsplash y a Charles 🇵🇭 por la imagen que sirve de portada, que edite gracias a Canva

Comparte este articulo en

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax