Contexte
Depuis mai 2015, le modèle chez Yproximité de “tout installer à la main en local en 1/2 journée” a été abandonné pour une solution basée sur une machine virtuelle, configurée à l’aide de playbook Ansible afin qu’en une commande, l’environnement de développement soit opérationnel après quelques minutes.
Les prérequis pour faire tourner les projets Symfony consistait à installer :
- Make, afin de lancer les commandes
- VirtualBox, pour la VM
- Vagrant, afin de piloter la VM
- Vagrant Landrush, un DNS pour accéder aux URL de développement
- mkcert, pour générer le certificat TLS local
L’utilisateur était alors invité alors à exécuter dans son terminal make setup
, puis une vingtaine de minutes plus tard, le projet était complètement opérationnel, les données de la base de données importés et toute l’équipe sur les mêmes versions mineures de PHP, Nginx, PostgreSQL.
❤️ Je tiens d’ailleurs à remercier les contributeurs du projet manala.io porté par mon ancienne agence web, Elao et notamment Nervo qui maintient les différents rôles Ansible. Merci pour leurs contributions open source et d’avoir permis de faire tourner notre usine de développement pendant 6 ans.
Nous sommes maintenant en 2021. Qu’est-ce qui m’a poussé à prévoir ce changement d’usine de développement et à abandonner ce modèle de machines virtuelles (VM) ?
Pourquoi s’éloigner du modèle machines virtuelles ?
Plusieurs arguments étaient pour l’abandon des VM :
- Docker qui devient de plus en plus mature et qui a un bon taux d’adoption dans la communauté web sur les environnements de développement.
- Les performances un peu en retrait (surtout sur Linux si j’en crois mon équipe) par rapport à une installation locale du projet.
- Le poids des machines virtuelles (compter plusieurs Go par projets)
Néanmoins, l’idée d’adopter une usine de développement uniquement basée sur Docker m’a énormément freiné. Avec une équipe réduite et sans compétence Docker particulière (notamment pour les problématiques de performances macOS vs Linux) ce choix s’avère risqué pour la stabilité de l’environnement de développement, qui doit être là pour permettre aux développeurs d’être le plus productif, et non pas comme étant une source de frustration, ou de ralentissement.
Je savais que les VM allaient bientôt être poussées vers la sortie, mais était-ce suffisant pour arrêter une date butoire de transition vers autre chose ? Prévoir un plan de formation ? Faire évoluer nos dizaines de projets vers un autre environnement de développement basé sur du Docker ? Pas tout à fait…
L’élément qui à permis de faire basculer la balance vers “autre chose” est l’annonce discrète par l’équipe de VirtualBox qui dit ne pas pouvoir techniquement porter VirtualBox sur un processeur ARM. VirtualBox, qui fait tourner les VM, est un hyperviseur, et non un émulateur CPU. VirtualBox ne peut donc pas émuler un processeur x86.
En quoi c’est gênant ? Et bien peut-être avez vous raté l’annonce d’Apple de leur plan de transition vers une architecture ARM sous 2 ans environ en débutant par leur puce “M1” (qui envoie du lourd), équipant les MacBook Pro 13”, le MacBook Air et le Mac Mini. Il semblerait même que devant les retours extrêmement positifs des utilisateurs, la firme ait même acceléré le déploiement de la nouvelle architecture ARM en annoncant il y à quelques jours les iMac 24” et les iPad Pro avec la puce M1. Pour le grand public, il ne reste donc plus que le MacBook Pro 16” et l’iMac 27” à sauter le cap.
Je vois venir les trolls d’Apple au loin, mais dans mon équipe, j’ai toujours eu à cœur de proposer aux développeurs/euses le choix entre du Linux et du macOS (voir baromètre AFUP pour les répartions d’OS). Donc merci de rediriger les troll vers les seuls qui le méritent : les devs PHP/JS sous Windows.
Une solution propulsée par Docker, mais pas que !
Dans mon contexte d’entreprise et d’équipe, une solution basée uniquement sur Docker est compromise tant que le problème de performance soit définitivement reglé sur Mac ou qu’il y ait besoin d’une ressource experte Docker/Linux pour implémenter la solution.
J’ai donc choisi de pousser l’adoption d’une solution hybride afin d’éviter les volumes partagés (et donc pas de problème de performances). Il semblerait, d’après ma mémoire de SymfonyLive que ça soit aussi le choix de Fabien Potencier.
Sur la machine hôte (macOS/Linux) :
- PHP et composer
- NodeJS
- Docker Desktop
- Symfony CLI. Ce dernier offre un serveur web avec gestion du TLS, le support docker, et la gestion des versions de PHP spécifiques.
- Binaire Manala (Permettra de générer votre
docker-compose.yaml
d’après un template. Voir aller plus loin)
⚠ Malgré tous les avantages qu’offre Symfony CLI, ce dernier n’est pas open source. C’est à noter, surtout si on se repose dessus pour une usine de développement.
Avec Docker :
- Base de données (MySQL, PostgreSQL, MariaDB…)
- Serveur Redis
L’expérience de développement avec cette nouvelle stack hybride est vraiment très plaisante et rapide, je retrouve les performances natives de ma machine.
Aller plus loin
Hugo Alliaume a repris le POC que j’ai réalisé en Août 2020 et levé tous les points de bloquages. Il s’est saisi de la problématique à bras le corps et a mené le projet de transition à son exécution sur tous les projets de l’équipe R&D.
Il a abordé des parties plus techniques dans sur son blog, notamment :
- Une analyse plus poussée des limites des VM et des workaround nécessaires
- Pourquoi remplacer son serveur web / reverse proxy et gestion DNS local par Symfony CLI
- Un exemple de
docker-compose.yaml
- Comment tirer profit de cette stack de développement pour le CI, avec GitHub Actions ?
- Comment aller plus loin avec le binaire
manala
pour faciliter la gestion et la maintenance (ex:Makefile
)
📖 Je vous invite à lire son article : migration de notre stack de développement vers Docker.
Installation des prérequis sous macOS
Ce chapitre s’adresse aux utilisateurs sous macOS 🍎 qui souhaiteraient retrouver ici toutes les procédures d’installation des outils sur votre machine.
Prérequis
Installation de brew
le gestionnaire de paquets pour macOS.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Installation de Zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
Installation de PHP
brew install php # installe la dernière version stable de PHP, soit la version 8.0.x à l'heure ou j'écris cet article.
brew install php@7.4 # installe php 7.4
brew install composer
Si vous avez des projets sous différentes versions de PHP, pas de soucis, installez toutes les versions de PHP nécessaires à vos projets, Symfony utilisera la bonne version grâce au fichier .php-version
déposé à la racine de votre projet.
Installation de NodeJS
brew install node # installe la dernière version stable de NodeJS, soit la version 16.0.x à l'heure ou j'écris cet article.
brew install node@14 # installe la nodeJS 14
brew install yarn # si vous avez besoin du gestionnaire de dépandance JS Yarn
Sur le même schéma, si vous avez des projets sous différentes versions de NodeJS, installez toutes les versions nécessaires à vos projets, nous utiliserons nvm
qui permet d’utiliser la bonne version de node selon le projet (la version étant contenue dans le fichier à la racine du projet .nvmrc
).
brew install nvm
Enfin lorsque vous lancerez votre yarn install
ou yarn dev-server
pour éviter de faire un nvm use
pour sélectionner la bonne version de nodejs, vous pouvez ajouter ce bloc dans votre ~/.zshrc
(puis relancer votre terminal, ou executez source ~/.zshrc
). Cela aura pour effet de regarder si dans votre chemin courant, un .nvmrc
existe avec une version de node spécifique au projet, et l’utilise, si elle est installée.
export NVM_DIR="$HOME/.nvm"
[ -s "/usr/local/opt/nvm/nvm.sh" ] && . "/usr/local/opt/nvm/nvm.sh" # This loads nvm
[ -s "/usr/local/opt/nvm/etc/bash_completion.d/nvm" ] && . "/usr/local/opt/nvm/etc/bash_completion.d/nvm" # This loads nvm bash_completion
# place this after nvm initialization!
autoload -U add-zsh-hook
load-nvmrc() {
local node_version="$(nvm version)"
local nvmrc_path="$(nvm_find_nvmrc)"
if [ -n "$nvmrc_path" ]; then
local nvmrc_node_version=$(nvm version "$(cat "${nvmrc_path}")")
if [ "$nvmrc_node_version" = "N/A" ]; then
nvm install
elif [ "$nvmrc_node_version" != "$node_version" ]; then
nvm use
fi
elif [ "$node_version" != "$(nvm version default)" ]; then
echo "Reverting to nvm default version"
nvm use default
fi
}
add-zsh-hook chpwd load-nvmrc
load-nvmrc
Bonus avec mux et tmuxinator
Si vous travaillez avec un projet Symfony contenant du JS ayant besoin d’être compilé, vous allez surement avoir besoin de 3 onglet de terminal:
- Une session sur
symfony serve
le webserveur - Une session à la racine de votre projet
- Une session sur
yarn dev-server
pour compiler votre Javascript quand il est modifié.
Pour gagner en confort, je vous suggère d’utiliser iTerm2 avec le multiplexer de terminal tmux
. Il permettra de scinder vos terminaux en terminaux virtuels et de pouvoir les splitter. Concrètement, quand je lance dans mon terminal mux start <nom_du_projet>
j’ai automatiquement mes containers qui se lancent, mon split de terminal qui se fait, et yarn dev-server
dans un onglet virtuel comme ci-dessous.
Pour profiter de la même chose:
brew install tmux
brew install tmuxinator
puis rajouter l’alias dans votre ~/.zshrc
alias mux=tmuxinator
puis la configuration d’un projet est assez basique
mux open <nom_du_projet>
et configurer votre projet à l’aide de la documentation de tmuxinator, comme par exemple :
# /Users/t.bessoussa/.config/tmuxinator/<nom_du_projet>.yml
name: <nom_du_projet>
root: ~/workspace/<mon_projet> # Chemin de votre projet
# Run on project exit ( detaching from tmux session )
on_project_exit: make halt
# Run on project stop
on_project_stop: make halt
windows:
- php:
layout: main-vertical
panes:
- make setup
- symfony serve
- js:
- sleep 70 # on attends un peu vu que mon make setup va lancer un build js de dev qui rentrerait en conflit avec le dev-server
- yarn dev-server
Il ne vous reste plus qu’à retenir mux start <nom_du_projet>
et mux stop <nom_du_projet>
et le tour est joué.
Une typo ? L’article est disponible sur mon dépôt Github