Impact de la distribution Linux sur la taille du container Docker

On recommence le multi-stage build Docker avec Golang

J’ai posté, il y a quelques temps, ce billet sur la mise en oeuvre d’une API Rest Golang avec MongoDB, où j’ai utilisé le multi-stage build de Docker. Et la semaine dernière, j’ai donné une conférence lors du Meetup Golang Bordeaux sur le thème Go déploiement avec Docker, je vais partager ci-dessous les informations de cette intervention.

Impact de la distribution Linux sur la taille de l’image Docker

Je repars de mon exemple d’API REST en Golang avec MongoDB et je vais changer l’image de référence, dans les phases de build des images Docker et mesurer la taille résultante de l’image Docker.

Je vais successivement contruire mon image en invoquant les distributions suivantes :

  • CentOS
  • Ubuntu
  • Debian
  • Alpine
  • Scratch (rootfs de base)

Ce script va me construire les différents images Docker :

#!/bin/bash
for filename in Dockerfile.*; do
        docker build -t itwars/rest-"${filename##*.}" -f "$filename" .
done

Les tailles d’images Docker sont les suivantes :

testor@bdx:~/go/stack-rest-api-go-mongo\ Ω docker image ls
REPOSITORY                SIZE
itwars/rest-golang        802MB
itwars/rest-centos        207MB
itwars/rest-debian        109MB
itwars/rest-ubuntu        89.4MB
itwars/rest-alpine        12.4MB
itwars/rest-scratch       8.22MB
itwars/rest-scratch-upx   1.76MB

Voici l’ensemble des Dockerfiles :

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM golang
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.golang

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM centos
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.centos

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM debian
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.debian

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM ubuntu
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.ubuntu

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.alpine

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

RUN set -x && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM scratch
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.scratch

Optimisons encore notre image Docker Golang avec upx

L’outil upx the Ultimate Packer for eXecutables permet de créer un fichier exécutable compressé, ainsi l’image Docker Golang fait moins de 1.8Mo.

FROM golang
WORKDIR /go/src/github.com/user/app
COPY . .

ADD https://github.com/upx/upx/releases/download/v3.94/upx-3.94-amd64_linux.tar.xz /usr/local
RUN set -x && \
    apt update && \
    apt install -y xz-utils && \
    xz -d -c /usr/local/upx-3.94-amd64_linux.tar.xz | \
    tar -xOf - upx-3.94-amd64_linux/upx > /bin/upx && \
    chmod a+x /bin/upx && \
    go get -d -v . && \
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . && \
    strip --strip-unneeded app && \
    upx app

FROM scratch
WORKDIR /root/
COPY --from=0 /go/src/github.com/user/app .
EXPOSE 3000
CMD ["./app"]

Dockerfile.scratch-upx

Mesure de performances

Comme on peut le constater, la taille des containers Docker varient dans des proportions importantes de 800Mo à 2Mo !
- J’ai mesuré la vitesse de démarrage de chacun d’eux : pas de différence,
- J’ai regardé l’utilisation de la RAM, là il y a une variation importante, qui va impacter le nombre de containers Docker qui peuvent s’exécuter sur un même noeud,
- J’ai constaté aussi que lors d’un docker pull d’une image Docker, la taille influence bien évidenment sur le temps de chargement. Dans le contexte d’un cluster Docker Swarm ou Kubernetes, plus le nombre de noeuds dans le cluster est important, plus le temps d’initialisation de la Stack de Services Docker sera long.