Déployer une application Symfony avec Capistrano

Comment réaliser le déploiement automatique de votre application Symfony via l'utilitaire Capistrano.

Petit rappel

Un système de déploiement automatique ca sert à quoi ? Principalement à pouvoir mettre en ligne de nouvelles versions sans la peur et le stress de faire une erreur sur le serveur de production, et j'ose vous dire que cela n'a pas de prix !

Avec un tel système, vous pourrez gérer votre projet avec un cycle de release très court et mettre en production trois fois par jour. Cela simplifie la pratique de méthode Agile ou d'Extreme Programming.

Par contre, si vous voulez mettre en production le vendredi à 16h, vielle de Noël, avec une coupe de Champagne à la main, il va falloir regarder les méthodes d'intégration continue, sujet qui ne sera pas abordé dans cet article.

Note: cet article s'intéresse particulièrement aux applications Symfony. Pour les utilisateurs de Capifony, il faut savoir que Capistrano 3 n'offre aucune rétro-compatibilité. L'équipe de développeurs conseille de ne pas chercher à migrer la configuration, mais plutôt de repartir from-scratch.
Restez sereins, il vous sera plus aisé de prendre en main Capistrano que Capifony.

Pour plus d'information, je vous recommande vivement de consulter le site officiel de Capistrano et si vous souhaitez créer des tâches avancées d'aller en parcourir le code source, ce qui vous apportera une meilleur vision du produit.

La stratégie de déploiement

Sur le serveur de destination, on définit le répertoire qui contiendra votre projet : /var/www/my-app/
Il contiendra alors cinq fichiers :

  • releases/ : chaque sous-répertoire correspondra à un déploiement estampillé avec la date;
  • current : est un lien symbolique pointant vers une release spécifique;
  • shared/ : les données partagées entre chaque déploiement, par exemple les fichiers de journalisation;
  • revisions.log : est l'historique des déploiements;
  • repo/ : sera le conteneur de votre repository git, les dossiers de release n'ayant aucune information concernant le système de version.

Si vous devez configurer la racine de votre serveur HTTP, cela sera donc vers /var/www/my-app/current/.

Les étapes importantes

Pour gérer votre déploiement, vous allez devoir passer plusieurs pré-requis :

  1. Avoir Capistrano sur votre poste.
  2. Le serveur sur lequel vous déployez doit permettre une authentification SSH clé privée / publique
  3. Le code de votre application doit-être hébergé sur un serveur git. Si celui-ci est privé, le plus simple sera de mettre en place le SSH Agent Forwarding.
  4. Avoir les droits en écriture sur vos répertoires de déploiement.

Installation de Capistrano

Via les utilitaires gem et bundler, vous pouvez installer Capistrano ainsi que l'ensemble des dépendances nécessaires au déploiement de votre projet.

Ajoutons un fichier Gemfile :

# File : Gemfile
source "https://rubygems.org"

gem 'capistrano', github: 'capistrano/capistrano', branch: 'master'
gem 'capistrano-symfony', :github => 'TheBigBrainsCompany/capistrano-symfony'
gem 'capistrano-composer'

Comme vous pouvez le voir, j'utilise la branche master de Capistrano. Actuellement, cela me permet d'avoir la version 3.1 qui n'est pas encore publiée mais qui apporte des fonctionnalités intéressantes par rapport à la 3.0.

Pour installer les dépendances, deux commandes :

$ gem install bundler
$ bundle install

Note: pour les non développeurs Ruby, le couple gem et bundler est l'équivalent de composer pour PHP. Les dépendances ne sont pas gérées dans le répertoire projet mais dans votre espace $HOME/.bundler & $HOME/.gem, c'est pour cela que pour l'execution de capistrano vous allez devoir utiliser le couple bundle exec cap.

L'authentification sur le serveur cible

Pour une bonne intégration du système de déploiement automatique par vos équipes, il est important de permettre une authentification sans interaction, et donc sans demande de mot de passe. Pour cela il faut mettre en place une authentification par clé SSH. Soit vous possédez déjà une paire de clé publique / privée, soit vous devez en générer une nouvelle.

Note: bien que cela ne soit pas le sujet de l'article, vous pouvez découvrir les faces cachées de l'authentification asymétrique sur Wikipédia.

Sur votre poste client, voici la commande à utiliser pour générer un couple de clés avec ssh-keygen. Pour copier votre clé publique, vous pouvez utiliser l'utilitaire ssh-copy-id:

$ ssh-keygen -t rsa -C "your_email@example.com"
$ ssh-copy-id ~/.ssh/id_rsa.pub thomas@my-host.wozbe.com

Maintenant, depuis votre poste client, vous devez pouvoir ouvrir une connection SSH à votre serveur sans demande de mot de passe ssh thomas@my-host.example.com

Le versionning de vos fichiers sources

Capistrano 3 ne supporte actuellement que git, les autres viendront avec le temps.

Que vous utilisiez Github, BitBucket ou Gitlab, cela revient au même, tous ces services supportent l'authentification par clé privée / publique ainsi que les clés de déploiement. Partons du principe que votre projet est confidentiel, et que vous gérez les sources sur un Gitlab privé.

L'objectif ici est de permettre au serveur de récupérer les sources de votre projet depuis Gitlab. Nous allons utiliser la notion de SSH Agent Forwarding pour réaliser l'authentification de votre serveur.

Note: voici la définition du SSH Agent Forwarding de Github : Technique pour déployer simplement sur un serveur distant pour vous et vos développeurs. Cela permet d'utiliser vos clés SSH locales à la place d'utiliser des clés sans passphrase copiées sur vos serveurs.
Pour aller plus loin sur les techniques SSH Agent Forwarding, vous trouverez de la documentation sur ce guide.

Pour faire efficace, simple et rapide, ajouter le code ci-dessous dans le fichier de configuration de votre client SSH : ~/.ssh/config

Host *
    ForwardAgent yes

Pour vérifier, connectez vous à votre serveur, et ensuite utilisez la commande : ssh -T git@github.com.

Si cela fonctionne, vous aurez le message suivant :

# Hi armetiz! You've successfully authenticated, but GitHub does not provide shell access.

Si vous avez des soucis liés à la configuration, vous trouverez plus d'information sur Github - Using SSH Agent Forwarding.

Nous conseillons cette méthode par rapport aux clés de déploiement,
Cela permet une meilleur maitrise de la sécurité de vos projets, l'authentification étant associée à un utilisateur et non à une machine.

Permissions du système de fichier

Si vous voulez quelque chose de simple et robuste, nous vous conseillons l'utilisation des ACLs.

Note: allez plus loin avec les permissions du système de fichier Linux.

Considérons que nous avons deux utilisateurs système pouvant réaliser des déploiements : thomas & boris. Chacun d'eux ayant la possibilité de s'authentifier par clé. Ces deux utilisateurs sont dans un groupe commun : deployers.

Au niveau application HTTP / PHP, nous avons l'utilisateur : www-data.

Tous ces utilisateurs doivent pouvoir lire et écrire dans /var/www/my-app/.

Voici les commandes à utiliser pour mettre en place les ACL

$ sudo setfacl -R -m group:deployers:rwx /var/www/my-app
$ sudo setfacl -R -m user:www-data:rwx /var/www/my-app
$ sudo setfacl -R -m default:group:deployers:rwx /var/www/my-app
$ sudo setfacl -R -m default:user:www-data:rwx /var/www/my-app

Ainsi, nous avons défini des autorisations explicites sur les fichiers existants, et des autorisations par défaut sur les fichiers qui seront créés lors des déploiements.

Pour vérifier les autorisations, utilisez l'utilitaire getfacl.

$ getfacl /var/www/my-app

# file: var/www/my-app/
# owner: root
# group: root
user::rwx
user:www-data:rwx
group::r-x
group:deployers:rwx
mask::rwx
other::r-x
default:user::rwx
default:user:www-data:rwx
default:group::r-x
default:group:deployers:rwx
default:mask::rwx
default:other::r-x

Nous n'avons pas évoqué l'utilisateur root. Grâce aux ACLs, vous n'avez pas besoin de privilèges spécifiques, et l'utilisation de sudo n'est pas nécessaire contrairement à Capistrano v2 et donc Capifony avec lesquels cela était conseillé.

Actuellement, aucune tâche de Capistrano n'utilise sudo, mais les tâches que vous créerez pourrons l'utiliser au besoin.

Note : si lors de vos déploiements vous avez besoin d'effectuer des tâches d'administration, comme par exemple le redémarrage du serveur Apache, l'utilisation de sudo sera nécessaire, et elle devra se faire sans interaction, donc sans demande de mot de passe.
Sachez configurer sudo pour respecter le principe de moindre privilège.

Jouer avec Capistrano

Capistrano est un utilitaire développé en Ruby, proposant un pattern de déploiement que nous avons vu en début d'article. Cet outil définit un ensemble de fonctions utiles et permet d'exécuter une liste de tâches suivant un ordre spécifique.

A chaque fois que vous exécuterez Capistrano, vous allez devoir spécifier l'environnement cible, ex : production, pre-production, demonstration, ... Vous serez responsable de créer autant d'environnements que nécessaire à votre projet.

Pour voir les tâches existantes.

$ bundle exec cap -T

Capistrano définit un système de hook permettant d'effectuer des tâches before ou after.
De même, il existe des tâches représentant des actions publishing et des états published.
C'est grâce à cela que s'exécute vos tâches à des moments précis, ou encore que les modules existants vous simplifient la vie.

Voici la liste des états disponibles out-of-box :

  1. deploy:started
  2. deploy:updated
  3. deploy:published
  4. deploy:finished

Note : pensez à consulter la liste des étapes liés à un déploiement ou à un retour-en-arrière.

Initions les configurations de Capistrano via la tâche install:

$ bundle exec cap install

Cette tâche crée à la racine :

  • Un fichier Capfile qui contiendra la liste des modules Capistrano à utiliser;
  • Un fichier deployment/deploy.rb qui contiendra la configuration de déploiement de votre projet, commune à tous vos environnements;
  • Deux fichiers deployment/deploy/production.rb & deployment/deploy/stating.rb, correspondant aux spécificités de vos deux environnements.

Note : la version 3.1 de Capistrano permet de modifier les chemins vers les fichiers de configuration grâce aux variables :deploy_config_path et :stage_config_path. Vous pouvez en voir l'utilisation sur le github wozbe.com

Les variables ont des noms explicites, et avec ce que nous avons vu en début d'article, vous devez pouvoir configurer ces fichiers vous même. En cas de doute, n'hésitez pas à consulter le projet wozbe.com utilisant Capistrano, mais attention, à cette étape pensez à faire abstraction de la partie Symfony.

Ensuite, vous pouvez réaliser un déploiement avec la commande suivante :

$ bundle exec cap development deploy

Application Symfony

Comme l'explique le Cookbook Symfony, déployer une application correspond à réaliser les étapes suivantes :

  1. Uploader votre code sur le serveur de production;
  2. Mettre à jour vos dépendances;
  3. Lancer les migrations de base de données ou toute tâche similaire qui apporterait des changements de structure à votre base;
  4. Vider et peupler à nouveau le cache;
  5. Supprimer les fichiers inutiles;
  6. Vider les systèmes de cache externes;

Voyons comment mettre cela en forme.

  • L'étape 1. sera réalisée entièrement par Capistrano et par le peu de configuration lui indiquant où mettre et où prendre les sources;
  • L'étape 2. sera automatiquement réalisée par le module capistrano-composer qui réalise un composer install avant l'état deploy:updated;
  • L'étape 3. sera à votre charge, et dépendra de votre projet. Ci-dessous vous verrez comment créer une tâche utilisant la console de Symfony et la commande doctrine:schema:update;
  • L'étape 4. sera automatiquement réalisée par le module capistrano-symfony;
  • Les étapes 5. et 6. seront aussi à votre charge, car très spécifiques à votre application.

Partons avec les modules Capistrano définis dans le Gemfile en début d'article, ceux de symfony & composer :

Voici le contenu du fichier deployement/deploy.rb:

set :application, 'wozbe'
set :repo_url, 'git@github.com:wozbe/wozbe.com.git'

set :ssh_user, 'thomas'
server 'my-host.wozbe.com', user: fetch(:ssh_user), roles: %w{web app db}

set :scm, :git

set :format, :pretty
set :log_level, :info
# set :log_level, :debug

set :composer_install_flags, '--no-dev --prefer-dist --no-interaction --optimize-autoloader'

set :linked_files, %w{app/config/parameters.yml}
set :linked_dirs, %w{app/logs web/uploads}

set :keep_releases, 3

after 'deploy:finishing', 'deploy:cleanup'

Les variables :linked_files & :linked_dirs définissent les éléments que vous souhaitez partager entre chaque releases.
Pour notre application Symfony, cela correspond au fichier parameters.yml, aux fichiers de journalisation, et à un répertoire uploads contenant des données utilisateurs.

Les fichiers et répertoires partagés seront créés par Capistrano, mais c'est à vous de remplir le fichier /var/www/my-app/shared/app/config/parameters.yml.

Voici le contenu du fichier deployement/stages/development.rb:

set :stage, :development

set :branch, 'development'
set :deploy_to, '/var/www/vhosts/com.wozbe.dev'

Et pour finir le fichier deployement/stages/production.rb qui ne diffère que très peu :

set :stage, :production

set :branch, 'master'
set :deploy_to, '/var/www/vhosts/com.wozbe.www'

A partir de ce moment, vous pouvez déployer votre application via la commande suivante :

$ bundle exec cap development deploy
$ bundle exec cap production deploy # Attention, ici on part en production

Création d'une nouvelle tâche

Nous allons créer deux tâches ayant deux aspects distincts :

  • Upload d'un fichier en fonction de l'environnement;
  • Exécution d'une commande système en utilisant sudo.

Pour cela, créons un fichier : deployement/tasks/wozbe.cap qui contiendra nos tâches personnelles:

namespace :wozbe do
  desc 'Install robots.txt'
  task :robots do
    on roles(:web) do
      upload! "capistrano/files/robots_#{fetch(:stage)}.txt", "#{release_path}/web/robots.txt"
    end
  end

  namespace :pagespeed do
    desc 'Flush the mod_pagespeed cache'
    task :flush do
      on roles(:web) do
        as :root do
          execute :touch, '/var/cache/mod_pagespeed/cache.flush'
        end
      end
    end
  end
end

La 1° tâche envoie le fichier deployement/files/robots_development.txt ou deployement/files/robots_production.txt en fonction de l'environnement. Notez l'utilisation de la fonction upload !
La 2° tâche exécute la commande correspondant à sudo touch /var/cache/mod_pagespeed/cache.flush. Notez la définition de context as :root do.

N'hésitez pas à créer d'autres fichiers dans le répertoire deployment/tasks pour organiser votre projet.

Vous remarquerez aussi l'utilisation des rôles. Capistrano définit la notion de rôle qui permet d'avoir pour un projet: un serveur d'application, un serveur de base de données, un serveur de diffusion, ... Dans le cas des deux tâches ci-dessus, cela ne concerne que le role :web.
Dans 80% des projets, vous n'aurez pas à vous soucier de cette notion, où tous vos services seront sur le même serveur.

Si vous omettez la description d'une tâche, celle-ci restera valide et fonctionnelle mais ne sera pas listée dans les tâches disponibles via bundle exec cap -T.

Utilisation de la console de Symfony

Le module capistrano-symfony vous permet d'interagir avec la console Symfony.

Une tâche générique peut être invoquée pour réaliser vos propres solutions. Reprenons notre fichier précédent deployement/tasks/wozbe.cap:

namespace :wozbe do
  desc 'Force database update'
  task :database do
    on roles(:app) do
      invoke 'symfony:run', :'doctrine:schema:update', '--force'
    end
  end

  after 'deploy:updated', 'wozbe:database'
end

La dernière instruction déclare un hook après le déploiement pour exécuter la mise à jour de base de données.

Vous pouvez voir l'ensemble des tâches existantes pour Symfony sur TheBigBrainsCompany/capistrano-symfony.

Autre exemple, un cas d'utilisation en CLI :

$ bundle exec cap development symfony:run['doctrine:schema:update','--force']

Cela n'a pas grand intérêt, mais reste bon à savoir en cas de problème.

L'application de démonstration.

Dans un soucis de simplification, cet article utilise les options par défaut de Capistrano.

Wozbe.com est déployé automatiquement par Capistrano. Vous trouverez l'ensemble des fichiers sur github.

  • Le Capfile définit de nouveaux répertoires pour les fichiers de configuration, ainsi que les modules à utiliser : npm, bower, grunt.
  • L'exécution des tests unitaires lors du processus de déploiement.

Et Capifony ?

Une Pull Request est ouverte pour que Capifony supporte Capistrano 3.
Mais maintenant que j'ai vu ce qu'apportait Capifony, je n'y vois plus d'intérêt :

  • Cela complexifie la compréhension de Capistrano.
  • Ajoute une quantité de tâches astronomique autour de Symfony dont je n'ai que très peu d'usage.

Avec Capistrano 3, la mise en place d'un déploiement automatique sur-mesure est un jeu d'enfant, alors pourquoi complexifier les choses et ajouter des fonctionnalités qui ne sont pas nécessaires ?