Mali sme 2 vrecia trávy, 75 tabliet meskalínu, unixové prostredie, docker repozitár a úlohu realizovať príkazy docker pull a docker push bez docker klienta.

UPD:
Otázka: Na čo je to všetko?
Odpoveď: Záťažové testovanie produktu (nie prostriedkami bashu, skripty sú uvedené na vzdelávacie účely). Nerozhodli sme sa použiť docker klienta, aby sme znížili ďalšie vrstvy (v rozumných medziach) a simulovali tak vyššiu záťaž. Výsledkom bolo odstránenie všetkých systémových oneskorení docker klienta. Dospeli sme k pomerne čistej záťaži priamo na produkte.
V článku boli použité nástroje GNU verzií.
Na začiatok si vysvetlíme, čo tieto príkazy robia.
Takže na čo sa používa docker pull? Podľa :
"Pull an image or a repository from a registry".
Tam tiež nájdeme odkaz na .

Odtiaľ môžeme pochopiť, že docker image je súbor určitých vrstiev, ktoré obsahujú informácie o posledných zmenách v obraze, ktoré sú očividne potrebné. Potom sa pozrieme na .
Tu sa hovorí nasledovné:
"An “image” is a combination of a JSON manifest and individual layer files. The process of pulling an > image centers around retrieving these two components."
Takže prvý krok podľa dokumentácie je “Pulling an Image Manifest”.
Samozrejme, že ho ťahať nebudeme, ale údaje z neho potrebujeme. Potom sa uvádza príklad požiadavky: GET /v2/{name}/manifests/{reference}
"The name and reference parameter identify the image and are required. The reference may include a tag or digest."
Náš docker repozitár je nasadený lokálne, skúsime vykonať požiadavku:
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"
V odpovedi dostávame json, z ktorého sú nám v súčasnosti zaujímavé len vrstvy, presnejšie ich hash. Získaním ich môžeme prejsť každým a vykonať nasledujúcu požiadavku: "GET /v2/{name}/blobs/{digest}"
“Access to a layer will be gated by the name of the repository but is identified uniquely in the registry by digest.”
digest v tomto prípade je hash, ktorý sme získali.
Skúsime
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer![]()
pozrieme sa, aký súbor sme nakoniec dostali ako prvú vrstvu.
file firstLayer
t.j. vrstvy predstavujú tar archívy, rozbalením ktorých v príslušnom poradí dostaneme obsah obrazu.
Napíšeme malý bash skript, aby sme to všetko mohli automatizovať.
#!/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 0Teraz ho môžeme spustiť s požadovanými parametrami a získať obsah potrebného obrazu.
.\/script.sh dirName “http:\/\/localhost:8081\/link\/to\/docker\/registry” myAwesomeImage 1.0Časť 2 — docker push
Tu to bude trochu zložitejšie.
Začneme znova s . Takže musíme nahrať každú vrstvu, zostaviť zodpovedajúci manifest a nahrať ho tiež. Znie to jednoducho.
Naštudovaním dokumentácie môžeme rozdeliť proces nahrávania na niekoľko krokov:
- Inicializácia procesu — "POST \/v2\/{repoName}\/blobs\/uploads\/
- Nahrávanie vrstvy (budeme používať monolitické nahrávanie, t.j. každú vrstvu posielame celú) — "PUT \/v2\/{repoName}\/blobs\/uploads\/{uuid}?digest={digest}
Content-Length: {size of layer}
Content-Type: application\/octet-stream
Layer Binary Data". - Nahrávanie manifestu — "PUT \/v2\/{repoName}\/manifests\/{reference}".
Ale v dokumentácii chýba jeden krok, bez ktorého to nebude fungovať. Pre monolitické nahrávanie, rovnako ako pre čiastočné (chunked), predtým než nahráte vrstvu, je potrebné sa uskutočniť PATCH požiadavku:
"PATCH \/v2\/{repoName}\/blobs\/uploads\/{uuid}
Content-Length: {size of chunk}
Content-Type: application\/octet-stream
{Layer Chunk Binary Data}".
Inak sa nedostanete ďalej než k prvému bodu, pretože namiesto očakávaného kódu odpovede 202 dostanete 4xx.
Teraz algoritmus vyzerá takto:
- Inicializácia
- Patch vrstvy
- Nahrávanie vrstvy
- Nahrávanie manifestu
Body 2 a 3 sa opakujú toľkokrát, koľko vrstiev je potrebné nahrať.
Na začiatok potrebujeme akýkoľvek obraz. Ja budem používať archlinux:latest
docker pull archlinux
Teraz si ho uložíme lokálne na ďalšie spracovanie.
docker save c24fe13d37b9 -o savedArch
Extrahujeme získaný archív do aktuálneho adresára.
tar xvf savedArch
Ako vidíme, každá vrstva leží v samostatnej zložke. Teraz sa pozrime na štruktúru manifestu, ktorý sme dostali.
cat manifest.json | json_pp
Nie je to bohviečo. Pozrieme sa, aký manifest je potrebný na nahrávanie, podľa .

Je zrejmé, že existujúci manifest nám nevyhovuje, takže si vytvoríme vlastný s čiernym jackom a kurtizánami, vrstvami a konfiguráciami.
Vždy budeme mať minimálne jeden konfiguračný súbor a pole vrstiev. Verzia schémy 2 (aktuálna v čase písania článku), mediaType necháme bez zmien:
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.jsonPo vytvorení základného manifestu je potrebné ho naplniť platnými údajmi. Na to použijeme šablónu JSON objektu vrstvy:
{
"mediaType": "application\/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
},ten budeme pridávať do manifestu pre každú vrstvu.
Ďalej musíme zistiť veľkosť konfiguračného súboru a nahradiť zástupné znaky v manifeste skutočnými údajmi.
sed -i "s/config_size/[32m$configSize[0m/g; s/config_hash/[32m$configName[0m/g" $manifestFileTeraz môžeme iniciovať proces nahrávania a uložiť si uuid, ktorý by mal sprevádzať všetky nasledujúce požiadavky.
Celý skript vyzerá približne 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žiť hotový skript:
./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0UPD:
Čo sme získali ako výsledok?
Po prvé, reálne údaje na analýzu, pretože testy sa spúšťajú v blazemeter a údaje z požiadaviek docker klienta sú veľmi nezauzlené na rozdiel od čistých HTTP požiadaviek.
Po druhé, prechod nám umožnil zvýšiť počet virtuálnych používateľov pre docker upload približne o 150 % a priemerný čas odpovede sa zlepšil o 20-25 %. Pre docker download sa nám podarilo zvýšiť počet používateľov o 500 %, priemerný čas odpovede sa pritom znížil približne o 60 %.
Ďakujem za pozornosť.
Zdroj: habr.com
