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!