Adrien Gallou
Billets

Réécrire Capistrano en une demi-journée

Introduction

Capistrano est un outil de déploiement. Depuis maintenant 4 ans nous l’utilisons chez Decitre pour déployer 3 de nos applications / sites. Après ces 4 ans d’utilisation, nous avons rencontré différents problèmes qui nous ont amenés à reconsidérer l’utilisation de cet outil : nous utilisons maintenant Ansible pour effectuer nos déploiements.

Nous utilisons Capistrano sur 3 projets : deux projets Symfony utilisant capifony (qui utilise la version 2 de Capistrano), et un projet Magento utilisant la version 3 de Capistrano.

Limites de Capistrano

Capifony et Capistrano 2 ne sont plus maintenus

Depuis 2015, capifony n’est plus maintenu. Capifony surchargeait une stratégie de déploiement permettant de builder les assets en local : la stratégie “copy”. Celle-ci a pour avantage de préparer les sources localement avant de les envoyer en production : il n’y a donc par exemple pas besoin de node ou ruby sur les serveurs de production, seulement sur le serveur de déploiement. De plus, ces étapes de build n’ont besoin d’être effectuées qu’une seule fois : autant éviter de les lancer sur chaque serveur de production. Avec Capistrano 3 cette stratégie n’est pas intégrée en standard, et aucun plugin (tels que xuwupeng2000/capistrano-scm-gitcopy ou wercker/capistrano-scm-copy) ne nous a paru, au moment de nos tests, satisfaisant.

Les déploiements de Capistrano n’étaient pas atomiques par défaut

Dans ses versions antérieures, Capistrano n’avait pas de déploiements atomiques, c’est un sujet qui a été abordé dans de nombreuses issues. Nous utilisions la version 3.2.1 de Capistrano, qui n’effectuait pas de déploiement atomique. Nous avons dû modifier la tâche deploy:symlink:release afin que celle-ci le soit. Cela a été corrigé dans la version 3.3.3 de Capistrano via cette pull request.

A noter que sur Ansible, le lien symbolique est posé de façon atomique et que nous n’avons pas eu de hack à effectuer. Cette limite n’en était donc plus une, nous aurions pu mettre à jour Capistrano (mais celle-ci n’était pas une des raisons principales du changement).

Le code de retour du build_script n’est pas vérifié

Il est possibe d’indiquer un build_script à Capistrano : une ou plusieurs commandes à exécuter lors de la préparation des sources. Nous utilisions cette fonctionalité pour préparer les assets (récupérer les dépendances via npm, bundler et lancer une tâche grunt). Le problème de ce build script, c’est que le code de retour n’est pas vérifié. Ainsi, sur 6 mois, un de nos projets a eu 2 indisponbilités de 3 minutes à cause d’une erreur lors d’une étape de build. Ceci n’est pas acceptable : nous devons avoir confiance dans notre processus de déploiement.

Lisibilité et maintenance du déploiement

L’ajout d’étapes au déploiement dans Capistrano se fait via un mécanisme de hooks. Nous avons ajouté plusieurs étapes à notre déploiement, notamment liées à la gestion du cache, de l’exécution de patchs ou l’ajout de liens symboliques spécifiques à notre environnement.

A moins de bien connaître le flow de Capistrano, il n’est pas simple de voir d’un coup d’œil le fonctionnement du déploiement.

De plus, même si le DSL de Capistrano a pour effet de ne pas nécessiter de connaître parfaitement ruby, c’est un langage que nous ne maîtrisions pas ; au contraire d’Ansible, qui est utilisé et connu par toute l’équipe, car déjà utilisé pour provisionner nos environnements de production et de préproduction. C’est cette connaissance de l’outil qui a permis à toute l’équipe d’effectuer des adaptations sur le déploiement dans les jours qui ont suivi sa mise en place : la changement d’outil nous a permis de gagner en maintenabilité sur notre outil de déploiement.

Des solutions possibles

deployer

deployer : nous l’avons testé sur un projet très peu critique. Il est possible de modifier l’outil pour faire en sorte d’avoir des déploiements effectués avec une étape de build en local. Le fait de devoir tordre cet outil pour arriver à nos fins, et surtout des questionnements à propos de la pérénité/maintenance de celui-ci (par rapport à Ansible), nous ont poussés à ne pas l’utiliser.

Ansible et Ansistrano

Ansistrano est un rôle pour Ansible pour déployer des applications dans le style de Capistrano. Il se défini comme un portage de Capistrano sous Ansible. C’est une solution que nous avons fortement envisagée, mais que nous n’avons pas choisie en raison de la solution intégrée d’Ansible que nous allons présenter ci-dessous : le deploy_helper. En effet si le helper nous permet d’effectuer notre déploiement sans devoir utiliser un rôle dont nous devrions suivre les éventuels correctifs (la maintenance est reportée sur Ansible et non pas sur Ansistrano).

Ansible et son deploy_helper

Ansible deploy_helper va permettre de faire un deploiement “à la Capistrano” en ayant une vision sur tout le process, et permettant de facilement le modifier. Le remplacement sera “drop in” vu que ce helper est pensé pour les utilisateurs migrants depuis Capistrano, en effet la structure des dossiers est exactement la même.

Solution choisie

Nous avons donc choisi d’utiliser le deploy_helper d’Ansible pour les raisons suivantes :

Exemple

Nous lançons Ansible dans un container docker ayant installé les dépendances nécéssaires au build (node, npm, ruby, bundler, php).

Les étapes de déploiement sont les suivantes :

Voici le détail de la configuration Ansible (simplifiée pour les besoins de l’exemple) :

- git:
    repo: "{{ deploy_repository }}"
    dest: "{{ local_build_dir }}"
    accept_hostkey: True
    version: "{{ deploy_branch }}"
    depth: 1
  run_once: true
  delegate_to: localhost
  register: git_clone_result
- get_url:
    url: https://getcomposer.org/installer
    dest: /tmp/installer
  run_once: true
  delegate_to: localhost

- shell: cat /tmp/installer | php -- --install-dir=/{{ local_build_dir }}
  args:
    creates: /usr/local/bin/composer
  run_once: true
  delegate_to: localhost
- shell: SYMFONY_ENV=prod php composer.phar install --no-dev --verbose --prefer-dist --optimize-autoloader --no-progress --no-interaction --no-scripts
  args:
    chdir: "{{ local_build_dir }}"
  run_once: true
  delegate_to: localhost
- file:
    path: "{{ local_build_dir }}/{{ item }}"
    state: absent
  with_items: "{{ deploy_exclude_paths }}"
  run_once: true
  delegate_to: localhost
- deploy_helper:
    path: "{{ deploy_path }}"
- file:
    path: '{{ deploy_helper.shared_path }}/{{ item }}'
    state: directory
  with_items: "{{ shared_dirs }}"
- synchronize:
    src: "{{ local_build_dir }}/"
    dest: '{{ deploy_helper.new_release_path }}/'
- file:
    path: '{{ deploy_helper.new_release_path }}/{{ item }}'
    src: '{{ deploy_helper.shared_path }}/{{ item }}'
    state: link
  with_items: "{{ shared_dirs }}"
- shell: php app/console cache:warmup --env=prod --no-debug
  args:
    chdir: '{{ deploy_helper.new_release_path }}'
- deploy_helper:
    path: "{{ deploy_path }}"
    release: '{{ deploy_helper.new_release }}'
    state: finalize
    keep_releases: 5

Si l’une des étapes échoue le déploiement s’arrête. Chaque étape est effectuée en parallèle sur chaque serveur. L’étape suivante attend que la précédente soit executée sur tous les serveurs avant de se lancer.

Au final, notre fichier YAML de description du déploiement fait donc moins de 150 lignes, et l’ensemble des étapes se trouvent dans fichier, ce qui rend le déploiement lisible et compréhensible rapidement par toute personne ayant déjà lu un playbook Ansible.

Conclusion

La migration vers le deploy_hepler a été très simple : il a nécéssité moins d’une demi-journée pour l’utiliser en environnement de préproduction sur nos deux projets.

Depuis le passage en production, nous avons eu une erreur lors du build des assets : avant la migration l’application n’aurait plus été accessible. Quand cette erreur a eu lieu, le déploiement s’est bien arrêté : cela nous a évité une indisponbilité sur notre application.

Enfin, depuis la mise en production, des changements sur le déploiement ont été effectués par toute l’équipe, ce qui n’était pas le cas précédemment.

Si vous utilisez déjà Ansible pour le provisionning de vos machines, le deploy_helper est donc une solution très adaptée pour effectuer vos déploiements applicatifs.

PS : Decitre recherche actuellement 2 développeur(euse)s. Si vous souhaitez venir déployer avec nous, n’hésitez pas à consulter et répondre à l’annonce.