Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Vi hade 2 påsar med gräs, 75 mescaline tabletter unix-miljö, ett docker-förråd och uppgiften att implementera docker pull och docker push-kommandon utan en docker-klient.

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

UPD:
Fråga: Vad är allt detta till för?
Svar: Lasttestning av produkten (inte använder bash, skripten tillhandahålls för utbildningsändamål). Det beslutades att inte använda docker-klienten för att minska ytterligare lager (inom rimliga gränser) och följaktligen emulera en högre belastning. Som ett resultat togs alla systemförseningar från Docker-klienten bort. Vi fick en relativt ren last direkt på produkten.
Artikeln använde GNU-versioner av verktyg.

Låt oss först ta reda på vad dessa kommandon gör.

Så vad används docker pull till? Enligt dokumentation:

"Dra en bild eller ett arkiv från ett register".

Där hittar vi också en länk till förstå bilder, behållare och lagringsdrivrutiner.

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Härifrån kan vi förstå att en docker-bild är en uppsättning av vissa lager som innehåller information om de senaste ändringarna i bilden, vilket uppenbarligen är vad vi behöver. Därefter tittar vi på register API.

Det står följande:

"En "bild" är en kombination av ett JSON-manifest och enskilda lagerfiler. Processen att dra en bild är centrerad kring att hämta dessa två komponenter."

Så det första steget enligt dokumentationen är "Dra ett bildmanifest".

Naturligtvis kommer vi inte att skjuta det, men vi behöver data från det. Följande är ett exempel på begäran: GET /v2/{name}/manifests/{reference}

"Namnet och referensparametern identifierar bilden och är obligatoriska. Referensen kan innehålla en tagg eller sammanfattning."

Vårt dockningsförråd är utplacerat lokalt, låt oss försöka utföra begäran:

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

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Som svar får vi json från vilken vi för närvarande bara är intresserade av livlinorna, eller snarare deras hash. Efter att ha tagit emot dem kan vi gå igenom var och en och utföra följande begäran: "GET /v2/{name}/blobs/{digest}"

"Åtkomst till ett lager kommer att grindas av namnet på förvaret men identifieras unikt i registret genom sammanfattning."

digest i det här fallet är hash som vi fick.

Låt oss försöka

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

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Låt oss se vilken typ av fil vi äntligen fick som första livlina.

file firstLayer

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

de där. räls är tjärarkiv, packar vi upp dem i lämplig ordning får vi innehållet i bilden.

Låt oss skriva ett litet bash-skript så att allt detta kan automatiseras

#!/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öra den med önskade parametrar och få innehållet i den önskade bilden

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

Del 2 - hamnarknuff

Det här blir lite mer komplicerat.

Låt oss börja igen med dokumentation. Så vi måste ladda ner varje ledare, samla in motsvarande manifest och ladda ner det också. Det verkar enkelt.

Efter att ha studerat dokumentationen kan vi dela upp nedladdningsprocessen i flera steg:

  • Processinitiering - "POST /v2/{repoName}/blobs/uploads/"
  • Ladda upp en livlina (vi använder en monolitisk uppladdning, dvs. vi skickar varje livlina i sin helhet) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Innehållslängd: {storlek på lager}
    Innehållstyp: applikation/oktettström
    Lager binära data".
  • Laddar manifestet - "PUT /v2/{repoName}/manifests/{referens}".

Men dokumentationen missar ett steg, utan vilket ingenting kommer att fungera. För monolitisk laddning, såväl som för partiell (chunked), innan du laddar skenan, måste du utföra en PATCH-begäran:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Innehållslängd: {size of chunk}
Innehållstyp: applikation/oktettström
{Layer Chunk Binary Data}".

Annars kommer du inte att kunna gå längre än den första punkten, eftersom... Istället för den förväntade svarskoden 202 får du 4xx.

Nu ser algoritmen ut så här:

  • initiering
  • Patchskena
  • Laddar ledstången
  • Laddar manifestet
    Punkterna 2 respektive 3 kommer att upprepas så många gånger som antalet rader behöver laddas.

Först behöver vi vilken bild som helst. Jag kommer att använda archlinux:latest

docker pull archlinux

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Låt oss nu spara det lokalt för vidare analys

docker save c24fe13d37b9 -o savedArch

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Packa upp det resulterande arkivet i den aktuella katalogen

tar xvf savedArch

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Som du kan se finns varje livlina i en separat mapp. Låt oss nu titta på strukturen för manifestet vi fick

cat manifest.json | json_pp

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Inte mycket. Låt oss se vilket manifest som behövs för att ladda, enligt dokumentation.

Implementera docker pull och docker push-kommandon utan en docker-klient som använder HTTP-förfrågningar

Uppenbarligen passar det befintliga manifestet inte oss, så vi kommer att göra vårt eget med blackjack och kurtisaner, livlinor och konfigurationer.

Vi kommer alltid att ha minst en konfigurationsfil och en rad livlinor. Schema version 2 (nuvarande i skrivande stund), mediaType kommer att lämnas oförändrad:

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 att ha skapat det grundläggande manifestet måste du fylla det med giltiga data. För att göra detta använder vi json-mallen för rail-objektet:

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

Vi kommer att lägga till det i manifestet för varje skena.

Därefter måste vi ta reda på storleken på konfigurationsfilen och ersätta stubbarna i manifestet med riktiga data

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

Nu kan du initiera nedladdningsprocessen och spara dig själv en uuid, som bör följa med alla efterföljande förfrågningar.

Det fullständiga skriptet ser ut ungefär så här:

#!/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 använda ett färdigt skript:

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

UPD:
Vad fick vi som resultat?
För det första, verklig data för analys, eftersom testen körs i blazemeter och data om docker-klientförfrågningar inte är särskilt informativa, till skillnad från rena HTTP-förfrågningar.

För det andra tillät övergången oss att öka antalet virtuella användare för dockningsuppladdning med cirka 150 % och få en genomsnittlig svarstid 20-25 % snabbare. För dockernedladdning lyckades vi öka antalet användare med 500 %, medan den genomsnittliga svarstiden minskade med cirka 60 %.

Tack för er uppmärksamhet.

Källa: will.com

Lägg en kommentar