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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
FROM centos:7
ENV PUPPET_VERSION 3.8.7
RUN rpm --import https://yum.puppetlabs.com/RPM-GPG-KEY-puppetlabs && \
rpm -ivh http://yum.puppetlabs.com/puppetlabs-release-el-7.noarch.rpm
RUN yum install yum-utils -y && \
yum-config-manager --enable centosplus >& /dev/null && \
yum clean all
RUN yum install puppet-server-$PUPPET_VERSION -y \
&& yum clean all
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
FROM xebia/centos7-systemd:latest
EXPOSE 8140
VOLUME ["/etc/puppet"]
COPY ./puppet-master.sh /opt/
COPY ./puppet-master.conf /etc/puppet/puppet.conf
COPY ./puppet.service /etc/systemd/system/
RUN chmod +x /opt/puppet-master.sh
RUN chmod +x /etc/systemd/system/puppet.service
RUN ln -s /etc/systemd/system/puppet.service /etc/systemd/system/multi-user.target.wants/puppet.service
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 :
2.
3.
4.
5.
6.
7.
8.
9.
FROM xebia/centos7-systemd:latest
COPY ./puppet-agent.sh /opt/
COPY ./puppet-agent.conf /etc/puppet/puppet.conf
RUN chmod +x /opt/puppet-agent.sh
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
version: '2'
services:
master:
image: xebia/puppet-master
hostname: master.docker
ports:
- "8140:8140"
cap_add:
- SYS_ADMIN
volumes:
- ./puppet/modules:/etc/puppet/modules
- ./puppet/hieradata:/etc/puppet/hieradata
- ./site.pp:/etc/puppet/manifests/site.pp
- ./hiera.yaml:/etc/puppet/hiera.yaml
repo:
image: xebia/puppet-agent
hostname: repo.docker
ports:
- "80"
links:
- master
depends_on:
- master
cap_add:
- SYS_ADMIN
volumes:
- ./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 :
2.
3.
4.
$
>
docker-compose up -d
Creating network "articlepuppetdocker_default"
with the default driver
Creating articlepuppetdocker_master_1
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
$
>
docker exec articlepuppetdocker_repo_1 /opt/puppet-agent.sh
$
>
docker exec articlepuppetdocker_repo_1 /usr/bin/createrepo /opt/repo
Spawning worker 0
with 1
pkgs
Spawning worker 1
with 0
pkgs
Spawning worker 2
with 0
pkgs
Spawning worker 3
with 0
pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
#!/usr/bin/env bash
docker-compose up -d
docker exec articlepuppetdocker_repo_1 /opt/puppet-agent.sh
docker exec articlepuppetdocker_repo_1 /usr/bin/createrepo /opt/repo
# Test
EXIT
=
0
for
env in
dev prod
do
echo "Launch container"
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
echo "Execute puppet"
docker exec puppet_test_1 puppet agent -t
RETURN
=
$?
if
[ $RETURN
-ne 2
] &&
[ $RETURN
-ne 0
]
then
echo "Fail in puppet run
${env}
."
EXIT
=
1
else
echo "Success in puppet run
${env}
."
fi
echo "Stop container"
docker stop puppet_test_1
echo "Rm container"
docker rm -v puppet_test_1
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.