Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Vi hadde 2 sekkar med gras, 75 tabletter meskalin unix-miljø, docker-repositorium og oppgåva å implementere kommandoane docker pull og docker push utan docker-klienten.

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

UPD:
Spørsmål: Kvifor alt dette?
Svar: Lastetesting av produktet (IKKJE med bash-verktøy, skript er gjeve til utdanningsføremål). Det vart bestemt å ikkje bruke docker-klienten for å redusere ekstra lag (innen rimelige grenser) og dermed emulere ein høgare belastning. Resultatet var at vi fjerna alle systemforsinkelsar frå docker-klienten. Vi fekk ein relativt rein last direkte på produktet.
I artikkelen vart det nytta GNU-verktøy.

La oss først sjå på kva desse kommandoane gjer.

Så kva er docker pull brukt til? I følgje dokumentasjonen:

"Hent eit bilete eller eit repositorium frå ein registry".

Der finn vi også lenka til forstå bilete, containerar og lagringsdrivarar.

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Herfrå kan vi forstå at docker-bilete er eit sett med lag, som inneheld informasjon om dei siste endringane i biletet, som sjølvsagt er det vi treng. Deretter ser vi på registry API.

Her blir det sagt følgjande:

"Eit “bilete” er ei kombinasjon av ein JSON-manifest og individuelle lagfiler. Prosessen med å hente eit > bilete handlar om å hente desse to komponentane."

Så det første steget ifølgje dokumentasjonen er “Henting av eit bilde-manifest”.

Vi vil sjølvsagt ikkje hente det, men vi treng data frå det. Deretter blir eit eksempel på ei førespurnad gjeve: GET /v2/{name}/manifests/{reference}

"Navnet og referanseparameteren identifiserer biletet og er påkravd. Referansen kan inkludere ei tag eller digest."

Vårt docker-repositorium er oppsett lokalt, la oss prøve å utføre førespurnaden:

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

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Som svar får vi JSON der vi akkurat nå kun er interessert i laga, nærmare bestemt deira hashes. Når vi har fått dei, kan vi gå gjennom kvar enkelt og utføre følgjande førespurnad: "GET /v2/{name}/blobs/{digest}"

“Tilgang til eit lag vil bli avgrensa av namnet på repositoriet, men er unikt identifisert i registren av digest.”

digest er her faktisk hashen vi fekk.

La oss prøve

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

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

La oss sjå kva fil vi fekk som den første laga.

file firstLayer

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

d.v.s. laga er tar-arkiv, og pakka ut i riktig rekkefølgje får vi innhaldet i biletet.

Vi skriv eit lite bash-skript for å automatisere alt dette.

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

Nå kan vi starte den med ønskede parametere og få innholdet til det nødvendige imaget.

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

Del 2 — docker push

Her blir det litt mer komplisert.

Vi begynner igjen med dokumentasjonen. Så vi må laste opp hver lag, samle den tilsvarende manifesten og laste den også. Det høres ganske enkelt ut.

Ved å studere dokumentasjonen kan vi dele opplastingsprosessen i flere trinn:

  • Initialisering av prosessen — "POST /v2/{repoName}/blobs/uploads/"
  • Opplasting av lag (vi vil bruke monolitisk opplasting, dvs. hvert lag sendes i sin helhet) — "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}"
    Content-Length: {size of layer}
    Content-Type: application/octet-stream
    Lag Binær Data".
  • Opplasting av manifest — "PUT /v2/{repoName}/manifests/{reference}".

Men i dokumentasjonen er et trinn utelatt, uten dette kommer ingenting til å fungere. For monolitisk opplasting, akkurat som for delvis (chunked), må du utføre et PATCH-forespørsel før du laster opp lageret:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}"
Content-Length: {size of chunk}
Content-Type: application/octet-stream
{Layer Chunk Binary Data}".

Ellers vil du ikke kunne komme videre fra første punkt, da du i stedet for den forventede 202-responsen vil få 4xx.

Nå ser algoritmen slik ut:

  • Initialisering
  • Patch lag
  • Opplasting av lag
  • Opplasting av manifest
    Punktene 2 og 3 vil gjentas så mange ganger som nødvendig lag må lastes opp.

Først trenger vi et hvilket som helst image. Jeg skal bruke archlinux:latest.

docker pull archlinux

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Nå skal vi lagre det lokalt for videre analyse.

docker save c24fe13d37b9 -o savedArch

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Vi pakker ut den mottatte arkivet i den nåværende katalogen.

tar xvf savedArch

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Som vi ser, ligger hvert lag i sin egen mappe. Nå skal vi se på strukturen av manifesten vi har mottatt.

cat manifest.json | json_pp

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Ikke mye. La oss se hvilket manifest som trengs for opplasting, ifølge dokumentasjonen.

Implementering av kommandoene docker pull og docker push uten docker-klient ved hjelp av HTTP-forespørsel

Det er åpenbart at det eksisterende manifestet ikke passer, så vi lager vårt eget med blackjack og prostituerte lag og konfigurasjoner.

Vi vil alltid ha minst én config-fil og en array av lag. Versjonen av skjemaet er 2 (aktuelt på tidspunktet for skrivingen), mediaType holder vi uendret:

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

Etter å ha laget et grunnleggende manifest må vi fylle det med gyldige data. For dette bruker vi mal for json-objektet av laget:

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

denne vil vi legge til i manifestet for hvert lag.

Neste må vi finne størrelsen på konfigurasjonsfilen og erstatte plassholderne i manifestet med reelle data.

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

Nå kan vi initiere opplastingsprosessen og lagre uuid, som alle påfølgende forespørselene må følge.

Det komplette skriptet ser omtrent slik ut:

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

vi kan bruke et ferdig skript:

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

UPD:
Hva fikk vi som resultat?
For det første, reelle data for analyse, ettersom testene kjører i blazemeter og dataene fra docker-klientens forespørsel er ganske uinformative i motsetning til rene HTTP-forespørsel.

For det andre, førte overgangen til at vi kunne øke antallet virtuelle brukere for docker-opplasting med omtrent 150 % og oppnå en gjennomsnittlig responstid som var 20-25 % raskere. For docker-nedlasting klarte vi å øke antallet brukere med 500 %, mens den gjennomsnittlige responstiden dermed falt med omtrent 60 %.

Takk for oppmerksomheten.

Kilde: habr.com

Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere 🔥 Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster