Aller au contenu principal

Build de RPM avec fpm-cookery et Docker

· 7 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Dans beaucoup de sociétés, on ne s'inquiète pas de la façon dont les artefacts d'applications sont gérés pour être déployées sur les serveurs: fichiers compressés ou binaires combinés à un ensemble de scripts. Mais voila, il faut garder un certain contrôle et une certaine organisation pour déployer des applications correctement, même si avec des outils comme Ansible, Chef ou Puppet on arrive à s'en passer.

Cependant gérer toutes les étapes du déploiement, de la mise à jour des artefacts à la restauration avec des scripts peut être compliqué. C'est pourquoi utiliser le gestionnaire de paquets du système d'exploitation, tel que DNF (CentOS) ou APT (Debian, Ubuntu), sera beaucoup plus aisé car il gère correctement les upgrade de versions.

Sur le papier génial mais voila les administrateurs systèmes ne sont pas des spécialistes de ces gestionnaires de packages. En effet, ils nécessitent la création d'un ou plusieurs fichiers de configuration qui ne sont pas forcément simple.

C'est là qu'intervient des applications comme fpm-cookery, que je vais vous présenter qui vont vraiment simplier le travail. Et le coupler à docker est un vrai bonheur.

Présentation de FPM et Fpm-Cookery

Au départ l'objectif de fpm était de rendre aussi simple que possible la création de packages natifs pour plusieurs plates-formes, le tout sans avoir à apprendre les subtilités du format de packaging de chaque distribution (.deb, .rpm, etc.) et des outils nécessaires au build.

Avec une seule commande, fpm permet de créer des packages à partir de diverses sources, notamment des gems Ruby, des modules Python, des archives tar et de simples répertoires.

Dans la pratique, cependant, les choses sont souvent plus compliqué. En effet il manque quelques briques éssentielles à fpm. C'est là qu'intervient fpm-cookery. fpm-cookery fournit à fpm fournit les pièces manquantes pour créer une infrastructure de construction automatique de packages. Inspiré par des projets comme Homebrew, fpm-cookery construit des packages basés sur des simples recettes écrites en Ruby.

fpm-cookery gère tous les aspects du processus de construction. Entre autres choses, il peut :

  • télécharger le code source à partir de lien, de clone de repository
  • d'automatiser les phases de build et d'installation
  • d'utiliser simples pour packager le tout

Ce qui manque aussi c'est une bonne documentation mais il existe plusieurs repos git pour vous aider à construire vos propres recettes. Documentation officielle

Étudions des recettes

Voici la structure d'une recette fpm-cookery pour packager Haproxy qui est un des plus complets mis à disposition par l'auteur de fpm-cookery.

class Haproxy < FPM::Cookery::Recipe
  # Info
  homepage 'http://www.haproxy.org/'
  source 'http://www.haproxy.org/download/1.8/src/haproxy-1.8.20.tar.gz'
  md5 'abf9b7b1aa84e0839501e006fc20d7fd'
  name 'haproxy'
  description 'The Reliable, High Performance TCP/HTTP Load Balancer'
  version '1.8.20'
  revision '1'

  # Platforms specific dependencies
  platforms [:debian, :ubuntu] do
    depends 'lua5.3', 'libssl1.0.0', 'zlib1g', 'libpcre3'
    build_depends 'liblua5.3-dev', 'libssl-dev', 'zlib1g-dev', 'libpcre3-dev'
  end
  platforms [:centos, :redhat] do
    depends 'openssl', 'pcre', 'zlib', 'logrotate', 'chkconfig', 'initscripts', 'shadow-utils', 'setup'
    build_depends 'openssl-devel', 'pcre-devel', 'zlib-devel', 'lua-devel'
  end

  config_files '/etc/haproxy/haproxy.cfg'

  pre_install 'pre-install'
  post_install 'post-install'
  pre_uninstall 'pre-uninstall'
  post_uninstall 'post-uninstall'

  def build
    build_flags = {
      'TARGET' => 'linux2628',
    }

    if %w(ubuntu debian).include?(FPM::Cookery::Facts.platform.to_s)
      build_flags['LUA_INC'] = '/usr/include/lua5.3'
    end

    make build_flags

    # halog
    make '-C', 'contrib/halog'
  end

  def install
    with_trueprefix do
      make :install, 'DESTDIR' => destdir, 'PREFIX' => prefix, 'DOCDIR' => haproxy_doc
    end

    etc('haproxy').install workdir('haproxy.cfg')

    etc('logrotate.d').install(workdir('haproxy.logrotate'), 'haproxy')

    etc('rc.d/init.d').install('examples/haproxy.init', 'haproxy')
    chmod 0755, etc('rc.d/init.d/haproxy')

    #GZip man page
    gzip_path = find_executable 'gzip'
    safesystem gzip_path, man1('haproxy.1')

    haproxy_doc.install Dir['doc/*']

    share('haproxy').install Dir['examples/errorfiles/*']

    var('lib/haproxy').mkpath

    # halog
    bin.install 'contrib/halog/halog'
  end

  def haproxy_doc(path = nil)
    doc("haproxy-#{version}")
  end
end

Cela commence toujours par class Redis < FPM::Cookery::Recipe et ensuite viennent les propriétés et les méthodes permettant de décrire le recette :

Les propriétés

Le code source

class Haproxy < FPM::Cookery::Recipe
  # Info
  homepage 'http://www.haproxy.org/'
  source 'http://www.haproxy.org/download/1.8/src/haproxy-1.8.20.tar.gz'
  md5 'abf9b7b1aa84e0839501e006fc20d7fd'
  name 'haproxy'
  description 'The Reliable, High Performance TCP/HTTP Load Balancer'
  version '1.8.20'
  revision '1'

  config_files '/etc/haproxy/haproxy.cfg'

  pre_install 'pre-install'
  post_install 'post-install'
  pre_uninstall 'pre-uninstall'
  post_uninstall 'post-uninstall'

Parmi ces propriétés certaines vont être utilisé pour construire le package mais la plus importante est d'indiquer ou se trouve le code source. Ici on indique une simple URL mais on peut aussi indiquer un repo git comme ceci :

  name     'jq'
  version  '1.5'
  revision '1'
  homepage 'http://stedolan.github.io/jq/'
  source   'https://github.com/stedolan/jq', :with => :git, :tag => "jq-#{version}"

Vous remarquez ici l'utilisation de la variable #{version}.

Parmi les autres propriétés on indique les noms des scripts, si besoin de pre-post installation et désinstallation.

Les dépendances

Pour indiquer les dépendances in utilise cette syntaxe :

  # Platforms specific dependencies
  platforms [:debian, :ubuntu] do
    depends 'lua5.3', 'libssl1.0.0', 'zlib1g', 'libpcre3'
    build_depends 'liblua5.3-dev', 'libssl-dev', 'zlib1g-dev', 'libpcre3-dev'
  end
  platforms [:centos, :redhat] do
    depends 'openssl', 'pcre', 'zlib', 'logrotate', 'chkconfig', 'initscripts', 'shadow-utils', 'setup'
    build_depends 'openssl-devel', 'pcre-devel', 'zlib-devel', 'lua-devel'
  end

Comme les packages sont différents en fonction l'os de destination on indique chacun d'eux.

Les scripts de build et d'installation

Cela correspond aux étapes ./configure, make, make install.

  def build
    build_flags = {
      'TARGET' => 'linux2628',
    }

    if %w(ubuntu debian).include?(FPM::Cookery::Facts.platform.to_s)
      build_flags['LUA_INC'] = '/usr/include/lua5.3'
    end

    make build_flags

    # halog
    make '-C', 'contrib/halog'
  end

  def install
    with_trueprefix do
      make :install, 'DESTDIR' => destdir, 'PREFIX' => prefix, 'DOCDIR' => haproxy_doc
    end

    etc('haproxy').install workdir('haproxy.cfg')

    etc('logrotate.d').install(workdir('haproxy.logrotate'), 'haproxy')

    etc('rc.d/init.d').install('examples/haproxy.init', 'haproxy')
    chmod 0755, etc('rc.d/init.d/haproxy')

    #GZip man page
    gzip_path = find_executable 'gzip'
    safesystem gzip_path, man1('haproxy.1')

    haproxy_doc.install Dir['doc/*']

    share('haproxy').install Dir['examples/errorfiles/*']

    var('lib/haproxy').mkpath

    # halog
    bin.install 'contrib/halog/halog'
  end

Si on prend l'exemple du build on y trouve la déclaration du paramètre build_flags, adapté pour une debian avec un if et le lancement de la commande make avec.

Dans la partie install on retrouve toute la partie copie des fichiers de configuration permettant de gérer aussi l'application comme logrotate, initd et l'installation de la documentation.

L'installation et le lancement de fpm-cookery avec Docker

Je vais vous présenter un moyen simple d'utiliser fpm-cookery avec Docker. Je vais utiliser une alpine, car plus légère. On installe simplement ruby et quelques librairies pour le build des applications. On passe ensuite à l'installation de fpm-cookery avec gem.

FROM alpine:3.13

RUN apk --no-cache --update add build-base rpm ruby ruby-dev ruby-etc gcc libffi-dev make libc-dev bash git curl tar unzip zlib-dev \
  && gem install fpm-cookery -v 0.35.1 && mkdir /ext

COPY . /

ENTRYPOINT ["/docker-entrypoint.sh"]

Comme je veux pouvoir enchainer plusieurs commandes j'indique que je veux utiliser un script en entrypoint.

!/bin/bash

 Signal fpm-cook that we are already running inside a container to avoid
 trying to start another one.
export FPMC_INSIDE_DOCKER=true

 Remove existing temporary directories to ensure a full build
cd recipes/$1
shift
fpm-cook "$@"
cp pkg/* /ext
rm -rf cache tmp-* pkg

Je vais utiliser des paramètres au bout de la ligne de commande docker pour lui indiquer quelle recette je veux exécuter.

Pour éviter d'embarquer trop de choses dans mon container je lui adjoins un fichier dockerignore.

**/cache/
**/pkg/
**/tmp*/
build/
recipes/

Je lui dis ne pas charger les recettes, car elles le seront monté via un volume. Ca évitera de rebuilder l'image à chaque modification d'une d'entre elles.

Ah oui :

  • les recettes se retrouvent dans le répertoire recipes/<votre appli à builder>/recipe.rb
  • il faut mettre aussi dans ce répertoire tous les fichiers utilisé dans votre recette : post-pre installation, ....

Je vous ai mis à dispostion dans mon compte gitlab tout ce qu'il faut pour le faire tourner après un simple git clone (à part installer docker).

Voilà il ne reste plus qu'à lancer le tout :

docker run -v $PWD/build:/ext -v $PWD/recipes:/recipes -it fpm-cook:0.1 python -p centos

Je demande ici de builder le package de python et ce que faire des packages RPM à destination d'une centos.

A partir de la vous pouvez construire un système qui va scruter les releases d'une appli et à chaque nouvelle version construire le package, l'installer sur une machine vagrant et tester si tout fonctionne.