Imeli smo 2 vrečki trave, 75 tablet meskalina, unix okolje, docker repozitorij in nalogo izvesti ukaze docker pull in docker push brez docker odjemalca.

UPD:
Vprašanje: Zakaj vse to?
Odgovor: Obremenitveno testiranje produkta (NE z orodji bash, skripte so navedene v izobraževalne namene). Odločili smo se, da ne bomo uporabili docker odjemalca, da bi zmanjšali dodatne plasti (v razumnih mejah) in s tem emulirali višjo obremenitev. Posledično smo odstranili vse sistemske zamude docker odjemalca. Pridobili smo razmeroma čisto obremenitev neposredno na produkta.
V članku so bili uporabljeni GNU orodja.
Najprej razjasnimo, kaj počnejo ti ukazi.
Torej, za kaj se uporablja docker pull? Po besedah :
"Prenesi sliko ali repozitorij iz registra".
Tam najdemo povezavo na .

Od tod lahko razumemo, da je docker slika niz nekaterih plasti, ki vsebujejo informacije o zadnjih spremembah v sliki, ki jih očitno potrebujemo. Nato si ogledamo .
Tukaj je zapisano naslednje:
"Slika" je kombinacija JSON manifest in posameznih datotek plasti. Postopek prenašanja slike se osredotoča na pridobivanje teh dveh komponent."
Tako je prvi korak po dokumentaciji "Prenos manifest slike”.
Seveda ga ne bomo prenašali, vendar potrebujemo podatke iz njega. Nato je naveden primer zahtevka: GET /v2/{name}/manifests/{reference}
"Parameter ime in referenca identificirata sliko in sta obvezna. Referenca lahko vključuje oznako ali digest."
Naš docker repozitorij je razporejen lokalno, poskusimo izvesti zahtevek:
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"
V odgovoru dobimo json, iz katerega nas trenutno zanimajo le plasti, natančneje njihovi heši. Ko jih pridobimo, lahko za vsak izvedemo naslednji zahtevek: "GET /v2/{name}/blobs/{digest}"
"Dostop do plasti bo omogočen z imenom repozitorija, vendar je edinstveno identificiran v registru z digestom."
digest v tem primeru je heš, ki smo ga pridobili.
Poskusimo
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer![]()
poglejmo, kakšno datoteko smo na koncu pridobili kot prvo plast.
file firstLayer
t.j. plasti predstavljajo tar arhive, katerih razpakiranje v ustreznem zaporedju nam bo dalo vsebino slike.
Napišimo majhno bash skripto, da lahko to vse avtomatiziramo.
#!/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 0Zdaj lahko zaženemo z željenimi parametri in pridobimo vsebino potrebne slike
.\/script.sh dirName "http:\/\/localhost:8081\/link\/to\/docker\/registry" myAwesomeImage 1.0Del 2 — docker push
Tukaj bo malo težje.
Začnimo spet z . Torej, moramo naložiti vsako plast, zbrati ustrezni manifest in tudi to naložiti. Zveni preprosto.
S študijem dokumentacije lahko proces nalaganja razdelimo na več korakov:
- Inicializacija procesa — "POST \/v2\/{repoName}\/blobs\/uploads\/"
- Nalaganje plasti (uporabljali bomo monolitno nalaganje, tj. vsako plast pošljemo v celoti) — "PUT \/v2\/{repoName}\/blobs\/uploads\/{uuid}?digest={digest}"
Content-Length: {size of layer}
Content-Type: application\/octet-stream
Layer Binary Data". - Nalaganje manifesta — "PUT \/v2\/{repoName}\/manifests\/{reference}".
Toda v dokumentaciji je bil izpuščen en korak, brez katerega ne bo šlo. Za monolitno nalaganje, prav tako kot za delno (chunked), pred nalaganjem plasti morate izvesti PATCH zahtevo:
"PATCH \/v2\/{repoName}\/blobs\/uploads\/{uuid}"
Content-Length: {size of chunk}
Content-Type: application\/octet-stream
{Layer Chunk Binary Data}".
V nasprotnem primeru ne boste mogli napredovati naprej v prvem koraku, saj boste namesto pričakovanega odgovora 202 prejemali 4xx.
Zdaj algoritem izgleda takole:
- Inicijalizacija
- Patch plasti
- Nalaganje plasti
- Nalaganje manifesta
Koraka 2 in 3 se bodo ustrezno ponavljala toliko krat, kolikor plasti je treba naložiti.
Najprej bomo potrebovali katero koli sliko. Uporabil bom archlinux:latest
docker pull archlinux
Zdaj ga shranimo lokalno za nadaljnjo obravnavo
docker save c24fe13d37b9 -o savedArch
Razpakirajmo pridobljeni arhiv v trenutno mapo
tar xvf savedArch
Kot vidimo, vsaka plast leži v ločeni mapi. Poglejmo strukturo manifesta, ki smo ga prejeli
cat manifest.json | json_pp
Ni bogato. Poglejmo, kateri manifest je potreben za nalaganje, v skladu z .

Očitno obstoječ manifest ne ustreza, torej bomo naredili svojega z blackjackom in kurbami, plastmi in konfiguracijami.
Vedno bomo imeli najmanj eno konfiguracijsko datoteko in niz plasti. Različica sheme 2 (veljavna ob pisanju članka), mediaType bomo pustili brez sprememb:
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 ustvarjanju osnovnega manifesta ga je treba napolniti z veljavnimi podatki. Za to bomo uporabili predlogo json objekta plasti:
{
"mediaType": "application\/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
},to bomo dodali v manifest za vsako plast.
Nato moramo ugotoviti velikost konfiguracijske datoteke in nadomestiti zameške v manifestu z dejanskimi podatki.
sed -i "s/config_size/[33m$configSize[0m/g; s/config_hash/[33m$configName[0m/g" $manifestFileZdaj lahko sprožimo postopek nalaganja in shranimo uuid, ki mora spremljati vse nadaljnje poizvedbe.
Poln skript izgleda približno tako:
#!/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
lahko uporabimo pripravljen skript:
./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0UPD:
Kaj smo dobili kot rezultat?
Prvič, dejanske podatke za analizo, saj testi potekajo v blazemeterju in podatki o poizvedbah Docker odjemalca so precej neinformativni v primerjavi s čistimi HTTP poizvedbami.
Drugič, prehod nam je omogočil povečati število virtualnih uporabnikov za docker upload za približno 150 % in hkrati doseči povprečen čas odziva 20-25 % hitreje. Pri docker downloadu se je število uporabnikov povečalo za 500 %, povprečen čas odziva pa se je zmanjšal za približno 60 %.
Hvala na pažnji.
Izvor: habr.com
