scratch
et du petit serveur http basé sur cette version, j'ai pu réduire le résultat à 6,32 Ko!

Si vous préférez la vidéo, voici une vidéo YouTube pour l'article!
Conteneurs gonflés
Les conteneurs sont souvent présentés comme une panacée pour faire face à tout défi de maintenance logicielle. De plus, comme j'aime les conteneurs, je rencontre souvent dans la pratique des images de conteneurs, chargées de problèmes divers. Un problème courant est la taille du conteneur; pour certaines images, il atteint plusieurs gigaoctets!
J'ai donc décidé de me défier moi-même et tout le monde et d'essayer de créer une image aussi compacte que possible.
Une tâche
Les règles sont assez simples:
- Le conteneur doit servir le contenu du fichier via http vers le port de votre choix
- Le montage de volumes n'est pas autorisé (ce que l'on appelle la "règle de Marek")
Solution simplifiée
Pour connaître la taille de l'image de base, vous pouvez utiliser node.js et créer un serveur simple
index.js
:
const fs = require("fs"); const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'content-type': 'text/html' }) fs.createReadStream('index.html').pipe(res) }) server.listen(port, hostname, () => { console.log(`Server: http://0.0.0.0:8080/`); });
et en faire une image en exécutant l'image de base officielle du nœud:
FROM node:14 COPY . . CMD ["node", "index.js"]
Celui-ci s'est accroché
943MB
!
Image de base réduite
L'une des approches tactiques les plus simples et les plus évidentes pour réduire la taille de la peau consiste à opter pour une peau de base plus maigre. L'image de base officielle du nœud existe dans une variante
slim
(toujours basée sur Debian, mais avec moins de dépendances préinstallées) et une variante
alpine
basée sur Alpine Linux .
En utilisant
node:14-slim
et
node:14-alpine
comme base, il est possible de rĂ©duire la taille de l'image Ă
167MB
et en
116MB
conséquence.
Étant donné que les images docker sont additives, chaque couche se construisant au-dessus de la suivante, il n'y a presque rien à faire ici pour réduire davantage la solution node.js.
Langages compilés
Pour faire passer les choses au niveau supérieur, vous pouvez passer à un langage compilé qui a beaucoup moins de dépendances d'exécution. Il existe un certain nombre d'options, mais golang est souvent utilisé pour créer des services Web .
J'ai créé le serveur de fichiers le plus simple
server.go
:
package main import ( "fmt" "log" "net/http" ) func main() { fileServer := http.FileServer(http.Dir("./")) http.Handle("/", fileServer) fmt.Printf("Starting server at port 8080\n") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } }
Et je l'ai intégré dans l'image du conteneur en utilisant l'image de base officielle du golang:
FROM golang:1.14 COPY . . RUN go build -o server . CMD ["./server"]
Qui s'accrochait…
818MB
.
Il y a un problème ici: il y a de nombreuses dépendances installées dans l'image golang de base, qui sont utiles lors de la construction de programmes go, mais pas nécessaires pour exécuter des programmes.
Assemblages à plusieurs étages
Docker dispose d'une fonctionnalité appelée builds à plusieurs étages , avec laquelle il est facile de créer du code dans un environnement contenant toutes les dépendances nécessaires, puis de copier le fichier exécutable résultant dans une autre image.
Ceci est utile pour plusieurs raisons, mais l'une des plus évidentes est la taille de l'image! En refactorisant le fichier docker comme ceci:
### ### FROM golang:1.14-alpine AS builder COPY . . RUN go build -o server . ### ### FROM alpine:3.12 COPY --from=builder /go/server ./server COPY index.html index.html CMD ["./server"]
La taille de l'image résultante est tout
13.2MB
!
Compilation statique + image Scratch
13 Mo, ce n'est pas mal du tout, mais il nous reste encore quelques astuces pour rendre ce look encore plus serré.
Il existe une image de base appelée scratch , qui est clairement vide, sa taille est nulle. Puisqu'il
scratch
n'y a rien à l' intérieur , toute image construite sur sa base doit porter toutes les dépendances nécessaires.
Pour rendre cela possible en fonction de notre serveur go, nous devons ajouter quelques indicateurs au moment de la compilation pour nous assurer que toutes les bibliothèques nécessaires sont liées statiquement dans l'exécutable:
### ### FROM golang:1.14 as builder COPY . . RUN go build \ -ldflags "-linkmode external -extldflags -static" \ -a server.go ### ### FROM scratch COPY --from=builder /go/server ./server COPY index.html index.html CMD ["./server"]
En particulier, nous définissons
external
le mode de liaison et passons le drapeau Ă l'
-static
éditeur de liens externe.
Grâce Ă ces deux changements, il est possible de porter la taille de l'image Ă
8.65MB
ASM comme garantie de victoire!
Une image de moins de 10 Mo, écrite dans un langage comme Go, est nettement miniaturisée pour presque toutes les circonstances ... mais vous pouvez la rendre encore plus petite! L'utilisateur nemasu a posté un serveur http à part entière écrit en assembleur sur Github. Il s'appelle assmttpd .
Tout ce qu'il a fallu pour le conteneuriser était d'installer quelques dépendances de construction dans l'image Ubuntu de base, avant d'exécuter la recette fournie
make release
:
### ### FROM ubuntu:18.04 as builder RUN apt update RUN apt install -y make yasm as31 nasm binutils COPY . . RUN make release ### ### FROM scratch COPY --from=builder /asmttpd /asmttpd COPY /web_root/index.html /web_root/index.html CMD ["/asmttpd", "/web_root", "8080"]
L'exécutable résultant est ensuite
asmttpd
copié dans l'image de travail et appelé via la ligne de commande. La taille de l'image résultante n'est que de 6,34 Ko!