So optimieren Sie Docker-Images für die Produktion

Der Autor hat Code.org ausgewählt, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

Einführung

In einer Produktionsumgebung ist es mit Docker einfach, Anwendungen in Containern zu erstellen, bereitzustellen und auszuführen. Mit Containern können Entwickler Anwendungen und alle ihre Hauptanforderungen und Abhängigkeiten in einem einzigen Paket zusammenfassen, das Sie in ein Docker-Image verwandeln und replizieren können. Docker-Images werden aus Dockerfiles erstellt. Die Docker-Datei ist eine Datei, in der Sie definieren, wie das Image aussehen soll, welches Basisbetriebssystem es haben soll und welche Befehle darin ausgeführt werden sollen.

Große Docker-Images können die Zeit zum Erstellen und Senden von Images zwischen Clustern und Cloud-Anbietern verlängern. Wenn Sie beispielsweise jedes Mal, wenn einer Ihrer Entwickler einen Build auslöst, ein Image in Gigabyte-Größe zum Pushen haben, summiert sich der Durchsatz, den Sie in Ihrem Netzwerk erstellen, während des CI / CD-Prozesses, was Ihre Anwendung träge macht und letztendlich Ihre Ressourcen kostet . Aus diesem Grund sollten Docker-Images, die für die Produktion geeignet sind, nur das Nötigste installiert haben.

Es gibt verschiedene Möglichkeiten, die Größe von Docker-Bildern zu verringern, um sie für die Produktion zu optimieren. Zuallererst benötigen diese Images normalerweise keine Build-Tools, um ihre Anwendungen auszuführen. Sie müssen sie also überhaupt nicht hinzufügen. Mithilfe eines multi-stage build process können Sie mithilfe von Zwischenimages den Code kompilieren und erstellen, Abhängigkeiten installieren und alles in ein Paket packen Kopieren Sie dann die endgültige Version Ihrer Anwendung auf ein leeres Image ohne Build-Tools. Darüber hinaus können Sie ein Image mit einer winzigen Basis wie Alpine Linux verwenden. Alpine ist eine geeignete Linux-Distribution für die Produktion, da sie nur das Nötigste enthält, was Ihre Anwendung zum Ausführen benötigt.

In diesem Lernprogramm optimieren Sie Docker-Bilder in wenigen einfachen Schritten, sodass sie kleiner, schneller und besser für die Produktion geeignet sind. Sie erstellen Bilder für ein Beispiel Go API in verschiedenen Docker-Containern, beginnend mit Ubuntu und sprachspezifischen Bildern, und fahren dann mit Alpine fort Verteilung. Sie verwenden auch mehrstufige Builds, um Ihre Bilder für die Produktion zu optimieren. Das Endziel dieses Tutorials ist es, den Größenunterschied zwischen der Verwendung von Standard-Ubuntu-Images und optimierten Gegenstücken aufzuzeigen und den Vorteil von mehrstufigen Builds aufzuzeigen. Nachdem Sie dieses Tutorial gelesen haben, können Sie diese Techniken auf Ihre eigenen Projekte und CI / CD-Pipelines anwenden.

Voraussetzungen

Bevor Sie beginnen, benötigen Sie:

Schritt 1 - Herunterladen der Sample Go-API

Bevor Sie Ihr Docker-Image optimieren können, müssen Sie zunächst die sample API herunterladen, aus der Sie Ihre Docker-Images erstellen. Die Verwendung einer einfachen Go-API zeigt alle wichtigen Schritte zum Erstellen und Ausführen einer Anwendung in einem Docker-Container. In diesem Tutorial wird Go verwendet, da es sich um eine kompilierte Sprache wie C++ oder Java handelt, die sich jedoch von diesen unterscheidet hat einen sehr kleinen Platzbedarf.

Beginnen Sie auf Ihrem Server mit dem Klonen der Beispiel-Go-API:

git clone https://github.com/do-community/mux-go-api.git

Sobald Sie das Projekt geklont haben, befindet sich auf Ihrem Server ein Verzeichnis mit dem Namen "+ mux-go-api w". Verschieben Sie mit + cd + in dieses Verzeichnis:

cd mux-go-api

Dies ist das Ausgangsverzeichnis für Ihr Projekt. Sie erstellen Ihre Docker-Images aus diesem Verzeichnis. Im Inneren finden Sie den Quellcode für eine in Go geschriebene API in der Datei + api.go +. Obwohl diese API minimal ist und nur wenige Endpunkte hat, ist sie für die Simulation einer produktionsbereiten API für die Zwecke dieses Lernprogramms geeignet.

Nachdem Sie die Beispiel-Go-API heruntergeladen haben, können Sie ein Basis-Ubuntu-Docker-Image erstellen, mit dem Sie die späteren, optimierten Docker-Images vergleichen können.

Schritt 2 - Ein Basis-Ubuntu-Image erstellen

Für Ihr erstes Docker-Image ist es hilfreich zu sehen, wie es aussieht, wenn Sie mit einem Basis-Ubuntu-Image beginnen. Dadurch wird Ihre Beispiel-API in eine Umgebung gepackt, die derjenigen ähnelt, die Sie bereits auf Ihrem Ubuntu-Server ausführen. Innerhalb des Images installieren Sie die verschiedenen Pakete und Module, die Sie zum Ausführen Ihrer Anwendung benötigen. Sie werden jedoch feststellen, dass dieser Prozess ein ziemlich umfangreiches Ubuntu-Image erstellt, das sich auf die Erstellungszeit und die Lesbarkeit des Codes Ihrer Docker-Datei auswirkt.

Beginnen Sie mit dem Schreiben einer Docker-Datei, die Docker anweist, ein Ubuntu-Image zu erstellen, Go zu installieren und die Beispiel-API auszuführen. Stellen Sie sicher, dass Sie das Dockerfile im Verzeichnis des geklonten Repos erstellen. Wenn Sie in das Ausgangsverzeichnis geklont haben, sollte es "+ $ HOME / mux-go-api" sein.

Erstellen Sie eine neue Datei mit dem Namen "+ Dockerfile.ubuntu". Öffne es in + nano + oder deinem Lieblings-Texteditor:

nano ~/mux-go-api/Dockerfile.ubuntu

In dieser Docker-Datei definieren Sie ein Ubuntu-Image und installieren Golang. Anschließend installieren Sie die erforderlichen Abhängigkeiten und erstellen die Binärdatei. Fügen Sie den folgenden Inhalt zu + Dockerfile.ubuntu hinzu:

~ / mux-go-api / Dockerfile.ubuntu

FROM ubuntu:18.04

RUN apt-get update -y \
 && apt-get install -y git gcc make golang-

ENV GOROOT /usr/lib/go-
ENV PATH $GOROOT/bin:$PATH
ENV GOPATH /root/go
ENV APIPATH /root/go/src/api

WORKDIR $APIPATH
COPY . .

RUN \
 go get -d -v \
 && go install -v \
 && go build

EXPOSE 3000
CMD ["./api"]

Von oben beginnend gibt der Befehl + FROM + an, welches Basisbetriebssystem das Image haben soll. Dann installiert der Befehl + RUN + die Sprache Go während der Erstellung des Images. + ENV + legt die spezifischen Umgebungsvariablen fest, die der Go-Compiler benötigt, um richtig zu funktionieren. "+ WORKDIR " gibt das Verzeichnis an, in das der Code kopiert werden soll, und der Befehl " COPY " übernimmt den Code aus dem Verzeichnis, in dem sich " Dockerfile.ubuntu " befindet, und kopiert ihn in das Bild. Der abschließende ' RUN'-Befehl installiert die Go-Abhängigkeiten, die der Quellcode benötigt, um die API zu kompilieren und auszuführen.

Speichern und schließen Sie die Datei. Jetzt können Sie den Befehl + build + ausführen, um ein Docker-Image aus der soeben erstellten Docker-Datei zu erstellen:

docker build -f Dockerfile.ubuntu -t ubuntu .

Der Befehl + build + erstellt ein Bild aus einer Docker-Datei. Das Flag "+ -f " gibt an, dass Sie aus der Datei " Dockerfile.ubuntu " erstellen möchten, während " -t " für "tag" steht. Dies bedeutet, dass Sie den Namen " ubuntu " verwenden. Der letzte Punkt steht für den aktuellen Kontext, in dem sich " Dockerfile.ubuntu +" befindet.

Dies wird eine Weile dauern, machen Sie also eine Pause. Sobald der Build abgeschlossen ist, haben Sie ein Ubuntu-Image, mit dem Sie Ihre API ausführen können. Die endgültige Größe des Bildes ist jedoch möglicherweise nicht ideal. Alles über ein paar hundert MB für diese API würde als zu großes Bild angesehen.

Führen Sie den folgenden Befehl aus, um alle Docker-Images aufzulisten und die Größe Ihres Ubuntu-Images zu ermitteln:

docker images

Sie erhalten eine Ausgabe mit dem soeben erstellten Bild:

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
ubuntu      latest  61b2096f6871    33 seconds ago
. . .

Wie in der Ausgabe hervorgehoben, hat dieses Bild eine Größe von * 636 MB * für eine grundlegende Golang-API, eine Zahl, die von Maschine zu Maschine leicht variieren kann. Bei mehreren Builds wirkt sich diese Größe erheblich auf die Bereitstellungszeiten und den Netzwerkdurchsatz aus.

In diesem Abschnitt haben Sie ein Ubuntu-Image mit allen erforderlichen Go-Tools und Abhängigkeiten erstellt, um die in Schritt 1 geklonte API auszuführen. Im nächsten Abschnitt verwenden Sie ein vorgefertigtes sprachspezifisches Docker-Image, um Ihre Docker-Datei zu vereinfachen und den Erstellungsprozess zu optimieren.

Schritt 3 - Erstellen eines sprachspezifischen Basisimages

Vorgefertigte Bilder sind gewöhnliche Basisbilder, die Benutzer mit situationsspezifischen Tools bearbeitet haben. Benutzer können diese Images dann in das Image-Repository Docker Hub verschieben, sodass andere Benutzer das freigegebene Image verwenden können, anstatt ihre eigenen Docker-Dateien schreiben zu müssen. Dies ist ein in Produktionssituationen üblicher Vorgang, und in Docker Hub finden Sie verschiedene vorgefertigte Images für nahezu jeden Anwendungsfall. In diesem Schritt erstellen Sie Ihre Beispiel-API mit einem Go-spezifischen Image, auf dem der Compiler und die Abhängigkeiten bereits installiert sind.

Mit vorgefertigten Basisimages, die bereits die Tools enthalten, die Sie zum Erstellen und Ausführen Ihrer App benötigen, können Sie die Erstellungszeit erheblich verkürzen. Da Sie mit einer Basis beginnen, auf der alle erforderlichen Tools vorinstalliert sind, können Sie das Hinzufügen dieser Tools zu Ihrer Docker-Datei überspringen, damit sie viel sauberer aussieht und letztendlich die Erstellungszeit verkürzt.

Erstellen Sie ein weiteres Dockerfile und nennen Sie es "+ Dockerfile.golang +". Öffne es in deinem Texteditor:

nano ~/mux-go-api/Dockerfile.golang

Diese Datei ist wesentlich übersichtlicher als die vorherige, da alle Go-spezifischen Abhängigkeiten, Tools und der Compiler vorinstalliert sind.

Fügen Sie nun die folgenden Zeilen hinzu:

~ / mux-go-api / Dockerfile.golang

FROM golang:

WORKDIR /go/src/api
COPY . .

RUN \
   go get -d -v \
   && go install -v \
   && go build

EXPOSE 3000
CMD ["./api"]

Wenn Sie von oben beginnen, werden Sie feststellen, dass die "+ FROM " - Anweisung jetzt " golang: +" lautet. Dies bedeutet, dass Docker ein vorgefertigtes Go-Image von Docker Hub abruft, auf dem bereits alle erforderlichen Go-Tools installiert sind.

Erstellen Sie jetzt erneut das Docker-Image mit:

docker build -f Dockerfile.golang -t golang .

Überprüfen Sie die endgültige Größe des Bildes mit dem folgenden Befehl:

docker images

Dies ergibt eine Ausgabe ähnlich der folgenden:

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
golang      latest  eaee5f524da2    40 seconds ago
. . .

Obwohl das Dockerfile selbst effizienter und die Erstellungszeit kürzer ist, hat sich die Gesamtgröße des Image tatsächlich erhöht. Das vorgefertigte Golang-Image ist mit etwa * 744MB * eine beachtliche Größe.

Dies ist die bevorzugte Methode zum Erstellen von Docker-Images. Sie erhalten ein Basis-Image, das von der Community als Standard für die angegebene Sprache genehmigt wurde, in diesem Fall Go. Um ein Bild für die Produktion vorzubereiten, müssen Sie jedoch Teile ausschneiden, die von der ausgeführten Anwendung nicht benötigt werden.

Denken Sie daran, dass die Verwendung dieser schweren Bilder in Ordnung ist, wenn Sie sich über Ihre Anforderungen nicht sicher sind. Fühlen Sie sich frei, sie sowohl als Wegwerfbehälter als auch als Basis für die Erstellung anderer Bilder zu verwenden. Für Entwicklungs- oder Testzwecke, bei denen Sie nicht über das Senden von Bildern über das Netzwerk nachdenken müssen, ist es völlig in Ordnung, schwere Bilder zu verwenden. Wenn Sie jedoch Bereitstellungen optimieren möchten, müssen Sie Ihr Bestes geben, um Ihre Images so klein wie möglich zu gestalten.

Nachdem Sie ein sprachspezifisches Image getestet haben, können Sie mit dem nächsten Schritt fortfahren, in dem Sie die schlanke Alpine Linux-Distribution als Basisimage verwenden, um Ihr Docker-Image aufzuhellen.

Schritt 4 - Aufbau von Base Alpine Images

Einer der einfachsten Schritte zur Optimierung Ihrer Docker-Bilder ist die Verwendung kleinerer Basisbilder. Alpine ist eine schlanke Linux-Distribution, die auf Sicherheit und Ressourceneffizienz ausgelegt ist. Das Alpine Docker-Image verwendet musl libc und BusyBox, um kompakt zu bleiben und benötigt nicht mehr als 8 MB in einem Container, um ausgeführt zu werden . Die winzige Größe ist darauf zurückzuführen, dass Binärpakete ausgedünnt und aufgeteilt werden, wodurch Sie mehr Kontrolle über Ihre Installation haben und die Umgebung so klein und effizient wie möglich bleibt.

Das Erstellen eines Alpine-Images ähnelt dem Erstellen des Ubuntu-Images in Schritt 2. Erstellen Sie zunächst eine neue Datei mit dem Namen "+ Dockerfile.alpine +":

nano ~/mux-go-api/Dockerfile.alpine

Fügen Sie nun diesen Ausschnitt hinzu:

~ / mux-go-api / Dockerfile.alpine

FROM alpine:

RUN apk add --no-cache \
   ca-certificates \
   git \
   gcc \
   musl-dev \
   openssl \
   go

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
ENV APIPATH $GOPATH/src/api
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" "$APIPATH" && chmod -R 777 "$GOPATH"

WORKDIR $APIPATH
COPY . .

RUN \
   go get -d -v \
   && go install -v \
   && go build

EXPOSE 3000
CMD ["./api"]

Hier fügen Sie den Befehl "+ apk add +" hinzu, um mit dem Paket-Manager von Alpine Go und alle erforderlichen Bibliotheken zu installieren. Wie beim Ubuntu-Image müssen Sie auch die Umgebungsvariablen festlegen.

Mach weiter und baue das Image:

docker build -f Dockerfile.alpine -t alpine .

Überprüfen Sie noch einmal die Bildgröße:

docker images

Sie erhalten eine Ausgabe ähnlich der folgenden:

OutputREPOSITORY  TAG     IMAGE ID        CREATED         SIZE
alpine      latest  ee35a601158d    30 seconds ago
. . .

Die Größe ist auf ca. * 426MB * gesunken.

Die geringe Größe des alpinen Basisbilds hat die endgültige Bildgröße verringert. Sie können jedoch noch einige weitere Maßnahmen ergreifen, um es noch kleiner zu machen.

Versuchen Sie als Nächstes, ein vorgefertigtes Alpenbild für Go zu verwenden. Dadurch wird das Dockerfile kürzer und das endgültige Bild verkleinert. Da das vorgefertigte Alpine-Image für Go mit aus dem Quellcode kompiliertem Go erstellt wird, ist der Platzbedarf erheblich geringer.

Erstellen Sie zunächst eine neue Datei mit dem Namen "++":

nano ~/mux-go-api/Dockerfile.golang-alpine

Fügen Sie der Datei den folgenden Inhalt hinzu:

~ / mux-go-api / Dockerfile.golang-alpine

FROM golang:

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
 && go install -v \
 && go build

EXPOSE 3000
CMD ["./api"]

Die einzigen Unterschiede zwischen "+ Dockerfile.golang-alpine " und " Dockerfile.alpine " sind der Befehl " FROM " und der erste Befehl " RUN ". Jetzt gibt der Befehl ` FROM ` ein ` golang ` Image mit dem Tag `+` an, und + RUN + enthält nur einen Befehl zum Installieren von Git. Sie benötigen Git, damit der Befehl + go get im zweiten Befehl` + RUN + am unteren Rand von + Dockerfile.golang-alpine` funktioniert.

Erstellen Sie das Image mit dem folgenden Befehl:

docker build -f Dockerfile.golang-alpine -t golang-alpine .

Rufen Sie Ihre Bilderliste ab:

docker images

Sie erhalten folgende Ausgabe:

OutputREPOSITORY      TAG     IMAGE ID        CREATED         SIZE
golang-alpine   latest  97103a8b912b    49 seconds ago

Jetzt ist die Bildgröße auf ca. * 288MB * gesunken.

Obwohl Sie es geschafft haben, die Größe erheblich zu reduzieren, können Sie noch eine letzte Maßnahme ergreifen, um das Bild für die Produktion vorzubereiten. Es handelt sich um einen mehrstufigen Build. Wenn Sie mehrstufige Builds verwenden, können Sie ein Image zum Erstellen der Anwendung verwenden, während Sie ein anderes, leichteres Image zum Packen der kompilierten Anwendung für die Produktion verwenden. Dieser Prozess wird im nächsten Schritt ausgeführt.

Schritt 5 - Ausschließen von Build-Tools mit einem mehrstufigen Build

Im Idealfall sollten auf Images, die in der Produktion ausgeführt werden, keine Build-Tools oder Abhängigkeiten installiert sein, die für die Ausführung der Produktionsanwendung redundant sind. Sie können diese aus dem endgültigen Docker-Image entfernen, indem Sie mehrstufige Builds verwenden. Dies funktioniert, indem die Binär- oder anders ausgedrückt, die kompilierte Go-Anwendung in einem Zwischencontainer erstellt und dann in einen leeren Container kopiert wird, der keine unnötigen Abhängigkeiten aufweist.

Beginnen Sie mit der Erstellung einer weiteren Datei mit dem Namen "++":

nano ~/mux-go-api/Dockerfile.multistage

Was Sie hier hinzufügen, ist bekannt. Beginnen Sie mit dem exakt gleichen Code wie mit + Dockerfile.golang-alpine +. Fügen Sie diesmal jedoch auch ein zweites Bild hinzu, in das Sie die Binärdatei aus dem ersten Bild kopieren.

~ / mux-go-api / Dockerfile.multistage

FROM golang:1.10-alpine3.8

RUN apk add --no-cache --update git

WORKDIR /go/src/api
COPY . .

RUN go get -d -v \
 && go install -v \
 && go build

##

FROM alpine:3.8
COPY  /go/bin/api /go/bin/
EXPOSE 3000
CMD ["/go/bin/api"]

Speichern und schließen Sie die Datei. Hier haben Sie zwei + FROM + Befehle. Das erste ist identisch mit "+ Dockerfile.golang-alpine ", außer dass das " FROM " - Kommando ein zusätzliches " AS multistage " enthält. Dies gibt ihm den Namen " mehrstufig ", auf den Sie dann im unteren Teil der Datei " Dockerfile.multistage " verweisen. Im zweiten " FROM" -Befehl nehmen Sie ein Basis-Alpen-Bild und "+ COPY +" über die kompilierte Go-Anwendung aus dem mehrstufigen Bild. Durch diesen Vorgang wird das endgültige Bild weiter verkleinert und für die Produktion vorbereitet.

Führen Sie den Build mit dem folgenden Befehl aus:

docker build -f Dockerfile.multistage -t prod .

Überprüfen Sie jetzt die Bildgröße, nachdem Sie einen mehrstufigen Build verwendet haben.

docker images

Sie finden zwei neue Bilder anstelle von nur einem:

OutputREPOSITORY      TAG     IMAGE ID        CREATED         SIZE
prod            latest  82fc005abc40    38 seconds ago
<none>          <none>  d7855c8f8280    38 seconds ago
. . .

Das + <keine> + Bild ist das + mehrstufige + Bild, das mit dem Befehl + FROM golang: 1.10-alpine3.8 + erstellt wurde. Es ist nur ein Vermittler, der zum Erstellen und Kompilieren der Go-Anwendung verwendet wird, während das "+ prod +" - Image in diesem Kontext das endgültige Image ist, das nur die kompilierte Go-Anwendung enthält.

Von anfänglichen * 744MB * haben Sie jetzt die Bildgröße auf ca. * 11,3MB * verringert. Ein winziges Image wie dieses zu verfolgen und über das Netzwerk an Ihre Produktionsserver zu senden, ist viel einfacher als mit einem Image von über 700 MB und spart Ihnen auf lange Sicht erhebliche Ressourcen.

Fazit

In diesem Lernprogramm haben Sie Docker-Images für die Produktion unter Verwendung verschiedener Docker-Basisimages und eines Zwischenimages optimiert, um den Code zu kompilieren und zu erstellen. Auf diese Weise haben Sie Ihre Beispiel-API auf die kleinstmögliche Größe verpackt. Mit diesen Techniken können Sie die Erstellungs- und Bereitstellungsgeschwindigkeit Ihrer Docker-Anwendungen und aller CI / CD-Pipelines verbessern.

Wenn Sie mehr über das Erstellen von Anwendungen mit Docker erfahren möchten, lesen Sie unsere How To Erstellen Sie eine Node.js-Anwendung mit Docker. Weitere Informationen zum Optimieren von Containern finden Sie unter Building Optimized Containers for Kubernetes.