Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Imali smo 2 vrećice trave, 75 tableta meskalina unix okruženje, docker repozitorij i zadatak implementacije naredbi docker pull i docker push bez docker klijenta.

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

UPS:
Pitanje: Čemu sve ovo?
Odgovor: Testiranje proizvoda pod opterećenjem (NE koristeći bash, skripte su dane u obrazovne svrhe). Odlučeno je da se ne koristi docker klijent za smanjenje dodatnih slojeva (unutar razumnih granica) i, sukladno tome, emulira veće opterećenje. Kao rezultat toga, uklonjena su sva kašnjenja sustava Docker klijenta. Dobili smo relativno čisto opterećenje izravno na proizvodu.
Članak je koristio GNU verzije alata.

Prvo, shvatimo što ove naredbe rade.

Dakle, za što se koristi docker pull? Prema dokumentacija:

"Izvuci sliku ili repozitorij iz registra".

Tamo također nalazimo poveznicu na razumjeti slike, spremnike i upravljačke programe za pohranu.

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Odavde možemo shvatiti da je docker slika skup određenih slojeva koji sadrže informacije o posljednjim promjenama na slici, što je očito ono što nam treba. Dalje gledamo API registra.

Kaže sljedeće:

"Slika je kombinacija JSON manifesta i datoteka pojedinačnih slojeva. Proces povlačenja > slike usredotočen je na dohvaćanje ove dvije komponente."

Dakle, prvi korak prema dokumentaciji je "Izvlačenje slikovnog manifesta".

Naravno, nećemo ga snimati, ali potrebni su nam podaci iz njega. Slijedi primjer zahtjeva: GET /v2/{name}/manifests/{reference}

"Ime i referentni parametar identificiraju sliku i obavezni su. Referenca može uključivati ​​oznaku ili sažetak."

Naše docker repozitorij raspoređeno je lokalno, pokušajmo izvršiti zahtjev:

curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Kao odgovor dobivamo json od kojeg nas trenutno zanimaju samo lifelines, odnosno njihovi hashovi. Nakon što ih primimo, možemo proći kroz svaki od njih i izvršiti sljedeći zahtjev: "GET /v2/{name}/blobs/{digest}"

"Pristup sloju bit će ograničen imenom repozitorija, ali se jedinstveno identificira u registru sažetkom."

digest u ovom slučaju je hash koji smo primili.

Težak

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

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Pogledajmo kakvu smo turpiju na kraju dočekali kao prvu slamku spasa.

file firstLayer

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

oni. rails su tar arhive, raspakirajući ih odgovarajućim redoslijedom dobit ćemo sadržaj slike.

Napišimo malu bash skriptu tako da se sve ovo može automatizirati

#!/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

Sada ga možemo pokrenuti sa željenim parametrima i dobiti sadržaj tražene slike

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

2. dio - docker push

Ovo će biti malo kompliciranije.

Počnimo ponovno s dokumentacija. Stoga moramo preuzeti svakog lidera, prikupiti odgovarajući manifest i preuzeti ga također. Čini se jednostavno.

Nakon proučavanja dokumentacije, proces preuzimanja možemo podijeliti u nekoliko koraka:

  • Inicijalizacija procesa - "POST /v2/{repoName}/blobs/uploads/"
  • Prijenos životnog niza (koristit ćemo monolitni prijenos, tj. šaljemo svaki životni niz u cijelosti) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Duljina sadržaja: {size of layer}
    Content-Type: aplikacija/octet-stream
    Sloj binarnih podataka".
  • Učitavanje manifesta - "PUT /v2/{repoName}/manifests/{reference}".

Ali dokumentaciji nedostaje jedan korak, bez kojeg ništa neće funkcionirati. Za monolitno učitavanje, kao i za djelomično (u komadima), prije učitavanja tračnice morate izvršiti PATCH zahtjev:

"ZAKRPA /v2/{repoName}/blobs/uploads/{uuid}
Duljina sadržaja: {size of chunk}
Content-Type: aplikacija/octet-stream
{Binarni podaci dijela sloja}".

U suprotnom, nećete moći prijeći dalje od prve točke, jer... Umjesto očekivanog koda odgovora 202, dobit ćete 4xx.

Sada algoritam izgleda ovako:

  • Inicijalizacija
  • Patch tračnica
  • Učitavanje rukohvata
  • Učitavanje manifesta
    Točke 2 i 3 će se ponavljati onoliko puta koliko redaka treba učitati.

Prvo, trebamo bilo koju sliku. Koristit ću archlinux:najnovije

docker pull archlinux

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Sada ga spremimo lokalno za daljnju analizu

docker save c24fe13d37b9 -o savedArch

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Raspakirajte dobivenu arhivu u trenutni direktorij

tar xvf savedArch

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Kao što vidite, svaka linija života je u zasebnoj mapi. Sada pogledajmo strukturu manifesta koji smo primili

cat manifest.json | json_pp

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Ne mnogo. Pogledajmo koji je manifest potreban za učitavanje, prema dokumentacija.

Implementacija docker pull i docker push naredbi bez docker klijenta pomoću HTTP zahtjeva

Očito nam postojeći manifest ne odgovara, pa ćemo napraviti svoj s blackjackom i kurtizanama, lifelinesima i konfiguracijama.

Uvijek ćemo imati barem jednu konfiguracijsku datoteku i niz linija života. Verzija sheme 2 (trenutna u vrijeme pisanja), mediaType će ostati nepromijenjen:

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

Nakon izrade osnovnog manifesta potrebno ga je ispuniti valjanim podacima. Da bismo to učinili, koristimo json predložak objekta rail:

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

Dodat ćemo ga u manifest za svaku tračnicu.

Zatim moramo saznati veličinu konfiguracijske datoteke i zamijeniti dopune u manifestu stvarnim podacima

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

Sada možete pokrenuti proces preuzimanja i spremiti sebi uuid, koji bi trebao pratiti sve sljedeće zahtjeve.

Kompletna skripta izgleda otprilike ovako:

#!/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

možemo koristiti gotovu skriptu:

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

UPS:
Što smo dobili kao rezultat?
Prvo, pravi podaci za analizu, budući da se testovi izvode u blazemetru i podaci o zahtjevima docker klijenta nisu jako informativni, za razliku od čistih HTTP zahtjeva.

Drugo, prijelaz nam je omogućio da povećamo broj virtualnih korisnika za učitavanje dockera za oko 150% i dobijemo prosječno vrijeme odgovora 20-25% brže. Za preuzimanje dockera uspjeli smo povećati broj korisnika za 500%, dok se prosječno vrijeme odziva smanjilo za oko 60%.

Hvala na pozornosti.

Izvor: www.habr.com

Dodajte komentar