Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Tivemos 2 bolsas de herba, 75 tabletas de mescalina entorno Unix, un repositorio docker e a tarefa de implementar os comandos docker pull e docker push sen un cliente docker.

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

ACTUALIZACIÓN:
Pregunta: para que serve todo isto?
Resposta: probas de carga do produto (NON se usa bash, os scripts ofrécense con fins educativos). Decidiuse non usar o cliente docker para reducir capas adicionais (dentro de límites razoables) e, en consecuencia, emular unha carga superior. Como resultado, elimináronse todos os atrasos do sistema do cliente Docker. Recibimos unha carga relativamente limpa directamente no produto.
O artigo utilizou versións de ferramentas GNU.

Primeiro, imos descubrir o que fan estes comandos.

Entón, para que serve docker pull? Dacordo con documentación:

"Sacar unha imaxe ou un repositorio dun rexistro".

Alí tamén atopamos unha ligazón a comprender imaxes, contedores e controladores de almacenamento.

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

A partir de aquí podemos entender que unha imaxe docker é un conxunto de determinadas capas que conteñen información sobre os últimos cambios na imaxe, que é obviamente o que necesitamos. A continuación miramos API de rexistro.

Di o seguinte:

"Unha "imaxe" é unha combinación dun manifesto JSON e ficheiros de capa individuais. O proceso de extraer unha > imaxe céntrase na recuperación destes dous compoñentes".

Así que o primeiro paso segundo a documentación é "Tirando un manifesto de imaxe".

Por suposto, non o dispararemos, pero necesitamos os datos del. O seguinte é un exemplo de solicitude: GET /v2/{name}/manifests/{reference}

"O nome e o parámetro de referencia identifican a imaxe e son necesarios. A referencia pode incluír unha etiqueta ou un resumo".

O noso repositorio docker está implantado localmente, imos tentar executar a solicitude:

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

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Como resposta, recibimos json do que actualmente só nos interesan as liñas de vida, ou máis ben os seus hash. Recibidos, podemos revisar cada un e executar a seguinte solicitude: "GET /v2/{name}/blobs/{digest}"

"O acceso a unha capa estará bloqueado polo nome do repositorio, pero identifícase de forma única no rexistro mediante un resumo".

dixest neste caso é o hash que recibimos.

Intentando

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ón de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Vexamos que tipo de arquivo recibimos finalmente como primeiro salvavidas.

file firstLayer

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

aqueles. os carrís son arquivos tar, desempaquetándoos na orde adecuada obteremos o contido da imaxe.

Escribamos un pequeno script bash para que todo isto poida ser automatizado

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

Agora podemos executalo cos parámetros desexados e obter o contido da imaxe requirida

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

Parte 2 - Docker push

Isto será un pouco máis complicado.

Comecemos de novo con documentación. Polo tanto, necesitamos descargar cada líder, recoller o manifesto correspondente e descargalo tamén. Parece sinxelo.

Despois de estudar a documentación, podemos dividir o proceso de descarga en varios pasos:

  • Inicialización do proceso - "POST /v2/{repoName}/blobs/uploads/"
  • Cargando unha liña de vida (utilizaremos unha carga monolítica, é dicir, enviamos cada liña de vida na súa totalidade) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Duración do contido: {tamaño da capa}
    Tipo de contido: aplicación/fluxo de octetos
    Capa de datos binarios".
  • Cargando o manifesto - "PUT /v2/{repoName}/manifests/{reference}".

Pero a documentación perde un paso, sen o cal nada funcionará. Para a carga monolítica, así como para a parcial (por fragmentos), antes de cargar o carril, debes realizar unha solicitude de PATCH:

"PARCHE /v2/{repoName}/blobs/uploads/{uuid}
Duración do contido: {tamaño do fragmento}
Tipo de contido: aplicación/fluxo de octetos
{Datos binarios de fragmentos de capa}".

En caso contrario, non poderás ir máis aló do primeiro punto, porque... En lugar do código de resposta esperado 202, recibirá 4xx.

Agora o algoritmo parece:

  • Inicialización
  • Raíl de parche
  • Cargando o pasamáns
  • Cargando o manifesto
    Os puntos 2 e 3, respectivamente, repetiranse tantas veces como sexa necesario cargar o número de liñas.

En primeiro lugar, necesitamos calquera imaxe. Vou usar archlinux:latest

docker pull archlinux

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Agora gardemolo localmente para unha análise posterior

docker save c24fe13d37b9 -o savedArch

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Descomprimir o arquivo resultante no directorio actual

tar xvf savedArch

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Como podes ver, cada liña de vida está nun cartafol separado. Agora vexamos a estrutura do manifesto que recibimos

cat manifest.json | json_pp

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Non moito. Vexamos que manifesto é necesario para cargar, segundo documentación.

Implementación de comandos docker pull e docker push sen un cliente docker mediante solicitudes HTTP

Obviamente, o manifesto existente non nos convén, así que faremos o noso con blackjack e cortesás, liñas de vida e configuracións.

Sempre teremos polo menos un ficheiro de configuración e unha serie de liñas de vida. Versión do esquema 2 (actual no momento da escritura), mediaType permanecerá sen cambios:

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

Despois de crear o manifesto básico, cómpre enchelo con datos válidos. Para iso, usamos o modelo json do obxecto rail:

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

Engadirémolo ao manifesto de cada carril.

A continuación, necesitamos descubrir o tamaño do ficheiro de configuración e substituír os stubs do manifesto por datos reais

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

Agora podes iniciar o proceso de descarga e gardar un uuid, que debería acompañar a todas as solicitudes posteriores.

O script completo é algo así:

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

podemos usar un script preparado:

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

ACTUALIZACIÓN:
Que conseguimos como resultado?
En primeiro lugar, datos reais para a análise, xa que as probas lévanse a cabo en blazemeter e os datos das solicitudes do cliente docker non son moi informativos, a diferenza das solicitudes HTTP puras.

En segundo lugar, a transición permitiunos aumentar o número de usuarios virtuais para a carga do docker nun 150 % e obter un tempo de resposta medio 20-25 % máis rápido. Para a descarga de docker, conseguimos aumentar o número de usuarios nun 500 %, mentres que o tempo medio de resposta diminuíu nun 60 %.

Grazas pola súa atención.

Fonte: www.habr.com

Engadir un comentario