Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Mali sme 2 vrecia trávy, 75 meskalínových tabliet unixové prostredie, docker úložisko a úlohu implementovať príkazy docker pull a docker push bez docker klienta.

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

UPS:
Otázka: Načo to všetko je?
Odpoveď: Záťažové testovanie produktu (NEpoužíva bash, skripty sú poskytované na vzdelávacie účely). Rozhodlo sa nepoužívať docker klienta na zmenšenie ďalších vrstiev (v rozumných medziach) a podľa toho emulovať vyššiu záťaž. V dôsledku toho boli odstránené všetky systémové oneskorenia klienta Docker. Priamo na produkte sme dostali pomerne čistý náklad.
V článku boli použité verzie nástrojov GNU.

Po prvé, poďme zistiť, čo tieto príkazy robia.

Na čo sa teda docker pull používa? Podľa dokumentáciu:

"Vytiahnuť obrázok alebo úložisko z registra".

Nájdeme tam aj odkaz na pochopiť obrázky, kontajnery a ovládače úložiska.

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Odtiaľ môžeme pochopiť, že obrázok docker je súbor určitých vrstiev, ktoré obsahujú informácie o najnovších zmenách v obrázku, čo je samozrejme to, čo potrebujeme. Ďalej sa pozrieme na Registry API.

Hovorí sa v ňom nasledovné:

"Obrázok" je kombináciou manifestu JSON a jednotlivých súborov vrstiev. Proces sťahovania obrázku sa sústreďuje na získanie týchto dvoch komponentov."

Takže prvý krok podľa dokumentácie je „Vytiahnutie obrazového manifestu".

Samozrejme, nebudeme to strieľať, ale potrebujeme z toho údaje. Nasleduje príklad žiadosti: GET /v2/{name}/manifests/{reference}

"Názov a parameter odkazu identifikujú obrázok a sú povinné. Odkaz môže obsahovať značku alebo súhrn."

Naše úložisko dockerov je nasadené lokálne, skúsme 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"

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Ako odpoveď dostávame json, z ktorého nás momentálne zaujímajú len záchranné linky, respektíve ich hashe. Po ich prijatí môžeme prejsť každým z nich a vykonať nasledujúcu požiadavku: "GET /v2/{name}/blobs/{digest}"

"Prístup k vrstve bude zabezpečený názvom úložiska, ale je jednoznačne identifikovaný v registri pomocou súhrnu."

digest je v tomto prípade hash, ktorý sme dostali.

Pokúša sa

curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Pozrime sa, aký súbor sme nakoniec dostali ako prvé záchranné lano.

file firstLayer

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

tie. koľajnice sú archívy dechtu, ich rozbalením v príslušnom poradí dostaneme obsah obrázku.

Poďme si napísať malý bash skript, aby sa to všetko dalo 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 0

Teraz ho môžeme spustiť s požadovanými parametrami a získať obsah požadovaného obrázka

./script.sh dirName “http://localhost:8081/link/to/docker/registry” myAwesomeImage 1.0

Časť 2 - docker push

Toto bude trochu komplikovanejšie.

Začnime znova s dokumentáciu. Takže musíme stiahnuť každého lídra, zhromaždiť zodpovedajúci manifest a stiahnuť si ho tiež. Vyzerá to jednoducho.

Po preštudovaní dokumentácie môžeme proces sťahovania rozdeliť do niekoľkých krokov:

  • Inicializácia procesu – „POST /v2/{repoName}/blobs/uploads/“
  • Nahrávanie záchranného lana (použijeme monolitické nahrávanie, t. j. každé záchranné lano posielame celé) – „PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Obsah – dĺžka: {veľkosť vrstvy}
    Content-Type: application/oktet-stream
    Binárne dáta vrstvy".
  • Načítava sa manifest - "PUT /v2/{repoName}/manifests/{reference}".

V dokumentácii ale chýba jeden krok, bez ktorého nebude nič fungovať. Pre monolitické nakladanie, ako aj pre čiastočné (chunked), pred zaťažením koľajnice, musíte vykonať požiadavku PATCH:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Content-Length: {veľkosť chunk}
Content-Type: application/oktet-stream
{Binárne dáta chunku vrstvy}“.

Inak sa za prvý bod nepohnete, pretože... Namiesto očakávaného kódu odpovede 202 dostanete 4xx.

Teraz algoritmus vyzerá takto:

  • Inicializácia
  • Patch rail
  • Nakladanie zábradlia
  • Načítava sa manifest
    Body 2 a 3 sa budú opakovať toľkokrát, koľko riadkov bude potrebné načítať.

Najprv potrebujeme akýkoľvek obrázok. Budem používať archlinux:latest

docker pull archlinux

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Teraz to uložme lokálne pre ďalšiu analýzu

docker save c24fe13d37b9 -o savedArch

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Rozbaľte výsledný archív do aktuálneho adresára

tar xvf savedArch

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Ako vidíte, každé záchranné lano je v samostatnom priečinku. Teraz sa pozrime na štruktúru manifestu, ktorý sme dostali

cat manifest.json | json_pp

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Nie veľa. Pozrime sa, aký manifest je potrebné načítať podľa dokumentáciu.

Implementácia príkazov docker pull a docker push bez klienta docker pomocou požiadaviek HTTP

Je zrejmé, že existujúci manifest nám nevyhovuje, takže si vytvoríme vlastný s blackjackom a kurtizánami, záchrannými lanami a konfiguráciami.

Vždy budeme mať aspoň jeden konfiguračný súbor a rad záchranných línií. Verzia schémy 2 (aktuálna v čase písania), mediaType zostane nezmenená:

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 vytvorení základného manifestu ho musíte vyplniť platnými údajmi. Na tento účel používame šablónu json objektu rail:

{
         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
         "size": ${layersSizes[$i]},
         "digest": "sha256:${layersNames[$i]}"
      },

Pridáme ho do manifestu pre každú koľajnicu.

Ďalej musíme zistiť veľkosť konfiguračného súboru a nahradiť útržky v manifeste skutočnými údajmi

sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFile

Teraz môžete spustiť proces sťahovania a uložiť si uuid, ktorý by mal sprevádzať všetky nasledujúce požiadavky.

Kompletný skript vyzerá 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žiť hotový skript:

./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0

UPS:
Čo sme dosiahli ako výsledok?
Po prvé, skutočné údaje na analýzu, keďže testy prebiehajú v blazemeter a údaje o požiadavkách klientov dockerov nie sú príliš informatívne, na rozdiel od čistých požiadaviek HTTP.

Po druhé, prechod nám umožnil zvýšiť počet virtuálnych používateľov na nahrávanie do dockerov približne o 150 % a dosiahnuť priemerný čas odozvy o 20 – 25 % rýchlejšie. Pri sťahovaní dockerov sa nám podarilo zvýšiť počet používateľov o 500 %, pričom priemerný čas odozvy sa znížil o približne 60 %.

Ďakujem vám za pozornosť.

Zdroj: hab.com

Pridať komentár