Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Vi havde 2 poser med græs, 75 mescaline tablets unix miljø, et docker repository og opgaven med at implementere docker pull og docker push kommandoerne uden en docker klient.

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

UPS:
Spørgsmål: Hvad er alt dette til for?
Svar: Belastningstest af produktet (IKKE ved brug af bash, scripts leveres til uddannelsesformål). Det blev besluttet ikke at bruge docker-klienten til at reducere yderligere lag (inden for rimelige grænser) og følgelig efterligne en højere belastning. Som et resultat blev alle systemforsinkelser for Docker-klienten fjernet. Vi fik en forholdsvis ren belastning direkte på produktet.
Artiklen brugte GNU-versioner af værktøjer.

Lad os først finde ud af, hvad disse kommandoer gør.

Så hvad bruges docker pull til? Ifølge dokumentation:

"Træk et billede eller et lager fra en registreringsdatabasen".

Der finder vi også et link til forstå billeder, containere og lagerdrivere.

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Herfra kan vi forstå, at et docker-billede er et sæt af bestemte lag, der indeholder information om de seneste ændringer i billedet, hvilket naturligvis er det, vi har brug for. Dernæst ser vi på registreringsdatabasen API.

Der står følgende:

"Et "billede" er en kombination af et JSON-manifest og individuelle lagfiler. Processen med at trække et billede er centreret omkring at hente disse to komponenter."

Så det første skridt ifølge dokumentationen er "Trækker et billedmanifest".

Selvfølgelig vil vi ikke skyde det, men vi har brug for dataene fra det. Følgende er et eksempel på anmodning: GET /v2/{name}/manifests/{reference}

"Navnet og referenceparameteren identificerer billedet og er påkrævet. Referencen kan indeholde et tag eller et sammendrag."

Vores docker-lager er installeret lokalt, lad os prøve at udføre anmodningen:

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

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Som svar modtager vi json, hvorfra vi i øjeblikket kun er interesseret i livslinjerne, eller rettere deres hashes. Når vi har modtaget dem, kan vi gennemgå hver enkelt og udføre følgende anmodning: "GET /v2/{navn}/blobs/{digest}"

"Adgang til et lag vil blive lukket af navnet på depotet, men identificeres unikt i registreringsdatabasen ved digest."

digest i dette tilfælde er den hash, vi har modtaget.

Lad os 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 af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Lad os se, hvilken slags fil vi endelig modtog som den første livline.

file firstLayer

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

de der. skinner er tjærearkiver, udpakning af dem i den rigtige rækkefølge vil vi få indholdet af billedet.

Lad os skrive et lille bash-script, så 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

Nu kan vi køre det med de ønskede parametre og få indholdet af det nødvendige billede

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

Del 2 - docker push

Dette vil være lidt mere kompliceret.

Lad os starte igen med dokumentation. Så vi skal downloade hver leder, samle det tilsvarende manifest og downloade det også. Det virker simpelt.

Efter at have studeret dokumentationen kan vi opdele downloadprocessen i flere trin:

  • Procesinitialisering - "POST /v2/{repoName}/blobs/uploads/"
  • Upload af en livline (vi bruger en monolitisk upload, dvs. vi sender hver livline i sin helhed) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Indhold-længde: {size of layer}
    Indholdstype: applikation/oktet-stream
    Lag binære data".
  • Indlæser manifestet - "PUT /v2/{repoName}/manifests/{reference}".

Men dokumentationen mangler et trin, uden hvilket intet vil fungere. For monolitisk læsning, såvel som for delvis (chunked), før læsning af skinnen, skal du udføre en PATCH-anmodning:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Indholdslængde: {size of chunk}
Indholdstype: applikation/oktet-stream
{Layer Chunk Binary Data}".

Ellers vil du ikke kunne komme længere end det første punkt, fordi... I stedet for den forventede svarkode 202 får du 4xx.

Nu ser algoritmen sådan ud:

  • Initialisering
  • Patch skinne
  • Læsning af gelænderet
  • Indlæser manifestet
    Henholdsvis punkt 2 og 3 vil blive gentaget lige så mange gange, som antallet af linjer skal indlæses.

Først har vi brug for ethvert billede. Jeg vil bruge archlinux:latest

docker pull archlinux

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Lad os nu gemme det lokalt til yderligere analyse

docker save c24fe13d37b9 -o savedArch

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Pak det resulterende arkiv ud i den aktuelle mappe

tar xvf savedArch

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Som du kan se, er hver livline i en separat mappe. Lad os nu se på strukturen af ​​det manifest, vi modtog

cat manifest.json | json_pp

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Ikke meget. Lad os se, hvilket manifest der skal til for at indlæse, ifølge dokumentation.

Implementering af docker pull og docker push-kommandoer uden en docker-klient ved hjælp af HTTP-anmodninger

Det er klart, at det eksisterende manifest ikke passer til os, så vi laver vores eget med blackjack og kurtisaner, livliner og konfigurationer.

Vi vil altid have mindst én konfigurationsfil og en række livslinjer. Scheme version 2 (aktuelt i skrivende stund), mediaType forbliver uændret:

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

Efter at have oprettet det grundlæggende manifest, skal du udfylde det med gyldige data. For at gøre dette bruger vi json-skabelonen for rail-objektet:

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

Vi tilføjer det til manifestet for hver skinne.

Dernæst skal vi finde ud af størrelsen på konfigurationsfilen og erstatte stubbene i manifestet med rigtige data

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

Nu kan du starte downloadprocessen og gemme dig selv en uuid, som skal ledsage alle efterfølgende anmodninger.

Det komplette script ser sådan her ud:

#!/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 bruge et færdigt script:

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

UPS:
Hvad fik vi som resultat?
For det første reelle data til analyse, da testene køres i blazemeter, og dataene om docker-klient-anmodninger ikke er særlig informative, i modsætning til rene HTTP-anmodninger.

For det andet gav overgangen os mulighed for at øge antallet af virtuelle brugere til docker-upload med omkring 150 % og få en gennemsnitlig responstid 20-25 % hurtigere. For docker-download lykkedes det at øge antallet af brugere med 500 %, mens den gennemsnitlige responstid faldt med omkring 60 %.

Tak for din opmærksomhed.

Kilde: www.habr.com

Tilføj en kommentar