Est-il faible de soulever un si petit conteneur? Construire un serveur HTTP conteneurisé de 6 Ko

TL; DR   J'ai dĂ©cidĂ© de crĂ©er la plus petite image de conteneur avec laquelle vous pouvez encore faire quelque chose d'utile. Profitant des versions en plusieurs Ă©tapes, de l'image de base  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!



All Articles