12.2. Mise en oeuvre du déploiement automatisé et continu

Dans sa forme la plus élémentaire, le Déploiement Automatisé peut être aussi simple que l'écriture de vos propres scripts afin de déployer votre application vers un serveur particulier. L'avantage principal de la solution scriptée est la simplicité et l'aisance en termes de configuration. Cependant, une telle approche peut atteindre ses limites si vous avez besoin de mettre en oeuvre des actions de déploiement plus élaborées, telles qu'installer un logiciel sur une machine ou redémarrer le serveur. Pour des scénarios plus avancés, vous pourriez avoir besoin d'un outil de déploiement/configuration plus sophistiqué tel que Puppet ou Chef.

12.2.1. Le script de déploiement

Une part essentielle de toute initiative de Déploiement Automatisé est un processus de déploiement scripté. Bien que celui puisse sembler évident, il y a encore de nombreuses organisations où le déploiement demeure un processus consommateur de resources, compliqué et lourd, incluant copie manuelle de fichiers, exécution manuelle de script, des notes écrites à la main et ainsi de suite. La bonne nouvelle est qu'en général cela n'a vocation pas à rester ainsi. Avec un peu de travail, il est généralement possible d'écrire un script pour automatisé la plupart, voir l'intégralité, du processus.

La complexité d'un script de déploiement varie énormément d'une application à une autre. Pour un simple site web, un déploiement de script peut se limiter à resynchroniser un dossier sur le serveur cible. De nombreuses applications Java ont Ant ou des plugins Maven qui peuvent être utilisés pour le déploiement. Pour une architecture plus compliquée, le déploiement peut impliquer plusieurs applications et services sur de multiples serveurs en grappe, le tout coordinné de façon extrêment précise. La plupart des processus de déploiement sont entre ces deux extrêmes.

12.2.2. Mises à jour de base de données

Déployer votre application vers le serveur d'application est généralement seulement une partie du puzzle. Les bases de données, relationnelles ou autres, jouent presque toujours un rôle central dans l'architecture logiciel. Bien sûr, idéalement, votre base de données devrait être parfaite dès le début, mais cela est rarement le cas dans le monde réel. En effet, lorsque vous mettez à jour votre application, vous avez généralement également besoin de mettre une ou plusieurs bases de données à jour.

Les mises à jour de base de données sont généralement plus difficile à mettre en oeuvre que les mises à jour applicatives, vu que tant la structure que les données sont susceptibles d'être modifiées. Cependant, les mises à jour de base de données sont critiques pour le développement et le déploiement. Elles méritent donc de l'attention et de la planification.

Certains frameworks, tels que Ruby on Rails et Hibernate, peuvent supporter automatiquement des changements structurels d'une base de données. En utilisant ces frameworks, vous pouvez usuellement spécifier si vous voulez créer un nouveau schéma de la base à chaque mise à jour ou si vous voulez mettre à jour le schéma tout en conservant les données. Bien que cela semble utile à première vue, ces fonctionnalités sont en fait juste suffisantes pour des environnements non critiques. Ces outils ne gèrent pas bien les migrations de données, point pourtant essentiel. Par exemple, si vous créez une colonne dans votre base de données, le processus de mise à jour va simplement créer une nouvelle colonne: il ne va pas copier les données de l'ancienne colonne vers la nouvelle, pas plus qu'il ne va supprimer l'ancienne colonne de la table mise à jour.

Heuresement, cela n'est pas la seule approche disponible. Un autre outil qui tente de résoudre le problème complexe des mises à jour de base de données est Liquibase. Liquibase est un outil open source qui permet d'organiser des processus de mises à jour entre versions d'une base de données à travers une approche de haut niveau.

Liquibase garde un historique des mises à jour appliquées dans une table de la base de données. Il peut ainsi aisément amener n'importe quelle base vers l'état souhaité. Par conséquent, pas de risque d'appliquer deux fois le même script de mise à jour : Liquibase applique seulement les scripts qui n'ont pas encore été appliqués. Liquibase est aussi capable de défaire des changements, du moins pour certains types de changements. Vu que cela ne fonctionne pas pour tous les changements (les données d'une table supprimée, par exemple, ne peuvent pas être restaurées) il est préférable de ne pas trop compter sur cette fonctionnalité.

Dans Liquibase, les changements de la base de données sont aggrégés sous la forme "d'ensembles de changement", chacun représentant la mise à jour dans un format XML indépendant de la base de données. Ces ensembles de changement peuvent inclure tous les changements que vous pourriez appliquer à une base de données, de la création ou suppression de table à la création ou mise à jour de colonne, index ou clés étrangères :

<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.6"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.6
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.6.xsd">
  <changeSet id="1" author="john">
    <createTable tableName="department">
      <column name="id" type="int">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="name" type="varchar(50)">
        <constraints nullable="false"/>
      </column>
      <column name="active" type="boolean" defaultValue="1"/>
    </createTable>
  </changeSet>
</databaseChangeLog>

Les ensembles de changement peuvent aussi refléter des modifications à une table existante. Par exemple, l'ensemble de changement suivant représente le renommage d'une colonne :

<changeSet id="1" author="bob">
  <renameColumn tableName="person" oldColumnName="fname" newColumnName="firstName"/>
</changeSet>

Etant donné que cette représentation concerne la nature sémantique du changement, Liquibase est capable de réaliser correctement tant la mise à jour du schéma que la migration de données correspondante.

Liquibase peut aussi bien gérer les changements de données que de structure. Par exemple, l'ensemble de changement suivant insère une nouvelle ligne de données dans une table :

<changeSet id="326" author="simon">
  <insert tableName="country">
    <column name="id" valueNumeric="1"/>
    <column name="code" value="AL"/>
    <column name="name" value="Albania"/>
  </addColumn>
</changeSet>

Chaque ensemble de changements a un identifiant et un auteur, ce qui facilite la traçabilité et réduit le risque de conflit. Les développeurs peuvent tester leurs ensembles de changements à leur propre base de données et les archiver une fois qu'ils sont prêts. Bien sûr, l'étape suivante est de configurer Jenkins pour qu'il applique les mises à jour Liquibase à la base de données appropriée avant que les tests d'intégration ou que les déploiements d'applications ne soient réalisés, généralement en tant que partie du script de build ordinaire du projet.

Liquibase s'intègre bien dans le processus de build. Il peut être exécuté depuis la ligne de commande ou être intégré dans Ant ou Maven. Pour ce dernier, par exemple, vous pouvez configurer le "Maven Liquibase Plugin" comme suit :

<project>
  <build>
    <plugins>
      <plugin>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-plugin</artifactId>
        <version>1.9.3.0</version>
        <configuration>
        <propertyFileWillOverride>true</propertyFileWillOverride>
        <propertyFile>src/main/resources/liquibase.properties</propertyFile>
      </configuration>
    </plugin>
  </plugins>
</build>
...
</project>

En utilisant ainsi Liquibase avec Maven, vous pourriez mettre à jour une base de données cible vers le schéma courant en utilisant ce plugin:

$ mvn liquibase:update

Les informations de connexion par défaut à la base de données sont spécifiées dans le fichier src/main/resources/liquibase.properties, et ont l'aspect suivant :

changeLogFile = changelog.xml
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost/ebank
username = scott
password = tiger
verbose = true
dropFirst = false

Vous pouvez toutefois surcharger n'importe laquelle de ces propriétés depuis la ligne de commande, ce qui rend facile de configurer un build Jenkins pour mettre à jour différentes bases de données.

D'autres commandes similaires vous permettent de générer un script SQL (si vous avez besoin de le soumettre à votre administrateur de base données par exemple), ou de revenir à une version précédente du schéma.

Cela n'est bien sûr qu'un exemple d'une solution possible. D'autres équipes préfèrent maintenir manuellement une série de scripts SQL de mises à jour, voir écrivent leur propre solution. L'important est d'avoir un outillage vous permettant de mettre à jour différentes bases de données de façon fiable et reproductible lors de vos déploiements applicatifs.

12.2.3. Tests fumigatoires

N'importe quel déploiement automatisé sérieux a besoin d'être suivi par une série de tests fumigatoires automatisés. Un sous ensemble des tests d'acceptance automatisés peut faire un bon candidat. Les tests fumigatoires devraient être non intrusifs et relativement rapides. Ils doivent pouvoir être exécutés en production, ce qui est susceptible de restreindre le nombre d'actions possibles durant les tests.

12.2.4. Revenir sur des changements

Un autre aspect important à considérer lors de la mise en place du Déploiement Automatisé est comment revenir en arrière si quelque chose se passe mal. Cela est encore plus important si vous voulez implémenter du Déploiement Continu. Il est en effet critique de pouvoir revenir en arrière si besoin.

La mise en oeuvre varie grandement en fonction de l'application. Bien qu'il soit relativement simple de redéployer une version précédente d'une application en utilisant Jenkins (nous verrons cela plus loin dans ce chapitre), l'application n'est généralement pas le seul acteur en jeu. Un retour en arrière de la base de données à un état précédent est souvent à mettre en oeuvre.

Nous avons vu comment il est possible d'utiliser Liquibase pour les mises à jour de base de données, et bien sûr de nombreuses autres stratégies sont également possibles. Cependant, revenir à un état précédent de la base de données présente des défis particuliers. Liquibase, par exemple, permet de revenir sur certains changements de structure de la base de données, mais pas de tous. De plus, des données perdues (lors de la suppression de table par exemple) ne peuvent être restaurées en utilisant que Liquibase.

La façon la plus fiable de retourner à un état précédent est probablement de prendre une image de la base juste avant la mise à jour. Cette image peut ensuite être utilisée lors de la restauration. Une méthode efficace est d'automatiser ce processus dans Jenkins dans la tâche de déploiment, et de sauver l'image de la base et le fichier binaire déployable en tant qu'artéfacts. De cette façon, vous pouvez aisément restaurer la base de données en utilisant l'image sauvegardée et ensuite de redéployer l'application en utilisant le fichier déployable sauvé. Nous allons regarder un exemple de cette stratégie plus loin dans ce chapitre.