• Ce blog — désormais archivé — est en lecture seule.

Déploiement automatisé avec Capistrano et Git pour symfony et Diem

Hier, j’introduisais mes directives de travail. Aujourd’hui je vais introduire un sujet souvent sensible : la mise en production. C’est une tâche qui peut être critique mais si l’on prend toutes les précautions, nous limitons les risques. On notera bien que le facteur chance est laissé à la porte pour ce genre de manipulation…

La mise en production c’est passer un projet sur le serveur du client pour le rendre accessible aux utilisateurs finaux. Facile ? Non, très souvent les architectures sont différentes et il convient d’utiliser plusieurs environnements. Pour ma part, je dispose de 3 environnements : dev (sur ma machine), test (sur serveur distant) et préprod (sur serveur distant également). Ces environnements me permettent de tester mes projets sur plusieurs OS, plusieurs types de machine et sur deux SGBDR (bases de données) : j’ai pris l’habitude d’effectuer mes tests sous SQLite, j’ai ainsi  deux sons de cloche (en plus de MySQL) me permettant de corriger des étourderies lors de la configuration de la base.

Ok, tout ce speech pourquoi ? Pour pouvoir comprendre mon intérêt à automatiser le process de mise en prod’. En effet, mes trois environnements me garantissent une certaine stabilité de mon application, je n’ai donc pas à jongler avec les serveurs de chacun de mes clients (pas toujours…). Je peux donc déployer (mettre en prod’) mon application presque les yeux fermés. Et ça, ça n’a pas de prix !

Mais je ne joue pas le lanceur de couteaux sans avoir de lames bien aiguisées. J’utilise des outils éprouvés comme Capistrano et Git.

Capistrano est l’outil par excellence pour déployer des applis Rails. Le problème c’est que je ne m’y suis pas encore mis… Mais qu’importe, il déploiera mes applications symfony et Diem. La souplesse de configuration via redéfinition des méthodes permet de déployer à peu près ce que l’on souhaite. De plus, l’interconnexion avec Git se fait sans problème.

Maintenant, courte description :

  • Capistrano va cloner votre projet (Git inside) ;
  • Capistrano peut compresser votre projet (tar) et l’envoyer à vos X serveurs  ;
  • ou Capistrano va demander à X serveurs de récupérer la dernière version de votre projet ;
  • Capistrano va lier (symboliquement) la dernière version en tant que version courante ;
  • Capistrano va migrer les bases de données (la migration ici, ce n’est pas l’histoire avec les oiseaux… C’est plutôt la mise à jour structurelle de vos bases) ;
  • Capistrano va déployer votre appli très rapidement sur autant de serveurs que vous le souhaiter ;
  • Capistrano exécute des commandes avant ou après le déploiement, pratique pour un sf cc ;
  • Enfin, Capistrano ne fait pas le café (il ne faut pas abuser quand même…)

Intéressant non ? Une fois Capistrano installé (explications ici), on va pouvoir commencer à travailler.

Ce que je fais généralement, je crée un répertoire project_name et je tape la commande :

capify .

Ceci me crée un fichier Capfile, un répertoire config/ avec un fichier deploy.rb. C’est ce dernier qui nous intéresse. Le mieux est de partir d’un fichier vide. Pour pouvoir utiliser Git, il faut écrire ceci :

set :scm, "git"
set :repository,  "~/#{application}.git"
set :branch, "master"
set :checkout, "export"
set :deploy_via, :remote_cache

J’indique le chemin de mon dépôt, ici local et la branche (par défaut master). Les paramètres :checkout et :deploy_via ainsi défini permettent de faire un clone complet du projet. Ensuite, quelques paramètres nécessaires :

# Port
set :port, 9999
# App ?
set :application, "project_name"

# Where ?
set :deploy_to, "/var/www/#{application}"

# Which server ?
role :app, "localhost"
role :web, "localhost"
role :db, "localhost", :primary => true

# Who ?
set :user, "myUs3r"
set :password, "p4ssw0rd"

# More config
set :keep_releases, 3
ssh_options[:forward_agent] = true

# Path to php executable
set :php, "/usr/bin/php"

# Symfony application name (used for migrations)
set :sf_app, "front"
# Symfony web directory (www, web, public_html, ...)
set :sf_webdir, "www"

Maintenant, une petite chose à savoir. Capistrano versionne les déploiements et ça, c’est bon ! Mon déploiement plante, rollback automatique vers la version précédente. Je m’aperçois que j’ai fait une grosse faute d’orthographe, je lance un rollback et l’appli passe en version précédente. Pour cela, Capistrano construit sa propre structure dans le répertoire défini par :deploy_to :

/var/www/project_name
  `_ current (symlink)
   |_ releases
   |_ shared

current est le lien symbolique qui pointe toujours vers la version active de l’application. Le répertoire releases/ possède X répertoires correspondant aux différents déploiements passés. Vous l’aurez compris, le lien symbolique current pointe vers l’un de ces répertoires. Le répertoire shared/ quant à lui contient des fichiers partagés par toutes les releases. Exemple : le fichier databases.yml de symfony qui définit les identifiants de connexion à la base de données ou encore le répertoire uploads/ qui contient des données utilisateurs.

On peut désormais personnaliser les actions de déploiement. Pour cela, on va redéfinir les actions de :deploy, qui contient les tâches de déploiement appelées lors de tout déploiement.

namespace (:deploy) do

  desc < <-DESC
    [internal] Overriding original task to fit to symfony project needs
  DESC
  task :finalize_update, :except => { :no_release => true } do
    # Fix permissions
    run "cd #{latest_release} && find * -type f -exec chmod 644 {} \\;"
    run "cd #{latest_release} && find * -type d -exec chmod 705 {} \\;"
 
    run < <-CMD
      rm -rf #{latest_release}/log &&
      ln -s #{shared_path}/log #{latest_release}/log
    CMD

    run <<-CMD
      rm -rf #{latest_release}/cache &&
      ln -s #{shared_path}/cache #{latest_release}/cache
    CMD

    stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
    asset_paths = %w(images css js).map { |p| "#{latest_release}/#{sf_webdir}/#{p}" }.join(" ")
    run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
  end
 
  desc < <-DESC
    Overriding original task to exclude restart
  DESC
  task :default do
    update
  end
 
  desc <<-DESC
    Overriding original task to use symfony migrations
  DESC
  task :migrations do
    update
    #sf.migrate
  end  
 
  after "deploy:update", 'deploy:customize'
 
  desc <<-DESC
    Custom tasks
  DESC
  task :customize do
    sf.symlinks
    sf.remove_dev_environments
    # clear cache
    sf.cc
    # Diem
    dm.cc
    dm.setup
  end
 
end

<

p style= »text-align: justify; »>

Après déploiement, on va créer fixer les droits, les répertoires de logs et de cache. Ensuite on met à jour la base de données (Doctrine migration). Pour finir on exécute une série de tâches « custom » qui permettent de supprimer les contrôleurs de développement (frontend_dev, backend_dev, …), de recréer les liens symboliques pour les fichiers partagés, de vider le cache symfony ou Diem, …

Voyons la suite de commandes pour symfony :

namespace (:sf) do
 
  desc < <-DESC
    Run the "doctrine migration" task
  DESC
  task :migrate do
    run "cd #{current_path} && #{php} symfony doctrine:generate-migrations-diff && #{php} symfony doctrine:migrate"    
  end

  desc <<-DESC
    Run the "symfony cc" task
  DESC
  task :cc do
    run "cd #{current_path} && #{php} symfony cc"
    run "cd #{current_path} && rm -rf cache/*"
  end

  desc <<-DESC
    Create symlink to symfony specific targets
  DESC
  task :symlinks do
    # symlink to database.yml
    run "rm -rf #{current_path}/config/databases.yml"
    run "ln -s #{shared_path}/databases.yml #{current_path}/config/databases.yml"
   
    # symlink to uploads
    run "rm -rf #{current_path}/#{sf_webdir}/uploads"
    run "ln -s #{shared_path}/uploads #{current_path}/#{sf_webdir}/uploads"
     
    # symlink to .htaccess
    run "rm -rf #{current_path}/#{sf_webdir}/.htaccess"
    run "ln -s #{shared_path}/.htaccess #{current_path}/#{sf_webdir}/.htaccess"

    # symlink to app.yml
    run "rm -rf #{current_path}/apps/#{sf_app}/config/app.yml"
    run "ln -s #{shared_path}/app.yml #{current_path}/apps/#{sf_app}/config/app.yml"
  end
 
  desc <<-DESC
    Remove DEV environments
  DESC
  task :remove_dev_environments do
    run "rm -rf #{current_path}/#{sf_webdir}/*dev.php"    
  end
 
  desc <<-DESC
    Disable symfony application
  DESC
  task :disable do
    run "cd #{current_path} && #{php} symfony project:disable #{sf_app} prod"    
  end
 
  desc <<-DESC
    Enable symfony application
  DESC
  task :enable do
    run "cd #{current_path} && #{php} symfony project:enable #{sf_app} prod"    
  end  
end

<

p style= »text-align: justify; »>Et voilà la suite de commandes pour Diem :

namespace (:dm) do

  desc < <-DESC
    Run the "Diem setup" task
  DESC
  task :setup do
    run "cd #{current_path} && #{php} symfony dm:setup"    
  end

  desc <<-DESC
    Clear Diem cache and fix permissions
  DESC
  task :cc do
    run "rm -rf #{current_path}/#{sf_webdir}/cache"
    run "mkdir #{current_path}/#{sf_webdir}/cache"
    run "chmod 777 #{current_path}/#{sf_webdir}/cache"    
  end
 
end

<

p style= »text-align: justify; »>Et voilà ! Un simple cap deploy fait tout le reste. Vous pouvez ajouter autant de serveurs que vous le souhaiter et lancer également toutes les commandes qui vous font plaisir.

Si vous avez des problèmes d’hébergement, exemple chez OVH qui ne laisse pas d’accès externe sur leurs serveurs Plan (…), vous pouvez utiliser la configuration suivante qui va archiver votre projet et l’envoyer en SFTP :

set :deploy_via, :copy

# Use copy to bypass firewall...
set :copy_strategy, :export
set :copy_cache, "/tmp/#{application}"
set :copy_exclude, [".git/*"]
set :copy_compression, :gzip

Cette configuration est largement inspirée de ces articles :

Pour terminer, hier je vous parlais d’Hudson, un serveur d’intégration continue. En liant les deux, j’obtiens une automatisation complète de mon « after code« . C’est-à-dire que j’écris mon code comme un barbu (et mes tests). Lorsque j’ai bien commité et que je push mes modifs, Hudson voit ces mises à jour, il va relancer les tests. Si tout passe la build estampillée « success » va être déployée automatiquement par Capistrano.

Et moi dans l’histoire ? Je n’ai rien à faire. Au pire, je suis alerté par mail si un test ne passe pas et si la build ne peut être déployée, Capistrano fera un rollback automatiquement sur sa dernière version stable. Manquerait plus qu’une cafetière USB et une tâche Hudson pour lancer le café et là… utopie !

A noter qu’un projet nommé Capifony a vu le jour depuis quelques temps. C’est un Capistrano réécrit pour gérer nativement les projets symfony, je n’ai pas testé mais cela me semble une excellente option : http://github.com/everzet/capifony. Pour Diem, j’ai vu passé quelque chose dans le Google Groups, à suivre donc.

Vous pouvez, si vous le voulez, utiliser Murder, un projet développé par Twitter pour déployer via Bittorent leurs applications sur tous leurs serveurs. C’est pas une mauvaise idée si vous avez beaucoup de serveurs, leurs temps de déploiements sont assez phénoménales. En parlant de temps, mes temps de déploiements sont de l’ordre de 2 minutes…

C’est tout, pour le moment :-)

  • Print
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Twitter
  • Google Bookmarks
  • FriendFeed
  • LinkedIn
  • MySpace
  • Netvibes
  • PDF
  • Ping.fm
  • RSS
  • Technorati
  • viadeo FR
  • Wikio
  • Yahoo! Buzz

Related Posts

Cet article a été publié dans Diem Project, symfony, Sysadmin avec les mots-clefs : , , , , , . Bookmarker le permalien. Les commentaires et les trackbacks sont fermés.

Un commentaire

  1. Le 12 août 2010 à 15 h 53 min | Permalien

    Dans le même genre que Capifony, voici Capidiem : http://github.com/elbouillon/capidiem.

3 trackbacks

  1. [...] Ce billet était mentionné sur Twitter par Clément, William DURAND, Hugo Hamon, Bazinga, Ghislain de FONTENAY et des autres. Ghislain de FONTENAY a dit: RT @clementj: Déploiement automatisé avec Capistrano et Git pour symfony et Diem (via @couac) http://icio.us/fa3mjp [...]

  2. [...] reading here: Déploiement automatisé avec Capistrano et Git pour symfony et Diem … No [...]

  3. [...] Ceci m’amène à penser qu’il devient intéressant de le déployer.Merci à Capistrano (lire ici mon article sur le déploiement d’applis symfony et diem avec Capistrano) et au SSH Plugin d’Hudson. Ce plugin n’est pas des plus parfaits mais il fonctionne. [...]