اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

ما 2 کیسه چمن، 75 قرص مسکالین محیط یونیکس، یک مخزن docker و وظیفه اجرای دستورات docker pull و docker push بدون کلاینت داکر داشتیم.

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

UPD:
سوال: این همه برای چیست؟
پاسخ: تست بارگذاری محصول (بدون استفاده از bash، اسکریپت ها برای اهداف آموزشی ارائه شده اند). تصمیم گرفته شد که برای کاهش لایه های اضافی (در محدوده معقول) و بر این اساس، بارگذاری بالاتر از مشتری docker استفاده نشود. در نتیجه، تمام تاخیرهای سیستم مشتری Docker حذف شد. ما بار نسبتاً تمیزی را مستقیماً روی محصول دریافت کردیم.
مقاله از نسخه‌های گنو ابزار استفاده می‌کرد.

ابتدا بیایید بفهمیم که این دستورات چه کاری انجام می دهند.

بنابراین Docker pull برای چه چیزی استفاده می شود؟ مطابق با مستندات:

"یک تصویر یا یک مخزن را از یک رجیستری بردارید".

در آنجا ما همچنین پیوندی به آن پیدا می کنیم درک تصاویر، کانتینرها و درایورهای ذخیره سازی.

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

از اینجا می توانیم بفهمیم که یک تصویر داکر مجموعه ای از لایه های خاصی است که حاوی اطلاعاتی درباره آخرین تغییرات در تصویر است، که بدیهی است که ما به آن نیاز داریم. در ادامه نگاه می کنیم رجیستری API.

در ادامه می گوید:

"تصویر" ترکیبی از یک مانیفست JSON و فایل‌های لایه جداگانه است. فرآیند کشیدن یک > تصویر حول بازیابی این دو جزء متمرکز می‌شود."

بنابراین اولین قدم طبق مستندات این استکشیدن مانیفست تصویر".

البته، ما آن را شلیک نمی کنیم، اما به داده های آن نیاز داریم. در زیر یک نمونه درخواست است: GET /v2/{name}/manifests/{reference}

"نام و پارامتر مرجع تصویر را مشخص می کند و لازم است. مرجع ممکن است شامل یک برچسب یا خلاصه باشد."

مخزن docker ما به صورت محلی مستقر شده است، بیایید سعی کنیم درخواست را اجرا کنیم:

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

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

در پاسخ، ما json را دریافت می کنیم که در حال حاضر فقط به خطوط زندگی یا به عبارت بهتر هش آنها علاقه مندیم. پس از دریافت آنها، می توانیم هر یک را مرور کنیم و درخواست زیر را اجرا کنیم: "GET /v2/{name}/blobs/{digest}"

"دسترسی به یک لایه با نام مخزن بسته می شود، اما به طور منحصر به فرد در رجیستری توسط خلاصه شناسایی می شود."

digest در این مورد هش است که ما دریافت کردیم.

تلاش کردن

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

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

بیایید ببینیم در نهایت چه نوع فایلی را به عنوان اولین راه نجات دریافت کردیم.

file firstLayer

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

آن ها لایف لاین ها آرشیوهای تار هستند که با بازکردن آنها به ترتیب مناسب، محتوای تصویر را دریافت می کنیم.

بیایید یک اسکریپت bash کوچک بنویسیم تا همه اینها خودکار شوند

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

حال می توانیم آن را با پارامترهای دلخواه اجرا کنیم و محتویات تصویر مورد نیاز را دریافت کنیم

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

قسمت 2 - فشار داکر

این کمی پیچیده تر خواهد بود.

بیایید دوباره شروع کنیم با مستندات. بنابراین ما باید هر رهبر را دانلود کنیم، مانیفست مربوطه را جمع آوری کنیم و آن را نیز دانلود کنیم. ساده به نظر می رسد.

پس از مطالعه مستندات، می توانیم فرآیند دانلود را به چند مرحله تقسیم کنیم:

  • مقداردهی اولیه فرآیند - "POST /v2/{repoName}/blobs/uploads/"
  • بارگذاری یک طناب نجات (ما از یک آپلود یکپارچه استفاده خواهیم کرد، یعنی هر خط نجات را به طور کامل ارسال می کنیم) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    طول محتوا: {اندازه لایه}
    نوع محتوا: برنامه / جریان هشتگانه
    داده های باینری لایه".
  • در حال بارگیری مانیفست - "PUT /v2/{repoName}/manifests/{reference}".

اما مستندات یک مرحله را از دست می دهد، بدون آن هیچ چیز کار نخواهد کرد. برای بارگذاری یکپارچه، و همچنین برای بارگیری جزئی (قطع شده)، قبل از بارگیری ریل، باید یک درخواست PATCH را انجام دهید:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
طول محتوا: {اندازه تکه}
نوع محتوا: برنامه / جریان هشتگانه
{Layer Chunk Binary Data}".

در غیر این صورت نمی توانید از نقطه اول فراتر بروید، زیرا ... به جای کد پاسخ مورد انتظار 202، 4xx دریافت خواهید کرد.

اکنون الگوریتم به صورت زیر است:

  • مقداردهی اولیه
  • پچ ریل
  • در حال بارگیری نرده
  • در حال بارگیری مانیفست
    نقاط 2 و 3 به ترتیب به تعداد خطوطی که باید بارگذاری شوند، تکرار خواهند شد.

ابتدا به هر تصویری نیاز داریم. من از archlinux:latest استفاده خواهم کرد

docker pull archlinux

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

اکنون اجازه دهید آن را به صورت محلی برای تجزیه و تحلیل بیشتر ذخیره کنیم

docker save c24fe13d37b9 -o savedArch

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

آرشیو به دست آمده را در دایرکتوری فعلی باز کنید

tar xvf savedArch

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

همانطور که می بینید، هر Lifeline در یک پوشه جداگانه قرار دارد. حال بیایید به ساختار مانیفست دریافتی خود نگاه کنیم

cat manifest.json | json_pp

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

زیاد نیست. بیایید ببینیم بر اساس چه چیزی برای بارگیری نیاز است مستندات.

اجرای دستورات docker pull و docker push بدون مشتری docker با استفاده از درخواست های HTTP

بدیهی است که مانیفست موجود برای ما مناسب نیست، بنابراین ما مانیفست خودمان را با بلاک‌جک و کورتیزان، لایف لاین‌ها و تنظیمات می‌سازیم.

ما همیشه حداقل یک فایل پیکربندی و یک آرایه از خطوط حیاتی خواهیم داشت. طرح نسخه 2 (در زمان نگارش فعلی)، mediaType بدون تغییر باقی خواهد ماند:

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

پس از ایجاد مانیفست اصلی، باید آن را با داده های معتبر پر کنید. برای این کار از قالب json شی rail استفاده می کنیم:

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

ما آن را برای هر ریل به مانیفست اضافه می کنیم.

در مرحله بعد، باید اندازه فایل کانفیگ را پیدا کنیم و خرده‌های مانیفست را با داده‌های واقعی جایگزین کنیم

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

اکنون می توانید فرآیند دانلود را آغاز کرده و یک uuid برای خود ذخیره کنید، که باید تمام درخواست های بعدی را همراهی کند.

اسکریپت کامل چیزی شبیه به این است:

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

می توانیم از یک اسکریپت آماده استفاده کنیم:

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

UPD:
در نتیجه چه چیزی به دست آوردیم؟
اولاً، داده‌های واقعی برای تجزیه و تحلیل، زیرا آزمایش‌ها در blazemeter اجرا می‌شوند و داده‌های مربوط به درخواست‌های مشتری docker، برخلاف درخواست‌های HTTP خالص، چندان آموزنده نیستند.

ثانیاً، این انتقال به ما اجازه داد تا تعداد کاربران مجازی برای بارگذاری داکر را حدود 150 درصد افزایش دهیم و میانگین زمان پاسخگویی را 20 تا 25 درصد سریع‌تر دریافت کنیم. برای دانلود داکر، ما موفق شدیم تعداد کاربران را 500% افزایش دهیم در حالی که میانگین زمان پاسخگویی حدود 60% کاهش یافت.

با تشکر از توجه شما.

منبع: www.habr.com

اضافه کردن نظر