Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Ne kishim 2 thasë me bar, 75 tableta meskaline mjedis unix, një depo docker dhe detyrën e zbatimit të komandave docker pull dhe docker push pa një klient docker.

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

UPD:
Pyetje: Për çfarë është e gjithë kjo?
Përgjigje: Testimi i ngarkesës së produktit (JO duke përdorur bash, skriptet janë dhënë për qëllime edukative). U vendos që të mos përdoret klienti docker për të reduktuar shtresat shtesë (brenda kufijve të arsyeshëm) dhe, në përputhje me rrethanat, të imitojë një ngarkesë më të lartë. Si rezultat, të gjitha vonesat e sistemit të klientit Docker u hoqën. Ne morëm një ngarkesë relativisht të pastër direkt në produkt.
Artikulli përdori versionet GNU të mjeteve.

Së pari, le të kuptojmë se çfarë bëjnë këto komanda.

Pra, për çfarë përdoret docker pull? Sipas dokumentacionin:

"Nxirr një imazh ose një depo nga një regjistër".

Aty gjejmë edhe një lidhje me kuptoni imazhet, kontejnerët dhe drejtuesit e ruajtjes.

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Nga këtu mund të kuptojmë se një imazh docker është një grup shtresash të caktuara që përmbajnë informacione rreth ndryshimeve më të fundit në imazh, gjë që është padyshim ajo që na nevojitet. Më tej shikojmë API-ja e regjistrit.

Ai thotë sa vijon:

"Një "imazh" është një kombinim i një manifesti JSON dhe skedarëve të shtresave individuale. Procesi i tërheqjes së një imazhi përqendrohet rreth marrjes së këtyre dy komponentëve."

Pra, hapi i parë sipas dokumentacionit është "Duke tërhequr një Manifest imazhi".

Sigurisht, ne nuk do ta gjuajmë atë, por na duhen të dhënat prej tij. Më poshtë është një shembull i kërkesës: GET /v2/{name}/manifests/{reference}

"Emri dhe parametri i referencës identifikojnë imazhin dhe kërkohen. Referenca mund të përfshijë një etiketë ose përmbledhje."

Depoja jonë e dokerit është vendosur në nivel lokal, le të përpiqemi të ekzekutojmë kërkesën:

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

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Si përgjigje, ne marrim json nga i cili aktualisht jemi të interesuar vetëm për linjat e shpëtimit, ose më saktë hash-et e tyre. Pasi i kemi marrë ato, ne mund të kalojmë në secilën prej tyre dhe të ekzekutojmë kërkesën e mëposhtme: "GET /v2/{name}/blobs/{digest}"

"Qasja në një shtresë do të kufizohet me emrin e depove, por identifikohet në mënyrë unike në regjistër sipas përmbledhjes."

digest në këtë rast është hash-i që kemi marrë.

Duke u përpjekur

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

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Le të shohim se çfarë lloj dosjeje morëm më në fund si litarin e parë të shpëtimit.

file firstLayer

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

ato. binarët janë arkiva të katranit, duke i shpaketuar ato në rendin e duhur do të marrim përmbajtjen e imazhit.

Le të shkruajmë një skenar të vogël bash në mënyrë që e gjithë kjo të mund të automatizohet

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

Tani mund ta ekzekutojmë me parametrat e dëshiruar dhe të marrim përmbajtjen e imazhit të kërkuar

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

Pjesa 2 - shtytje doker

Kjo do të jetë pak më e komplikuar.

Le të fillojmë përsëri me dokumentacionin. Pra, ne duhet të shkarkojmë çdo drejtues, të mbledhim manifestin përkatës dhe ta shkarkojmë gjithashtu. Duket e thjeshtë.

Pas studimit të dokumentacionit, ne mund ta ndajmë procesin e shkarkimit në disa hapa:

  • Inicializimi i procesit - "POST /v2/{repoName}/blobs/uploads/"
  • Ngarkimi i një linje shpëtimi (ne do të përdorim një ngarkim monolit, d.m.th. dërgojmë çdo litar shpëtimi në tërësinë e tij) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Gjatësia e përmbajtjes: {madhësia e shtresës}
    Lloji i Përmbajtjes: aplikacion/lumë oktet
    Të dhënat binare të shtresave".
  • Po ngarkon manifestin - "PUT /v2/{repoName}/manifests/{reference}".

Por dokumentacionit i mungon një hap, pa të cilin asgjë nuk do të funksionojë. Për ngarkimin monolit, si dhe për ngarkimin e pjesshëm (të copëtuar), përpara se të ngarkoni hekurudhën, duhet të kryeni një kërkesë PATCH:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Gjatësia e përmbajtjes: {madhësia e copës}
Lloji i Përmbajtjes: aplikacion/lumë oktet
{Të dhënat binare të copëzave të shtresave}".

Përndryshe, nuk do të mund të lëvizni përtej pikës së parë, sepse... Në vend të kodit të pritshëm të përgjigjes 202, do të merrni 4xx.

Tani algoritmi duket si ky:

  • Inicializimi
  • Patch hekurudhor
  • Ngarkimi i parmakut
  • Duke ngarkuar manifestin
    Pikat 2 dhe 3, respektivisht, do të përsëriten aq herë sa duhet të ngarkohet numri i rreshtave.

Së pari, na duhet ndonjë imazh. Unë do të përdor archlinux: fundit

docker pull archlinux

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Tani le ta ruajmë atë në nivel lokal për analiza të mëtejshme

docker save c24fe13d37b9 -o savedArch

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Shpaketoni arkivin që rezulton në drejtorinë aktuale

tar xvf savedArch

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Siç mund ta shihni, çdo litar shpëtimi është në një dosje të veçantë. Tani le të shohim strukturën e manifestit që kemi marrë

cat manifest.json | json_pp

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Jo shume. Le të shohim se çfarë manifesti nevojitet për të ngarkuar, sipas dokumentacionin.

Zbatimi i komandave docker pull dhe docker push pa një klient docker duke përdorur kërkesat HTTP

Natyrisht, manifesti ekzistues nuk na përshtatet, kështu që ne do ta bëjmë tonën me blackjack dhe kurtizane, linja shpëtimi dhe konfigurime.

Ne gjithmonë do të kemi të paktën një skedar konfigurimi dhe një sërë linjash jetësore. Versioni i skemës 2 (aktual në kohën e shkrimit), mediaType do të lihet i pandryshuar:

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

Pas krijimit të manifestit bazë, duhet ta plotësoni me të dhëna të vlefshme. Për ta bërë këtë, ne përdorim shabllonin json të objektit hekurudhor:

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

Ne do ta shtojmë atë në manifest për çdo hekurudhë.

Më pas, duhet të zbulojmë madhësinë e skedarit të konfigurimit dhe të zëvendësojmë cungët në manifest me të dhëna reale

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

Tani mund të filloni procesin e shkarkimit dhe t'i ruani vetes një uuid, i cili duhet të shoqërojë të gjitha kërkesat pasuese.

Skenari i plotë duket diçka si kjo:

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

ne mund të përdorim një skript të gatshëm:

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

UPD:
Çfarë morëm si rezultat?
Së pari, të dhëna reale për analizë, pasi testet kryhen në blazemeter dhe të dhënat për kërkesat e klientëve docker nuk janë shumë informuese, ndryshe nga kërkesat e pastra HTTP.

Së dyti, tranzicioni na lejoi të rrisim numrin e përdoruesve virtualë për ngarkimin e dokerit me rreth 150% dhe të marrim kohën mesatare të përgjigjes 20-25% më shpejt. Për shkarkimin e docker, ne arritëm të rrisim numrin e përdoruesve me 500%, ndërsa koha mesatare e përgjigjes u ul me rreth 60%.

Faleminderit për vëmendjen tuaj.

Burimi: www.habr.com

Shto një koment