Měli jsme 2 pytle trávy, 75 meskalinových tabletů unixové prostředí, docker úložiště a úkol implementovat příkazy docker pull a docker push bez docker klienta.
UPD:
Otázka: K čemu to všechno je?
Odpověď: Zátěžové testování produktu (NEpoužívá bash, skripty jsou poskytovány pro vzdělávací účely). Bylo rozhodnuto nepoužívat docker klienta ke snížení dalších vrstev (v rozumných mezích) a v souladu s tím emulovat vyšší zatížení. V důsledku toho byla odstraněna všechna systémová zpoždění klienta Docker. Dostali jsme poměrně čistý náklad přímo na produkt.
V článku byly použity verze nástrojů GNU.
Nejprve pojďme zjistit, co tyto příkazy dělají.
K čemu se tedy docker pull používá? Podle
"Vytáhnout obrázek nebo úložiště z registru".
Tam také najdeme odkaz na
Odtud můžeme pochopit, že obrázek dockeru je sada určitých vrstev, které obsahují informace o nejnovějších změnách v obrázku, což je samozřejmě to, co potřebujeme. Dále se podíváme na
Říká se v něm následující:
"Obrázek" je kombinací manifestu JSON a souborů jednotlivých vrstev. Proces stahování obrázku se soustředí na získávání těchto dvou komponent."
Takže první krok podle dokumentace je „Vytažení obrazového manifestu".
Samozřejmě to nebudeme střílet, ale potřebujeme z toho data. Následuje příklad požadavku: GET /v2/{name}/manifests/{reference}
"Název a parametr reference identifikují obrázek a jsou povinné. Reference může obsahovat značku nebo výtah."
Naše úložiště dockerů je nasazeno lokálně, zkusme požadavek provést:
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"
Jako odpověď dostáváme json, ze kterého nás aktuálně zajímají pouze záchranné linky, respektive jejich hashe. Jakmile je obdržíme, můžeme je projít a provést následující požadavek: "GET /v2/{name}/blobs/{digest}"
"Přístup k vrstvě bude brán podle názvu úložiště, ale je v registru jednoznačně identifikován pomocí výtahu."
digest je v tomto případě hash, který jsme obdrželi.
Zkouší
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer
Pojďme se podívat, jaký soubor jsme nakonec dostali jako první záchranné lano.
file firstLayer
těch. kolejnice jsou archivy dehtu, jejich rozbalením v příslušném pořadí získáme obsah obrázku.
Pojďme napsat malý bash skript, aby se to všechno dalo automatizovat
#!/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 0
Nyní jej můžeme spustit s požadovanými parametry a získat obsah požadovaného obrázku
./script.sh dirName “http://localhost:8081/link/to/docker/registry” myAwesomeImage 1.0
Část 2 - docker push
Tohle bude trochu složitější.
Začněme znovu s
Po prostudování dokumentace můžeme proces stahování rozdělit do několika kroků:
- Inicializace procesu – "POST /v2/{repoName}/blobs/uploads/"
- Nahrání záchranného lana (použijeme monolitický upload, tj. každé záchranné lano posíláme celé) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
Content-Length: {velikost vrstvy}
Content-Type: application/oktet-stream
Binární data vrstvy". - Načítání manifestu - "PUT /v2/{repoName}/manifests/{reference}".
V dokumentaci ale chybí jeden krok, bez kterého nebude nic fungovat. Pro monolitické nakládání i pro částečné (rozdělené) před načtením kolejnice musíte provést požadavek PATCH:
"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Content-Length: {velikost chunk}
Content-Type: application/oktet-stream
{Binární data bloku vrstvy}“.
Jinak se nebudete moci posunout za první bod, protože... Místo očekávaného kódu odpovědi 202 obdržíte 4xx.
Nyní algoritmus vypadá takto:
- Inicializace
- Patch rail
- Zatížení zábradlí
- Načítání manifestu
Body 2 a 3 se budou opakovat tolikrát, kolik řádků bude potřeba načíst.
Nejprve potřebujeme jakýkoli obrázek. Budu používat archlinux:latest
docker pull archlinux
Nyní to uložme lokálně pro další analýzu
docker save c24fe13d37b9 -o savedArch
Rozbalte výsledný archiv do aktuálního adresáře
tar xvf savedArch
Jak vidíte, každé záchranné lano je v samostatné složce. Nyní se podívejme na strukturu manifestu, který jsme obdrželi
cat manifest.json | json_pp
Nic moc. Podívejme se, jaký manifest je potřeba načíst podle
Stávající manifest nám evidentně nevyhovuje, takže si vytvoříme vlastní s blackjackem a kurtizánami, záchrannými lany a konfiguracemi.
Vždy budeme mít alespoň jeden konfigurační soubor a řadu záchranných linií. Verze schématu 2 (aktuální v době psaní), mediaType zůstane nezměněn:
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.json
Po vytvoření základního manifestu je potřeba jej naplnit platnými údaji. K tomu používáme šablonu json objektu rail:
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
},
Přidáme to do manifestu pro každou kolej.
Dále musíme zjistit velikost konfiguračního souboru a nahradit pahýly v manifestu skutečnými daty
sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFile
Nyní můžete zahájit proces stahování a uložit si uuid, který by měl doprovázet všechny následující požadavky.
Kompletní skript vypadá asi takto:
#!/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
můžeme použít hotový skript:
./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0
UPD:
Co jsme jako výsledek získali?
Za prvé, skutečná data pro analýzu, protože testy probíhají v blazemeteru a data o požadavcích klienta dockeru nejsou příliš informativní, na rozdíl od čistých požadavků HTTP.
Za druhé, přechod nám umožnil zvýšit počet virtuálních uživatelů pro nahrávání do dockeru asi o 150 % a získat průměrnou dobu odezvy o 20–25 % rychlejší. U stahování dockeru se nám podařilo zvýšit počet uživatelů o 500 %, zatímco průměrná doba odezvy se snížila o cca 60 %.
Děkuji vám za pozornost.
Zdroj: www.habr.com