Implementation of docker pull and docker push commands without the docker client through HTTP requests

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.

Implementation of docker pull and docker push commands without the docker client through HTTP requests

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 dokumenti:

"Prenesi sliko ali repozitorij iz registra".

Tam najdemo povezavo na razumevanje slik, kontejnerjev in gonilnikov za shranjevanje.

Implementation of docker pull and docker push commands without the docker client through HTTP requests

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 API registra.

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"

Implementation of docker pull and docker push commands without the docker client through HTTP requests

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

Implementation of docker pull and docker push commands without the docker client through HTTP requests

poglejmo, kakšno datoteko smo na koncu pridobili kot prvo plast.

file firstLayer

Implementation of docker pull and docker push commands without the docker client through HTTP requests

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 0

Zdaj lahko zaženemo z željenimi parametri in pridobimo vsebino potrebne slike

.\/script.sh dirName "http:\/\/localhost:8081\/link\/to\/docker\/registry" myAwesomeImage 1.0

Del 2 — docker push

Tukaj bo malo težje.

Začnimo spet z dokumenti. 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

Implementation of docker pull and docker push commands without the docker client through HTTP requests

Zdaj ga shranimo lokalno za nadaljnjo obravnavo

docker save c24fe13d37b9 -o savedArch

Implementation of docker pull and docker push commands without the docker client through HTTP requests

Razpakirajmo pridobljeni arhiv v trenutno mapo

tar xvf savedArch

Implementation of docker pull and docker push commands without the docker client through HTTP requests

Kot vidimo, vsaka plast leži v ločeni mapi. Poglejmo strukturo manifesta, ki smo ga prejeli

cat manifest.json | json_pp

Implementation of docker pull and docker push commands without the docker client through HTTP requests

Ni bogato. Poglejmo, kateri manifest je potreben za nalaganje, v skladu z dokumenti.

Implementation of docker pull and docker push commands without the docker client through HTTP requests

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.json

Po 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" $manifestFile

Zdaj 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.0

UPD:
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

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster