Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

We hadden 2 zakken gras, 75 mescaline-tablets unix-omgeving, een docker-repository en de taak om de docker pull- en docker push-opdrachten te implementeren zonder een docker-client.

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

UPD:
Vraag: Waar is dit allemaal voor?
Antwoord: Testen van het product (NIET met bash, de scripts zijn bedoeld voor educatieve doeleinden). Er werd besloten om de docker-client niet te gebruiken om extra lagen te verminderen (binnen redelijke grenzen) en dienovereenkomstig een hogere belasting te emuleren. Als gevolg hiervan werden alle systeemvertragingen van de Docker-client verwijderd. Wij kregen een relatief schone lading direct op het product.
In het artikel werden GNU-versies van tools gebruikt.

Laten we eerst eens kijken wat deze commando's doen.

Dus waar wordt docker pull voor gebruikt? Volgens documentatie:

"Haal een afbeelding of een repository uit een register".

Daar vinden we ook een link naar begrijp afbeeldingen, containers en opslagstuurprogramma's.

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Vanaf hier kunnen we begrijpen dat een docker-afbeelding een reeks bepaalde lagen is die informatie bevatten over de laatste wijzigingen in de afbeelding, wat uiteraard is wat we nodig hebben. Vervolgens kijken wij register-API.

Er staat het volgende:

"Een 'afbeelding' is een combinatie van een JSON-manifest en individuele laagbestanden. Het proces van het ophalen van een afbeelding draait om het ophalen van deze twee componenten."

Dus de eerste stap volgens de documentatie is “Een beeldmanifest trekken'.

Natuurlijk gaan we er geen opnames van maken, maar we hebben de gegevens ervan nodig. Het volgende is een voorbeeldverzoek: GET /v2/{name}/manifests/{reference}

"De naam en referentieparameter identificeren de afbeelding en zijn vereist. De referentie kan een tag of samenvatting bevatten."

Onze docker-repository wordt lokaal geïmplementeerd, laten we proberen het verzoek uit te voeren:

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

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Als reactie ontvangen we json waarvan we momenteel alleen geïnteresseerd zijn in de levenslijnen, of beter gezegd hun hashes. Nadat we ze hebben ontvangen, kunnen we ze allemaal doornemen en het volgende verzoek uitvoeren: "GET /v2/{name}/blobs/{digest}"

“Toegang tot een laag wordt afgesloten door de naam van de repository, maar wordt uniek in het register geïdentificeerd door middel van digest.”

digest is in dit geval de hash die we hebben ontvangen.

Laten we proberen

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

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Laten we eens kijken wat voor soort bestand we uiteindelijk als eerste levenslijn hebben ontvangen.

file firstLayer

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

die. rails zijn teerarchieven. Als u ze in de juiste volgorde uitpakt, krijgen we de inhoud van de afbeelding.

Laten we een klein bash-script schrijven, zodat dit allemaal kan worden geautomatiseerd

#!/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 kunnen we het uitvoeren met de gewenste parameters en de inhoud van de vereiste afbeelding ophalen

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

Deel 2 - docker-push

Dit zal iets ingewikkelder zijn.

Laten we opnieuw beginnen met documentatie. We moeten dus elke leider downloaden, het bijbehorende manifest verzamelen en dit ook downloaden. Het lijkt eenvoudig.

Na het bestuderen van de documentatie kunnen we het downloadproces in verschillende stappen verdelen:

  • Procesinitialisatie - "POST /v2/{repoName}/blobs/uploads/"
  • Een levenslijn uploaden (we zullen een monolithische upload gebruiken, d.w.z. we sturen elke levenslijn in zijn geheel) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Inhoudslengte: {grootte van laag}
    Inhoudstype: applicatie/octet-stream
    Laag binaire gegevens".
  • Het manifest laden - "PUT /v2/{repoName}/manifests/{reference}".

Maar de documentatie mist één stap, zonder welke niets zal werken. Voor monolithisch laden, maar ook voor gedeeltelijk (chunked) laden, moet u vóór het laden van de rail een PATCH-verzoek uitvoeren:

"PATCH /v2/{reponaam}/blobs/uploads/{uuid}
Inhoudslengte: {grootte van deel}
Inhoudstype: applicatie/octet-stream
{Layer Chunk binaire gegevens}".

Anders kom je niet verder dan het eerste punt, omdat... In plaats van de verwachte responscode 202 ontvangt u 4xx.

Het algoritme ziet er nu als volgt uit:

  • Initialisatie
  • Patchrail
  • Het laden van de leuning
  • Het manifest laden
    Punten 2 en 3 worden respectievelijk zo vaak herhaald als het aantal lijnen moet worden geladen.

Ten eerste hebben we een afbeelding nodig. Ik zal archlinux:latest gebruiken

docker pull archlinux

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Laten we het nu lokaal opslaan voor verdere analyse

docker save c24fe13d37b9 -o savedArch

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Pak het resulterende archief uit in de huidige map

tar xvf savedArch

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Zoals u kunt zien, bevindt elke levenslijn zich in een aparte map. Laten we nu eens kijken naar de structuur van het manifest dat we hebben ontvangen

cat manifest.json | json_pp

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Niet veel. Laten we eens kijken welk manifest nodig is om te laden, volgens documentatie.

Implementatie van docker pull- en docker push-opdrachten zonder een docker-client met behulp van HTTP-verzoeken

Het is duidelijk dat het bestaande manifest niet bij ons past, dus zullen we ons eigen manifest maken met blackjack en courtisanes, reddingslijnen en configuraties.

We zullen altijd minimaal één configuratiebestand en een reeks levenslijnen hebben. Schemaversie 2 (actueel op het moment van schrijven), mediaType blijft ongewijzigd:

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

Nadat u het basismanifest heeft gemaakt, moet u het vullen met geldige gegevens. Hiervoor gebruiken we de json-sjabloon van het railobject:

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

Wij voegen het voor elke rail toe aan het manifest.

Vervolgens moeten we de grootte van het configuratiebestand achterhalen en de stubs in het manifest vervangen door echte gegevens

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

Nu kunt u het downloadproces starten en een uuid opslaan, die bij alle volgende verzoeken moet worden gevoegd.

Het volledige script ziet er ongeveer zo uit:

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

we kunnen een kant-en-klaar script gebruiken:

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

UPD:
Wat hebben we als resultaat gekregen?
Ten eerste echte gegevens voor analyse, aangezien de tests in Blazemeter worden uitgevoerd en de gegevens over docker-clientverzoeken niet erg informatief zijn, in tegenstelling tot pure HTTP-verzoeken.

Ten tweede heeft de transitie ons in staat gesteld het aantal virtuele gebruikers voor docker-uploads met ongeveer 150% te verhogen en de gemiddelde responstijd 20-25% sneller te krijgen. Voor het downloaden van Docker zijn we erin geslaagd het aantal gebruikers met 500% te verhogen, terwijl de gemiddelde responstijd met ongeveer 60% daalde.

Dank u voor uw aandacht.

Bron: www.habr.com

Voeg een reactie