Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Vi hadde 2 poser med gress, 75 meskalin-tabletter unix-miljø, et docker-lager og oppgaven med å implementere docker pull og docker push-kommandoene uten en docker-klient.

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

OPP:
Spørsmål: Hva er alt dette til for?
Svar: Lasttesting av produktet (IKKE ved bruk av bash, skriptene er gitt for pedagogiske formål). Det ble besluttet å ikke bruke docker-klienten til å redusere flere lag (innenfor rimelige grenser) og følgelig emulere en høyere belastning. Som et resultat ble alle systemforsinkelser til Docker-klienten fjernet. Vi fikk en relativt ren belastning direkte på produktet.
Artikkelen brukte GNU-versjoner av verktøy.

La oss først finne ut hva disse kommandoene gjør.

Så hva brukes docker pull til? I følge dokumentasjon:

"Trekk et bilde eller et depot fra et register".

Der finner vi også en link til forstå bilder, beholdere og lagringsdrivere.

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Herfra kan vi forstå at et docker-bilde er et sett med visse lag som inneholder informasjon om de siste endringene i bildet, som åpenbart er det vi trenger. Neste ser vi på register API.

Det står følgende:

"Et "bilde" er en kombinasjon av et JSON-manifest og individuelle lagfiler. Prosessen med å trekke et bilde er sentret rundt å hente disse to komponentene."

Så det første trinnet i henhold til dokumentasjonen er "Å trekke et bildemanifest".

Selvfølgelig vil vi ikke skyte det, men vi trenger dataene fra det. Følgende er et eksempel på en forespørsel: GET /v2/{name}/manifests/{reference}

"Navnet og referanseparameteren identifiserer bildet og er obligatoriske. Referansen kan inneholde en kode eller sammendrag."

Docker-lageret vårt er distribuert lokalt, la oss prøve å utføre forespørselen:

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 docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Som svar mottar vi json som vi for øyeblikket kun er interessert i livslinjene, eller snarere hashen deres. Etter å ha mottatt dem, kan vi gå gjennom hver enkelt og utføre følgende forespørsel: "GET /v2/{name}/blobs/{digest}"

"Tilgang til et lag vil bli lukket av navnet på depotet, men identifiseres unikt i registeret etter sammendrag."

Digest i dette tilfellet er hashen vi mottok.

Prøver

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 docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

La oss se hva slags fil vi til slutt mottok som første livline.

file firstLayer

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

de. skinner er tjærearkiver, pakker vi dem ut i riktig rekkefølge får vi innholdet i bildet.

La oss skrive et lite bash-skript slik at alt dette kan automatiseres

#!/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 kjøre den med de ønskede parameterne og få innholdet i det nødvendige bildet

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

Del 2 - docker push

Dette blir litt mer komplisert.

La oss starte på nytt med dokumentasjon. Så vi må laste ned hver leder, samle det tilsvarende manifestet og laste det ned også. Det virker enkelt.

Etter å ha studert dokumentasjonen, kan vi dele ned nedlastingsprosessen i flere trinn:

  • Prosessinitialisering - "POST /v2/{repoName}/blobs/uploads/"
  • Opplasting av en livline (vi vil bruke en monolitisk opplasting, dvs. vi sender hver livline i sin helhet) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Innhold-lengde: {size of layer}
    Innholdstype: applikasjon/oktettstrøm
    Lag binære data".
  • Laster inn manifestet - "PUT /v2/{repoName}/manifests/{reference}".

Men dokumentasjonen går glipp av ett trinn, uten noe vil ingenting fungere. For monolittisk lasting, så vel som for delvis (chunked), før lasting av skinnen, må du utføre en PATCH-forespørsel:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Innholdslengde: {size of chunk}
Innholdstype: applikasjon/oktettstrøm
{Layer Chunk Binary Data}".

Ellers vil du ikke kunne gå utover det første punktet, fordi... I stedet for forventet svarkode 202 vil du motta 4xx.

Nå ser algoritmen slik ut:

  • Initialisering
  • Patch rail
  • Lasting av rekkverket
  • Laster inn manifestet
    Henholdsvis punkt 2 og 3 gjentas så mange ganger som antall linjer må lastes.

Først trenger vi et hvilket som helst bilde. Jeg vil bruke archlinux:latest

docker pull archlinux

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

La oss nå lagre det lokalt for videre analyse

docker save c24fe13d37b9 -o savedArch

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Pakk ut det resulterende arkivet i gjeldende katalog

tar xvf savedArch

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Som du kan se, er hver livline i en egen mappe. La oss nå se på strukturen til manifestet vi mottok

cat manifest.json | json_pp

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Ikke mye. La oss se hvilket manifest som trengs for å laste, ifølge dokumentasjon.

Implementering av docker pull og docker push-kommandoer uten en docker-klient ved hjelp av HTTP-forespørsler

Det er klart at det eksisterende manifestet ikke passer oss, så vi lager vårt eget med blackjack og kurtisaner, livlinjer og konfigurasjoner.

Vi vil alltid ha minst én konfigurasjonsfil og en rekke livslinjer. Scheme versjon 2 (gjeldende i skrivende stund), mediaType vil forbli 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 opprettet det grunnleggende manifestet, må du fylle det med gyldige data. For å gjøre dette bruker vi json-malen til rail-objektet:

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

Vi vil legge det til manifestet for hver skinne.

Deretter må vi finne ut størrelsen på konfigurasjonsfilen og erstatte stubbene i manifestet med ekte data

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

Nå kan du starte nedlastingsprosessen og lagre deg selv en uuid, som skal følge med alle påfølgende forespørsler.

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

OPP:
Hva fikk vi som resultat?
For det første, ekte data for analyse, siden testene kjøres i blazemeter og dataene om docker-klientforespørsler ikke er veldig informative, i motsetning til rene HTTP-forespørsler.

For det andre tillot overgangen oss å øke antallet virtuelle brukere for docker-opplasting med omtrent 150 % og få gjennomsnittlig responstid 20–25 % raskere. For docker-nedlasting klarte vi å øke antall brukere med 500 %, mens gjennomsnittlig responstid gikk ned med omtrent 60 %.

Takk for oppmerksomheten.

Kilde: www.habr.com

Legg til en kommentar