Wenn Sie die Größe des Docker-Bildes reduzieren möchten , müssen Sie beim Erstellen eines Docker-Images die standardmäßigen Best Practices anwenden.
Dieser Blog spricht über verschiedene Optimierungstechniken, die Sie schnell implementieren können, um das kleinste und minimale Docker-Image zu erstellen . Wir werden uns auch einige der besten Tools für die Docker-Image-Optimierung ansehen .
Docker als Container-Engine macht es einfach, ein Stück Code zu nehmen und es in einem Container auszuführen. Es ermöglicht Ingenieuren, alle Code-Abhängigkeiten und Dateien an einem einzigen Ort zu sammeln, der überall schnell und einfach ausgeführt werden kann.
Das gesamte Konzept von „Run Anywhere“-Images beginnt mit einer einfachen Konfigurationsdatei namens Dockerfile. Zuerst fügen wir alle Build-Anweisungen wie Code-Abhängigkeiten, Befehle und Basis-Image-Details in Dockerfile hinzu.
Notwendigkeit für Docker-Image-Optimierung
Obwohl der Docker-Build-Prozess einfach ist, machen viele Organisationen den Fehler, aufgeblähte Docker-Images zu erstellen ohne die Container-Images zu optimieren.
Bei der typischen Softwareentwicklung hat jeder Dienst mehrere Versionen/Releases, und jede Version erfordert mehr Abhängigkeiten, Befehle und Konfigurationen. Dies führt wie jetzt zu einer Herausforderung beim Erstellen von Docker-Images – derselbe Code erfordert mehr Zeit und Ressourcen, um erstellt zu werden, bevor er als Container versendet werden kann.
Ich habe Fälle gesehen, in denen das anfängliche Anwendungs-Image mit 350 MB begann und im Laufe der Zeit auf über 1,5 GB anwuchs.
Außerdem erhöhen wir durch die Installation unerwünschter Bibliotheken die Wahrscheinlichkeit eines potenziellen Sicherheitsrisikos, indem wir die Angriffsfläche vergrößern.
Daher müssen DevOps-Ingenieure die Docker-Images optimieren um sicherzustellen, dass das Docker-Image nach dem Erstellen von Anwendungen oder zukünftigen Versionen nicht aufgebläht wird. Nicht nur für Produktionsumgebungen, in jeder Phase des CI/CD-Prozesses sollten Sie Ihre Docker-Images optimieren.
Außerdem ist es bei Container-Orchestrierungstools wie Kubernetes am besten, kleine Images zu haben, um die Image-Übertragung und die Bereitstellungszeit zu verkürzen .
Wie kann ich die Docker-Image-Größe reduzieren?
Wenn wir ein Container-Image einer typischen Anwendung nehmen, enthält es ein Basis-Image, Abhängigkeiten/Dateien/Konfigurationen , und cruft (unerwünschte Software).
Es läuft also alles darauf hinaus, wie effizient wir diese Ressourcen innerhalb des Container-Images verwalten können.
Sehen wir uns verschiedene etablierte Methoden zur Optimierung von Docker-Images an Darüber hinaus haben wir praktische Beispiele gegeben, um die Docker-Bildoptimierung in Echtzeit zu verstehen.
Entweder Sie verwenden die im Artikel angegebenen Beispiele oder probieren die Optimierungstechniken an vorhandenen Dockerfiles aus.
Im Folgenden sind die Methoden aufgeführt, mit denen wir eine Docker-Image-Optimierung erreichen können.
- Verwendung distroloser/minimaler Basis-Images
- Mehrstufige Builds
- Minimierung der Anzahl der Ebenen
- Caching verstehen
- Mit Dockerignore
- Aufbewahrung von Anwendungsdaten an anderer Stelle
Docker-Übungsdateien: Alle in diesem Artikel verwendeten Anwendungscodes, Dockerfiles und Konfigurationen werden in diesem Github-Repository gehostet. Sie können es klonen und dem Tutorial folgen.
Methode 1:Minimale Basisbilder verwenden
Ihr erster Fokus sollte auf der Auswahl des richtigen Basis-Images mit minimalem Betriebssystem-Fußabdruck liegen.
Ein solches Beispiel sind alpine Basisbilder. Alpenbilder können so klein wie 5,59 MB sein . Es ist nicht nur klein; es ist auch sehr sicher.
alpine latest c059bfaa849c 5.59MB
Nginx Alpine das Basis-Image ist nur 22 MB groß.
Standardmäßig wird es mit der sh-Shell geliefert, die beim Debuggen des Containers hilft, indem sie ihn anhängt.
Sie können die Basis-Image-Größe weiter reduzieren, indem Sie Distroless-Images verwenden. Es ist eine abgespeckte Version des Betriebssystems. Distroless-Basisimages sind für Java, nodejs, Python, Rust usw. verfügbar.
Distroless-Images sind so minimal, dass sie nicht einmal eine Hülle enthalten . Sie fragen sich vielleicht, wie debuggen wir dann Anwendungen? Sie haben die Debug-Version desselben Bildes, das mit der Busybox zum Debuggen geliefert wird.
Außerdem haben die meisten Distributionen jetzt ihre minimalen Basis-Images.
Hinweis: Sie können die öffentlich verfügbaren Basisimages nicht direkt in Projektumgebungen verwenden. Sie müssen die Genehmigung des Unternehmenssicherheitsteams einholen, um das Basisimage zu verwenden. In einigen Organisationen veröffentlicht das Sicherheitsteam selbst jeden Monat Basis-Images nach dem Testen und Sicherheitsscannen. Diese Bilder wären im privaten Docker-Repository der gemeinsamen Organisation verfügbar.
Methode 2:Docker Multistage Builds verwenden
Das mehrstufige Build-Muster wurde aus dem Konzept des Builder-Musters entwickelt, bei dem wir verschiedene Dockerfiles zum Erstellen und Packen des Anwendungscodes verwenden. Auch wenn dieses Muster hilft, die Bildgröße zu reduzieren, verursacht es wenig Overhead, wenn es um das Erstellen von Pipelines geht.
Beim mehrstufigen Build erhalten wir ähnliche Vorteile wie beim Builder-Muster. Wir verwenden Zwischenbilder (Build-Stufen) zum Kompilieren von Code, Installieren von Abhängigkeiten und Packen von Dateien in diesem Ansatz. Die Idee dahinter ist, unerwünschte Schichten zu eliminieren im Bild.
Danach werden nur die zum Ausführen der Anwendung erforderlichen App-Dateien auf ein anderes Image kopiert, das nur die erforderlichen Bibliotheken enthält, d. h. leichter zum Ausführen der Anwendung.
Lassen Sie uns dies anhand eines praktischen Beispiels in Aktion sehen, in dem wir eine einfache Nodejs-Anwendung erstellen und ihr Dockerfile optimieren.
Lassen Sie uns zuerst den Code erstellen. Wir haben die folgende Ordnerstruktur.
├── Dockerfile1
├── Dockerfile2
├── env
├── index.js
└── package.json
Speichern Sie Folgendes als index.js
.
const dotenv=require('dotenv');
dotenv.config({ path: './env' });
dotenv.config();
const express=require("express");
const app=express();
app.get('/',(req,res)=>{
res.send(`Learning to Optimize Docker Images with DevOpsCube!`);
});
app.listen(process.env.PORT,(err)=>{
if(err){
console.log(`Error: ${err.message}`);
}else{
console.log(`Listening on port ${process.env.PORT}`);
}
}
)
Speichern Sie Folgendes als package.json
.
{
"name": "nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^10.0.0",
"express": "^4.17.2"
}
}
Speichern Sie die folgende Portvariable in einer Datei namens env
.
PORT=8080
Ein einfaches Dockerfile
für diese Anwendung möchte dies – Speichern Sie es als Dockerfile1
.
FROM node:16
COPY . .
RUN npm installEXPOSE 3000
CMD [ "node", "index.js" ]
Sehen wir uns den Speicherplatz an, den es benötigt, indem wir es bauen.
docker build -t devopscube/node-app:1.0 --no-cache -f Dockerfile1 .
Nachdem der Bau abgeschlossen ist. Lassen Sie uns seine Größe mit –
überprüfendocker image ls
Das bekommen wir.
devopscube/node-app 1.0 b15397d01cca 22 seconds ago 910MB
Die Größe beträgt also 910MBs
.
Lassen Sie uns nun diese Methode verwenden, um einen mehrstufigen Build zu erstellen.
Wir verwenden node:16
als Basis-Image, d. h. das Image für alle Abhängigkeiten und Modulinstallationen Danach verschieben wir den Inhalt in ein minimales und leichteres „alpine
‘basiertes Bild. Die „alpine
Das Bild hat nur das Nötigste und ist daher sehr leicht.
Hier ist eine bildliche Darstellung eines mehrstufigen Docker-Builds.
Auch in einem einzigen Dockerfile
, können Sie mehrere Phasen mit unterschiedlichen Basisimages haben. Beispielsweise können Sie verschiedene Phasen für Build, Test, statische Analyse und Paketierung haben mit unterschiedlichen Basisbildern.
Mal sehen, wie das neue Dockerfile aussehen könnte. Wir kopieren nur die notwendigen Dateien vom Basis-Image auf das Haupt-Image.
Speichern Sie Folgendes als Dockerfile2
.
FROM node:16 as build
WORKDIR /app
COPY package.json index.js env ./
RUN npm install
FROM node:alpine as main
COPY --from=build /app /
EXPOSE 8080
CMD ["index.js"]
Sehen wir uns den Speicherplatz an, den es benötigt, indem wir es bauen.
docker build -t devopscube/node-app:2.0 --no-cache -f Dockerfile2 .
Nachdem der Bau abgeschlossen ist. Lassen Sie uns seine Größe mit
überprüfendocker image ls
Das bekommen wir.
devopscube/node-app 2.0 fa6ae75da252 32 seconds ago 171MB
Die neue reduzierte Bildgröße beträgt also 171 MB im Vergleich zum Image mit allen Abhängigkeiten.
Das ist eine Optimierung von über 80 %!
Hätten wir jedoch dasselbe Basis-Image wie in der Build-Phase verwendet, würden wir keinen großen Unterschied feststellen.
Sie können die Bildgröße mit distrolosen Bildern weiter reduzieren . Hier ist das gleiche Dockerfile
mit einem mehrstufigen Build-Schritt, der das distroless-Image von Google NodeJS anstelle von Alpine verwendet.
FROM node:16 as build
WORKDIR /app
COPY package.json index.js env ./
RUN npm install
FROM gcr.io/distroless/nodejs
COPY --from=build /app /
EXPOSE 3000
CMD ["index.js"]
Wenn Sie das obige Dockerfile erstellen, wird Ihr Image 118 MB groß sein ,
devopscube/distroless-node 1.0 302990bc5e76 118MB
Methode 3:Minimieren Sie die Anzahl der Ebenen
Docker-Images funktionieren folgendermaßen – jeweils RUN, COPY, FROM
Dockerfile-Anweisungen fügen eine neue Ebene hinzu und jede Ebene verlängert die Build-Ausführungszeit und erhöht die Speicheranforderungen des Bildes.
Lassen Sie uns dies anhand eines praktischen Beispiels in Aktion sehen:Lassen Sie uns ein Ubuntu-Image mit aktualisierten und aktualisierten Bibliotheken erstellen, zusammen mit einigen erforderlichen installierten Paketen wie vim, net-tools, dnsutils.
Ein Dockerfile
um dies zu erreichen, wäre wie folgt – Speichern Sie dies als Dockerfile3
.
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y
Wir möchten auch die Erstellungszeit für dieses Image untersuchen.
Der Docker-Daemon hat eine eingebaute Fähigkeit, die Gesamtausführungszeit anzuzeigen, die eine Docker-Datei benötigt.
Führen Sie die folgenden Schritte aus, um diese Funktion zu aktivieren –
- Erstellen Sie eine
daemon.json
Datei mit folgendem Inhalt unter/etc/docker/
{
"experimental": true
}
2. Führen Sie den folgenden Befehl aus, um die Funktion zu aktivieren.
export DOCKER_BUILDKIT=1
Bauen wir es und sehen uns die Speicher- und Bauzeit an.
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .
Es würde die Ausführungszeiten im Terminal anzeigen.
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .
[+] Building 117.1s (10/10) FINISHED
=> [internal] load build definition from Dockerfile
.
.
.
.
=> => writing image sha256:9601bcac010062c656dacacbc7c554b8ba552c7174f32fdcbd24ff9c7482a805 0.0s
=> => naming to docker.io/devopscube/optimize:3.0 0.0s
real 1m57.219s
user 0m1.062s
sys 0m0.911s
Nachdem der Build abgeschlossen ist, beträgt die Ausführungszeit 117,1 Sekunden .
Lassen Sie uns seine Größe mit
überprüfendocker image ls
Das bekommen wir.
devopscube/optimize 3.0 9601bcac0100 About a minute ago 227MB
Die Größe beträgt also 227 MB .
Kombinieren wir die RUN-Befehle in einer einzigen Ebene und speichern Sie sie als Dockerfile4.
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y && apt-get upgrade -y && apt-get install --no-install-recommends vim net-tools dnsutils -y
Im obigen RUN-Befehl haben wir --no-install-recommends
verwendet Flag, um empfohlene Pakete zu deaktivieren. Es wird immer dann empfohlen, wenn Sie install
verwenden in Ihren Dockerfiles
Sehen wir uns die Speicher- und Bauzeit an, die für den Bau erforderlich ist.
time docker build -t devopscube/optimize:4.0 --no-cache -f Dockerfile4 .
Es würde die Ausführungszeiten im Terminal anzeigen.
time docker build -t devopscube/optimize:0.4 --no-cache -f Dockerfile4 .
[+] Building 91.7s (6/6) FINISHED
=> [internal] load build definition from Dockerfile2 0.4s
.
.
.
=> => naming to docker.io/devopscube/optimize:4.0 0.0s
real 1m31.874s
user 0m0.884s
sys 0m0.679s
Nachdem der Build abgeschlossen ist, beträgt die Ausführungszeit 91,7 Sekunden.
Lassen Sie uns seine Größe mit
überprüfendocker image ls
Das bekommen wir.
devopscube/optimize 4.0 37d746b976e3 42 seconds ago 216MB
Die Größe beträgt also 216 MB.
Mithilfe dieser Optimierungstechnik wurde die Ausführungszeit von 117,1 s auf 91,7 s und die Speichergröße von 227 MB auf 216 MB reduziert.
Methode 4:Caching verstehen
Oft muss das gleiche Image mit geringfügigen Änderungen im Code immer wieder neu erstellt werden.
Docker hilft in solchen Fällen, indem es den Cache jeder Schicht eines Builds speichert, in der Hoffnung, dass es in Zukunft nützlich sein könnte.
Aufgrund dieses Konzepts wird empfohlen, die Zeilen, die zum Installieren von Abhängigkeiten und Paketen verwendet werden, früher in die Dockerdatei einzufügen – vor den COPY-Befehlen.
Der Grund dafür ist, dass Docker das Bild mit den erforderlichen Abhängigkeiten zwischenspeichern kann und dieser Cache dann in den folgenden Builds verwendet werden kann, wenn der Code geändert wird.
Schauen wir uns zum Beispiel die folgenden beiden Dockerfiles an.
Dockerfile5
FROM ubuntu:latest
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y
COPY . .
Dockerfile6
FROM ubuntu:latest
COPY . .
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y
RUN apt-get upgrade -y
RUN apt-get install vim -y
RUN apt-get install net-tools -y
RUN apt-get install dnsutils -y
Docker könnte die Cache-Funktionalität besser nutzen mit Dockerfile6 als Dockerfile5 aufgrund der besseren Platzierung des COPY-Befehls.
Methode 5:Verwenden Sie Dockerignore
In der Regel müssen nur die notwendigen Dateien über das Docker-Image kopiert werden.
Docker kann die im Arbeitsverzeichnis vorhandenen Dateien ignorieren, wenn dies in .dockerignore
konfiguriert ist Datei.
Diese Funktion sollte bei der Optimierung des Docker-Images berücksichtigt werden.
Methode 6:Anwendungsdaten an anderer Stelle aufbewahren
Durch das Speichern von Anwendungsdaten im Bild wird die Größe der Bilder unnötig erhöht.
Es wird dringend empfohlen, die Volume-Funktion der Containerlaufzeiten zu verwenden, um das Image von den Daten getrennt zu halten.
Docker-Image-Optimierungstools
Im Folgenden sind einige der Open-Source-Tools aufgeführt, die Ihnen bei der Optimierung helfen werden
- Tauchen :Es ist ein Image-Explorer-Tool, mit dem Sie Ebenen in den Docker- und OCI-Container-Images entdecken können. Mit dive finden Sie Möglichkeiten, Ihre Docker-Images zu optimieren. Weitere Informationen finden Sie im Dive Github-Repo.
- Docker Slim: Es hilft Ihnen, Ihre Docker-Images hinsichtlich Sicherheit und Größe zu optimieren. Weitere Informationen finden Sie im Docker Slim Github-Repo.
Ich werde dieser Liste weitere Tools hinzufügen.