Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Imali smo 2 vreće trave, 75 meskalin tableta unix okruženje, docker repozitorijum i zadatak implementacije docker pull i docker push komandi bez docker klijenta.

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

UPS:
Pitanje: Čemu sve ovo?
Odgovor: Testiranje učitavanja proizvoda (NE koristi bash, skripte su date u obrazovne svrhe). Odlučeno je da se docker klijent ne koristi za smanjenje dodatnih slojeva (u razumnim granicama) i, shodno tome, emuliranje većeg opterećenja. Kao rezultat toga, sva sistemska kašnjenja Docker klijenta su uklonjena. Dobili smo relativno čisto opterećenje direktno na proizvodu.
Članak je koristio GNU verzije alata.

Prvo, hajde da shvatimo šta ove komande rade.

Dakle, za šta se koristi docker pull? Prema dokumentaciju:

"Izvucite sliku ili spremište iz registra".

Tamo nalazimo i link do razumjeti slike, kontejnere i drajvere za pohranu.

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Odavde možemo shvatiti da je docker slika skup određenih slojeva koji sadrže informacije o najnovijim promjenama na slici, što nam je očito potrebno. Dalje ćemo pogledati API registra.

Piše sljedeće:

"Slika" je kombinacija JSON manifesta i pojedinačnih datoteka slojeva. Proces povlačenja > slike se usredotočuje na preuzimanje ove dvije komponente."

Dakle, prvi korak prema dokumentaciji je “Izvlačenje manifesta slike".

Naravno, nećemo ga snimati, ali su nam potrebni podaci iz njega. Slijedi primjer zahtjeva: GET /v2/{name}/manifests/{reference}

"Naziv i parametar reference identificiraju sliku i obavezni su. Referenca može uključivati ​​oznaku ili sažetak."

Naše docker spremište je raspoređeno lokalno, hajde da pokušamo izvršiti zahtjev:

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

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Kao odgovor, dobijamo json od kojeg nas trenutno zanimaju samo linije spašavanja, odnosno njihove hešove. Nakon što ih primimo, možemo proći kroz svaki i izvršiti sljedeći zahtjev: "GET /v2/{name}/blobs/{digest}"

“Pristup sloju će biti ograničen imenom spremišta, ali se jedinstveno identificira u registru pomoću sažetka.”

digest u ovom slučaju je hash koji smo primili.

Pokušavam

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

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Hajde da vidimo kakav smo fajl konačno dobili kao prvi spas.

file firstLayer

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

one. rails su tar arhive, raspakujući ih odgovarajućim redoslijedom dobićemo sadržaj slike.

Hajde da napišemo malu bash skriptu kako bi se sve ovo moglo automatizovati

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

Sada ga možemo pokrenuti sa željenim parametrima i dobiti sadržaj tražene slike

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

Dio 2 - docker push

Ovo će biti malo komplikovanije.

Počnimo ponovo sa dokumentaciju. Dakle, moramo preuzeti svakog lidera, prikupiti odgovarajući manifest i preuzeti ga također. Izgleda jednostavno.

Nakon proučavanja dokumentacije, proces preuzimanja možemo podijeliti u nekoliko koraka:

  • Inicijalizacija procesa - "POST /v2/{repoName}/blobs/uploads/"
  • Učitavanje linije spasavanja (koristit ćemo monolitno otpremanje, tj. šaljemo svaku liniju spasa u cijelosti) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Dužina sadržaja: {veličina sloja}
    Content-Type: aplikacija/oktet-tok
    Binarni podaci sloja".
  • Učitavanje manifesta - "PUT /v2/{repoName}/manifests/{reference}".

Ali dokumentaciji nedostaje jedan korak, bez kojeg ništa neće raditi. Za monolitno opterećenje, kao i za djelomično (u komadima), prije utovara šine, morate izvršiti PATCH zahtjev:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Dužina sadržaja: {veličina komada}
Content-Type: aplikacija/oktet-tok
{Layer Chunk Binary Data}".

U suprotnom, nećete moći da se pomerite dalje od prve tačke, jer... Umjesto očekivanog koda odgovora 202, dobit ćete 4xx.

Sada algoritam izgleda ovako:

  • Inicijalizacija
  • Patch rail
  • Utovar rukohvata
  • Učitavanje manifesta
    Tačke 2 i 3, respektivno, će se ponoviti onoliko puta koliko je potrebno učitati redove.

Prvo, trebamo bilo koju sliku. Koristiću archlinux:latest

docker pull archlinux

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Sada ga spremimo lokalno za dalju analizu

docker save c24fe13d37b9 -o savedArch

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Raspakirajte rezultujuću arhivu u trenutni direktorij

tar xvf savedArch

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Kao što vidite, svaka linija života nalazi se u zasebnom folderu. Pogledajmo sada strukturu manifesta koji smo dobili

cat manifest.json | json_pp

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Ne mnogo. Hajde da vidimo koji je manifest potreban za učitavanje, prema dokumentaciju.

Implementacija docker pull i docker push komandi bez docker klijenta koristeći HTTP zahtjeve

Očigledno nam postojeći manifest ne odgovara, pa ćemo napraviti svoj s blackjackom i kurtizanama, spasilačkim užetom i konfigovima.

Uvijek ćemo imati barem jednu konfiguracijsku datoteku i niz linija spasavanja. Shema verzija 2 (aktualna u vrijeme pisanja), mediaType će ostati nepromijenjen:

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

Nakon kreiranja osnovnog manifesta, morate ga popuniti važećim podacima. Da bismo to učinili, koristimo json predložak željezničkog objekta:

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

Dodaćemo ga u manifest za svaku šinu.

Zatim moramo saznati veličinu konfiguracijske datoteke i zamijeniti stubove u manifestu stvarnim podacima

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

Sada možete pokrenuti proces preuzimanja i spremiti sebi uuid, koji bi trebao pratiti sve naredne zahtjeve.

Kompletna skripta izgleda otprilike ovako:

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

možemo koristiti gotovu skriptu:

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

UPS:
Šta smo dobili kao rezultat?
Prvo, pravi podaci za analizu, pošto se testovi izvode u blazemeteru, a podaci o zahtjevima docker klijenta nisu baš informativni, za razliku od čistog HTTP zahtjeva.

Drugo, tranzicija nam je omogućila da povećamo broj virtuelnih korisnika za docker upload za oko 150% i dobijemo prosječno vrijeme odgovora 20-25% brže. Za preuzimanje docker-a uspjeli smo povećati broj korisnika za 500%, dok je prosječno vrijeme odgovora smanjeno za oko 60%.

Hvala na pažnji.

izvor: www.habr.com

Dodajte komentar