Intégration continue avec Jenkins et Docker

Tout bon développeur se doit connaitre le très bon mix github et travis-ci. Je ne saurais que trop vous conseiller d’utiliser ces outils mais il y a toutefois différentes raisons qui pourraient vous pousser à ne pas vouloir utiliser ces services:

  • Les VM de travis sont des Ubuntu Linux 12.04 64 bit, ce qui n’est pas forcément votre cible.
  • Votre code est tellement secret et vous ne voulez pas forcément qu’il se retrouve sur le kloug
  • Vous voulez rester maitre de vos outils et de vos infrastructures.
  • Vous avez envie de faire votre propre travis-ci pour le lulz

Voilà donc une infrastructure que j’ai mise en place avec jenkins un très bon outil d’intégration continue (malgré le fait qu’il soit codé en java) et docker qui est un système de container linux ultra léger basé sur les cgroups, lxc et aufs

L’idée est la suivante pour un build:

  • Jenkins lance un container docker
  • Jenkins lance un slave temporaire dans le container
  • Le build se fait sur le container et jenkins récupère le résultat du build (logs, metadonnées, artifacts)
  • Quand le build est terminé, le container docker est détruit

De cette manière le build est toujours lancé dans un environnement sain et contrôlé, vous n’avez pas besoin de maintenir des slaves jenkins et ça scale particulièrement bien puisque vous pouvez rajouter des serveurs docker capables de lancer pleins de containers.

Voilà la manip sur Debian wheezy pour faire des containers Debian wheezy.

Installation de docker

root@ci:~# wget https://get.docker.io/builds/Linux/x86_64/docker-latest -O /usr/bin/docker
root@ci:~# curl -o /etc/init.d/docker https://raw.githubusercontent.com/dotcloud/docker/master/contrib/init/sysvinit-debian/docker
root@ci:~# chmod +x /usr/bin/docker /etc/init.d/docker
root@ci:~# addgroup docker
root@ci:~# update-rc.d -f docker defaults
root@ci:~# cat << EOF > /etc/default/docker
DOCKER_OPTS="-H 127.0.0.1:4243 -H unix:///var/run/docker.sock"
EOF
root@ci:~# service docker restart
root@ci:~# docker info
Containers: 0
Images: 0
Storage Driver: aufs
Root Dir: /var/lib/docker/aufs
Dirs: 0
Execution Driver: native-0.1
Kernel Version: 3.2.0-4-amd64
WARNING: No memory limit support
WARNING: No swap limit support

Installation de Jenkins

root@ci:~# echo "deb http://pkg.jenkins-ci.org/debian binary/" > /etc/apt/sources.list.d/jenkins.list
root@ci:~# wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | apt-key add -
root@ci:~# apt-get update
root@ci:~# apt-get install jenkins
root@ci:~# service jenkins start
root@ci:~# if curl http://localhost:8080 2>/dev/null | grep -iq jenkins; then echo "OK"; else echo "FAIL"; fi
OK

Création du container

Il faut maintenant créer une image qui servira de base pour notre container. On part d’un debootstrap et on installe ce qu’il faut pour lancer le slave jenkins.

root@ci:~# apt-get install debootstrap
root@ci:~# curl -o mkimage-debootstrap.sh https://raw.githubusercontent.com/dotcloud/docker/master/contrib/mkimage-debootstrap.sh
root@ci:~# chmod +x mkimage-debootstrap.sh
root@ci:~# ./mkimage-debootstrap -s wheezy64 wheezy http://ftp.fr.debian.org/debian/
root@ci:~# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
wheezy64            wheezy              7867d3b51969        About a minute ago   116.7 MB
root@ci:~# cat << EOF > Dockerfile
FROM wheezy64:wheezy
RUN apt-get install -y openssh-server openjdk-7-jre-headless
RUN useradd -m -s /bin/bash jenkins
RUN echo jenkins:jenkins | chpasswd
RUN mkdir -p /var/run/sshd
EXPOSE 22
CMD /usr/sbin/sshd -D
EOF
root@ci:~# docker build -t wheezy64:jenkins .
root@ci:~# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
wheezy64            jenkins             77f27af6a9d5        4 minutes ago       333.4 MB
wheezy64            wheezy              7867d3b51969        27 minutes ago      116.7 MB

On teste le container en le lançant et en essayant de se connecter avec l’user jenkins.

root@ci:~# jenkins=$(docker run -d -p 0.0.0.0:2222:22 -t -i wheezy64:jenkins)
root@ci:~# ssh jenkins@localhost -p 2222
jenkins@localhost's password: 
Linux ci 3.2.0-4-amd64 #1 SMP Debian 3.2.54-2 x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
jenkins@2eb438b131f4:~$ exit
logout
Connection to localhost closed.
root@ci:~# docker kill $jenkins && docker rm $jenkins

Jenkins et docker

C’est maintenant que la magie s’opère, on va installer le plugin docker sur jenkins via l’interface web et dans la configuration générale, paramètres “cloud”, on déclare notre image docker. On rajoute au passage un credential “Username with password” pour qu’il puisse se connecter en ssh sur le container (username: jenkins, password: jenkins) et on met un label “wheezy64” pour ces containers.

jenkins docker

Ensuite on configure un job qui va se lancer sur un container jenkins avec le système de labels.

jenkins job

On lance le job et voilà:

Started by user anonymous
Building remotely on 8c736a572ff6 (wheezy64) in workspace /home/jenkins/workspace/test
Finished: SUCCESS

Et maintenant ?

Vous pouvez adapter l’image docker pour y installer l’environnement de build dont vous avez besoin (base de données, compilateurs, interpréteurs python, ruby etc). Pour plus de flexibilité vous pouvez aussi installer sudo et donner tous les droits sans mot de passe à l’utilisateur jenkins, ainsi le script de build peut installer des packages et modifier la configuration de l’image.