Blog Zenika

#CodeTheWorld

DevOps

Intégrer RPM avec Maven et Jenkins 1/2

Le mouvement DevOps fait de plus en plus parler de lui, et parmi les concepts qu’on y trouve, figure le packaging natif. Après quelques mois passés sur un projet livré en RPM, je vous propose de partager mon retour d’expérience sur le sujet. Pour faire simple, je suis convaincu par le packaging natif, surtout dans un cadre entreprise. Mais plutôt que de vous présenter ce qui a été fait en mission, je vous propose un cocktail Maven-RPM servi par Jenkins. Dans ce premier article, je découvre avec vous le rpm-maven-plugin que je n’avais encore jamais utilisé.

A la découverte de RPM

Cet article suppose une connaissance préalable de Maven, Jenkins sert surtout à illustrer la partie intégration continue. Avant de mettre les mains dans le code, je vous propose un tour d’horizon de RPM.
Pour commencer, RPM signifie RPM Package Manager (très geek le nom récursif =) et fait partie de la LSB en tant que gestionnaire de paquets officiel. Les puristes préfèreront le packaging Deb, mais pour des environnements cibles RHEL, le choix est vite fait. Le packaging natif apporte plus qu’une simple archive (zip, tar.gz, jar, war…) c’est un mélange de fichiers, de métadonnées et de scripts, une archive autonome et autosuffisante (aux dépendances près). Dans le cas de RPM, on dispose de plusieurs outils en ligne de commande (principalement la commande rpm) pour exploiter ces paquets. Outre les classiques installations et désinstallations, il est possible d’extraire beaucoup d’informations à partir d’un paquet.
Côté exploitation donc, on utilisera rarement la commande rpm directement, on lui préfèrera généralement des surcouches. La plus connue est probablement YUM qui propose en ligne de commandes un jeu d’instructions plus humain et de plus haut niveau (eg. yum install <nom> à la place de rpm -i <fichier>) et permet surtout de récupérer automatiquement les dépendances. Il n’est pas forcément nécessaire de passer par de la ligne de commande, il existe des surcouches web , et certains outils comme Chef sont capables de travailler avec des RPM. A noter qu’il est recommandé de signer ses RPM, en particulier pour un usage sur environnement cible.

Socle d’installation

Un fichier RPM, ce n’est pas juste des fichiers, des métadonnées et des scripts comme dit précédemment. C’est aussi des workflows d’exécution prédéfinis, signe d’une certaine maturité de l’outil. L’installation d’un paquet par exemple, passera par une série prédéfinie d’étapes qui correspondent chacunes à l’exécution d’une scriptlet. On peut y faire à peu près ce qu’on veut, comme créer un utilisateur dédié à l’application, enregistrer un service au démarrage du système.

Worflow simplifié d’installation :

exécution de la scriptlet %pre

copie des fichiers

exécution de la scriptlet %post

Worflow simplifié de désinstallation :

exécution de la scriptlet %preun

suppression des fichiers

exécution de la scriptlet %postun

Les scriptlets ne sont executées que si elles existent dans le RPM, et pour en savoir plus sur la construction de RPM, nous allons nous intéresser à la commande rpmbuild.

Construire des RPM

La construction de RPM n’est pas un exercice difficile en soit, il nécessite cependant quelques connaissances systèmes. La difficulté principale, se situera dans les paquets à créer, au cas par cas, en fonction de ce qu’on cherche à réaliser. Par exemple, comment réaliser une mise à jour (eg. yum upgrade ou rpm -U) d’une application en cours d’exécution ? C’est à mon sens un des points clés du DevOps : certaines questions de packaging pourront être adressées par les développeurs, d’autres en revanche nécessiteront une collaboration avec d’autres acteurs (administrateurs systèmes, DBA etc).
La commande rpmbuild donc, s’appuie sur des fichiers spec qui décrivent au moins un paquet. Le fichier spec est l’équivalent de notre pom.xml, et va contenir les éléments suivants :

  • Une (des) fiche(s) d’identité
  • Les relations de dépendances (dans les deux sens)
  • Des scripts
  • Un changelog

On distinguera plusieurs types de scripts, ceux exécutés au build-time, et ceux embarqués dans le RPM, les scriptlets.
Le cycle de vie de construction passe par les sections suivantes, l’équivalent des phases de Maven :

%prep -> à peu près process-sources

%build -> compile

%check -> test

%install -> à peu près prepare-package[1]

%files -> particulier, on peut l’assimiler de loin à verify

%clean -> n’est pas un cycle à part entière comme pour Maven

Je ne vais pas détailler l’ensemble aujourd’hui, parce qu’il y a beaucoup à dire, et qu’aujourd’hui nous allons utiliser le rpm-maven-plugin qui nous masque l’invocation de rpmbuild et la création de la spec RPM.
On notera cependant qu’il existe une étape de construction dans rpmbuild qui s’utilise traditionnellement avec les outils make, automake et autoconf. J’ai donc identifié deux intégrations possibles entre Maven et RPM :

  • naturelle du point de vue RPM : invocation de mvn dans la section %build
  • naturelle du point de vue Maven : extension du packaging à l’aide d’un plugin dédié

Chaque approche essaye de résoudre le conflit sur le point d’entrée du build, que les deux outils révendiquent de par leurs conceptions respectives. On notera cependant que la construction du fichier RPM sera toujours postérieure au build Maven.

Sirkuttaa

Afin d’illustrer la construction de RPM, il est nécessaire d’avoir plus qu’un simple livrable Java à construire. J’ai donc décidé de créer un client twitter en ligne de commande. Pour offrir une expérience utilisateur convenable, la ligne de commande ne doit pas commencer par java -jar ou nécessiter de faire passer toute la configuration à grand renfort de -Dsystem.properties. En terme de configuration, il doit être possible de choisir le nombre de tweets maximum à récupérer et de définir un timeout global.
Le paquet contiendra donc :

un script dans /usr/bin

des jar dans /usr/lib

de la configuration dans /etc

C’est ainsi qu’est né Sirkuttaa, fruit d’un intense brainstorming avec Google Traduction. Après être parti à la découverte des API Twitter (oui sirkuttaa est mon hello world Twitter :), en l’espace de quelques classes j’obtenais mon client en ligne de commande. Il ne restait donc plus qu’à attaquer (enfin !) la création du RPM. C’est donc armé d’un moteur de recherche, que je suis allé voir ce que Maven a à m’offrir.

Plugins Maven

N’ayant jamais construit de RPM à l’aide de Maven, un petit travail de recherche était nécessaire. J’ai rapidement trouvé le maven-rpm-plugin [2] et le rpm-maven-plugin. Aucun de ces plugins n’est activement maintenu, la mise à jour la plus récente remonte à 2010, une version alpha!
Après quelques tests, je découvre que ces plugins sont en fait des wrappers à la commande rpmbuild, qui fonctionnent un peu comme le maven-assembly-plugin. La documentation est aussi assez claire là-dessus: l’utilisation de ces plugins suppose une connai
ssance préalable dans la construction de RPM. Cela-dit, cette connaissance est à relativiser. Pour des paquets simples, je pense que ce n’est pas critique. Par contre c’est indispensable lorsqu’on commence à utiliser des mécanismes plus avancés comme les scriptlets. Le rpm-maven-plugin semble cependant se démarquer : dernière mise à jour plus récente, et c’est celui évoqué dans toutes les présentations que j’ai vues sur le sujet. J’ai en toute logique choisi de l’utiliser.
A l’utilisation, le plugin s’avère un peu bancal. Le vocabulaire adopté n’est pas toujours celui de RPM (par exemple license devient copyright). Ensuite, pas sa conception, le plugin mélange maladroitement les sections %install et %files.

<plugin>
	<groupId>org.codehaus.mojo</groupId>
	<artifactId>rpm-maven-plugin</artifactId>
	<version>2.0.1</version>
	<configuration>
		<name>sirkuttaa</name>
		<group>Zenika/Blog</group>
		<packager>Zenika</packager>
		<needarch>noarch</needarch>
		<copyright>GPLv2+</copyright>
		<defaultFilemode>644</defaultFilemode>
		<defaultUsername>root</defaultUsername>
		<defaultGroupname>root</defaultGroupname>
		<defaultDirmode>755</defaultDirmode>
		<requires>
			<require>java</require>
		</requires>
		<mappings>
			<mapping>
				<directory>/usr/lib/sirkuttaa</directory>
				<dependency/>
				<sources>
					<source>
						<location>${project.build.directory}/${project.build.finalName}.jar</location>
					</source>
				</sources>
			</mapping>
			<mapping>
				<directory>/usr/bin</directory>
				<directoryIncluded>false</directoryIncluded>
				<filemode>755</filemode>
				<sources>
					<source>
						<location>src/main/scripts</location>
					</source>
				</sources>
			</mapping>
			<mapping>
				<directory>/etc</directory>
				<directoryIncluded>false</directoryIncluded>
				<configuration>noreplace</configuration>
				<sources>
					<source>
						<location>src/main/config</location>
					</source>
				</sources>
			</mapping>
		</mappings>
	</configuration>
</plugin>

 
Et surtout, le plugin invoque explicitement la commande rpmbuild, ce qui faire perdre sa portabilité au build. Selon moi, je dois pouvoir arriver sur un projet Maven et exécuter mvn install, si ce n’est pas le fonctionnement par défaut (la convention), c’est que le build est à revoir (idéalement j’invoque juste mvn et une phase par défaut a été prévue). Le développeur doit pouvoir reproduire le build, et sous Windows ce n’est possible qu’avec Cygwin et son paquet rpm-build. Heureusement, Maven propose via ses mécanismes de profils de ne générer le RPM que si la commande rpmbuild est disponible, et en plus ce n’est pas verbeux :

<!-- un "simple" if file /usr/bin/rpmbuild exists then attach rpm -->
<profiles>
	<profile>
		<id>rpmbuild</id>
		<activation>
			<file>
				<exists>/usr/bin/rpmbuild</exists>
			</file>
		</activation>
		<build>
			<plugins>
				<plugin>
					<groupId>org.codehaus.mojo</groupId>
					<artifactId>rpm-maven-plugin</artifactId>
					<executions>
						<execution>
							<phase>package</phase>
							<goals>
								<goal>attached-rpm</goal>
							</goals>
						</execution>
					</executions>
				</plugin>
			</plugins>
		</build>
	</profile>
</profiles>

Au final, j’ai tout de même eu l’impression de me battre avec l’outil et de devoir faire sans arrêt des compromis, un peu comme avec JPA. Mais j’obtiens tout de même un RPM, dont l’installation se déroule correctement, et je suis capable d’utiliser Sirkuttaa. Je peux même exécuter quelques commandes pour par exemple connaître la liste des des fichiers, voire la liste des fichiers de configuration :

$ rpm -ql sirkuttaa
/etc/sirkuttaa
/etc/sysconfig/sirkuttaa
/usr/bin/sirkuttaa
/usr/lib/sirkuttaa
/usr/lib/sirkuttaa/commons-io-2.3.jar
/usr/lib/sirkuttaa/jackson-core-asl-1.9.7.jar
/usr/lib/sirkuttaa/jackson-mapper-asl-1.9.7.jar
/usr/lib/sirkuttaa/sirkuttaa-1.jar
$ rpm -qc sirkuttaa
/etc/sirkuttaa
/etc/sysconfig/sirkuttaa

Continuous Delivery

La construction du RPM en elle même ne présente au final pas tant d’intérêt que ça, si ce n’est que l’équipe de développement est impliquée très tôt dans la construction des livrables pour les environnements cibles. Si on ajoute une petite dose d’intégration continue à notre cocktail Maven-RPM, on obtient à tout moment un livrable vérifié, stable, prêt à être installé. On peut aller plus loin en signant automatiquement tout RPM construit, voire en le déployant automatiquement : rappelez-vous, un paquet natif est autonome[3].
La seule chose à prévoir du côté de Jenkins, c’est évidemment d’installer rpmpbuild. Le rpm-maven-plugin attache automatiquement le RPM généré au Reactor de Maven, il sera donc automatiquement archivé par Jenkins. Il sera également déployé (au sens Maven cette fois) automatiquement. On obtient donc clé en main de quoi faire de la livraison continue, sans avoir à fournir d’autre effort que créer un job de type Maven.
Récupération des artéfacts du build

Conclusion

Arrivé ici, vous devriez normalement vous dire qu’au final vous n’avez pas appris grand chose sur RPM, c’est normal. Pour ma part j’ai découvert avec vous le rpm-maven-plugin et je dois avouer que je suis plutôt surpris. J’avais un à-priori très négatif sur ce plugin, et je pense aujourd’hui que c’est probablement la solution la plus efficace pour construire des RPM avec un projet Maven. On obtient à moindre coût une infrastructure de livraison continue, sans effort important de configuration! Ça ne dispense par contre pas de connaître rpmbuild et la construction classique de RPM, ce que je vous propose de découvrir dans un prochain article, avec à nouveau Sirkuttaa en guise d’exemple.
En attendant, vous pouvez toujours jouer avec Sirkuttaa :

$ mvn clean package
$ sudo rpm -i target/rpm/sirkuttaa/RPMS/noarch/sirkuttaa-1-1.noarch.rpm
$ sirkuttaa ZenikaIT
> Retour sur le #MongoDB Day Paris: http://t.co/M7uwPdgg, merci @10gen pour cet évènement!
> Bilan, photos et interviews vidéos des équipes de la #zNight, tout est ici : http://t.co/3XifivM9 #hackathon #GoogleTV #Android
> RT @alecharp: Prochain @HckrgartenParis chez @ZenikaIT. Une nouvelle édition pleine de bonne humeur et de commiter! http://t.co/sSw2ZRHu
> La 1ère #zNight est finie, bravo à tous pour vos super idées #Android #GoogleTV, un grand, grand merci à #Google et #Sony pour leur soutien
> Plus que quelques minutes à la #zNight avant les démos des applis réalisées! #Android #GoogleTV
> RT @rolios: Hey @googletvdev we have about 20 developers spending their night coding on #googleTV in France! Hope to get nice apps! #zni ...
> RT @queinnec: #GoogleTV by #Sony http://t.co/lb1yHLUq
> #Sony #GoogleTV at Zenika, admiring the remote featuring 2 sides, keyboard + classic remote… very nice looking! #Android #hackathon
> Zenika organise une conférence sur l'architecture de #Varnish jeudi; il reste quelques places, inscrivez-vous vite sur http://t.co/oHhUl5WU
> Y'a plein de boissons énergisantes :) RT @n0tnull: Ce soir hackathon sur une Google TV, ça va être funky vu le peu de sommeil... *_*

Les sources de l’article sont récupérables sur github.

Notes

[1] En adoptant cette correspondance entre section rpmbuild et phase Maven on peut faire une croix sur les tests d’intégration
[2] Il existe un second maven-rpm-plugin que je n’ai pas testé
[3] Attention aux régressions dans la spec RPM, il faut de toute façon tester son RPM (installation, mise à jour …)

2 réflexions sur “Intégrer RPM avec Maven et Jenkins 1/2

  • Excellent article qui permet de voir comment avoir une production de packages natifs RPM dans un cycle Maven pur.
    Pour le déploiement continu, quelques jobs seraient à ajouter sur un jenkins :

    – Job de déploiement du RPM nouvellement construit vers un repository yum (servi par HTTPd ou Artifactory)
    – Job de mise à jour (ou installation) du package sur l’instance devant être testé, via yum/zypper

    Peut être à l’occasion d’un prochain article ? 🙂

    Bravo

    Répondre
  • Dridi Boukelmoune

    Excellent, à ce point ? J’espère que la seconde partie sera à la hauteur !

    Pour le « continuous deployment », il faut en effet mettre beaucoup plus de moyens. C’est un sujet très ambitieux et j’ai préféré m’arrêter à la « continous delivery » tout simplement parce qu’elle est induite par RPM.

    Pas d’article continous deployment dans ma TODO list, mais pourquoi pas.
    Merci 🙂

    Répondre

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading