Dockerfile-Referenz
Docker kann Images automatisch erstellen, indem es die Anweisungen aus einem Dockerfile liest. Ein Dockerfile ist ein Textdokument, das alle Befehle enthält, die ein Benutzer über die Kommandozeile aufrufen könnte, um ein Image zusammenzustellen. Diese Seite beschreibt die Befehle, die Sie in einem Dockerfile verwenden können.
Übersicht
Das Dockerfile unterstützt die folgenden Anweisungen:
Anweisung | Beschreibung |
---|---|
ADD | Lokale oder entfernte Dateien und Verzeichnisse hinzufügen. |
ARG | Build-Zeit Variablen verwenden. |
CMD | Standardbefehle angeben. |
COPY | Dateien und Verzeichnisse kopieren. |
ENTRYPOINT | Standardausführbare Datei angeben. |
ENV | Umgebungsvariablen setzen. |
EXPOSE | Beschreiben, welche Ports Ihre Anwendung abhört. |
FROM | Eine neue Bauetappe von einem Basis-Image erstellen. |
HEALTHCHECK | Überprüfen der Gesundheit eines Containers beim Start. |
LABEL | Metadaten zu einem Image hinzufügen. |
MAINTAINER | Den Autor eines Images angeben. |
ONBUILD | Anweisungen für den Einsatz des Images in einem Build angeben. |
RUN | Build-Befehle ausführen. |
SHELL | Die Standardshell eines Images setzen. |
STOPSIGNAL | Das Systemaufrufsignal zum Beenden eines Containers angeben. |
USER | Benutzer- und Gruppen-ID setzen. |
VOLUME | Volume-Mounts erstellen. |
WORKDIR | Arbeitsverzeichnis wechseln. |
ADD
Die Anweisung ADD
und COPY
haben im Wesentlichen das gleiche Format und die gleiche Natur. Allerdings fügt ADD
einige zusätzliche Funktionen hinzu.
Zum Beispiel kann der <Quellpfad>
eine URL
sein, in diesem Fall versucht die Docker-Engine, die Datei von diesem Link herunterzuladen und im <Zielpfad>
zu platzieren. Die Berechtigungen der heruntergeladenen Datei werden automatisch auf 600
gesetzt. Wenn dies nicht die gewünschten Berechtigungen sind, wird eine zusätzliche RUN
-Schicht benötigt, um die Berechtigungen anzupassen. Zusätzlich, wenn die heruntergeladene Datei ein komprimiertes Archiv ist, wird auch eine zusätzliche RUN
-Schicht benötigt, um es zu extrahieren. Deshalb ist es vernünftiger, direkt die Anweisung RUN
zu verwenden, dann das Tool wget
oder curl
zu verwenden, um herunterzuladen, Berechtigungen zu handhaben, zu extrahieren und dann unnötige Dateien zu bereinigen. Also ist diese Funktionalität nicht wirklich praktisch und es wird nicht empfohlen, sie zu verwenden.
Wenn der <Quellpfad>
eine tar
-komprimierte Datei ist und das Kompressionsformat gzip
, bzip2
oder xz
ist, wird die Anweisung ADD
diese komprimierte Datei automatisch im <Zielpfad>
dekomprimieren.
In einigen Fällen ist diese automatische Dekomprimierungsfunktion sehr nützlich, wie im offiziellen ubuntu
-Image:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
Aber in einigen Fällen, wenn wir wirklich eine komprimierte Datei ohne sie zu extrahieren kopieren möchten, dann können wir den Befehl ADD
nicht verwenden.
In der offiziellen Dockerfile-Best-Practices-Dokumentation von Docker wird gefordert, COPY
wann immer möglich zu verwenden, denn die Semantik von COPY
ist sehr klar – es kopiert nur Dateien, während ADD
komplexere Funktionen beinhaltet und sein Verhalten möglicherweise nicht so klar ist. Der geeignetste Anwendungsfall für ADD
ist der erwähnte Bedarf an automatischer Dekomprimierung.
Zusätzlich sollte beachtet werden, dass die Anweisung ADD
dazu führen wird, dass der Image-Build-Cache ungültig wird, was den Image-Build-Prozess verlangsamen kann.
Daher, wenn Sie zwischen den Anweisungen COPY
und ADD
wählen, können Sie diesem Prinzip folgen: verwenden Sie die Anweisung COPY
für alle Dateikopieroperationen und verwenden Sie ADD
nur, wenn automatische Dekomprimierung benötigt wird.
Bei Verwendung dieser Anweisung können Sie auch die Option --chown=<Benutzer>:<Gruppe>
hinzufügen, um den Eigentümer Benutzer und die Gruppe der Datei zu ändern.
ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/
ARG
Format: ARG <parameter_name>[=<default_value>]
Build-Argumente haben die gleiche Wirkung wie ENV
, beide setzen Umgebungsvariablen. Der Unterschied ist, dass die durch ARG
während der Build-Umgebung gesetzten Umgebungsvariablen nicht existieren, wenn der Container läuft. Verwenden Sie ARG
jedoch nicht, um Passwörter oder andere sensible Informationen zu speichern, nur weil dies der Fall ist, da docker history
immer noch alle Werte anzeigen kann.
Die ARG
-Anweisung in der Dockerfile definiert Parameternamen und ihre Standardwerte. Dieser Standardwert kann im docker build
-Befehl mit --build-arg <parameter_name>=<value>
überschrieben werden.
Die flexible Verwendung der ARG
-Anweisung ermöglicht es Ihnen, verschiedene Bilder zu bauen, ohne das Dockerfile zu ändern.
Die ARG
-Anweisung hat einen Wirkungsbereich. Wenn sie vor der FROM
-Anweisung angegeben wird, kann sie nur in der FROM
-Anweisung verwendet werden.
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo ${DOCKER_USERNAME}
Wenn Sie das obige Dockerfile verwenden, werden Sie feststellen, dass der Wert der Variablen ${DOCKER_USERNAME}
nicht ausgegeben werden kann. Um ihn korrekt auszugeben, müssen Sie ARG
nach FROM
erneut angeben.
# Nur wirksam in FROM
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# Um es nach FROM zu verwenden, müssen Sie es erneut angeben
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
Bei mehrstufigen Builds achten Sie besonders auf diese Angelegenheit.
# Diese Variable ist in jedem FROM wirksam
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 1
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 2
Für das obige Dockerfile können beide FROM
-Anweisungen ${DOCKER_USERNAME}
verwenden. Für in jeder Phase verwendete Variablen müssen sie in jeder Phase separat angegeben werden:
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# Um die Variable nach FROM zu verwenden, muss sie in jeder Phase separat angegeben werden
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
FROM ${DOCKER_USERNAME}/alpine
# Um die Variable nach FROM zu verwenden, muss sie in jeder Phase separat angegeben werden
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
CMD
Die CMD
-Anweisung hat ein ähnliches Format wie RUN
, mit zwei Formaten:
shell
-Format:CMD <Befehl>
exec
-Format:CMD ["ausführbares Programm", "Param1", "Param2"...]
- Parameterlistenformat:
CMD ["Param1", "Param2"...]
. Wird verwendet, um die eigentlichen Parameter zu spezifizieren, nachdem dieENTRYPOINT
-Anweisung angegeben wurde.
Als früher Container eingeführt wurden, wurde erwähnt, dass Docker keine virtuelle Maschine ist und Container Prozesse sind. Da es sich um Prozesse handelt, muss beim Starten eines Containers ein Programm und seine Parameter angegeben werden. Die CMD
-Anweisung wird verwendet, um den Standard-Startbefehl für den Hauptprozess des Containers zu spezifizieren.
Während der Laufzeit kann ein neuer Befehl angegeben werden, um diesen Standardbefehl, der im Image gesetzt wurde, zu ersetzen. Zum Beispiel ist der Standard-CMD
für das ubuntu
-Image /bin/bash
. Wenn wir direkt docker run -it ubuntu
ausführen, gelangen wir in bash
. Wir können auch einen anderen Befehl zur Laufzeit angeben, wie docker run -it ubuntu cat /etc/os-release
. Dies ersetzt den Standardbefehl /bin/bash
durch cat /etc/os-release
, was die Systemversioninformationen ausgibt.
In Bezug auf das Anweisungsformat wird im Allgemeinen das exec
-Format empfohlen, da es während der Analyse als JSON-Array geparst wird, so dass Anführungszeichen "
verwendet werden müssen und keine Apostrophe.
Wenn das shell
-Format verwendet wird, wird der eigentliche Befehl als Argument für sh -c
zur Ausführung verpackt. Zum Beispiel:
CMD echo $HOME
Während der tatsächlichen Ausführung wird es zu:
CMD [ "sh", "-c", "echo $HOME" ]
geändert. Das ist der Grund, warum wir Umgebungsvariablen verwenden können, weil diese Umgebungsvariablen vom Shell geparst und verarbeitet werden.
Wenn man CMD
erwähnt, kommt unweigerlich die Frage auf, ob Anwendungen im Container im Vordergrund oder im Hintergrund laufen sollten. Dies ist eine häufige Verwirrung für Anfänger.
Docker ist keine virtuelle Maschine, und Anwendungen in Containern sollten im Vordergrund laufen, nicht wie in virtuellen Maschinen oder physischen Maschinen, wo systemd
verwendet wird, um Hintergrunddienste zu starten. Im Inneren eines Containers gibt es kein Konzept von Hintergrunddiensten.
Einige Anfänger schreiben das CMD
als:
CMD service nginx start
Dann stellen sie fest, dass der Container sofort nach der Ausführung beendet wird. Auch wenn sie den Befehl systemctl
innerhalb des Containers verwenden, stellen sie fest, dass er überhaupt nicht ausgeführt werden kann. Dies liegt daran, dass sie die Konzepte von Vordergrund und Hintergrund nicht verstanden haben und den Unterschied zwischen Containern und virtuellen Maschinen nicht erkannt haben und immer noch versuchen, Container aus der Perspektive von traditionellen virtuellen Maschinen zu verstehen.
Für einen Container ist sein Startprogramm der Anwendungsprozess des Containers. Der Container existiert für den Hauptprozess, und wenn der Hauptprozess beendet wird, verliert der Container seinen Zweck und wird ebenfalls beendet. Andere Hilfsprozesse sind nichts, worum er sich kümmern muss.
Mit dem Befehl service nginx start
wird versucht, das Init-System zu veranlassen, den nginx-Dienst als Hintergrund-Daemon-Prozess zu starten. Wie bereits erwähnt, wird CMD service nginx start
als CMD ["sh", "-c", "service nginx start"]
interpretiert, so dass der Hauptprozess eigentlich sh
ist. Wenn der Befehl service nginx start
endet, endet auch sh
, was dazu führt, dass der Hauptprozess beendet wird und der Container ganz natürlich beendet wird.
Der richtige Ansatz besteht darin, die ausführbare nginx
-Datei direkt auszuführen und sie zu verlangen, im Vordergrund zu laufen. Zum Beispiel:
CMD ["nginx", "-g", "daemon off;"]
COPY
COPY [--chown=<user>:<group>] <source_path>... <destination_path>
COPY [--chown=<user>:<group>] ["<source_path1>",... "<destination_path>"]
Ähnlich wie bei der `RUN`-Anweisung gibt es zwei Formate, eines ähnelt der Kommandozeile und das andere einem Funktionsaufruf.
Die `COPY`-Anweisung kopiert Dateien/Verzeichnisse vom `<source_path>` im Build-Kontext zum `<destination_path>` Ort in der neuen Schicht des Images. Zum Beispiel:
```bash
COPY package.json /usr/src/app/
<source_path>
kann mehrere Pfade sein und kann sogar ein Wildcard sein, wobei die Wildcard-Regeln den Regeln von Go's filepath.Match
entsprechen müssen, zum Beispiel:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<destination_path>
kann ein absoluter Pfad im Container sein oder ein relativer Pfad relativ zum Arbeitsverzeichnis (das Arbeitsverzeichnis kann mit der WORKDIR
-Anweisung angegeben werden). Der Ziel-Pfad muss vorher nicht erstellt werden. Wenn das Verzeichnis nicht existiert, wird es vor dem Kopieren der Dateien erstellt.
Zusätzlich sollte man beachten, dass bei Verwendung der COPY
-Anweisung verschiedene Metadaten der Quelldateien erhalten bleiben, wie Lese-, Schreib- und Ausführungsrechte sowie Dateiänderungszeiten. Dieses Merkmal ist nützlich für die Image-Anpassung, insbesondere wenn alle relevanten Dateien mit Git verwaltet werden.
Bei Verwendung dieser Anweisung können Sie auch die Option --chown=<user>:<group>
hinzufügen, um den Besitzer und die Gruppe der Dateien zu ändern.
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
Wenn der Quellpfad ein Ordner ist, werden beim Kopieren nicht direkt der Ordner kopiert, sondern die Inhalte des Ordners zum Ziel-Pfad.
ENTRYPOINT
Das Format von ENTRYPOINT
ist dasselbe wie bei der RUN
-Anweisung, unterteilt in exec
Format und shell
Format.
Der Zweck von ENTRYPOINT
ist derselbe wie von CMD
, beide dienen dazu, das Startprogramm des Containers und seine Parameter zu spezifizieren. ENTRYPOINT
kann auch zur Laufzeit ersetzt werden, aber es ist etwas umständlicher als CMD
und erfordert die Verwendung des --entrypoint
Parameters von docker run
um es anzugeben.
Sobald ENTRYPOINT
spezifiziert ist, ändert sich die Bedeutung von CMD
. Es führt nicht mehr direkt seinen Befehl aus, sondern behandelt den Inhalt von CMD
als Argumente, die an die ENTRYPOINT
-Anweisung übergeben werden. Mit anderen Worten, wenn es ausgeführt wird, wird es:
<ENTRYPOINT> "<CMD>"
Also mit CMD
, warum brauchen wir ENTRYPOINT
? Was sind die Vorteile von diesem <ENTRYPOINT> "<CMD>"
? Schauen wir uns ein paar Szenarien an.
Szenario 1: Das Image verhält sich wie ein Befehl
Angenommen, wir benötigen ein Image, um unsere aktuelle öffentliche IP-Adresse zu kennen, können wir es zuerst mit CMD
implementieren:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]
Wenn wir das Image mit docker build -t myip .
bauen, wenn wir die aktuelle öffentliche IP abfragen müssen, brauchen wir nur auszuführen:
$ docker run myip
Aktuelle IP: 8.8.8.8 von Kalifornien, USA
Nun, es scheint, als könnten wir das Image als Befehl verwenden, aber Befehle haben normalerweise Argumente. Was ist, wenn wir Argumente hinzufügen möchten? Aus dem oben angegebenen CMD
können wir sehen, dass der eigentliche Befehl curl
ist, also wenn wir die HTTP-Header-Informationen anzeigen möchten, müssen wir das -i
Parameter hinzufügen. Können wir direkt das -i
Parameter zu docker run myip
hinzufügen?
$ docker run myip -i
docker: Fehlerantwort vom Daemon: ungültiger Header-Feldwert "oci-Laufzeitfehler: container_linux.go:247: Starten des Containerprozesses verursacht \"exec: \\\"-i\\\": ausführbare Datei nicht gefunden in $PATH\"\n".
Wir sehen einen Fehler, dass die ausführbare Datei nicht gefunden wird, ausführbare Datei nicht gefunden
. Wie bereits erwähnt, ist das, was nach dem Image-Namen kommt, der Befehl
, der zur Laufzeit den Standardwert von CMD
ersetzen wird. Daher ersetzt hier -i
das ursprüngliche CMD
anstatt am Ende des Originals curl -s http://myip.ipip.net
hinzugefügt zu werden. Aber -i
ist gar kein Befehl, also kann es natürlich nicht gefunden werden.
Wenn wir also das -i
Parameter hinzufügen möchten, müssen wir den kompletten Befehl erneut eingeben:
$ docker run myip curl -s http://myip.ipip.net -i
Dies ist offensichtlich keine gute Lösung, aber mit ENTRYPOINT
kann dieses Problem gelöst werden. Jetzt implementieren wir dieses Image neu mit ENTRYPOINT
:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
Jetzt versuchen wir wieder docker run myip -i
zu verwenden:
$ docker run myip
Aktuelle IP: 8.8.8.8 von: Kalifornien, USA
$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Datum: Di, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Akzept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS von cache-2
X-Cache-Lookup: MISS von cache-2:80
X-Cache: MISS von proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Verbindung: keep-alive
Aktuelle IP: 8.8.8.8 von: Kalifornien, USA
Wir können sehen, dass es diesmal funktioniert hat. Dies liegt daran, dass, wenn ENTRYPOINT
vorhanden ist, der Inhalt von CMD
als Argumente an ENTRYPOINT
übergeben wird, und hier ist -i
das neue CMD
, also wird es als Argument an curl
übergeben, was den gewünschten Effekt erzielt.
Szenario 2: Vorbereitungsarbeit vor dem Start der Anwendung
Einen Container zu starten bedeutet, den Hauptprozess zu starten, aber manchmal ist vor dem Start des Hauptprozesses eine Vorbereitungsarbeit erforderlich.
Zum Beispiel, für Datenbanktypen wie mysql
, kann eine Datenbankkonfiguration und Initialisierung erforderlich sein, und diese Aufgaben müssen gelöst werden, bevor der endgültige mysql-Server läuft.
Zusätzlich möchten Sie vielleicht vermeiden, dass der Dienst als root
-Benutzer gestartet wird, um die Sicherheit zu erhöhen, aber vor dem Start des Dienstes müssen Sie noch einige notwendige Vorbereitungsarbeiten als root
-Benutzer ausführen, und schließlich zur Dienstbenutzeridentität wechseln, um den Dienst zu starten. Alternativ können Befehle, die nicht der Dienst sind, immer noch als root
-Benutzer ausgeführt werden, um die Bequemlichkeit wie Debugging zu ermöglichen.
Diese Vorbereitungsaufgaben sind nicht mit dem CMD
des Containers verwandt und müssen als Vorverarbeitungsschritt unabhängig davon, was das CMD
ist, durchgeführt werden. In diesem Fall können Sie ein Skript schreiben und es in ENTRYPOINT
setzen, um auszuführen, und dieses Skript wird die empfangenen Argumente (d.h. <CMD>
) als den Befehl am Ende auszuführen nehmen. Zum Beispiel macht das offizielle redis
-Image das:
FROM alpine:3.4
...
RUN addgroup -S redis und adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 6379
CMD [ "redis-server" ]
Sie können sehen, dass es den redis
-Benutzer für den redis
-Dienst erstellt und schließlich ENTRYPOINT
als das docker-entrypoint.sh
Skript spezifiziert.
#!/bin/sh
# Erlauben des Startens des Containers mit `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
find . ! -user redis -exec chown redis '{}' +
exec gosu redis "$0" "$@"
fi
exec "$@"
Der Inhalt dieses Skripts ist es, anhand des Inhalts von CMD
zu beurteilen. Wenn es redis-server
ist, wird es zur redis
-Benutzeridentität wechseln, um den Server zu starten. Andernfalls wird es immer noch als root
-Benutzer laufen. Zum Beispiel:
$ docker run -it redis id
uid=0(root) gid=0(root) Gruppen=0(root)
ENV
Es gibt zwei Formate:
ENV <Schlüssel> <Wert>
ENV <Schlüssel1>=<Wert1> <Schlüssel2>=<Wert2>...
Diese Anweisung ist einfach und dient dazu, Umgebungsvariablen zu setzen. Nachfolgende Anweisungen wie RUN
oder Anwendungen, die zur Laufzeit ausgeführt werden, können direkt die hier definierten Umgebungsvariablen verwenden.
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
Dieses Beispiel zeigt, wie man Zeilen umbricht und wie man doppelte Anführungszeichen verwendet, um Werte, die Leerzeichen enthalten, einzuschließen, was konsistent mit dem Verhalten in der Shell ist.
Sind Umgebungsvariablen einmal definiert, können sie in nachfolgenden Anweisungen verwendet werden. Zum Beispiel gibt es im offiziellen node
Image Dockerfile
folgenden Code:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
Die folgenden Anweisungen unterstützen die Erweiterung von Umgebungsvariablen: ADD, COPY, ENV, EXPOSE, FROM, LABEL, USER, WORKDIR, VOLUME, STOPSIGNAL, ONBUILD, RUN.
Aus dieser Liste von Anweisungen kann man entnehmen, dass Umgebungsvariablen an vielen Stellen verwendet werden können, was sehr mächtig ist. Mit Umgebungsvariablen können wir aus einer Dockerfile mehrere Images erstellen, indem wir einfach verschiedene Umgebungsvariablen verwenden.
EXPOSE
Das Format ist EXPOSE <port1> [<port2>...]
.
Die EXPOSE
-Anweisung deklariert die Ports, auf denen der Container bei der Ausführung Dienste anbieten wird. Dies ist nur eine Deklaration, und die Anwendung wird diese Ports nicht automatisch öffnen, wenn der Container aufgrund dieser Deklaration ausgeführt wird. Es gibt zwei Vorteile, diese Deklaration in der Dockerfile zu schreiben: Zum einen hilft es dem Image-Benutzer, die Daemon-Ports dieses Image-Dienstes zu verstehen, was für die Konfiguration der Portzuordnung praktisch ist; zum anderen wird bei der Verwendung von zufälliger Portzuordnung zur Laufzeit, also docker run -P
, automatisch eine zufällige Zuordnung der von EXPOSE
freigelegten Ports vorgenommen.
Die EXPOSE
-Anweisung sollte von der Verwendung von -p <host port>:<container port>
zur Laufzeit unterschieden werden. -p
bildet den Host-Port auf den Container-Port ab, mit anderen Worten, es macht den entsprechenden Port-Service des Containers für den externen Zugriff verfügbar, während EXPOSE
nur deklariert, welche Ports der Container zu verwenden beabsichtigt, und führt keine automatische Portzuordnung auf dem Host durch.
FROM
In einer Dockerfile gibt die FROM
-Anweisung das Basis-Image an, das der Ausgangspunkt für den Aufbau eines neuen Images ist. Es ist die erste Anweisung in der Dockerfile, die dazu dient, die Basisumgebung für den Bauprozess zu definieren.
Häufige Verwendungen der FROM
-Anweisung
-
Erstellung auf Basis eines offiziellen Images:
FROM image:tag
Diese Verwendung gibt an, dass ein vorhandenes offizielles Image als Basis-Image verwendet wird.
image
ist der Name des Images undtag
ist der Versionstag. Beispielsweise kannubuntu:18.04
als Basis-Image verwendet werden. -
Erstellung auf Basis eines benutzerdefinierten Images:
FROM <username>/<imagename>:<tag>
Diese Verwendung gibt ein benutzerdefiniertes Image als Basis-Image an.
<username>
ist der Benutzername auf Docker Hub,<imagename>
ist der Name des Images und<tag>
ist der Versionstag. -
Mehrstufiger Aufbau:
FROM <base-image> AS <stage-name>
Diese Verwendung ermöglicht es, mehrere Bauschritte in einer einzigen Dockerfile zu definieren und für jede Stufe unterschiedliche Basis-Images zu verwenden.
<base-image>
ist das Basis-Image und<stage-name>
ist der Name der Stufe.Mehrstufige Builds werden typischerweise verwendet, um verschiedene Werkzeuge und Abhängigkeiten während des Bauprozesses zu nutzen und dann die benötigten Dateien oder ausführbaren Dateien von einer Stufe zur nächsten zu kopieren, wodurch die Größe des endgültigen Images reduziert wird.
-
Erstellung von einem lokalen Dateisystem:
FROM scratch
Diese Verwendung zeigt an, dass der Aufbau von einem leeren Image ohne Verwendung eines vorhandenen Basis-Images gestartet wird. In diesem Fall müssen Sie die erforderlichen Dateien und Konfigurationen selbst hinzufügen.
Die FROM
-Anweisung kann in einer Dockerfile nur einmal erscheinen und muss die erste Anweisung sein. Sie definiert den Ausgangspunkt für den Bauprozess, und nachfolgende Anweisungen werden auf dieser Grundlage basieren.
HEALTHCHECK
Format:
HEALTHCHECK [Optionen] CMD <Befehl>
: Legt den Befehl fest, um den Gesundheitszustand des Containers zu überprüfen.HEALTHCHECK NONE
: Wenn das Basisbild eine Health-Check-Anweisung hat, verwenden Sie diese Zeile, um deren Health-Check-Anweisung zu überschreiben.
Die HEALTHCHECK
-Anweisung teilt Docker mit, wie festgestellt werden soll, ob der Zustand des Containers normal ist. Dies ist eine neue Anweisung, die in Docker 1.12 eingeführt wurde.
Vor der HEALTHCHECK
-Anweisung konnte die Docker-Engine nur feststellen, ob ein Container sich in einem abnormalen Zustand befand, indem überprüft wurde, ob der Hauptprozess im Container beendet wurde. In vielen Fällen stellt dies kein Problem dar, aber wenn das Programm in einen Deadlock oder eine Endlosschleife gerät, wird der Anwendungsprozess nicht beendet, aber der Container kann keine Dienste mehr bereitstellen. Vor 1.12 würde Docker einen solchen Zustand im Container nicht erkennen und nicht neu planen, was dazu führen würde, dass einige Container keine Dienste mehr bereitstellen könnten, aber immer noch Benutzeranfragen akzeptieren würden.
Seit 1.12 hat Docker die HEALTHCHECK
-Anweisung bereitgestellt, die einen Befehl spezifiziert, um festzustellen, ob der Status des Dienstes des Hauptprozesses noch normal ist, und damit den tatsächlichen Zustand des Containers genauer widerspiegelt.
Wenn eine HEALTHCHECK
-Anweisung in einem Bild angegeben wird und ein Container daraus gestartet wird, ist der Anfangszustand starting
. Nachdem die HEALTHCHECK
-Anweisung erfolgreich ist, ändert sich der Zustand in healthy
. Wenn die Überprüfung konsekutiv eine bestimmte Anzahl von Malen fehlschlägt, ändert sich der Zustand in unhealthy
.
HEALTHCHECK
unterstützt die folgenden Optionen:
--interval=<Intervall>
: Das Intervall zwischen zwei Gesundheitsüberprüfungen, standardmäßig 30 Sekunden.--timeout=<Dauer>
: Das Timeout für die Ausführung des Health-Check-Befehls. Wenn diese Zeit überschritten wird, gilt die aktuelle Gesundheitsprüfung als Fehlschlag, standardmäßig 30 Sekunden.--retries=<Anzahl>
: Nachdem konsekutiv die angegebene Anzahl von Malen fehlgeschlagen ist, wird der Zustand des Containers alsunhealthy
angesehen, standardmäßig 3 Mal.
Wie CMD
und ENTRYPOINT
kann HEALTHCHECK
nur einmal erscheinen. Wenn mehrere Instanzen geschrieben werden, wird nur die letzte wirksam.
Der Befehl nach HEALTHCHECK [Optionen] CMD
hat das gleiche Format wie ENTRYPOINT
, mit sowohl shell
als auch exec
Formaten. Der Rückgabewert des Befehls bestimmt den Erfolg oder Misserfolg der Gesundheitsprüfung: 0
: Erfolg; 1
: Fehlschlag; 2
: reserviert, verwenden Sie diesen Wert nicht.
Nehmen wir an, wir haben ein Bild, das ein einfacher Webdienst ist, und wir möchten eine Gesundheitsprüfung hinzufügen, um festzustellen, ob sein Webdienst ordnungsgemäß funktioniert. Wir können curl
verwenden, um bei der Feststellung zu helfen. Der HEALTHCHECK
im Dockerfile
kann so geschrieben werden:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
Hier setzen wir die Überprüfung so, dass sie alle 5 Sekunden läuft (dieses Intervall ist für Testzwecke sehr kurz und sollte in der Praxis relativ länger sein), und wenn der Health-Check-Befehl nicht innerhalb von 3 Sekunden reagiert, wird er als Fehlschlag angesehen. Wir verwenden curl -fs http://localhost/ || exit 1
als den Health-Check-Befehl.
Verwenden Sie docker build
, um dieses Bild zu bauen:
$ docker build -t myweb:v1 .
Nach dem Bau starten wir einen Container:
$ docker run -d --name web -p 80:80 myweb:v1
Nach dem Ausführen dieses Bildes können Sie den Anfangszustand (health: starting)
mit docker container ls
sehen:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web
Nachdem Sie ein paar Sekunden gewartet und docker container ls
erneut ausgeführt haben, wird sich der Gesundheitsstatus in (healthy)
geändert haben:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web
Wenn die Gesundheitsprüfung konsekutiv öfter fehlschlägt als die Anzahl der Wiederholungsversuche, ändert sich der Status in (unhealthy)
.
Zur Fehlerbehebung wird die Ausgabe des Health-Check-Befehls (einschließlich stdout
und stderr
) im Gesundheitsstatus gespeichert und kann mit docker inspect
angezeigt werden.
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n body {\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}
LABEL
Die Anweisung LABEL
wird verwendet, um Schlüssel-Wert-Paar-Metadaten zu einem Bild hinzuzufügen.
LABEL <Schlüssel>=<Wert> <Schlüssel>=<Wert> <Schlüssel>=<Wert> ...
Wir können auch einige Labels verwenden, um den Autor des Bildes, die Adresse der Dokumentation usw. zu deklarieren:
LABEL org.opencontainers.image.authors="yeasy"
LABEL org.opencontainers.image.documentation="https://www.ubitools.com"
RUN
In einer Dockerfile wird die Anweisung RUN
verwendet, um Befehle im Container auszuführen. Es können alle gültigen Befehle und Shell-Skripte ausgeführt werden.
Häufige Verwendungen der Anweisung RUN
-
Einen einzigen Befehl ausführen:
RUN <Befehl>
Bei dieser Verwendung ist
<Befehl>
der einzige Befehl, der im Container ausgeführt wird. Zum Beispiel:RUN apt-get update
RUN apt-get install -y packageDies führt die Befehle aus, um die Paketlisten zu aktualisieren und ein Paket im Container zu installieren.
-
Mehrere Befehle ausführen:
RUN <Befehl1> && <Befehl2>
Diese Verwendung ermöglicht es, mehrere Befehle hintereinander auf einer Zeile auszuführen, wobei der Operator
&&
verwendet wird, um sicherzustellen, dass jeder Befehl erfolgreich ist, bevor der nächste ausgeführt wird. Zum Beispiel:RUN apt-get update && apt-get install -y package
Dies führt die Befehle aus, um die Paketlisten zu aktualisieren und ein Paket im Container nacheinander zu installieren, wobei sichergestellt wird, dass der vorherige Befehl erfolgreich ist, bevor der nächste ausgeführt wird.
-
Ein Shell-Skript ausführen:
RUN /bin/bash -c "<Skript>"
Diese Verwendung ermöglicht es, komplexe Shell-Skripte im Container auszuführen. Das Skript wird in Anführungszeichen gesetzt, und
/bin/bash -c
wird verwendet, um anzugeben, dass Bash das Skript interpretieren und ausführen soll. Zum Beispiel:RUN /bin/bash -c "source setup.sh && build.sh"
Dies führt die Skripte
setup.sh
undbuild.sh
im Container aus.
Die Anweisung RUN
kann mehrfach verwendet werden, und jede Anweisung führt einen Befehl im Container aus. Jede RUN
-Anweisung erstellt eine neue Bildschicht auf der vorherigen.
Notizen:
- Befehle, die in einer
RUN
-Anweisung ausgeführt werden, wirken sich dauerhaft auf den Container aus, daher sollten Bereinigungsbefehle enthalten sein, um unnötige Dateien und Daten im Bild zu vermeiden. - Wenn Sie Umgebungsvariablen in einer
RUN
-Anweisung verwenden müssen, können Sie sie in der Dockerfile mit der AnweisungENV
definieren.
SHELL
Format: SHELL ["ausführbares Programm", "Parameter"]
Die SHELL
-Anweisung kann die Shell spezifizieren, die für die RUN
, ENTRYPOINT
und CMD
-Anweisungen verwendet werden soll. Der Standard unter Linux ist ["/bin/sh", "-c"]
.
SHELL ["/bin/sh", "-c"]
RUN ls ; ls
SHELL ["/bin/sh", "-cex"]
RUN ls ; ls
Die beiden RUN
-Anweisungen führen denselben Befehl aus, aber die zweite RUN
-Anweisung wird jeden Befehl ausgeben und bei einem Fehler beenden.
Wenn ENTRYPOINT
und CMD
im Shell-Format angegeben sind, wird die durch die SHELL
-Anweisung spezifizierte Shell auch für diese beiden Anweisungen verwendet.
SHELL ["/bin/sh", "-cex"]
# /bin/sh -cex "nginx"
ENTRYPOINT nginx
SHELL ["/bin/sh", "-cex"]
# /bin/sh -cex "nginx"
CMD nginx
STOPSIGNAL
Die STOPSIGNAL
-Anweisung wird verwendet, um das Systemaufrufsignal festzulegen, das gesendet wird, wenn ein Stopp-Signal an den Container gesendet werden soll. Diese Anweisung akzeptiert den Signalwert oder den entsprechenden Signalnamen als Argument.
Syntax:
STOPSIGNAL signal
Wo:
signal
kann der numerische Wert oder der Name des Signals sein, wie zum BeispielSIGKILL
.
Wenn die STOPSIGNAL
-Anweisung nicht angegeben ist, wird das SIGTERM
-Signal gesendet, wenn der docker stop
-Befehl verwendet wird. Wenn der Container innerhalb der angegebenen Zeit nicht stoppt, wird das SIGKILL
-Signal gesendet, um den Container zwangsweise zu beenden.
Beispiel 1:
FROM ubuntu:18.04
STOPSIGNAL SIGTERM
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]
Im obigen Beispiel haben wir das SIGTERM
-Signal als Stopp-Signal eingestellt. Wenn der docker stop
-Befehl ausgeführt wird, erhält der Container zuerst das SIGTERM
-Signal, und wenn er nicht innerhalb der vorgegebenen Zeit normal stoppt, wird das SIGKILL
-Signal gesendet, um den Container zu beenden.
Beispiel 2:
FROM ubuntu:18.04
STOPSIGNAL 9
CMD ["/usr/bin/bash", "-c", "while true; do sleep 1; done"]
In diesem Beispiel verwenden wir den Signalwert 9
, um das Senden des SIGKILL
-Signals zu spezifizieren, das direkt den Containerprozess beendet, ohne auf einen normalen Stopp zu warten.
Beachten Sie, dass selbst wenn STOPSIGNAL
gesetzt ist, Docker möglicherweise immer noch das SIGKILL
-Signal sendet, um den Container unter bestimmten Umständen zu beenden, wie zum Beispiel, wenn der Container sich in einem nicht wiederherstellbaren Zustand befindet.
USER
Format: USER <Benutzername>[:<Gruppe>]
Die USER
-Anweisung ist ähnlich wie WORKDIR
, da beide den Zustand der Umgebung ändern und nachfolgende Schichten beeinflussen. WORKDIR
ändert das Arbeitsverzeichnis, während USER
die Benutzeridentität ändert, um nachfolgende RUN
, CMD
und ENTRYPOINT
-Befehle auszuführen.
Beachten Sie, dass USER
Ihnen nur hilft, zum angegebenen Benutzer zu wechseln; dieser Benutzer muss vorher erstellt worden sein, sonst kann nicht zu ihm gewechselt werden.
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
Wenn ein Skript als root
ausgeführt wird, Sie aber die Identität während der Ausführung ändern möchten, wie beispielsweise einen Dienstprozess als vorher erstellten Benutzer ausführen möchten, verwenden Sie nicht su
oder sudo
, da sie eine komplizierte Konfiguration erfordern und oft in Umgebungen ohne TTY scheitern. Es wird empfohlen, gosu
zu verwenden.
# Erstellen Sie den Redis-Benutzer und verwenden Sie gosu, um zu einem anderen Benutzer zu wechseln und Befehle auszuführen
RUN groupadd -r redis && useradd -r -g redis redis
# gosu herunterladen
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# CMD setzen und als anderen Benutzer ausführen
CMD [ "exec", "gosu", "redis", "redis-server" ]
VOLUME
Format:
VOLUME ["<path1>", "<path2>"...]
VOLUME <path>
Wie bereits erwähnt, sollten die Speicherebenen von Containern während der Laufzeit so weit wie möglich schreibgeschützt gehalten werden. Für Anwendungen, die dynamische Daten speichern müssen, wie z.B. Datenbanken, sollten ihre Datenbankdateien in Volumes gespeichert werden. Wir werden das Konzept der Docker-Volumes in späteren Kapiteln weiter einführen. Um zu verhindern, dass Benutzer vergessen, Verzeichnisse für dynamische Daten während der Laufzeit als Volumes zu mounten, können wir bestimmte Verzeichnisse als anonyme Volumes im Dockerfile
festlegen. Auf diese Weise kann die Anwendung auch dann normal laufen, wenn der Benutzer keinen Mount angibt, ohne eine große Menge von Daten in die Containerspeicherebene zu schreiben.
VOLUME /data
In diesem Beispiel wird das Verzeichnis /data
automatisch als anonymes Volume während der Laufzeit des Containers gemountet. Alle Daten, die in /data
geschrieben werden, werden nicht in der Containerspeicherebene aufgezeichnet, was die zustandslose Natur der Containerspeicherebene gewährleistet. Natürlich kann diese Mount-Einstellung beim Ausführen des Containers überschrieben werden. Zum Beispiel:
$ docker run -d -v mydata:/data xxxx
In diesem Befehl wird das benannte Volume mydata
an der Position /data
gemountet, was die im Dockerfile
definierte Konfiguration des anonymen Volume-Mounts überschreibt.
WORKDIR
Format: WORKDIR <Arbeitsverzeichnispfad>
Mit der Anweisung WORKDIR
kann das Arbeitsverzeichnis (oder das aktuelle Verzeichnis) festgelegt werden. Nach dieser Anweisung wird das aktuelle Verzeichnis für nachfolgende Ebenen auf das angegebene Verzeichnis gesetzt. Wenn das Verzeichnis nicht existiert, wird WORKDIR
es für Sie erstellen.
Zuvor haben wir einen häufigen Fehler von Anfängern erwähnt, der darin besteht, dass sie die Dockerfile
wie ein Shell-Skript behandeln. Dieses Missverständnis könnte auch zum folgenden Fehler führen:
RUN cd /app
RUN echo "hello" > world.txt
Wenn Sie ein Image von diesem Dockerfile
bauen und ausführen, werden Sie feststellen, dass die Datei /app/world.txt
nicht gefunden wird, oder ihr Inhalt nicht hello
ist. Der Grund ist einfach: In der Shell sind zwei aufeinanderfolgende Zeilen Teil derselben Prozessausführungsumgebung, sodass der durch den vorherigen Befehl modifizierte Speicherzustand den nächsten Befehl direkt beeinflusst. In einem Dockerfile
jedoch werden diese beiden RUN
-Befehle in völlig verschiedenen Containerumgebungen ausgeführt, die zwei völlig separate Container sind. Dies ist ein Fehler, der durch ein Unverständnis des Schichten-Speicherkonzepts in Dockerfile
-Builds verursacht wird.
Wie bereits erwähnt, startet jedes RUN
einen neuen Container, führt den Befehl aus und übernimmt dann die Dateiänderungen. Die erste Ebene RUN cd /app
ändert nur das Arbeitsverzeichnis des aktuellen Prozesses, was nur eine Änderung im Speicher ist und keine Dateiänderungen zur Folge hat. Bis die zweite Ebene erreicht ist, wird ein brandneuer Container gestartet, der völlig unabhängig von dem Container der ersten Ebene ist, so dass er die Änderungen im Speicher des vorherigen Buildprozesses nicht erben kann.
Daher sollten Sie, wenn Sie den Ort des Arbeitsverzeichnisses für nachfolgende Ebenen ändern müssen, die Anweisung WORKDIR
verwenden.
WORKDIR /app
RUN echo "hello" > world.txt
Wenn Sie einen relativen Pfad in Ihrer WORKDIR
-Anweisung verwenden, ist das Verzeichnis, zu dem gewechselt wird, relativ zu dem vorherigen WORKDIR
:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
Das Arbeitsverzeichnis für RUN pwd
wird /a/b/c
sein.