scratch
y el pequeño servidor http basado en esta compilación, ¡pude reducir el resultado a 6.32kB!
Si prefieres el video, aquí tienes un video de YouTube para el artículo.
Contenedores hinchados
Los contenedores a menudo se promocionan como una panacea para hacer frente a cualquier desafío de mantenimiento de software. Además, como me gustan los contenedores, en la práctica a menudo me encuentro con imágenes de contenedores, cargadas de varios problemas. Un problema común es el tamaño del contenedor; ¡para algunas imágenes alcanza muchos gigabytes!
Así que decidí desafiarme a mí mismo y a todos los demás e intentar crear una imagen lo más compacta posible.
Una tarea
Las reglas son bastante simples:
- El contenedor debe servir el contenido del archivo a través de http al puerto de su elección
- No se permiten volúmenes de montaje (la llamada "Regla de Marek")
Solución simplificada
Para averiguar el tamaño de la imagen base, puede usar node.js y crear un servidor 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/`); });
y crea una imagen ejecutando la imagen base oficial del nodo:
FROM node:14 COPY . . CMD ["node", "index.js"]
¡Este aguantó
943MB
!
Imagen base reducida
Uno de los enfoques tácticos más simples y obvios para reducir el tamaño de la piel es optar por una piel de base más delgada. La imagen base oficial del nodo existe en una variante
slim
(todavía basada en debian, pero con menos dependencias preinstaladas) y una variante
alpine
basada en Alpine Linux .
Usando
node:14-slim
y
node:14-alpine
como base, es posible reducir el tamaño de la imagen a
167MB
y en
116MB
consecuencia.
Dado que las imágenes de la ventana acoplable son aditivas, y cada capa se construye sobre la siguiente, no hay casi nada que hacer aquí para reducir aún más la solución node.js.
Idiomas compilados
Para llevar las cosas al siguiente nivel, puede pasar a un lenguaje compilado que tenga muchas menos dependencias en tiempo de ejecución. Hay varias opciones, pero golang se usa a menudo para crear servicios web .
Creé el servidor de archivos más 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) } }
Y lo construí en la imagen del contenedor usando la imagen base oficial de golang:
FROM golang:1.14 COPY . . RUN go build -o server . CMD ["./server"]
Que colgó…
818MB
.
Aquí hay un problema: hay muchas dependencias instaladas en la imagen base de golang, que son útiles al crear programas go, pero no son necesarias para ejecutar programas.
Montajes de varias etapas
Docker tiene una función llamada compilaciones de varias etapas , con la que es fácil compilar código en un entorno que contiene todas las dependencias necesarias y luego copiar el archivo ejecutable resultante en otra imagen.
Esto es útil por varias razones, ¡pero una de las más obvias es el tamaño de la imagen! Al refactorizar el dockerfile de esta manera:
### ### 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"]
¡El tamaño de la imagen resultante lo es todo
13.2MB
!
Recopilación estática + imagen Scratch
13 MB no está nada mal, pero todavía nos quedan un par de trucos para hacer que esto se vea aún más ajustado.
Hay una imagen base llamada scratch , que está inequívocamente vacía, su tamaño es cero. Como
scratch
no hay nada en el interior , cualquier imagen construida sobre esta base debe tener todas las dependencias necesarias.
Para que esto sea posible en base a nuestro servidor go, necesitamos agregar algunos indicadores en el momento de la compilación para asegurarnos de que todas las bibliotecas necesarias estén vinculadas estáticamente al ejecutable:
### ### 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 particular, configuramos
external
el modo de vinculación y pasamos la bandera al
-static
vinculador externo.
Gracias a estos dos cambios, es posible aumentar el tamaño de la imagen
8.65MB
¡ASM como garantía de victoria!
Una imagen de menos de 10 MB, escrita en un lenguaje como Go, está claramente miniaturizada para casi cualquier circunstancia ... ¡pero puede hacerla aún más pequeña! El usuario nemasu ha publicado un servidor http completo escrito en ensamblador en Github. Se llama assmttpd .
Todo lo que se necesitó para contenerlo fue instalar algunas dependencias de compilación en la imagen base de Ubuntu, antes de ejecutar la receta proporcionada
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"]
Luego, el ejecutable resultante se
asmttpd
copia en la imagen temporal y se invoca a través de la línea de comandos. ¡El tamaño de la imagen resultante es de solo 6,34 kB!