¿Es débil levantar un recipiente tan pequeño? Construyendo un servidor HTTP en contenedores de 6kB

TL; DR   Decidí crear la imagen de contenedor más pequeña con la que aún puedes hacer algo útil. Aprovechando las compilaciones de múltiples etapas, la imagen base  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!



All Articles