dimanche 10 juin 2012

Déployer une application Play! sur le cloud

Le but de cet article est de présenter une solution simple que j'ai mis en place, si ça peut servir à d'autres ...

Introduction - Contexte


Mon besoin est simple. Pour un client, j'ai commencé à développer une application avec Play! framework version 2. Les développements se font sur un poste Windows. L'hébergement doit se faire sur un VPS (Virtual Private Server) sur le cloud.

Pour ce projet, le développement se fait en mode agile sur toute la ligne. Le déploiement de l'application doit donc se faire souvent, et donc de façon automatisée et rapide. Les objectifs sont les suivants : ne pas me faire perdre de temps à chaque déploiement, avoir une opération sûre, donc pouvoir déployer aussi souvent que nécessaire et mettre souvent des versions à disposition de mon Product Owner et du client.

Je présente ici une solution que j'ai mise en place suite à des recherches ici et là, elle est simple mais efficace et me satisfait (pour l'instant).

Les grandes opérations nécessaires sont les suivantes :

  • En local, packager l'application pour avoir un ensemble cohérent et autonome pour exécuter l'application sur le serveur
  • Transmettre le(s) fichier(s) sur le serveur
  • Sur le serveur, arrêter l'application en cours, et la relancer avec la nouvelle version



Packaging


Pour commencer, on demande à Play! de packager l'application pour qu'elle puisse être déployée :

play dist

Cette commande génère le fichier dist\myapp-1.0-SNAPSHOT.zip contenant un fichier start (script Linux pour lancer l'application) et un répertoire lib contenant tous les JARs nécessaires au fonctionnement de l'application, dont évidemment le JAR contenant la compilation des sources et de la configuration de mon application.

Copie intégrale sur le serveur (étape provisoire)


L'étape suivante est de copier ce fichier sur le serveur. La première méthode envisagée est un peu brute puisqu'il s'agit simplement de copier le fichier par SSH. Pour cela, j'ai installé PuTTY en téléchargeant et exécutant le fichier putty-0.62-installer.exe sur http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html. J'ai également ajouté le chemin C:\Program Files (x86)\PuTTY à la variable d'environnement PATH de mon poste.

Je peux alors réaliser la copie par la commande suivante :

pscp -pw mypassword dist\myapp-1.0-SNAPSHOT.zip myuser@my.server.on.cloud.com:myapp-1.0-SNAPSHOT.zip

Il ne me manque plus que unzip à installer sur le serveur pour extraire les fichiers du ZIP de déploiement (merci Linux et son "apt-get install" !) :

apt-get install unzip

Cette opération m'a permis, en me connectant sur mon serveur, de vérifier que l'application peut se lancer et que je peux m'y connecter. Première étape néanmoins intéressante !

Mais le fichier à copier fait environ 35Mo, et mon débit n'est pas énorme, la copie dure donc entre 10 et 15'. C'est beaucoup trop long par rapport aux objectifs de livraisons fréquentes : avec cette solution, il me faudra bien réfléchir avant une livraison, et je risque d'hésiter plus d'une fois. Sans compter le manque de réactivité s'il y a des corrections ou améliorations à faire ...

Il me faut donc trouver une autre solution !

Copie différentielle sur le serveur (étape finale)


J'oriente donc naturellement mes recherches pour faire une copie différentielle, type RSYNC. J'ai trouvé Unison, un synchroniseur de fichier pour Linux et Windows. J'ai donc installé Unison sur mon serveur :

apt-get install unison

Ensuite, j'ai téléchargé le fichier Unison-2.32.52.zip sur http://alan.petitepomme.net/unison/assets/ qui contient Unison-2.32.52 Text.exe, version pour ligne de commande, que j'ai renommé en unison.exe.

En lançant une première fois une commande unison (voir ci-dessous), on a une erreur "Uncaught exception Unix.Unix_error(20, "create_process", "ssh")". Dans C:\Users\Xavier\.unison, j'édite le fichier default.prf (généré lors de la commande précédente) pour ajouter la ligne :

sshcmd=plink4unison.bat

On indique ainsi à Unison qu'en guise de commande SSH, il faut appeler le fichier indiqué. Dans C:\Program Files (x86)\PuTTY, je crée le fichier plink4unison.bat avec ce contenu, notamment pour utiliser plink, la commande SSH de PuTTY :

@plink %1 %2 %3 -pw mypassword -ssh  "unison -server -contactquietly"

La commande finale pour lancer la copie est donc la suivante :

unison -batch -force dist\myapp-1.0-SNAPSHOT.zip dist\myapp-1.0-SNAPSHOT.zip ssh://myuser@my.server.on.cloud.com/myapp-1.0-SNAPSHOT.zip

L'option -force dist\myapp-1.0-SNAPSHOT.zip permet d'indiquer l'opération par défaut, à savoir la copie de la source vers la destination (quelque soit les états et horodatages des fichiers de chaque côté), et la commande -batch permet de ne pas poser de question de confirmation.

La copie est effectivement différentielle et dure très peu de temps (selon la quantité de différences bien sûr).

Opération de déploiement sur le serveur


L'opération suivante, une fois le fichier copié sur le serveur, consiste à relancer l'application Play! qui tourne actuellement sur le serveur. La commande est la suivante :

plink -pw mypassword myuser@my.server.on.cloud.com ./deploy.sh

En SSH, je demande donc l'exécution du script deploy.sh situé sur le serveur, son contenu étant le suivant :

#!/bin/bash
unzip -o myapp-1.0-SNAPSHOT.zip
cd myapp-1.0-SNAPSHOT
chmod a+x start
gline=$(ps -ewwo pid,args | grep "play.core.server.NettyServer" | grep java)
echo $gline
IN=$gline
set -- "$IN"
IFS=" "; declare -a Array=($*) 
PlayProcess="$(ps -ewwo pid,args | grep "play.core.server.NettyServer" | grep java)"
echo $PlayProcess
condition=""
if [[ $PlayProcess != $condition ]]
then
 echo "Killing process: ${Array[0]}"
 kill ${Array[0]}
fi
nohup ./start > /dev/null 2>&1 & 
exit

Bilan


Au final, l'ensemble des commandes est regroupé dans le fichier local de déploiement deploy.bat qui contient :

play dist
unison -batch -force dist\myapp-1.0-SNAPSHOT.zip dist\myapp-1.0-SNAPSHOT.zip ssh://myuser@my.server.on.cloud.com/myapp-1.0-SNAPSHOT.zip
plink -pw mypassword myuser@my.server.on.cloud.com ./deploy.sh

Les objectifs sont atteints. Pour une modification mineure, le packaging prend environ 20" et la synchro avec le serveur également environ 20". Quant au déploiement et au redémarrage de l'application, ils ne prennent que quelques secondes.

Je peux donc déployer mon application en moins d'une minute, je n'hésiterai donc pas à le faire très souvent !

Sources


Quelques sources qui m'ont bien aidé :




4 commentaires:

  1. Sympa de partager la recette détaillée. En te lisant, je suis conforté dans l'idée que les PaaS sont l'avenir. Je viens de déployer ma première application play2 en une ligne de commande "bees app:deploy", et je n'ai plus qu'à définir le job jenkins adapté pour passer en déploiement continu.

    RépondreSupprimer
  2. Bonjour Nicolas,

    Merci pour ton commentaire, et bon coup de pub ! ;-D

    Je connais bien CloudBees, j'y ai une application Play! depuis 3 ans (Stax Network à l'époque ...), et j'en suis bien content, surtout du déploiement facile et différentiel en une ligne.

    Pour ce projet, j'avais pas mal hésité par rapport aux offres CloudBees, et notamment le DEV@cloud. Mais pour différentes raisons, dont le besoin de faire tourner d'autres applications (pas adapté PaaS), j'ai opté pour un VPS.

    Du coup, ça me permet de voir les choses plus "bas niveau" et d'avoir une meilleure vision des différences PaaS / IaaS, à suivre ...

    Merci

    Xavier

    RépondreSupprimer
  3. Avec Google App Engine, je... bon ok, ok. ;-)

    RépondreSupprimer
  4. Super exemple d'application play.
    ça me donne envie de m'y coller tiens ;)
    bonne journée

    RépondreSupprimer