Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Chúng tôi có 2 túi cỏ, 75 môi trường unix máy tính bảng mescaline, một kho lưu trữ docker và nhiệm vụ thực hiện các lệnh kéo docker và đẩy docker mà không cần máy khách docker.

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

UPD:
Câu hỏi: Tất cả những điều này là để làm gì?
Trả lời: Kiểm tra tải sản phẩm (KHÔNG sử dụng bash, các tập lệnh được cung cấp cho mục đích giáo dục). Người ta đã quyết định không sử dụng máy khách docker để giảm các lớp bổ sung (trong giới hạn hợp lý) và theo đó, mô phỏng tải cao hơn. Kết quả là tất cả độ trễ hệ thống của máy khách Docker đã bị loại bỏ. Chúng tôi đã nhận được tải tương đối sạch trực tiếp trên sản phẩm.
Bài viết đã sử dụng các phiên bản GNU của công cụ.

Đầu tiên, hãy tìm hiểu xem những lệnh này làm gì.

Vậy docker pull dùng để làm gì? Dựa theo tài liệu:

"Kéo hình ảnh hoặc kho lưu trữ từ sổ đăng ký".

Ở đó chúng tôi cũng tìm thấy một liên kết đến hiểu hình ảnh, vùng chứa và trình điều khiển lưu trữ.

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Từ đây chúng ta có thể hiểu rằng hình ảnh docker là một tập hợp các lớp nhất định chứa thông tin về những thay đổi mới nhất trong hình ảnh, đây rõ ràng là thứ chúng ta cần. Tiếp theo chúng ta xem xét API đăng ký.

Nó nói như sau:

"Một “hình ảnh” là sự kết hợp giữa một tệp kê khai JSON và các tệp lớp riêng lẻ. Quá trình kéo một > hình ảnh xoay quanh việc truy xuất hai thành phần này."

Vì vậy, bước đầu tiên theo tài liệu là “Kéo một bản kê khai hình ảnh".

Tất nhiên, chúng tôi sẽ không bắn nó, nhưng chúng tôi cần dữ liệu từ nó. Sau đây là một yêu cầu ví dụ: GET /v2/{name}/manifests/{reference}

"Tên và tham số tham chiếu xác định hình ảnh và được yêu cầu. Tham chiếu có thể bao gồm thẻ hoặc thông báo."

Kho lưu trữ docker của chúng tôi được triển khai cục bộ, hãy thử thực hiện yêu cầu:

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

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Đáp lại, chúng tôi nhận được json mà từ đó chúng tôi hiện chỉ quan tâm đến dây cứu sinh, hay đúng hơn là hàm băm của chúng. Sau khi nhận được chúng, chúng ta có thể đi qua từng cái và thực hiện yêu cầu sau: "GET /v2/{name}/blobs/{digest}"

“Quyền truy cập vào một lớp sẽ được kiểm soát theo tên của kho lưu trữ nhưng được xác định duy nhất trong sổ đăng ký theo thông báo.”

thông báo trong trường hợp này là hàm băm mà chúng tôi nhận được.

Cố gắng

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

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Hãy xem cuối cùng chúng tôi đã nhận được loại tập tin nào làm cứu cánh đầu tiên.

file firstLayer

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

những thứ kia. Rails là kho lưu trữ tar, giải nén chúng theo thứ tự thích hợp chúng ta sẽ có được nội dung của hình ảnh.

Hãy viết một tập lệnh bash nhỏ để tất cả điều này có thể được tự động hóa

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

Bây giờ chúng ta có thể chạy nó với các tham số mong muốn và lấy nội dung của hình ảnh được yêu cầu

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

Phần 2 - đẩy docker

Điều này sẽ phức tạp hơn một chút.

Hãy bắt đầu lại với tài liệu. Vì vậy, chúng tôi cần tải xuống từng nhà lãnh đạo, thu thập bảng kê khai tương ứng và tải xuống. Nó có vẻ đơn giản.

Sau khi nghiên cứu tài liệu, chúng ta có thể chia quá trình tải xuống thành nhiều bước:

  • Khởi tạo quy trình - "POST /v2/{repoName}/blobs/uploads/"
  • Tải lên đường dây cứu sinh (chúng tôi sẽ sử dụng tính năng tải lên nguyên khối, tức là chúng tôi gửi toàn bộ đường dây cứu sinh) - "PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
    Độ dài nội dung: {kích thước của lớp}
    Content-Type: application / octet-stream
    Dữ liệu nhị phân lớp".
  • Đang tải tệp kê khai - "PUT /v2/{repoName}/manifests/{reference}".

Nhưng tài liệu này thiếu một bước, nếu không có bước này thì sẽ không có gì hoạt động được. Để tải nguyên khối, cũng như tải một phần (phân đoạn), trước khi tải đường ray, bạn phải thực hiện yêu cầu PATCH:

"VÁ /v2/{repoName}/blobs/uploads/{uuid}
Độ dài nội dung: {size of chunk}
Content-Type: application / octet-stream
{Dữ liệu nhị phân phân lớp}".

Nếu không, bạn sẽ không thể vượt qua điểm đầu tiên, bởi vì... Thay vì mã phản hồi dự kiến ​​là 202, bạn sẽ nhận được 4xx.

Bây giờ thuật toán trông như sau:

  • Khởi tạo
  • Đường ray vá
  • Đang tải lan can
  • Đang tải bảng kê khai
    Điểm 2 và 3 tương ứng sẽ được lặp lại nhiều lần tùy theo số dòng cần tải.

Đầu tiên, chúng ta cần bất kỳ hình ảnh nào. Tôi sẽ sử dụng Archlinux:mới nhất

docker pull archlinux

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Bây giờ hãy lưu nó cục bộ để phân tích thêm

docker save c24fe13d37b9 -o savedArch

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Giải nén kho lưu trữ kết quả vào thư mục hiện tại

tar xvf savedArch

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Như bạn có thể thấy, mỗi dây cứu sinh nằm trong một thư mục riêng. Bây giờ hãy xem cấu trúc của bảng kê khai mà chúng tôi nhận được

cat manifest.json | json_pp

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Không nhiều. Hãy xem bảng kê khai nào cần được tải, theo tài liệu.

Triển khai các lệnh kéo docker và đẩy docker mà không cần máy khách docker bằng cách sử dụng các yêu cầu HTTP

Rõ ràng, bản tuyên ngôn hiện tại không phù hợp với chúng tôi, vì vậy chúng tôi sẽ tự tạo ra bản tuyên ngôn của riêng mình với blackjack và gái điếm, dây an toàn và cấu hình.

Chúng tôi sẽ luôn có ít nhất một tệp cấu hình và một loạt các dây cứu sinh. Lược đồ phiên bản 2 (hiện tại tại thời điểm viết bài), mediaType sẽ không thay đổi:

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

Sau khi tạo bảng kê khai cơ bản, bạn cần điền dữ liệu hợp lệ vào đó. Để làm điều này, chúng tôi sử dụng mẫu json của đối tượng đường ray:

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

Chúng tôi sẽ thêm nó vào bảng kê khai cho từng đường ray.

Tiếp theo, chúng ta cần tìm ra kích thước của tệp cấu hình và thay thế các phần sơ khai trong bảng kê khai bằng dữ liệu thực

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

Bây giờ bạn có thể bắt đầu quá trình tải xuống và lưu cho mình một uuid, sẽ đi kèm với tất cả các yêu cầu tiếp theo.

Kịch bản hoàn chỉnh trông giống như thế này:

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

chúng ta có thể sử dụng tập lệnh tạo sẵn:

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

UPD:
Kết quả là chúng ta đã nhận được gì?
Thứ nhất, dữ liệu thực để phân tích, vì các thử nghiệm được chạy trong blazemeter và dữ liệu về các yêu cầu của máy khách docker không có nhiều thông tin, không giống như các yêu cầu HTTP thuần túy.

Thứ hai, quá trình chuyển đổi cho phép chúng tôi tăng số lượng người dùng ảo để tải lên docker lên khoảng 150% và nhận được thời gian phản hồi trung bình nhanh hơn 20-25%. Đối với việc tải xuống docker, chúng tôi đã cố gắng tăng số lượng người dùng lên 500%, trong khi thời gian phản hồi trung bình giảm khoảng 60%.

Cảm ơn bạn đã quan tâm của bạn.

Nguồn: www.habr.com