Avevamo due sacchi di erba, 75 compresse di mescalina in un ambiente Unix, un repository docker e l'obiettivo di eseguire i comandi docker pull e docker push senza il client docker.

UPD:
Domanda: A cosa serve tutto questo?
Risposta: Test di carico del prodotto (NON con strumenti bash, gli script sono forniti a scopo educativo). È stata presa la decisione di non utilizzare il client docker per ridurre ulteriori strati (nei limiti ragionevoli) e quindi simulare un carico più elevato. Di conseguenza, abbiamo eliminato tutti i ritardi sistemici del client docker, ottenendo un carico relativamente pulito direttamente sul prodotto.
Nell'articolo sono stati usati strumenti GNU versioni.
Per iniziare, chiariremo cosa fanno questi comandi.
Allora, a cosa serve docker pull? Secondo :
"Tira un'immagine o un repository da un registro".
Lì troviamo anche il link a .

Da qui possiamo capire che l'immagine docker è un insieme di strati che contengono informazioni sulle ultime modifiche all'immagine, che ovviamente ci servono. Proseguiamo quindi con .
Qui si dice quanto segue:
"Un'«immagine» è una combinazione di un manifesto JSON e singoli file di layer. Il processo per recuperare un'immagine si concentra sull'ottenere questi due componenti."
Quindi il primo passo secondo la documentazione è “Recuperare un manifesto dell'immagine”.
Non lo recupereremo, ma abbiamo bisogno dei dati contenuti. Di seguito è riportato un esempio di richiesta: GET /v2/{name}/manifests/{reference}
"Il parametro name e reference identificano l'immagine e sono obbligatori. Il reference può includere un tag o un digest."
Il nostro repository Docker è stato installato localmente, proviamo a eseguire la richiesta:
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"
In risposta otteniamo un JSON da cui ci interessano al momento solo i layer, più precisamente i loro hash. Ottenendoli, possiamo eseguire la seguente richiesta per ciascuno: "GET /v2/{name}/blobs/{digest}"
“L'accesso a un layer sarà controllato dal nome del repository ma è identificato in modo univoco nel registro dal digest.”
Il digest in questo caso è l'hash che abbiamo ottenuto.
Proviamo
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer![]()
Vediamo che tipo di file abbiamo ottenuto come primo layer.
file firstLayer
Quindi, i layer sono archivi tar; estraendoli nell'ordine corretto otterremo il contenuto dell'immagine.
Scriviamo un piccolo script bash per automatizzare tutto questo.
#!/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 0Ora possiamo eseguirlo con i parametri desiderati e ottenere il contenuto dell'immagine necessaria.
./script.sh dirName "http://localhost:8081/link/to/docker/registry" myAwesomeImage 1.0Parte 2 — docker push
Qui sarà un po' più complicato.
Iniziamo di nuovo con . Quindi dobbiamo caricare ogni layer, assemblare il manifesto corrispondente e caricarlo anche. Sembra semplice.
Esaminando la documentazione possiamo suddividere il processo di caricamento in più passaggi:
- Inizializzazione del processo — "POST /v2/{repoName}/blobs/uploads/"
- Caricamento del layer (utilizzeremo il caricamento monolitico, ovvero inviamo ogni layer per intero) — "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}"
Content-Length: {dimensione del layer}
Content-Type: application/octet-stream
Dati binari del layer". - Caricamento del manifesto — "PUT /v2/{repoName}/manifests/{reference}".
Ma la documentazione omette un passaggio, senza il quale nulla funzionerà. Per il caricamento monolitico, così come per quello parziale (chunked), prima di caricare il layer è necessario eseguire una richiesta PATCH:
"PATCH /v2/{repoName}/blobs/uploads/{uuid}"
Content-Length: {dimensione del chunk}
Content-Type: application/octet-stream
{Dati binari del chunk del layer}".
Altrimenti non potrai procedere oltre il primo punto, poiché invece del codice di risposta 202 previsto, riceverai un codice 4xx.
Ora l'algoritmo appare come segue:
- Inizializzazione
- Layer patch
- Caricamento del layer
- Caricamento del manifesto
I punti 2 e 3 verranno ripetuti tante volte quante sono le layer da caricare.
Per iniziare avremo bisogno di un'immagine qualsiasi. Userò archlinux:latest.
docker pull archlinux
Ora salviamo questa immagine localmente per ulteriori analisi.
docker save c24fe13d37b9 -o savedArch
Estraiamo l'archivio ottenuto nella directory corrente.
tar xvf savedArch
Come vediamo, ogni layer si trova in una cartella separata. Ora diamo un'occhiata alla struttura del manifesto che abbiamo ottenuto.
cat manifest.json | json_pp
Non è molto. Controlliamo quale manifesto è necessario per il caricamento, secondo. .

Ovviamente, il manifesto esistente non è adatto, quindi ne creeremo uno nostro con layer, configurazioni e… blackjack.
Avremo sempre almeno un file di configurazione e un array di layer. La versione dello schema è 2 (valida al momento della scrittura di questo articolo), mentre il mediaType rimarrà invariato.
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.jsonDopo aver creato il manifesto di base, è necessario compilarlo con dati validi. Per fare ciò, utilizziamo il modello dell'oggetto JSON del layer:
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
},questo sarà aggiunto al manifesto per ogni layer.
In seguito, dobbiamo conoscere la dimensione del file di configurazione e sostituire i segnaposto nel manifesto con i dati reali
sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFileOra possiamo avviare il processo di caricamento e salvare l'UUID, che deve accompagnare tutte le richieste successive.
Lo script completo appare più o meno così:
#!/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
possiamo utilizzare uno script pronto:
./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0UPD:
Cosa abbiamo ottenuto come risultato?
In primo luogo, dati reali per l'analisi, poiché i test vengono eseguiti in blazemeter e i dati delle richieste del client Docker sono piuttosto poco informativi rispetto a richieste HTTP pulite.
In secondo luogo, la transizione ci ha permesso di aumentare il numero di utenti virtuali per il caricamento di Docker di circa il 150% e, nel contempo, di ottenere un tempo di risposta medio più veloce del 20-25%. Per il download di Docker, siamo riusciti ad aumentare il numero di utenti del 500%, riducendo il tempo di risposta medio di circa il 60%.
Grazie per l'attenzione.
Fonte: habr.com
