Ist es schwach, einen so kleinen Container zu heben? Erstellen eines 6-KB-Container-HTTP-Servers

TL; DR   Ich habe beschlossen, das kleinste Container-Image zu erstellen, mit dem Sie noch etwas Nützliches tun können. Durch die Nutzung der mehrstufigen Builds, des Basis-Images  scratch



 und des winzigen http-Servers, die auf diesem Build basieren, konnte ich das Ergebnis auf 6,32 KB reduzieren!











Wenn Sie ein Video bevorzugen, finden Sie hier ein YouTube-Video zum Artikel!



Aufgedunsene Behälter



Container werden oft als Allheilmittel für die Bewältigung von Herausforderungen bei der Softwarewartung angepriesen. Da ich Container mag, stoße ich in der Praxis häufig auf Containerbilder, die mit verschiedenen Problemen belastet sind. Ein häufiges Problem ist die Größe des Containers. für einige Bilder erreicht es viele Gigabyte!  



Deshalb habe ich mich und alle anderen herausgefordert und versucht, ein möglichst kompaktes Bild zu erstellen.



Eine Aufgabe



Die Regeln sind ziemlich einfach:



  • Der Container sollte den Inhalt der Datei über http an den Port Ihrer Wahl senden 
  • Das Mounten von Volumes ist nicht zulässig (sogenannte "Marek-Regel").


Vereinfachte Lösung



Um die Größe des Basisimages zu ermitteln, können Sie node.js verwenden und einen einfachen Server erstellen  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/`);
});

      
      





und machen Sie ein Image daraus, indem Sie das offizielle Basis-Image des Knotens ausführen:



FROM node:14
COPY . .
CMD ["node", "index.js"]

      
      





Dieser hing durch  943MB



!



Reduziertes Basisbild



Einer der einfachsten und naheliegendsten taktischen Ansätze zur Reduzierung der Hautgröße ist die Entscheidung für eine schlankere Basishaut. Das offizielle Basis-Image des Knotens existiert in einer Variante slim



 (immer noch basierend auf Debian, aber mit weniger vorinstallierten Abhängigkeiten) und einer Variante  alpine



 basierend auf  Alpine Linux .



Mit  node:14-slim



 und  node:14-alpine



 als Basis ist es möglich, die Bildgröße auf 167MB



 und  116MB



 entsprechend zu reduzieren  .



Da Docker-Images additiv sind und jede Ebene auf der nächsten aufbaut, gibt es hier fast nichts zu tun, um die Lösung von node.js weiter zu reduzieren.



Kompilierte Sprachen



Um die Dinge auf die nächste Ebene zu bringen, können Sie zu einer kompilierten Sprache wechseln, die viel weniger Laufzeitabhängigkeiten aufweist. Es gibt eine Reihe von Optionen, aber Golang wird häufig zum Erstellen von Webdiensten verwendet .



Ich habe den einfachsten Dateiserver erstellt  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)
	}
}

      
      





Und ich habe es mit dem offiziellen Golang-Basis-Image in das Container-Image eingebaut:



FROM golang:1.14
COPY . .
RUN go build -o server .
CMD ["./server"]

      
      





Welche hing an ...  818MB



.



Hier gibt es ein Problem: Im Basis-Golang-Image sind viele Abhängigkeiten installiert, die beim Erstellen von Go-Programmen hilfreich sind, aber nicht zum Ausführen von Programmen benötigt werden. 



Mehrstufige Baugruppen



Docker verfügt über eine Funktion namens  mehrstufige Builds , mit der es einfach ist, Code in einer Umgebung zu erstellen, die alle erforderlichen Abhängigkeiten enthält, und die resultierende ausführbare Datei dann in ein anderes Image zu kopieren.



Dies ist aus mehreren Gründen nützlich, aber einer der offensichtlichsten ist die Größe des Bildes! Durch Umgestaltung der Docker-Datei wie folgt:



###   ###
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"]

      
      





Die Größe des resultierenden Bildes ist alles  13.2MB



!



Statische Kompilierung + Scratch-Image



13 MB sind überhaupt nicht schlecht, aber wir haben noch ein paar Tricks übrig, um diesen Look noch enger zu machen. 



Es gibt ein Basisbild namens  Scratch , das eindeutig leer ist und dessen Größe Null ist. Da scratch



 sich nichts darin befindet , muss jedes auf seiner Basis erstellte Bild alle erforderlichen Abhängigkeiten aufweisen.



Um dies basierend auf unserem go-Server zu ermöglichen, müssen wir beim Kompilieren einige Flags hinzufügen, um sicherzustellen, dass alle erforderlichen Bibliotheken statisch mit der ausführbaren Datei verknüpft sind:



###   ###
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"]

      
      





Insbesondere setzen wir external



 den Verknüpfungsmodus und übergeben das Flag an den  -static



 externen Linker.



Dank dieser beiden Änderungen ist es möglich, die Bildgröße auf zu erhöhen 8.65MB



 



ASM als Garantie für den Sieg!



Ein Bild mit einer Größe von weniger als 10 MB, das in einer Sprache wie Go geschrieben ist, ist für fast alle Umstände deutlich miniaturisiert ... aber Sie können es noch kleiner machen! Benutzer  nemasu hat  einen vollwertigen http-Server veröffentlicht, der in Assembler auf Github geschrieben wurde. Es heißt assmttpd .



Um Container zu erstellen, mussten lediglich einige Build-Abhängigkeiten im Ubuntu-Basisimage installiert werden, bevor das bereitgestellte Rezept ausgeführt wurde 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"] 

      
      





Die resultierende ausführbare Datei wird dann  asmttpd



 in das Arbeitsbild kopiert und über die Befehlszeile aufgerufen. Die Größe des resultierenden Bildes beträgt nur 6,34 KB!



All Articles