Devops corner, épisode 2 : Chef, les limitations

Cette épisode sera dédié aux limitations découvertes dans Chef; cet outil, même si je l’adore, n’est pas parfait ! Il est de plus intéressant de constater que les limitations que je vais présenter ici sont souvent aussi présentes dans les outils concurrents.

Note : Il est fortement recommandé de lire le premier épisode avant celui-ci :).

La désinstallation

Prenons un exemple. Après quelques mois d’utilisation de MySQL, l’équipe décide de passer sur une base NoSQL type MongoDB. Que se passe-t-il au niveau de Chef ? Avant, une recette décrivait l’installation d’un serveur MySQL. On écrit donc une recette pour déployer MongoDB. Chef va t il pour autant désinstaller MySQL ? Et bien, non, sauf si on lui demande explicitement. Par exemple en exécutant la recette suivante :

package "mysql" do
  action :delete
end

Cela pose cependant quelques problèmes :

  • Au bout d’un moment, il va y avoir de nombreuses désinstallation dans les recettes, ce qui va les complexifier et alourdir leur maintenance. D’autant plus que ce code ne sert plus à rien une fois qu’il a été exécuté une fois.
  • La suppression ‘propre’ d’un package est souvent plus complexe que la désintallation d’un package : fichiers de configuration, de données, symlink …

Comment faire ?

Une solution est, en environment « Cloud », de reconstruire les instances sur changement majeur d’architecture : repartir de machines vierges qui seront directement déployées avec MongoDB. Mais tous les environments ne sont pas « Cloud ».

Il est aussi possible de décrire la totalité des packages installés, mais les recettes deviendraient trop nombreuses et trop dépendantes d’un système d’exploitation spécifique.

Nous n’avons pas encore trouvé de solution miracle. Nous sommes cependant partisans de limiter la taille du code à maintenir, donc de ne pas mettre de commandes de desinstallation dans les recettes quand c’est possible.

Note : la problématique est identique avec Puppet ou CfEngine.

Les dossiers conf.d

De nombreux programmes utilisent des dossiers de configuration. Par exemple, les dossiers sites-enabled et sites-available d’Apache2, ou le dossier /etc/apt/sources.list.d d’APT sur les Debian ou Ubuntu. Ce type de dossier est problématique à gérer avec Chef.

Pourquoi ? Prenons un exemple :

  1. Ecrivons une recette Chef qui déploie un Virtual Host site1.com dans un fichier nommé site1.com.conf
  2. Suite à des contrainte projets, le nom du Virtual Host change pour www.site1.com. On modifie donc la recette, et on change le nom du fichier en www.site1.com.conf
  3. « Et là, c’est le drame ». On lance Chef. Celui créé le nouveau fichier www.site1.com.conf. Mais il ne supprime pas l’ancien fichier site1.com.conf. On se retrouve donc avec 2 fichiers Virtual Host, qui vont tous les deux être chargés par Apache 2…

Je n’ai cité que 2 exemples (Apache 2 et APT), mais les dossiers de type conf.d sont extrêmement courants.

Nous avons cependant développé un contournement pour cela. Il fonctionne en demandant à Chef de supprimer les fichiers des dossiers conf.d quand ils ne sont pas installés par Chef :

delayed_exec "Remove useless apache2 vhost" do
  block do
    vhosts = find_resources_by_name_pattern(/^#{node.apache2.server_root}\/sites-enabled\/.*\.conf$/).map{|r| r.name}
    Dir["#{node.apache2.server_root}/sites-enabled/*.conf"].each do |n|
      unless vhosts.include? n
        Chef::Log.info "Removing vhost #{n}"
        File.unlink n
        notifies :reload, resources(:service => "apache2")
      end
    end
  end
end

Ce code utilise des bibliothèques que nous avons développées dans master-chef : notamment delayed_exec, qui permet d’exécuter du code ruby après l’exécution des recettes Chef.

Note : cela ne fonctionne bien que pour les dossiers qui sont complètement remplis par Chef. Si le système d’exploitation place aussi des fichiers dans un dossier, il faut faire en sorte que Chef ne les supprime pas, bien qu’il ne les installe pas. Un exemple est disponible ici, mais est hélas spécifique aux distributions de type Debian.

Les fichiers de configuration des services

Vous pouvez demander à Chef de redémarrer un service quand vous modifiez un fichier de configuration de service. Par exemple, vous pouvez demander à Chef de redémarrer Apache quand vous déployez un nouveau fichier de configuration. Sauf que

  • Pour faire cela, il faut déclarer un service apache2 dans Chef, avant le fichier de configuration
  • Lorsque que l’on déclare un service, Chef va vérifier que le service existe et est bien démarré.

Donc, si vous demandez le déploiement d’un fichier de configuration contenant malheureusement une erreur de syntaxe :

  1. Chef vérifie que le service existe et est démarré.
  2. Chef déploie le nouveau fichier de configuration.
  3. Chef redémarre le service, qui ne redémarre pas, puisque le fichier contient une erreur de syntaxe.
  4. Erreur …

Et ce n’est que le début : si vous relancez Chef, vous restez coincé à l’étape 1, sans pouvoir redéployer le fichier de configuration avec Chef (puisque vous n’arrivez jamais à cette étape).

Là aussi, il existe un contournement : déployer quand même les fichiers de configuration quand un service ne démarre pas. Cela n’évite pas la première erreur, mais au moins, on ne se retrouve pas bloqué.

Voici comment l’utiliser :

Chef::Config.exception_handlers << ServiceErrorHandler.new("apache2", ".*apache2.*")

La bibliothèque qui contient le ServiceErrorHandler est ici.

Note : ce problème ne doit normalement apparaître qu’en développement et en test. Normalement, en production, cela ne se produira jamais.

Conclusion

Que conclure de ces quelques exemples (j’en ai d’autres sous la main, hélas) ?

Que Chef n’est pas un produit parfait et qu’il a certaines limitations, et que dès que vous allez vouloir faire des choses complexes, vous allez atteindre des limitations. Faut il l’abandonner pour autant ? Non. Tous les produits ont des limites. Et le fait qu’il soit en Ruby (sans DSL intermédiaire comme Puppet) permet de l’étendre facilement et de contourner ces problèmes.