Lorsqu’on développe une application, la mise en place de l’environnement de build et la gestion des dépendances est un moment frustrant. Découvrons un cas d’utilisation de Docker permettant de résoudre cette problématique en laissant votre poste de travail propre.
Le contexte
Me voici nouvel équipier sur un projet et ma première tâche consiste classiquement à installer l’environnement sur mon poste de travail.
Le projet est une application dont le but est de fournir l’heure. Afin d’être multiplateforme, l’application est écrite en go. Les logs de l’application sont gérés par une bibliothèque tierce.
Enfin, pour simplifier le déploiement, l’application sera livrée en tant qu’image Docker.
Vous l’aurez compris, l’idée est de fournir les outils pour construire l’application ainsi que l’image Docker finale
Le code
Le code de l’application est ici très simple.
package main import ( log "github.com/Sirupsen/logrus" // Use some external dep "time" ) func main() { log.Println(time.Now()) }
Le seul point d’intérêt réside dans l’utilisation de logrus pour la gestion des logs.
Image de build
Dans le projet créons un fichier Dockerfile dédié au conteneur de build. Sans grande originalité je vous propose de le nommer build.Dockerfile, permettant ainsi la reconnaissance du format de fichier par un éditeur de texte.
FROM golang:1.5 RUN go get github.com/Sirupsen/logrus VOLUME $GOPATH/src/github.com/tauffredou/clock VOLUME /output WORKDIR $GOPATH/src/github.com/tauffredou/clock CMD GOOS=linux GOARCH=amd64 go build -o /output/clock
Quelques explications:
FROM golang:1.5
L’image de base utilisée est directement l’image « officielle » golang en précisant la version souhaitée. Nous évitons ainsi de gérer l’installation du SDK go.
RUN go get github.com/Sirupsen/logrus
go get permet de télécharger la dépendance logrus dans le GOPATH. D’autres solutions telles que godep existent pour gérer les dépendances plus finement mais nous ne les utiliserons pas ici.
VOLUME $GOPATH/src/github.com/tauffredou/clock
Ce volume contiendra les sources du projet. Pour être précis, il n’est pas obligatoire de le déclarer dans le Dockerfile mais cela fournit une information sur l’emplacement attendu directement.
VOLUME /output
Ce volume va recevoir les binaires compilés.
WORKDIR $GOPATH/src/github.com/tauffredou/clock
Par commodité le répertoire d’exécution est celui des sources du projet
CMD GOOS=linux GOARCH=amd64 go build -o /output/clock
La commande par défaut du conteneur compile le projet dans le répertoire /output
Image finale
FROM busybox COPY bin/clock / CMD /clock
FROM busybox
Le binaire étant statiquement compilé, il serait possible d’utiliser une image « scratch », l’image finale ne contenant alors que l’application. Cependant, pour disposer d’un minimum d’outils, nous utiliserons une image de base « busybox »
COPY bin/clock /
Le binaire est copié depuis le répertoire bin.
CMD /clock
Sans surprise, la commande par défaut exécute le programme
Un peu d’outillage
La démarche étant de limiter le plus possible les prérequis, il n’est pas judicieux de se reposer sur un autre outil de build, sauf si vous avez la certitude que celui-ci est présent sur les postes cibles. make, par exemple sera probablement présent sur un environnement linux mais moins généralement sur un poste windows. L’utilisation de Docker sous Windows impose l’utilisation d’un terminal cygwin, nous sommes donc certain d’avoir un shell sur tous les postes.
#!/bin/sh CWD=$(cd $(dirname $0);pwd) MAKE=$0 IMAGE=tauffredou/clock usage(){ cat<<-EOUSAGE make.sh [Action] Actions: builder create builder image build create binary using builder image image create final image run run the final image build-chain builder,build,image EOUSAGE } case $1 in builder) docker build -f build.Dockerfile -t $IMAGE-builder . ;; build) docker run -v gopath:/go/src -v $CWD:/go/src/github.com/tauffredou/clock -v $CWD/bin:/output $IMAGE-builder #note 1 echo "Built in $CWD/bin" ;; image) docker build -t $IMAGE . echo run clock with: docker run $IMAGE ;; run) docker run $IMAGE ;; build-chain) $MAKE builder && $MAKE build && $MAKE image ;; *) usage ;; esac
note 1 : J’utilise lors du build deux volumes : le premier est un volume nommé gopath (disponible depuis Docker 1.9), le second monte le répertoire de travail dans le conteneur en respectant la nomenclature go.
docker volume ls DRIVER VOLUME NAME local gopath
Un volume gopath est automatiquement créé si celui-ci n’existe pas. Il permet d’enregistrer les dépendances en dehors du conteneur; plusieurs projets peuvent ainsi partager ce même volume et éviter de télécharger les dépendances communes.
L’essayer c’est l’adopter
S’il est besoin de vous convaincre, vous pouvez essayer avec le projet présenté. Les sources sont disponibles sur github.
git clone https://github.com/tauffredou/clock.git cd clock ./make.sh build-chain ./make.sh run
Plusieurs images ont été créées:
docker images REPOSITORY TAG IMAGE ID CREATED SIZE tauffredou/clock latest d41a6cf3695b 4 days ago 4.148 MB tauffredou/clock-builder latest 6b56026a925b 4 days ago 725.9 MB golang 1.5 2e1c8fd5ddc3 3 weeks ago 725.2 MB
Nous constatons que l’écart de taille entre le builder et l’image finale est important. En effet le builder contient le SDK et les dépendances ; de plus nous utilisons une image de base officielle et avons donc peu de possibilités d’optimisation.
Pour conclure cet article, je vous propose de faire le ménage
# Suppression des conteneurs docker rm $(docker ps -a | grep " tauffredou/clock" | awk '{print $1}' ) # Suppression des images docker rmi tauffredou/clock-builder tauffredou/clock
L’image golang et le volume gopath sont toujours présents, si vous ne les utilisez pas pour un autre projet, vous pouvez les supprimer. J’ai choisi de les garder.
Et notre poste de travail retrouve l’odeur du neuf !