Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Mayroon kaming 2 bag ng damo, 75 mescaline tablets unix environment, isang docker repository at ang gawain ng pagpapatupad ng docker pull at docker push command nang walang docker client.

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

UPS:
Tanong: Para saan ang lahat ng ito?
Sagot: I-load ang pagsubok ng produkto (HINDI gumagamit ng bash, ang mga script ay ibinigay para sa mga layuning pang-edukasyon). Napagpasyahan na huwag gamitin ang docker client para bawasan ang mga karagdagang layer (sa loob ng makatwirang limitasyon) at, nang naaayon, tularan ang mas mataas na load. Bilang resulta, ang lahat ng mga pagkaantala sa system ng kliyente ng Docker ay inalis. Nakatanggap kami ng medyo malinis na pagkarga nang direkta sa produkto.
Gumamit ang artikulo ng mga bersyon ng GNU ng mga tool.

Una, alamin natin kung ano ang ginagawa ng mga utos na ito.

Kaya para saan ginagamit ang docker pull? Ayon kay dokumentasyon:

"Hilahin ang isang imahe o isang repository mula sa isang registry".

Doon din namin mahahanap ang isang link sa maunawaan ang mga larawan, container, at storage driver.

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Mula rito, mauunawaan natin na ang imahe ng docker ay isang hanay ng ilang mga layer na naglalaman ng impormasyon tungkol sa mga pinakabagong pagbabago sa larawan, na malinaw naman kung ano ang kailangan natin. Sunod naming tingnan registry API.

Sinasabi nito ang sumusunod:

"Ang isang "larawan" ay isang kumbinasyon ng isang JSON manifest at indibidwal na mga file ng layer. Ang proseso ng paghila ng isang > imahe ay nakasentro sa pagkuha ng dalawang bahaging ito."

Kaya ang unang hakbang ayon sa dokumentasyon ay "Pagkuha ng Image Manifest".

Siyempre, hindi namin ito kukunan, ngunit kailangan namin ang data mula dito. Ang sumusunod ay isang halimbawa ng kahilingan: GET /v2/{name}/manifests/{reference}

"Ang pangalan at reference na parameter ay tumutukoy sa larawan at kinakailangan. Ang reference ay maaaring may tag o digest."

Ang aming docker repository ay lokal na naka-deploy, subukan nating isagawa ang kahilingan:

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

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Bilang tugon, natatanggap namin ang json kung saan kami ay kasalukuyang interesado lamang sa mga lifeline, o sa halip ang kanilang mga hash. Matapos matanggap ang mga ito, maaari nating suriin ang bawat isa at isakatuparan ang sumusunod na kahilingan: "GET /v2/{name}/blobs/{digest}"

"Ang pag-access sa isang layer ay lagyan ng gate ng pangalan ng repositoryo ngunit nakikilala sa natatanging registry sa pamamagitan ng digest."

digest sa kasong ito ay ang hash na natanggap namin.

Sinusubukan

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

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Tingnan natin kung anong uri ng file ang sa wakas ay natanggap natin bilang unang lifeline.

file firstLayer

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

mga. Ang mga riles ay mga tar archive, ang pag-unpack ng mga ito sa naaangkop na pagkakasunud-sunod ay makukuha namin ang mga nilalaman ng imahe.

Sumulat tayo ng isang maliit na script ng bash upang ang lahat ng ito ay maging awtomatiko

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

Ngayon ay maaari na nating patakbuhin ito gamit ang nais na mga parameter at makuha ang mga nilalaman ng kinakailangang imahe

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

Bahagi 2 - docker push

Ito ay magiging mas kumplikado ng kaunti.

Magsimula tayo muli sa dokumentasyon. Kaya kailangan nating i-download ang bawat pinuno, kolektahin ang kaukulang manifest at i-download din ito. Parang simple lang.

Pagkatapos pag-aralan ang dokumentasyon, maaari naming hatiin ang proseso ng pag-download sa ilang hakbang:

  • Pagsisimula ng proseso - "POST /v2/{repoName}/blobs/uploads/"
  • Pag-upload ng lifeline (gagamitin namin ang monolitikong pag-upload, ibig sabihin, ipapadala namin ang bawat lifeline sa kabuuan nito) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Haba ng Nilalaman: {size of layer}
    Uri ng Nilalaman: application/octet-stream
    Layer Binary Data".
  • Nilo-load ang manifest - "PUT /v2/{repoName}/manifests/{reference}".

Ngunit ang dokumentasyon ay nakakaligtaan ng isang hakbang, kung wala ito ay walang gagana. Para sa monolithic loading, pati na rin para sa partial (chunked), bago i-load ang rail, dapat kang magsagawa ng PATCH request:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Haba ng Nilalaman: {size of chunk}
Uri ng Nilalaman: application/octet-stream
{Layer Chunk Binary Data}".

Kung hindi, hindi ka makakagalaw sa unang punto, dahil... Sa halip na ang inaasahang sagot na code 202, makakatanggap ka ng 4xx.

Ngayon ang algorithm ay mukhang:

  • Pagsisimula
  • Patch rail
  • Nilo-load ang handrail
  • Nilo-load ang manifest
    Ang mga puntos 2 at 3, ayon sa pagkakabanggit, ay uulitin nang maraming beses hangga't ang bilang ng mga linya ay kailangang i-load.

Una, kailangan namin ng anumang imahe. Gagamitin ko ang archlinux:latest

docker pull archlinux

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Ngayon, i-save natin ito nang lokal para sa karagdagang pagsusuri

docker save c24fe13d37b9 -o savedArch

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

I-unpack ang resultang archive sa kasalukuyang direktoryo

tar xvf savedArch

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Tulad ng nakikita mo, ang bawat lifeline ay nasa isang hiwalay na folder. Ngayon tingnan natin ang istraktura ng manifest na natanggap namin

cat manifest.json | json_pp

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Hindi gaano. Tingnan natin kung anong manifest ang kailangan para i-load, ayon sa dokumentasyon.

Pagpapatupad ng docker pull at docker push command nang walang docker client gamit ang mga HTTP request

Malinaw, hindi nababagay sa amin ang umiiral na manifesto, kaya gagawa kami ng sarili namin gamit ang blackjack at courtesans, lifelines at config.

Palagi kaming magkakaroon ng kahit isang config file at isang hanay ng mga lifeline. Bersyon 2 ng Scheme (kasalukuyan sa oras ng pagsulat), ang mediaType ay iiwang hindi magbabago:

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

Pagkatapos gawin ang pangunahing manifest, kailangan mong punan ito ng wastong data. Upang gawin ito, ginagamit namin ang json template ng rail object:

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

Idaragdag namin ito sa manifest para sa bawat riles.

Susunod, kailangan nating malaman ang laki ng config file at palitan ang mga stub sa manifest ng totoong data

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

Ngayon ay maaari mong simulan ang proseso ng pag-download at i-save ang iyong sarili ng uuid, na dapat na kasama ng lahat ng kasunod na kahilingan.

Ang kumpletong script ay mukhang ganito:

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

maaari tayong gumamit ng isang handa na script:

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

UPS:
Ano ang nakuha namin bilang isang resulta?
Una, ang totoong data para sa pagsusuri, dahil ang mga pagsubok ay pinapatakbo sa blazemeter at ang data sa mga kahilingan ng docker client ay hindi masyadong nagbibigay-kaalaman, hindi tulad ng mga purong HTTP na kahilingan.

Pangalawa, pinahintulutan kami ng paglipat na pataasin ang bilang ng mga virtual na user para sa pag-upload ng docker nang humigit-kumulang 150% at makakuha ng avg na oras ng pagtugon nang 20-25% na mas mabilis. Para sa pag-download ng docker, nagawa naming pataasin ang bilang ng mga user ng 500%, habang ang avg na oras ng pagtugon ay bumaba ng humigit-kumulang 60%.

Salamat sa iyo para sa iyong pansin.

Pinagmulan: www.habr.com

Magdagdag ng komento