A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Két zsák fű volt nálunk, 75 tabletta meszkalin, egy Unix környezet, egy Docker tároló, és feladat: megvalósítani a docker pull és docker push parancsokat Docker kliens nélkül.

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

UPD:
Kérdés: Mire jó ez az egész?
Válasz: A termék terheléses tesztelése (NEM bash eszközökkel, a szkriptek az oktatás céljából kerültek bemutatásra). A Docker kliens kihagyását azért döntöttük el, hogy csökkentsük a további rétegeket (ésszerű határokon belül) és ezzel emuláljuk a magasabb terhelést. Ennek eredményeként eltávolítottuk a Docker kliens által okozott rendszerteszteket. Viszonylag tiszta terhelést kaptunk közvetlenül a terméken.
A cikkben GNU eszközök verziói lettek használva.

Először nézzük meg, mit csinálnak ezek a parancsok.

Tehát miért használjuk a docker pull-t? Az alábbi szerint: dokumentáció:

"Pull an image or a repository from a registry".

Ott találjuk a linket a következőhöz: értsd meg a képeket, konténereket és tároló drivereket.

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Itt megláthatjuk, hogy a docker image egy rétegekből álló készlet, amelyek információkat tartalmaznak a képen végzett legutolsó módosításokról, amelyeket nyilvánvalóan szükségünk van. Tovább nézzük a registry API-t.

Itt a következő áll:

"An “image” is a combination of a JSON manifest and individual layer files. The process of pulling an > image centers around retrieving these two components."

Tehát az első lépés a dokumentáció szerint: "Egy Image Manifeszt Pullolása”.

Nyilván nem fogjuk pullolni, de az adatokra szükségünk van. Továbbá itt egy példa kérelem: GET /v2/{name}/manifests/{reference}

"The name and reference parameter identify the image and are required. The reference may include a tag or digest."

A Docker tárolónk helyben van telepítve, próbáljuk meg végrehajtani a kérést:

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

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

A válaszban json-t kapunk, amiből csak a rétegekre, pontosabban a hash-ukra van szükségünk. Ezeket megkapva, mindegyiken végigmehetünk és következő kérést tehetünk: "GET /v2/{name}/blobs/{digest}"

"A layerhez való hozzáférést a tároló neve korlátozza, de a registry-ben egyedileg a digest azonosítja."

A digest ebben az esetben a hash, amit megkaptunk.

Próbálkozunk

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

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Nézzük meg, milyen fájlt kaptunk végül az első rétegként.

file firstLayer

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Tehát a rétegek tar archívumok, amelyeket megfelelő sorrendben kibontva megkapjuk a kép tartalmát.

Írjunk egy kis bash szkriptet, hogy mindezt automatizálhassuk.

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

Most már elindíthatjuk a kívánt paraméterekkel, és megkaphatjuk a szükséges image tartalmát.

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

2. rész — docker push

Itt egy kicsit bonyolultabb lesz.

Újra kezdjük a dokumentáció. Tehát minden lépést fel kell töltenünk, megfelelő manifestet kell összegyűjtenünk, és azt is fel kell töltenünk. Éppenséggel hangzik.

A dokumentáció tanulmányozása után a feltöltési folyamatot több lépésre osztjuk:

  • A folyamat inicializálása — "POST \/v2\/{repoName}\/blobs\/uploads\/"
  • A lé réteg feltöltése (monolitikus feltöltést fogunk használni, azaz minden lé réteget egészben küldünk) — "PUT \/v2\/{repoName}\/blobs\/uploads\/{uuid}?digest={digest}"
    Content-Length: {size of layer}
    Content-Type: application\/octet-stream
    Réteg bináris adatai".
  • A manifest feltöltése — "PUT \/v2\/{repoName}\/manifests\/{reference}".

De a dokumentációból hiányzik egy lépés, nélküle nem fog menni. A monolitikus feltöltéshez, akárcsak a részlegeshez (chunked), a lé réteg feltöltése előtt PATCH kérést kell végrehajtani:

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

Ellenkező esetben nem tud előrébb jutni az első ponthoz, mivel a várt 202-es válasz helyett 4xx-t fog kapni.

Most az algoritmus így néz ki:

  • Inicializálás
  • Réteg patch
  • Réteg feltöltése
  • Manifest feltöltése
    A 2-es és 3-as lépések megfelelően meg fognak ismétlődni, ahányszor a szükséges lé rétegek szerepelnek.

Először is szükségünk lesz bármilyen image-re. Én az archlinux:latest verziót fogom használni.

docker pull archlinux

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Most mentjük el helyben a további elemzéshez.

docker save c24fe13d37b9 -o savedArch

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Kitömörítjük a kapott archívumot a jelenlegi könyvtárba.

tar xvf savedArch

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Ahogy látjuk, minden lé réteg külön mappában található. Most nézzük meg a manifest struktúráját, amelyet kaptunk.

cat manifest.json | json_pp

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Nem túl sok. Nézzük meg, hogy milyen manifest szükséges a feltöltéshez, a következők szerint: dokumentáció.

A docker pull és docker push parancsok megvalósítása docker kliens nélkül HTTP kérések révén

Nyilvánvaló, hogy a meglévő manifest nem megfelelő, ezért készítünk sajátot, blackjack-el és kurvák lé rétegeivel és konfigurációival.

Mindig lesz minimum egy config fájlunk és egy lé réteg tömbünk. A sémaverzió 2 (aktuális a cikk írásának időpontjában), a mediaType-ot változatlanul hagyjuk:

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

A alap manifest létrehozása után érvényes adatokkal kell feltöltenünk. Ehhez a lé réteg JSON objektum sablont használjuk:

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

ezt hozzáadjuk majd a manifesthez minden lé réteghez.

Ezután meg kell tudnunk a konfigurációs fájl méretét, és a helyettesítőket a manifestben valódi adatokra kell cserélnünk.

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

Most elindíthatjuk a feltöltési folyamatot, és meg kell őriznünk az uuid-t, amelynek kísérnie kell az összes későbbi kérést.

A teljes szkriptnak körülbelül így kell kinéznie:

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

Használhatunk egy kész szkriptet:

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

UPD:
Mit kaptunk eredményül?
Először is, valós adatokat kaptunk az elemzéshez, mivel a tesztek a blazemeter-ben futnak, és a docker kliens kéréseinek adatai nem túl informatívak a tiszta HTTP kérésekhez képest.

Másodszor, az átállás lehetővé tette számunkra, hogy a virtuális felhasználók számát a docker feltöltésénél körülbelül 150%-kal növeljük, és ez idő alatt az átlagos válaszidő 20-25%-kal csökkent. A docker letöltésnél a felhasználók számát 500%-kal tudtuk növelni, az átlagos válaszidő pedig körülbelül 60%-kal csökkent.

Köszönöm a figyelmet.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster