Testez localement et automatiquement vos déploiements Puppet grâce à Docker

Romain Niveau

Puppet est un outil permettant de déployer rapidement des applications et des outils sur les différents environnements d'un projet.

Basés sur un modèle maître/agents, les déploiements, avec Puppet, sont incrémentaux et reproductibles d'un environnement à l'autre ou d'une instance de machine à une autre.

Néanmoins, tout n'est pas parfait et il peut être compliqué de tester ses développements en local ou de tester de manière automatisée les déploiements.

Heureusement, Docker va permettre d'améliorer tout cela.

Pour les besoins du tutoriel, un simple projet Node.Js illustrera les choses.

Les sources utilisées sont disponibles sur Github.

Pour vos suggestions et commentaires, un espace est créé sur le forum.
Commentez

- Titre : Testez localement et automatiquement vos déploiements Puppet grâce à Docker

- Auteur : Romain Niveau

- Parution : 28 avril 2017

- Public visé :

- Licence : Copyright ® 2017 Romain Niveau. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Besoin initial

- II. Tester en local grâce à Docker

- II-A. Création des images

- II-B. Provisionning

- II-C. Lancement

- III. Un peu d'automatisation

- IV. Conclusion

- Notes de la rédaction Developpez.com

I - Besoin initial

Sur ma mission actuelle, tous les déploiements sont effectués avec Puppet. Comme sur la plupart des projets, plusieurs environnements sont disponibles pour les différentes phases de développement : plateforme de développement, intégration, recette, préproduction et production.

Les applications sont packagées sous forme de RPM (format des packets sous Red Hat, CentOS) et un repository yum personnalisé est disponible pour les déployer.

La première problématique rencontrée est la suivante :

aucun environnement spécifique n'est dédié aux développements Puppet. Ceux-ci se font donc directement sur l'environnement de développement, au risque de parfois bloquer les déploiements d'applications qui se font en continu sur cet environnement ;

de plus, ces développements ne sont pas versionnés directement depuis l'environnement et doivent être reportés sur le poste du développeur pour qu'il puisse les pusher.

II - Tester en local grâce à Docker

Pour répondre à cette problématique, Docker semble être la solution la plus pertinente. Il va permettre de démarrer facilement son propre environnement en local semblable à la dev et de tester tout type de machine.

II-A - Création des images

Tous les services utilisés sur les environnements sont démarrés à partir de systemd sur une base centOS 7. L'image suivante va servir de base à la mise en place de la solution :

1. FROM centos:7

3. ENV PUPPET_VERSION 3.8.7

5. RUN rpm --import https://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs && \

6. rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm

7. RUN yum install yum-utils -y && \

8. yum-config-manager --enable centosplus >& /dev/null && \

9. yum clean all

10. RUN yum install puppet-server-$PUPPET_VERSION -y \

11. && yum clean all

13. RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \

14. rm -f /lib/systemd/system/multi-user.target.wants/*;\

15. rm -f /etc/systemd/system/*.wants/*;\

16. rm -f /lib/systemd/system/local-fs.target.wants/*; \

17. rm -f /lib/systemd/system/sockets.target.wants/*udev*; \

18. rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \

19. rm -f /lib/systemd/system/basic.target.wants/*;\

20. rm -f /lib/systemd/system/anaconda.target.wants/*;

22. VOLUME [ "/sys/fs/cgroup" ]

24. CMD ["/usr/sbin/init"]

Cette image réalise deux choses importantes :

- installation de Puppet à partir d'une base CentOS 7 (ligne 5 à 11). Il s'agit du dernier RPM disponible et non de la dernière version de Puppet ;

- activation de systemd. Par défaut, l'image CentOS ne permet pas l'exécution de systemd et l'erreur suivante est renvoyée : systemd failed to get d-bus connection.
Les services utilisant systemd, cette image l'active et permettra de simuler l'environnement souhaité (ligne 13 à 22).

La philosophie de Docker prône un container pour un service. C'est pourquoi systemd est désactivé dans l'image de base CentOS. L'utilisation faite ici de Docker va à l'encontre de ce principe, ceci afin de simuler une machine complète et non un container classique.

Comme évoqué plus haut, Puppet fonctionne en master/slaves, deux images sont nécessaires. Voici le master :

1. FROM xebia/centos7-systemd:latest

3. EXPOSE 8140

5. VOLUME ["/etc/puppet"]

7. COPY ./puppet-master.sh /opt/

8. COPY ./puppet-master.conf /etc/puppet/puppet.conf

9. COPY ./puppet.service /etc/systemd/system/

11. RUN chmod +x /opt/puppet-master.sh

13. RUN chmod +x /etc/systemd/system/puppet.service

15. RUN ln -s /etc/systemd/system/puppet.service /etc/systemd/system/multi-user.target.wants/puppet.service

17. CMD ["/usr/sbin/init"]

- Cette image se base sur celle créée précédemment.

- Les agents Puppet communiquent avec le master via le port 8140. Il est donc exposé ligne 3.

- Ligne 7 à 9, copie des fichiers nécessaires au démarrage du service puppet-master.

- Ligne 15, activation du service puppet-master qui sera démarré automatiquement via systemd.

- Ligne 17, la commande /usr/sbin/init permet d'initialiser le container ainsi que les services systemd (donc le puppet-master).

Voici une description rapide des fichiers copiés dans cette image :

- puppet-master.sh : élimine tous les certificats enregistrés dans le puppet-master. Ceci permet de lancer plusieurs fois un container agent avec le même nom. Il démarre également Puppet en mode master ;

- puppet-master.conf : configuration de Puppet. Point important, les certificats SSL sont autoacceptés afin de permettre l'automatisation de la création d'agents ;

- puppet.service : fichier de description de service pour systemd démarrant le puppet-master.sh.

Voici l'image de l'agent Puppet :

1. FROM xebia/centos7-systemd:latest

3. COPY ./puppet-agent.sh /opt/

5. COPY ./puppet-agent.conf /etc/puppet/puppet.conf

7. RUN chmod +x /opt/puppet-agent.sh

9. CMD ["/usr/sbin/init"]

Plus simple, elle copie un script permettant la connexion au puppet-master.

II-B - Provisionning

À présent, toutes les images nécessaires au développement en local sont disponibles. Pour faciliter l'utilisation de celles-ci, voici un fichier docker-compose décrivant les containers à lancer :

1. version: '2'

2. services:

3. master:

4. image: xebia/puppet-master

5. hostname: master.docker

6. ports:

7. - "8140:8140"

8. cap_add:

9. - SYS_ADMIN

10. volumes:

11. - ./puppet/modules:/etc/puppet/modules

12. - ./puppet/hieradata:/etc/puppet/hieradata

13. - ./site.pp:/etc/puppet/manifests/site.pp

14. - ./hiera.yaml:/etc/puppet/hiera.yaml

16. repo:

17. image: xebia/puppet-agent

18. hostname: repo.docker

19. ports:

20. - "80"

21. links:

22. - master

23. depends_on:

24. - master

25. cap_add:

26. - SYS_ADMIN

27. volumes:

28. - ./repo:/opt/repo/

Sans rentrer dans les détails de ce fichier, voici les points essentiels.

- Deux services sont présents : le puppet master et le service repo permettant d'avoir un repository yum local. Le principe d'installation d'un repository yum personnel étant hors scope de l'article, je vous invite à voir le code source pour plus de détails.

- Chaque service possède la capacité SYS_ADMIN nécessaire au bon fonctionnement de systemd.

- Les volumes mis à disposition via le docker-compose.yml permettent la mise en place d'un « hot reload » des sources Puppet. Ainsi il ne sera pas nécessaire de relancer les containers pour tester les développements.

II-C - Lancement

Tout est à présent prêt pour tester les développements en local. Pour ce faire, il suffit de lancer les commandes suivantes :

1. $> docker-compose up -d

2. Creating network "articlepuppetdocker_default" with the default driver

3. Creating articlepuppetdocker_master_1

4. Creating articlepuppetdocker_repo_1

Cette commande lance la base nécessaire au développement. À noter le nom des containers créés qui vont servir par la suite.

1. $> docker exec articlepuppetdocker_repo_1 /opt/puppet-agent.sh

2. $> docker exec articlepuppetdocker_repo_1 /usr/bin/createrepo /opt/repo

3. Spawning worker 0 with 1 pkgs

4. Spawning worker 1 with 0 pkgs

5. Spawning worker 2 with 0 pkgs

6. Spawning worker 3 with 0 pkgs

7. Workers Finished

8. Saving Primary metadata

9. Saving file lists metadata

10. Saving other metadata

11. Generating sqlite DBs

12. Sqlite DBs complete

La première commande permet à l'agent d'attendre que le puppet master soit up, puis lance Puppet afin qu'il provisionne le nécessaire au repository local.

La deuxième commande crée le repository en tant que tel avec les RPM stockés dans le répertoire ./repo de la machine.

À présent, le développement local porte sur la machine app1. Pour tester ce développement, la commande suivante permet d'instancier le container :

$> docker run -h app.docker --rm --cap-add=SYS_ADMIN --name puppet_test_1 -p 3000:3000
-h app1.docker -e FACTER_environment="dev" --link articlepuppetdocker_master_1
--link articlepuppetdocker_repo_1 --network articlepuppetdocker_default xebia/puppet-agent

Les paramètres importants de cette commande sont :

- --cap-add=SYS_ADMIN : comme pour les autres containers, cette capacité est nécessaire pour systemd ;

- -e FACTER_environment="dev" : variable d'environnement Puppet permettant de spécifier l'environnement du container. Elle pourra être passée à « prod » pour tester la configuration de prod ;

- Les deux --link pour que le container puisse communiquer avec le puppet master et le repo (nom de container obtenu lors de la commande docker-compose) ;

- --network articlepuppetdocker_default afin de connecter le container au réseau créé par la commande docker-compose.

Maintenant, il est possible de lancer autant de fois que nécessaire l'agent puppet pour tester le développement :

$> docker exec puppet_test_1 puppet agent -t

Dans l'exemple de l'article, le curl suivant permet de valider les déploiements :

$> curl localhost:3000
Hello World!

III - Un peu d'automatisation

Nous pouvons maintenant tester les développements Puppet en local. Mais, toujours grâce à Docker, il est possible de pousser le principe plus loin et de tester automatiquement le déploiement de l'application sur les différentes plateformes.

L'application nodeJs écoute le port 3000 sur la dev, mais le port 8000 en production. Ceci est une version simpliste de ce que l'on trouve dans la vraie vie. En fonction de l'environnement, les configurations des applications et des machines changent. Ces changements sont visibles dans les fichiers hieradata et il n'est pas rare de voir un oubli de configuration sur un environnement en particulier.

Encore une fois, Docker va permettre de tester tout ça facilement. Un des paramètres parmi ceux utilisés pour lancer le container de test (FACTER_environment) permet de spécifier l'environnement où se déploie virtuellement l'application.

Ainsi, il est possible de tester n'importe quelle configuration de déploiement. Voici un exemple de script shell permettant de tester tous les environnements de l'application (dev, prod) run_jenkins.sh :

1. #!/usr/bin/env bash

3. docker-compose up -d

4. docker exec articlepuppetdocker_repo_1 /opt/puppet-agent.sh

5. docker exec articlepuppetdocker_repo_1 /usr/bin/createrepo /opt/repo

7. # Test

8. EXIT=0

9. for env in dev prod

10. do

11. echo "Launch container"

12. docker run -h app1.docker.$env -d --cap-add=SYS_ADMIN --name puppet_test_1 -e FACTER_environment="${env}" --link articlepuppetdocker_master_1 --link articlepuppetdocker_repo_1 --network articlepuppetdocker_default xebia/puppet-agent

13. echo "Execute puppet"

14. docker exec puppet_test_1 puppet agent -t

15. RETURN=$?

16. if [ $RETURN -ne 2 ] && [ $RETURN -ne 0 ]

17. then

18. echo "Fail in puppet run ${env}."

19. EXIT=1

20. else

21. echo "Success in puppet run ${env}."

22. fi

23. echo "Stop container"

24. docker stop puppet_test_1

25. echo "Rm container"

26. docker rm -v puppet_test_1

27. done

Intégré dans une usine logicielle type Jenkins, ce script permettra de tester automatiquement tous les environnements pour toutes les machines.

IV - Conclusion

Cette solution a été mise en place suite à un besoin réel chez un client. Exécuté toutes les nuits à travers Jenkins, le script permet le test de 12 types de machines différents sur 6 environnements soit 72 types de déploiements. Ce script permet d'éviter les oublis de configuration sur les environnements post-dev.

La partie développement en local facilite grandement la vie de l'équipe DevOps qui ne bloque plus les développeurs pour tester les développements Puppet. Ils peuvent également travailler avec leur IDE préféré et avoir tous les outils classiques d'un développeur à disposition.

Notes de la rédaction Developpez.com

Nous remercions Xebia pour l'autorisation à publier ce tutoriel. Nos remerciements également à Winjerome pour la mise au gabarit Developpez.com et Claude Leloup pour sa relecture orthographique.