Layer eines Container Image bestimmen
Container Image sind dank OCI Standard und Verbreitung von Docker und Kubernetes zu dem Mittel der Wahl geworden, um Anwendungen zu verteilen.
Dabei verwenden Container Runtimes wie Docker ein geschicktes Verfahren zur Deduplizierung von Daten:
Ein Container Image ist in Layer aufgeteilt und diese können unabhängig voneinander verteilt und gespeichert werden.
Da die Layer selbst nicht veränderlich sind, können Layer wiederverwendet werden.
Typischerweise finden sich auch viele Gemeinsamkeiten: Die verwendete Basisdistribution, Laufzeitumgebungen wie Python oder Java und ggf. auch firmenspezifische Konfigurationen wie TLS Zertifikate.
Dabei muss ein Tradeoff beachtet werden: Je mehr Layer, desto mehr Overhead entsteht durch sie. Gleichzeitig ist es um so wahrscheinlicher, dass bei feingranularen Image Layern eine gute Wiederverwendbarkeit ermöglicht wird.
Bei der Auswahl von Base-Images, auf die eigene Anwendungsimages aufgebaut werden sollen, gilt es also darauf zu achten, dass diese möglichst geschickt augeteilt sind und nicht nur aus einem Layer bestehen, der stets vollständig neu geladen und gespeichert werden muss, wenn es Aktualisierungen gibt, aber auch nicht aus zehn oder mehr Layern.
Zur Bestimmung der Anzahl der Layer gibt es verschiedene Ansätze.
Am Beispiel eines Java Basisimages, openjdk
, werden diese im folgenden betrachtet.
docker pull
Bei einem docker pull
werden die einzelnen Layer übertragen.
Dies wird auch entsprechend in der Ausgabe angezeigt.
Das gilt allerdings nur, falls das Image noch nicht vorhanden ist - sonst wird lediglich eine Meldung ausgegeben, dass es kein Update gibt.
docker pull
$ docker pull openjdk
Using default tag: latest
latest: Pulling from library/openjdk
197c1adcd755: Pull complete
57b698b7af4b: Pull complete
95a27dbe0150: Pull complete
Digest: sha256:9b448de897d211c9e0ec635a485650aed6e28d4eca1efbc34940560a480b3f1f
Status: Downloaded newer image for openjdk:latest
docker.io/library/openjdk:latest
Sollen viele Images miteinander verglichen werden, müssen diese alle frisch heruntergeladen werden. Diese Ausgabe ist dabei auch schwer automatisch auszuwerten.
docker history
Ein weiterer möglicher Ansatz, um die Anzahl von Layer in einem Container Image zu zählen, ist die Verwendung von docker history
.
Eine Zeile der Ausgabe entspricht dabei einer Zeile in einem Dockerfile
.
docker history
$ docker history openjdk
IMAGE CREATED CREATED BY SIZE COMMENT
71260f256d19 16 months ago /bin/sh -c #(nop) CMD ["jshell"] 0B
<missing> 16 months ago /bin/sh -c set -eux; arch="$(objdump="$(co… 324MB
<missing> 16 months ago /bin/sh -c #(nop) ENV JAVA_VERSION=18.0.2.1 0B
<missing> 16 months ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0B
<missing> 16 months ago /bin/sh -c #(nop) ENV PATH=/usr/java/openjd… 0B
<missing> 16 months ago /bin/sh -c #(nop) ENV JAVA_HOME=/usr/java/o… 0B
<missing> 16 months ago /bin/sh -c set -eux; microdnf install gzi… 36.7MB
<missing> 16 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 16 months ago /bin/sh -c #(nop) ADD file:6a3fb962576ab4237… 110MB
Hier fällt direkt auf, dass hier deutlich mehr Zeilen ausgegeben werden, als Layer im pull bezogen wurden. Das liegt daran, dass einige Anweisungen im Dockerfile lediglich dazu führen, dass Metadaten abgelegt werden, die nicht zu einem separaten Layer führen.
docker image inspect
Docker erlaubt es, Images durch inspect
zu analysieren.
Dabei werden Konfigurationsdaten und auch Layerinformationen zurückgeliefert.
docker inspect
(gekürzt)$ docker inspect openjdk
[
{
"Id": "sha256:71260f256d19f4ae5c762601e5301418d2516ca591103b1376f063be0b7ba056",
"RepoTags": [
"openjdk:latest"
],
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:9cd9df9ffc972e9abc946d855162ef0c40dff9a89f10c962b7920154a3d943d8",
"sha256:077bff59ce5723e3c7d78bdf4fd8b10d72f6f8474b97cdb9323816aa5d8314a6",
"sha256:56285d9a776094205dc0b66078bf0719f50c734a00754292e6fcbd13b17f5155"
]
}
}
]
Da die Image und auch Layerinformationen im JSON Format vorliegen, können diese mit dem Werkzeug jq
praktisch weiterverarbeitet werden.
So lassen sich beispielsweise lediglich die Layerinformationen extrahieren.
Mit jq
lassen sich sogar die Elemente zählen, so dass sich damit direkt die Anzahl der Layer ermitteln lässt.
jq
$ docker inspect openjdk | jq ".[].RootFS.Layers"
[
"sha256:9cd9df9ffc972e9abc946d855162ef0c40dff9a89f10c962b7920154a3d943d8",
"sha256:077bff59ce5723e3c7d78bdf4fd8b10d72f6f8474b97cdb9323816aa5d8314a6",
"sha256:56285d9a776094205dc0b66078bf0719f50c734a00754292e6fcbd13b17f5155"
]
$ docker inspect openjdk | jq ".[].RootFS.Layers | length"
3
Registry API
Bei den bisherigen Ansätzen mussten die Container Images stets lokal vorliegen. Das ist unpraktisch, wenn lediglich Metainformationen benötigt werden.
Zum Glück bieten Container Registries durch eine normierte API die Layer-Metadaten direkt an.
Auch hier wird JSON als Ausgabeformat genutzt, so dass mit jq
eine Verarbeitung möglich ist.
Das folgende Shellscript ermittelt für ein Image die Anzahl der Layer, ohne dass das Image vorher heruntergeladen werden muss.
REGISTRY=registry-1.docker.io
REPOSITORY=library/openjdk
TAG=latest
#bei DockerHub ist in jedem Fall Bearer-Token notwendig
TOKEN=$(curl -s -u "$USERNAME:$PASSWORD" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${REPOSITORY}:pull" | jq -r .token)
#oder anonyme Authentifizierung (Rate Limit)
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${REPOSITORY}:pull" | jq -r .token)
#das Token muss im Request mitgegeben werden
MANIFEST=$(curl -s -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://${REGISTRY}/v2/${REPOSITORY}/manifests/${TAG}")
#falls keine Authentifizierung erfoderlich ist, kann das Token entfallen
MANIFEST=$(curl -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://${REGISTRY}/v2/${REPOSITORY}/manifests/${TAG}")
echo -n "Layers ${REGISTRY}/${REPOSITORY}:${TAG} : "
echo "$MANIFEST" | jq -r '.layers | length'
Bei neueren Registries, wie der Google Container Registry, steht lediglich das OCI Format zur Verfügung. In dem Fall muss zunächst das eigentliche Manifest für ein Image ermittelt werden. Als Rückgabe wird im neuen Format nämlich lediglich eine Art Übersicht geliefert, die auf verschiedene konkrete Images für unterschiedliche Plattformen verweist.
REGISTRY=gcr.io
REPOSITORY=distroless/java21
TAG=latest
#OCI Manifest Media Type
META_MANIFEST=$(curl -s -H "Accept: application/vnd.oci.image.index.v1+json" \
"https://${REGISTRY}/v2/${REPOSITORY}/manifests/${TAG}")
AMD64_DIGEST=$(echo "$META_MANIFEST" | jq -r '.manifests[] | select(.platform.architecture == "amd64") | .digest')
MANIFEST=$(curl -s \
-H "Accept: application/vnd.oci.image.manifest.v1+json" \
"https://${REGISTRY}/v2/${REPOSITORY}/manifests/$AMD64_DIGEST")
echo -n "Layers ${REGISTRY}/${REPOSITORY}:${TAG} : "
echo "$MANIFEST" | jq -r '.layers | length'
Je nach Anwendungsfall gibt es verschiedene Ansätze, um die Anzahl der Layer eines Container Images zu bestimmen. Besonders attraktiv ist ohne einen Download des Image auszukommen, indem die Registry API abgefragt wird.
Zu den Themen Kubernetes und Docker bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:
Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.