Wir hatten 2 SĂ€cke Gras, 75 Tabletten Mescalin, eine Unix-Umgebung, ein Docker-Repository und die Aufgabe, die Befehle docker pull und docker push ohne den Docker-Client zu implementieren.

UPD:
Frage: Wozu das alles?
Antwort: Lasttests des Produkts (NICHT mit Bash-Methoden, die Skripte sind zu Bildungszwecken beigefĂŒgt). Es wurde entschieden, den Docker-Client nicht zu verwenden, um zusĂ€tzliche Schichten (in einem angemessenen Rahmen) zu reduzieren und entsprechend eine höhere Last zu emulieren. Dadurch wurden alle systembedingten Verzögerungen des Docker-Clients entfernt. Wir erhielten eine relativ reine Last direkt auf das Produkt.
In dem Artikel wurden Werkzeuge der GNU-Versionen verwendet.
Lassen Sie uns zunÀchst klÀren, was diese Befehle tun.
Also, wozu wird docker pull verwendet? Laut :
"Ziehe ein Bild oder ein Repository aus einem Registry."
Dort finden wir auch den Link zu .

Von hier aus können wir verstehen, dass ein Docker-Image eine Sammlung von Schichten ist, die Informationen ĂŒber die letzten Ănderungen im Image enthalten, die offensichtlich fĂŒr uns von Bedeutung sind. Danach schauen wir in die .
Hier steht Folgendes:
"Ein 'Image' ist eine Kombination aus einem JSON-Manifest und individuellen Schichtdateien. Der Vorgang des Ziehens eines Images konzentriert sich auf das Abrufen dieser beiden Komponenten."
Der erste Schritt laut Dokumentation ist âZiehen eines Image-Manifests.â.
Wir werden es natĂŒrlich nicht ziehen, aber die Daten daraus benötigen wir. Weiterhin folgt ein Beispiel fĂŒr eine Anfrage: GET /v2/{name}/manifests/{reference}
"Der Name und der Referenzparameter identifizieren das Bild und sind erforderlich. Die Referenz kann ein Tag oder Digest enthalten."
Unser Docker-Repository ist lokal bereitgestellt, versuchen wir, die Anfrage auszufĂŒhren:
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"
Im Antwort erhalten wir JSON, aus dem uns derzeit nur die Schichten, genauer gesagt ihre Hashes, interessieren. Nachdem wir sie erhalten haben, können wir jeden durchgehen und folgende Anfrage ausfĂŒhren: "GET /v2/{name}/blobs/{digest}"
âDer Zugang zu einer Schicht wird durch den Namen des Repositories geregelt, ist jedoch im Registry eindeutig durch Digest identifiziert.â
Digest ist in diesem Fall der Hash, den wir erhalten haben.
Wir probieren es aus
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer![]()
Sehen wir uns an, welche Datei wir letztendlich als erste Schicht erhalten haben.
file firstLayer
d.h. die Schichten sind tar-Archive, deren Entpackung in entsprechender Reihenfolge den Inhalt des Images ergibt.
Lassen Sie uns ein kleines Bash-Skript schreiben, um all dies zu automatisieren.
#!/bin/bash -eu
downloadDir=$1
# url as http://localhost:8081/link/to/docker/registry
url=$2
imageName=$3
tag=$4
# array of layers
layers=($(curl -s -X GET "$url/v2/$imageName/manifests/$tag" | grep -oP '(?<=blobSum" : ").+(?=")'))
# download each layer from array
for layer in "${layers[@]}"; do
echo "Downloading ${layer}"
curl -v -X GET "$url/v2/$imageName/blobs/$layer" --output "$downloadDir/$layer.tar"
done
# find all layers, untar them and remove source .tar files
cd "$downloadDir" && find . -name "sha256:*" -exec tar xvf {} ;
rm sha256:*.tar
exit 0Jetzt können wir es mit den gewĂŒnschten Parametern starten und den Inhalt des benötigten Images erhalten.
.\/script.sh dirName "http:\/\/localhost:8081\/link\/to\/docker\/registry" myAwesomeImage 1.0Teil 2 â docker push
Hier wird es etwas komplizierter.
Lass uns wieder mit beginnen. Also, wir mĂŒssen jede Schicht hochladen, das entsprechende Manifest erstellen und es auch hochladen. Klingt einfach.
Nachdem wir die Dokumentation studiert haben, können wir den Upload-Prozess in mehrere Schritte unterteilen:
- Initialisierung des Prozesses â "POST \/v2\/{repoName}\/blobs\/uploads\/"
- Hochladen der Schicht (wir werden die monolithische Ăbertragung verwenden, d.h. jede Schicht wird ganz gesendet) â "PUT \/v2\/{repoName}\/blobs\/uploads\/{uuid}?digest={digest}
Content-Length: {size of layer}
Content-Type: application\/octet-stream
Layer Binary Data". - Hochladen des Manifests â "PUT \/v2\/{repoName}\/manifests\/{reference}".
Aber in der Dokumentation wurde ein Schritt ausgelassen, ohne den nichts funktionieren wird. FĂŒr den monolithischen Upload, ebenso wie fĂŒr den partiellen (chunked), muss vor dem Hochladen einer Schicht ein PATCH-Antrag ausgefĂŒhrt werden:
"PATCH \/v2\/{repoName}\/blobs\/uploads\/{uuid}
Content-Length: {size of chunk}
Content-Type: application\/octet-stream
{Layer Chunk Binary Data}".
Andernfalls können Sie nicht ĂŒber den ersten Punkt hinauskommen, da Sie anstelle des erwarteten Antwortcodes 202 einen 4xx-Code erhalten werden.
Jetzt sieht der Algorithmus so aus:
- Initialisierung
- Patch der Schicht
- Hochladen der Schicht
- Hochladen des Manifests
Die Punkte 2 und 3 werden entsprechend so oft wiederholt, wie Schichten hochgeladen werden mĂŒssen.
ZunÀchst benötigen wir ein beliebiges Image. Ich werde archlinux:latest verwenden.
docker pull archlinux
Jetzt speichern wir es lokal fĂŒr weitere Analysen.
docker save c24fe13d37b9 -o savedArch
Entpacken Sie das erhaltene Archiv im aktuellen Verzeichnis.
tar xvf savedArch
Wie wir sehen, liegt jede Schicht in einem eigenen Ordner. Lassen Sie uns nun die Struktur des Manifests betrachten, das wir erhalten haben.
cat manifest.json | json_pp
Nicht viel. Lassen Sie uns sehen, welches Manifest fĂŒr den Upload erforderlich ist, gemÀà .

Offensichtlich ist das vorhandene Manifest nicht geeignet, also machen wir unser eigenes mit Blackjack und Layern und Konfigurationen.
Wir werden immer mindestens eine config-Datei und ein Array von Schichten haben. Versionsschema 2 (gĂŒltig zum Zeitpunkt des Schreibens des Artikels), mediaType bleibt unverĂ€ndert:
echo â{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": config_size,
"digest": "config_hash"
},
"layers": [
â > manifest.jsonNach der Erstellung des Basis-Manifests muss es mit gĂŒltigen Daten gefĂŒllt werden. Dazu verwenden wir das Template des JSON-Objekts fĂŒr die Schicht:
{
"mediaType": "application\/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
},dies werden wir fĂŒr jedes Layer im Manifest hinzufĂŒgen.
Als NĂ€chstes mĂŒssen wir die GröĂe der Konfigurationsdatei herausfinden und die Platzhalter im Manifest durch die tatsĂ€chlichen Daten ersetzen.
sed -i "s/config_size/[33m$configSize[0m/g; s/config_hash/[33m$configName[0m/g" $manifestFileNun können wir den Upload-Prozess initiieren und uns die UUID speichern, die alle nachfolgenden Anfragen begleiten sollte.
Das vollstÀndige Skript sieht ungefÀhr so aus:
#!/bin/bash -eux
imageDir=$1
# url as http://localhost:8081/link/to/docker/registry
url=$2
repoName=$3
tag=$4
manifestFile=$(readlink -f ${imageDir}/manifestCopy)
configFile=$(readlink -f $(find $imageDir -name "*.json" ! -name "manifest.json"))
# calc layers sha 256 sum, rename them accordingly, and add info about each to manifest file
function prepareLayersForUpload() {
info_file=$imageDir/info
# lets calculate layers sha256 and use it as layers names further
layersNames=($(find $imageDir -name "layer.tar" -exec shasum -a 256 {} ; | cut -d" " -f1))
# rename layers according to shasums. !!!Set required amount of fields for cut command!!!
# this part definitely can be done easier but i didn't found another way, sry
find $imageDir -name "layer.tar" -exec bash -c 'mv {} "$(echo {} | cut -d"/" -f1,2)/$(shasum -a 256 {} | cut -d" " -f1)"' ;
layersSizes=($(find $imageDir -name "*.tar" -exec ls -l {} ; | awk '{print $5}'))
for i in "${!layersNames[@]}"; do
echo "{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
}," >> $manifestFile
done
# remove last ','
truncate -s-2 $manifestFile
# add closing brakets to keep json consistent
printf "nt]n}" >> $manifestFile
}
# calc config sha 256 sum and add info about it to manifest
function setConfigProps() {
configSize=$(ls -l $configFile | awk '{print $5}')
configName=$(basename $configFile | cut -d"." -f1)
sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFile
}
#prepare manifest file
prepareLayersForUpload
setConfigProps
cat $manifestFile
# initiate upload and get uuid
uuid=$(curl -s -X POST -I "$url/v2/$repoName/blobs/uploads/" | grep -oP "(?<=Docker-Upload-Uuid: ).+")
# patch layers
# in data-binary we're getting absolute path to layer file
for l in "${!layersNames[@]}"; do
pathToLayer=$(find $imageDir -name ${layersNames[$l]} -exec readlink -f {} ;)
curl -v -X PATCH "$url/v2/$repoName/blobs/uploads/$uuid"
-H "Content-Length: ${layersSizes[$i]}"
-H "Content-Type: application/octet-stream"
--data-binary "@$pathToLayer"
# put layer
curl -v -X PUT "$url/v2/$repoName/blobs/uploads/$uuid?digest=sha256:${layersNames[$i]}"
-H 'Content-Type: application/octet-stream'
-H "Content-Length: ${layersSizes[$i]}"
--data-binary "@$pathToLayer"
done
# patch and put config after all layers
curl -v -X PATCH "$url/v2/$repoName/blobs/uploads/$uuid"
-H "Content-Length: $configSize"
-H "Content-Type: application/octet-stream"
--data-binary "@$configFile"
curl -v -X PUT "$url/v2/$repoName/blobs/uploads/$uuid?digest=sha256:$configName"
-H 'Content-Type: application/octet-stream'
-H "Content-Length: $configSize"
--data-binary "@$configFile"
# put manifest
curl -v -X PUT "$url/v2/$repoName/manifests/$tag"
-H 'Content-Type: application/vnd.docker.distribution.manifest.v2+json'
--data-binary "@$manifestFile"
exit 0
Wir können ein fertiges Skript verwenden:
./uploadImage.sh "~\/path\/to\/saved\/image" "http:\/\/localhost:8081\/link\/to\/docker\/registry" myRepoName 1.0UPD:
Was haben wir als Ergebnis erhalten?
Erstens, echte Daten zur Analyse, da die Tests in BlazeMeter ausgefĂŒhrt werden und die Daten zu den Docker-Client-Anfragen im Vergleich zu reinen HTTP-Anfragen wenig informativ sind.
Zweitens haben wir durch den Wechsel die Anzahl der virtuellen Benutzer fĂŒr den Docker-Upload um etwa 150 % erhöhen und gleichzeitig die durchschnittliche Antwortzeit um 20-25 % verkĂŒrzen können. Beim Docker-Download konnten wir die Anzahl der Benutzer um 500 % erhöhen, wĂ€hrend die durchschnittliche Antwortzeit um etwa 60 % gesenkt wurde.
Danke fĂŒr Ihre Aufmerksamkeit.
Quelle: habr.com
