Cet article présente un exemple de mise en place d'une Pipeline Gitlab pour la compilation, les tests et le déploiement de projets Laravel.
Prérequis
Laravel 5.8 / 6
Maitrise de Docker
Savoir déployer une image docker custom sur dockerhub ou autres repos https://hub.docker.com (cf. Article todo...).
Maitrise de Gitlab
Définition de la Pipeline
Gitlab permet de découper la Pipeline en "stages". Ici nous utiliserons 3 stages :
Build : c'est l'état où seront compilées toutes les dépendances et les composants en exécutant composer install et npm install. Nous verrons également que ces actions peuvent être exécutées en parallèles sur différents runners.
Test : ici nous exécuterons les tests de code et de qualité.
Deploy : c'est la dernière étape qui permettra le déploiement de l'application vers vos serveurs de preprod, staging et prod. Cette action peut être automatique ou manuelle, ce qui est fortement recommandé pour un déploiement en production par exemple.
Voici un exemple de Pipeline qui build en parallèle composer et npm, puis qui joue une série de test et qui permet le déploiement (en prod) si tout a correctement fonctionné.
Amélioration : Cette pipeline peut être améliorée en ajoutant une dernière étape s'exécutant en parallèle du déploiement manuel (en prod) et permettant le déploiement automatique sur un autre environnement staging ou preprod. Il est également possible d'ajouter un second couche de test plus fonctionnel par exemple en utilisant Dusk.
Le partage de fichier entre stages
Gitlab permet 2 méthodes pour partager des fichiers entre les stages, l'utilisation du cache et les artefacts. Nous utiliserons ces 2 méthodes pour passer les fichiers dans nos différents stages. Cette action est évidement possible que pour les stages exécutés séquentiellement. Nous noterons également que cela améliore grandement les temps d'exécution et réduit la charge des runners.
Le Cache
Le cache est le premier mécanisme proposé par Giltlab pour le partage de fichier. Il est général à toute une branche et permet de conserver et partager ce cache entre plusieurs Pipelines.
Nous pouvons par exemple mettre en cache les /vendor pour la branche master et réduire ainsi significativement le temps de traitement global.
Les Artefacts
C'est le second mécanisme proposé par Gitlab qui permet de générer des artefacts. Les artefacts sont exclusivement partagés entre les stages d'une même Pipeline. Ce sont les outputs des stages, ils sont compressés, versionnés et transmis dans le sens d'exécution de la pipeline.
Gitlab-ci.yml
Le fichier utilisé par Gitlab pour la définition des pipelines est le fichier gitlab-ci.yml directement ajouté à la racine du projet. Nous allons maintenant reprendre pas à pas la construction de ce fichier pour la mise en place de notre pipeline décrite ci-dessus.
Image Docker custom
Je recommande fortement d'utiliser votre propre image docker pour la conception de votre pipeline. Vous pourrez également trouver beaucoup d'images préconstruites mais qui ne répondront certainement pas exactement à votre config serveur cible. Le but étant d'être le plus possible iso avec l'architecture serveur finale.
Nous ne détaillerons pas dans cet article le contenu et la conception de cette image. Je vous joins le lien de mon image custom déposée sur le hub docker officiel laravel-docker ainsi que le code associé Dockerfile.
L'intégration d'une image dans gitlab-ci.yml :
image: vincentcau/laravel-docker:latest
Vous remarquerez que j'utilise la même image pour tous mes stages, néanmoins il est possible d'utiliser des images différentes en fonction de besoin de vos stages. Voici l'implémentation d'une image spécifique à un stage :
npm: stage: build image: vincentcau/laravel-docker:latest
Vous pouvez également avoir besoin d'une image supplémentaire pour l'exécution d'un jobs comme MySQL. Il faudra alors utiliser l'argument service dans gitlab-ci.yml et définir si besoin de variables associés :
# Variables variables: MYSQL_ROOT_PASSWORD: root MYSQL_USER: homestead MYSQL_PASSWORD: secret MYSQL_DATABASE: sapiendo_id DB_HOST: mysql testing: stage: test services: - mysql:5.7 image: vincentcau/laravel-docker:latest
Le build stage
Dans ce stage nous pouvons paralléliser composer et npm sur 2 runners différents si vous avec configurez plusieurs runners dans votre Gitlab. Pour utiliser plusieurs runners en parallèles il faut utiliser le paramètre tag et leur attribuer le même nom de stage.
composer: stage: build image: vincentcau/laravel-docker:latest tags: - tag-runner1 .... npm: stage: build image: vincentcau/laravel-docker:latest tags: - tag-runner2 ....
Voici le code complet avec l'utilisation de tag pour l'exécution des runners en parallèle, le cache, les artefacts et les scripts associés à composer et npm :
composer: stage: build image: vincentcau/laravel-docker:latest tags: - tag-runner1 cache: key: ${CI_COMMIT_REF_SLUG}-composer paths: - vendor/ script: - composer install --prefer-dist --no-ansi --no-interaction --no-progress --no-scripts - cp .env.example .env - php artisan key:generate artifacts: expire_in: 1 month paths: - vendor/ - .env npm: stage: build image: vincentcau/laravel-docker:latest tags: - tag-runner1 cache: key: ${CI_COMMIT_REF_SLUG}-npm paths: - node_modules/ - Modules/mon-module/node_modules/ #si utilisation de modules supp. script: - npm install - npm run production - cd Modules/mon-module/ && npm install && npm run production artifacts: expire_in: 1 month paths: - public/css/ - public/js/ - public/modules/ - public/mix-manifest.json
Le test stage
C'est dans ce stage que seront exécutés tous les tests mis en place dans votre projet. Dans notre exemple nous utilisons security-checker, phpcs, phpunit et artisan code:analyse. Le stage test récupère directement les artefacts et le cache des stages de build. Nous aurons également besoin d'utiliser une image de mysql pour exécuter nos tests.
testing: stage: test services: - mysql:5.7 image: vincentcau/laravel-docker:latest tags: - tag-runner1 script: - ./vendor/bin/security-checker security:check - ./vendor/bin/phpcs --report=summary - php artisan migrate - php artisan migrate:refresh - ./vendor/phpunit/phpunit/phpunit --no-coverage - php artisan code:analyse
Ce stage est bloquant pour la suite du déploiement. Le développeur qui a push sur sa branche et qui rencontre une erreur recevra une notification d'échec et pourra se reporter aux logs du pipeline pour retrouver les erreurs en cours.
Le deploy stage
Ce stage est executé uniquement sur les branches designées par le paramettre only : et destinées au deploiement comme master, dev, preprod, rc. Il ne tournera pas sur les autres branches qui ne sont pas encore mergées sur ces branches designées.
Dans ce stage vous pouvez utiliser les outils de déploiement comme deployer ou envoyer qui vous permettront de versionner et d'exécuter les commandes serveur nécessaires au bon déploiement de votre app sur le serveur distant.
Il faudra également préparer une connexion SSH avec échange de clés pour vous connecter à votre serveur distant depuis le container docker de votre pipeline.
Configurer la connexion SSH
Pour communiquer de façon sécurisée entre votre pipeline et votre/vos serveur(s) distant(s) il est nécessaire de mettre en place une connexion SSH avec échange de clés privées/publiques.
Voici la démarche à suivre pour la mise en place de cette connexion :
- Créer une nouvelle paire de clés SSH sur votre machine
- Déposer la clé publique sur votre serveur
- Déposer la clé privée sur Gitlab en utilisant les variables secrètes
- Utiliser la clé privée dans vos pipelines
1. Création de la clé SSH
Exécuter la commande ci-dessous dans votre terminal :
ssh-keygen -t rsa -C "gitlab@yourdomain.com" -b 4096
Ne pas mettre de passphrase.
Attention également à ne pas écraser votre clé locale si vous en utilisez déjà une.
2. Déposer la clé publique sur votre serveur
Connectez-vous sur votre serveur distant et coller la clé id_rsa.pub dans ~/.ssh/authorized_keys
3. Déposer la clé privée sur Gitlab en utilisant les variables secrètes
Dans Gitlab aller dans Settings > CI/CD et ouvrir l'onglet Variables
Utiliser une clé que vous pourrez ensuite passer en paramètre de votre pipeline par exemple SSH_PRIVATE_KEY.
4. Utiliser la clé privée dans vos pipelines
Vous devez ensuite écrire un script qui permettra de passer la clé privée dans votre pipeline.
Voici un exemple :
.init_ssh: &init_ssh | eval $(ssh-agent -s) echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null mkdir -p ~/.ssh chmod 700 ~/.ssh [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
Vous noterez que le format yaml permet d'écrire des scripts. Vous pouvez utiliser "." pour masque l'exécution de ce script set "&" pour le rendre réutilisable.
Vous pouvez rencontrer une erreur dans l'utilisation des fichiers dans Gitlab notamment au moment du déploiement. Je vous conseille donc d'écrire également un script pour modifier les droits des répertoires et des fichiers vendor :
.change_file_permissions: &change_file_permissions | find . -type f -not -path "./vendor/*" -exec chmod 664 {} \; find . -type d -not -path "./vendor/*" -exec chmod 775 {} \;
Au final voici le résultat de notre stage deploy :
.init_ssh: &init_ssh | eval $(ssh-agent -s) echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null mkdir -p ~/.ssh chmod 700 ~/.ssh [[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config .change_file_permissions: &change_file_permissions | find . -type f -not -path "./vendor/*" -exec chmod 664 {} \; find . -type d -not -path "./vendor/*" -exec chmod 775 {} \; deploying: stage: deploy image: vincentcau/laravel-docker:latest tags: - tag-runner1 script: - *init_ssh - *change_file_permissions - php artisan deploy yourdomain.com -s upload when: manual only: - master
Un dernier point, le déploiement est donc manuel et se déclenchera au click sur le bouton deploying. Cette action est possible en définissant le paramètre when : manual dans votre pipeline.