Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Meil oli 2 koti rohtu, 75 tabletti meskalina unix keskkonnas, docker hoidla ja ülesanne teostada käske docker pull ja docker push ilma docker kliendita.

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

UPD:
Küsimus: Milleks see kõik on?
Vastus: Toote koormustestimine (MITTE bash'i vahendite abil, skriptid on esitatud hariduse eesmärkidel). Docker kliendi mittekasutamise otsustamisel püüti vähendada lisavahekihte (mõistlike piiride piires) ja seega jäljendada suuremat koormust. Tulemuseks oli, et eemaldati kõik süsteemi viivitused docker kliendist. Saime suhteliselt puhta koormuse otse tootest.
Artiklis kasutati GNU versioone.

Alustuseks selgitame, mida need käsud teevad.

Nii et milleks kasutatakse docker pull? Vastavalt dokumentatsioon:

"Tõmba pilt või hoidla registrist".

Seal leiame lingi mõista pilte, konteinerite ja salvestusjuhtide kohta.

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Sealt saame aru, et docker image on komplekt teatud kihtidest, mis sisaldavad teavet viimaste muudatuste kohta pildis, mis on meile ilmselgelt vajalik. Edasi vaatame registry API.

Siin öeldakse järgmist:

"Pilt" on JSON manifesti ja individuaalsete kihifailide kombinatsioon. Pildi tõmbamise protsess keskendub nende kahe komponendi hankimisele."

Nii et esimene samm vastavalt dokumentatsioonile on "Pildi manifesti tõmbamine”.

Kuid me ei kavatse seda tõmmata, kuid andmed sellest on meile vajalikud. Edasi toome näite päringust: GET /v2/{name}/manifests/{reference}

"Nimi ja viitamisparameeter tuvastavad pildi ja on kohustuslikud. Viitamine võib sisaldada silti või väljakuulutust."

Meie docker hoidla on kohalikult üles seatud, proovime teha päringu:

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

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Vastusena saame json-i, millest meid huvitavad hetkel ainult kihid, täpsemalt nende hash'id. Saades need, saame igaühe kohta edasi minna ja teostada järgmise päringu: "GET /v2/{name}/blobs/{digest}"

"Kihile pääsemine on piiratud hoidla nime järgi, kuid tuvastatakse registris unikaalselt hash'i kaudu."

Hash on antud juhul see hash, mille me saime.

Katsume

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

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

vaatame, milline fail me lõpuks esimese kihina saime.

file firstLayer

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

st. kihid on tar-arhive, mille õigesti järjestatud lahtipakkimise korral saame pildi sisu.

Kirjutame väikese bash'i skripti, et seda kõike automatiseerida.

#!/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üüd saame selle soovitud parameetritega käivitada ja vajalikku imaget saada.

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

Osa 2 - docker push

Siin läheb natuke keerulisemaks.

Alustame jälle dokumentatsioon. Nii et peame laadima üles iga kihi, looma vastava manifeesti ja laadima selle ka üles. Näib lihtne.

Dokumentatsiooni uurides saame laadimisprotsessi jagada mitmeks sammuks:

  • Protsessi algatamine - "POST \/v2\/{repoName}\/blobs\/uploads\/"
  • Kihi üleslaadimine (kasutame monoliitset laadimist, ehk iga kihi saadame tervikuna) - "PUT \/v2\/{repoName}\/blobs\/uploads\/{uuid}?digest\={digest}
    Content-Length: {layeri suurus}
    Content-Type: application\/octet-stream
    Kihi binaarsed andmed".
  • Manifeesti üleslaadimine - "PUT \/v2\/{repoName}\/manifests\/{reference}".

Kuid dokumentatsioonist jääb puudu üks samm, ilma milleta midagi ei õnnestu. Nii monoliitse kui ka osalise (chunked) laadimise puhul peab enne kihi laadimist teostama PATCH-päringu:

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

Vastasel juhul ei saa te edasi liikuda esimese punktini, sest oodatud vastuskoodi 202 asemel saate 4xx.

Nüüd näeb algoritm välja järgmine:

  • Algatamine
  • Kihi patchimine
  • Kihi üleslaadimine
  • Manifeesti üleslaadimine
    Punktid 2 ja 3 korduvad vastavalt nii palju kordi, kui kihte on vaja üles laadida.

Alustuseks on meil vaja mingit imaget. Kasutan archlinux:latest

docker pull archlinux

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Nüüd salvestame selle endale lokaalselt edasiseks analüüsiks.

docker save c24fe13d37b9 -o savedArch

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Lahti pakkime saadud arhiivi praegusesse kausta.

tar xvf savedArch

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Nagu näeme, on iga kiht eraldi kaustas. Vaadakem nüüd manifeesti struktuuri, mille saime.

cat manifest.json | json_pp

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Pole palju. Uurime, milline manifeet on üleslaadimiseks vajalik, vastavalt dokumentatsioon.

Docker pull ja docker push käskude rakendamine ilma docker kliendita HTTP-päringute kaudu

Ilmselgelt ei sobi olemasolev manifeet, seega teeme oma mustandiga, kus on mustad kaardid ja proovid.

Meil on alati vähemalt üks config-fail ja kihi massiiv. Skeemi versioon on 2 (aktuaalne artikli kirjutamise hetkel), mediaType jätame muutmata:

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

Pärast põhimanifeesti loomist on vajalik see täita kehtivate andmetega. Selleks kasutame kihi json-objekti mall:

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

seda lisame manifeesti iga kihi jaoks.

Jätkusuutlikult peame teadma konfigureerimisfaili suurust ja asendama manifestis plokid reaalse teabega.

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

Nüüd saame algatada laadimisprotsessi ja salvestada uuid, millega peavad kõik järgmised päringud kaasas olema.

Täielik skript näeb välja umbes selline:

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

saame kasutada valmis skripti:

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

UPD:
Mida me tulemusena saime?
Esiteks, reaalsed andmed analüüsi jaoks, kuna testid käivitatakse blazemeter'is ja Docker kliendi päringud ei ole kuigi informatiivsed võrreldes puhaste HTTP päringutega.

Teiseks, üleminek võimaldas meil suurendada virtuaalsete kasutajate arvu Docker'i üleslaadimisel umbes 150% ja saada keskmine vastusaeg 20-25% kiiremini. Docker'i allalaadimisel õnnestus suurendada kasutajate arvu 500%, keskmine vastusaeg langes samas umbes 60%.

Aitäh tähelepanu eest.

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster