Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Teníem 2 bosses d'herba, 75 tauletes de mescalina entorn Unix, un dipòsit docker i la tasca d'implementar les ordres docker pull i docker push sense un client docker.

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

ACTUALITZACIÓ:
Pregunta: per a què serveix tot això?
Resposta: prova de càrrega del producte (NO utilitza bash, els scripts es proporcionen amb finalitats educatives). Es va decidir no utilitzar el client Docker per reduir capes addicionals (dins de límits raonables) i, en conseqüència, emular una càrrega més gran. Com a resultat, es van eliminar tots els retards del sistema del client Docker. Vam rebre una càrrega relativament neta directament sobre el producte.
L'article utilitzava versions de GNU d'eines.

Primer, anem a esbrinar què fan aquestes ordres.

Aleshores, per a què serveix Docker pull? D'acord amb documentació:

"Extreu una imatge o un repositori d'un registre".

També hi trobem un enllaç a comprendre imatges, contenidors i controladors d'emmagatzematge.

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

A partir d'aquí podem entendre que una imatge docker és un conjunt de determinades capes que contenen informació sobre els últims canvis de la imatge, que és evidentment el que necessitem. A continuació ens mirem API de registre.

Diu el següent:

"Una "imatge" és una combinació d'un manifest JSON i fitxers de capes individuals. El procés d'extracció d'una > imatge se centra en la recuperació d'aquests dos components".

Així que el primer pas segons la documentació és "Tirant d'un manifest d'imatge".

Per descomptat, no el dispararem, però en necessitem les dades. El següent és un exemple de sol·licitud: GET /v2/{name}/manifests/{reference}

"El nom i el paràmetre de referència identifiquen la imatge i són obligatoris. La referència pot incloure una etiqueta o un resum."

El nostre repositori Docker es desplega localment, intentem executar la sol·licitud:

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

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Com a resposta, rebem json del qual actualment només ens interessen les línies de vida, o més aviat els seus hash. Un cop rebuts, podem revisar-los i executar la següent sol·licitud: "GET /v2/{name}/blobs/{digest}"

"L'accés a una capa es limitarà pel nom del dipòsit, però s'identifica únicament al registre mitjançant un resum".

digest en aquest cas és el hash que hem rebut.

Intentant

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

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Vegem quin tipus d'arxiu vam rebre finalment com a primera línia de vida.

file firstLayer

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

aquells. els rails són arxius tar, desempaquetant-los en l'ordre adequat obtindrem el contingut de la imatge.

Escrivim un petit script bash perquè tot això es pugui automatitzar

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

Ara podem executar-lo amb els paràmetres desitjats i obtenir el contingut de la imatge requerida

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

Part 2 - Docker push

Això serà una mica més complicat.

Comencem de nou amb documentació. Per tant, hem de descarregar cada líder, recollir el manifest corresponent i descarregar-lo també. Sembla senzill.

Després d'estudiar la documentació, podem dividir el procés de descàrrega en diversos passos:

  • Inicialització del procés - "POST /v2/{repoName}/blobs/uploads/"
  • Càrrega d'una línia de vida (utilitzarem una càrrega monolítica, és a dir, enviarem cada línia de vida en la seva totalitat) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Durada del contingut: {mida de la capa}
    Tipus de contingut: aplicació/stream d'octets
    Capa de dades binàries".
  • S'està carregant el manifest - "PUT /v2/{repoName}/manifests/{reference}".

Però la documentació perd un pas, sense el qual res funcionarà. Per a la càrrega monolítica, així com per a la parcial (en trossos), abans de carregar el rail, heu de realitzar una sol·licitud PATCH:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Durada del contingut: {mida del tros}
Tipus de contingut: aplicació/stream d'octets
{Layer Chunk Binary Data}".

En cas contrari, no podreu avançar més enllà del primer punt, perquè... En lloc del codi de resposta esperat 202, rebreu 4xx.

Ara l'algorisme sembla:

  • Inicialització
  • Raíl de pegat
  • Carregant la barana
  • S'està carregant el manifest
    Els punts 2 i 3, respectivament, es repetiran tantes vegades com calgui carregar el nombre de línies.

En primer lloc, necessitem qualsevol imatge. Faré servir archlinux:latest

docker pull archlinux

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Ara desem-lo localment per a una anàlisi posterior

docker save c24fe13d37b9 -o savedArch

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Descomprimiu l'arxiu resultant al directori actual

tar xvf savedArch

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Com podeu veure, cada línia de vida es troba en una carpeta separada. Vegem ara l'estructura del manifest que hem rebut

cat manifest.json | json_pp

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

No gaire. Vegem quin manifest es necessita per carregar, segons documentació.

Implementació d'ordres docker pull i docker push sense un client docker mitjançant sol·licituds HTTP

Evidentment, el manifest existent no ens convé, així que ens en farem el nostre amb blackjack i cortesanes, línies de vida i configuracions.

Sempre tindrem almenys un fitxer de configuració i una sèrie de línies de vida. Esquema versió 2 (actual en el moment d'escriure), mediaType es deixarà sense canvis:

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

Després de crear el manifest bàsic, heu d'omplir-lo amb dades vàlides. Per fer-ho, utilitzem la plantilla json de l'objecte rail:

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

L'afegirem al manifest de cada rail.

A continuació, hem d'esbrinar la mida del fitxer de configuració i substituir els talons del manifest per dades reals

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

Ara podeu iniciar el procés de descàrrega i desar-vos un uuid, que hauria d'acompanyar totes les sol·licituds posteriors.

L'script complet sembla una cosa així:

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

podem utilitzar un script ja fet:

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

ACTUALITZACIÓ:
Què hem obtingut com a resultat?
En primer lloc, dades reals per a l'anàlisi, ja que les proves s'executen en blazemeter i les dades de les sol·licituds dels clients docker no són gaire informatives, a diferència de les sol·licituds HTTP pures.

En segon lloc, la transició ens va permetre augmentar el nombre d'usuaris virtuals per a la càrrega de Docker en un 150% i obtenir un temps de resposta mitjà entre un 20 i un 25% més ràpid. Per a la descàrrega de Docker, hem aconseguit augmentar el nombre d'usuaris en un 500%, mentre que el temps de resposta mitjà es va reduir en un 60%.

Gràcies per la seva atenció.

Font: www.habr.com

Afegeix comentari