ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Nous avions 2 sacs d'herbe, 75 comprimés de mescaline, un environnement unix, un dépÎt docker et la tùche de réaliser les commandes docker pull et docker push sans client docker.

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Mise Ă  jour :
Question : À quoi bon tout ça ?
Réponse : Test de charge du produit (PAS par des moyens bash, les scripts sont fournis à des fins éducatives). Ne pas utiliser le client docker a été décidé pour réduire les couches supplémentaires (dans des limites raisonnables) et ainsi émuler une charge plus importante. En conséquence, nous avons éliminé tous les délais systÚme du client docker. Nous avons obtenu une charge relativement pure directement sur le produit.
Des outils GNU de différentes versions ont été utilisés dans l'article.

Pour commencer, voyons ce que font ces commandes.

Alors, Ă  quoi sert docker pull ? Selon documentation:

"Tirer une image ou un dépÎt depuis un registre".

LĂ , nous trouvons un lien vers comprendre les images, les conteneurs et les pilotes de stockage.

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

D'oĂč nous pouvons comprendre que l'image docker est un ensemble de couches qui contiennent les derniĂšres modifications de l'image, qui sont Ă©videmment ce qui nous intĂ©resse. Ensuite, regardons dans l'API du registre.

Il est dit ce qui suit :

"Une 'image' est une combinaison d'un manifeste JSON et de fichiers de couche individuels. Le processus de tirage d'une image se concentre sur la récupération de ces deux composants."

Donc, la premiĂšre Ă©tape selon la documentation est “Tirer un manifeste d'image”.

Nous ne le tirerons Ă©videmment pas, mais les donnĂ©es qui en proviennent nous sont nĂ©cessaires. Ensuite, un exemple de requĂȘte est donnĂ© : GET /v2/{name}/manifests/{reference}

"Le nom et le paramÚtre de référence identifient l'image et sont obligatoires. La référence peut inclure une balise ou un résumé."

Notre dĂ©pĂŽt docker est dĂ©ployĂ© localement, essayons de faire la requĂȘte :

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

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

En rĂ©ponse, nous obtenons un json dont nous ne sommes intĂ©ressĂ©s pour le moment que par les couches, plus prĂ©cisĂ©ment leurs hachages. Une fois obtenus, nous pouvons passer Ă  chacun d'eux et effectuer la requĂȘte suivante : "GET /v2/{name}/blobs/{digest}"

“L'accĂšs Ă  une couche sera conditionnĂ© par le nom du dĂ©pĂŽt mais est identifiĂ© de maniĂšre unique dans le registre par son hachage.”

Le hachage, dans ce cas, est le hachage que nous avons obtenu.

Essayons

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

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Voyons quel fichier nous avons finalement obtenu en tant que premiĂšre couche.

file firstLayer

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

c'est-à-dire que les couches se présentent sous forme d'archives tar, en les décompressant dans l'ordre approprié, nous obtiendrons le contenu de l'image.

Écrivons un petit script bash pour automatiser tout cela.

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

Nous pouvons maintenant le lancer avec les paramÚtres souhaités et obtenir le contenu de l'image nécessaire.

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

Partie 2 — docker push

Cela va ĂȘtre un peu plus compliquĂ©.

Recommençons par documentation. Donc, nous devons télécharger chaque couche, assembler le manifeste correspondant et le télécharger également. Cela semble simple.

En étudiant la documentation, nous pouvons diviser le processus de téléchargement en plusieurs étapes :

  • Initialisation du processus — "POST /v2/{repoName}/blobs/uploads/"
  • TĂ©lĂ©chargement de la couche (nous allons utiliser le tĂ©lĂ©chargement monolithique, c'est-Ă -dire que chaque couche est envoyĂ©e dans son intĂ©gralitĂ©) — "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Content-Length: {size of layer}
    Content-Type: application/octet-stream
    Données binaires de la couche."
  • TĂ©lĂ©chargement du manifeste — "PUT /v2/{repoName}/manifests/{reference}".

Mais la documentation omet une Ă©tape qui est essentielle. Pour le tĂ©lĂ©chargement monolithique, tout comme pour le tĂ©lĂ©chargement partiel (chunked), avant de tĂ©lĂ©charger la couche, il est nĂ©cessaire d'effectuer une requĂȘte PATCH :

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Content-Length: {size of chunk}
Content-Type: application/octet-stream
{Données binaires de la couche chunk}".

Sinon, vous ne pourrez pas progresser au-delà du premier point, car au lieu du code de réponse attendu 202, vous recevrez 4xx.

Maintenant, l'algorithme ressemble Ă  :

  • Initialisation
  • Patch de la couche
  • TĂ©lĂ©chargement de la couche
  • TĂ©lĂ©chargement du manifeste
    Les points 2 et 3 seront répétés autant de fois qu'il y a de couches à télécharger.

Pour commencer, nous aurons besoin de n'importe quelle image. Je vais utiliser archlinux:latest

docker pull archlinux

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Maintenant, enregistrons-le localement pour une analyse ultérieure.

docker save c24fe13d37b9 -o savedArch

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Décompressons l'archive obtenue dans le répertoire courant.

tar xvf savedArch

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Comme nous pouvons le voir, chaque couche se trouve dans un dossier séparé. Regardons maintenant la structure du manifeste que nous avons obtenu.

cat manifest.json | json_pp

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Ce n'est pas trÚs riche. Voyons quel manifeste est nécessaire pour le téléchargement, selon documentation.

ImplĂ©mentation des commandes docker pull et docker push sans client Docker via des requĂȘtes HTTP

Évidemment, le manifeste existant ne nous convient pas, nous allons donc crĂ©er le nĂŽtre avec des couches et des configurations.

Nous aurons toujours au moins un fichier de configuration et un tableau de couches. La version du schéma 2 (valide au moment de la rédaction de l'article), le mediaType restera inchangé :

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

AprÚs la création du manifeste de base, il est nécessaire de le remplir avec des données valides. Pour cela, nous utiliserons le modÚle d'objet JSON de la couche :

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

celui-ci sera ajouté au manifeste pour chaque couche.

Ensuite, nous devons connaßtre la taille du fichier de configuration et remplacer les placeholders dans le manifeste par des données réelles.

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

Nous pouvons maintenant initier le processus de tĂ©lĂ©chargement et conserver le uuid, qui doit accompagner toutes les requĂȘtes suivantes.

Le script complet ressemble Ă  ceci :

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

nous pouvons utiliser un script prĂȘt Ă  l'emploi :

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

Mise Ă  jour :
Qu'avons-nous obtenu en conséquence ?
Tout d'abord, des donnĂ©es rĂ©elles pour l'analyse, puisque les tests sont exĂ©cutĂ©s dans blazemeter et les donnĂ©es sur les requĂȘtes du client docker sont trĂšs peu informatives par rapport aux requĂȘtes HTTP pures.

DeuxiÚmement, la transition nous a permis d'augmenter le nombre d'utilisateurs virtuels pour le téléchargement docker d'environ 150 %, tout en obtenant un temps de réponse moyen 20-25 % plus rapide. Pour le téléchargement docker, nous avons pu augmenter le nombre d'utilisateurs de 500 %, le temps de réponse moyen ayant diminué d'environ 60 %.

Merci pour votre attention.

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster