HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

私たちは草の袋 2 つ、メスカリン錠 75 個の UNIX 環境、Docker リポジトリ、および Docker クライアントを使用せずに docker pull コマンドと docker Push コマンドを実装するタスクを用意しました。

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

UPD:
質問: これは一体何のためにあるのでしょうか?
回答: 製品の負荷テスト (bash は使用しません。スクリプトは教育目的で提供されています)。 追加のレイヤーを (合理的な制限内で) 削減し、それに応じてより高い負荷をエミュレートするために Docker クライアントを使用しないことが決定されました。 その結果、Docker クライアントのすべてのシステム遅延が解消されました。 比較的きれいな状態で製品を直接受け取りました。
この記事では、GNU バージョンのツールを使用しました。

まず、これらのコマンドが何を行うかを理解しましょう。

では、docker pull は何に使用されるのでしょうか? によると ドキュメンテーション:

「レジストリからイメージまたはリポジトリをプルします」。

そこには、へのリンクもあります イメージ、コンテナ、ストレージドライバーを理解する.

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

ここから、Docker イメージは、イメージ内の最新の変更に関する情報を含む特定のレイヤーのセットであることが理解できます。これは明らかに必要なものです。 次に見ていきます レジストリAPI.

次のように書かれています。

「「イメージ」は、JSON マニフェストと個々のレイヤー ファイルの組み合わせです。イメージを取得するプロセスは、これら XNUMX つのコンポーネントの取得を中心としています。」

したがって、ドキュメントによると、最初のステップは次のとおりです。イメージマニフェストの取得"。

もちろん撮影はしませんが、データは必要です。 以下はリクエストの例です。 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"

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

応答として、JSON を受け取ります。この JSON から、現時点ではライフライン、つまりライフラインのハッシュのみに関心があります。 それらを受信したら、それぞれを調べて次のリクエストを実行します: "GET /v2/{name}/blobs/{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

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

最終的に最初のライフラインとしてどのようなファイルを受け取ったのか見てみましょう。

file firstLayer

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

それらの。 Rails は tar アーカイブであり、適切な順序で解凍すると、イメージの内容が取得されます。

これらすべてを自動化できるように、小さな 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}
    Content-Length: {レイヤーのサイズ}
    Content-Type:application / octet-stream
    レイヤーバイナリデータ」。
  • マニフェストをロードしています - 「PUT /v2/{repoName}/manifests/{reference}」。

しかし、ドキュメントには XNUMX つのステップが欠けており、それがなければ何も機能しません。 モノリシックロードの場合、部分的 (チャンク化された) ロードの場合と同様に、レールをロードする前に PATCH リクエストを実行する必要があります。

「PATCH /v2/{repoName}/blobs/uploads/{uuid}」
Content-Length: {チャンクのサイズ}
Content-Type:application / octet-stream
{レイヤーチャンクバイナリデータ}」。

そうしないと、最初のポイントから先に進むことができなくなります。なぜなら... 予期された応答コード 202 の代わりに、4xx を受け取ります。

アルゴリズムは次のようになります。

  • 初期化
  • パッチレール
  • 手すりの読み込み
  • マニフェストのロード
    ポイント 2 と 3 はそれぞれ、ロードする必要がある行の数だけ繰り返されます。

まず、任意の画像が必要です。 Archlinux:latestを使用します

docker pull archlinux

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

さらに分析するためにローカルに保存しましょう

docker save c24fe13d37b9 -o savedArch

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

結果のアーカイブを現在のディレクトリに解凍します。

tar xvf savedArch

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

ご覧のとおり、各ライフラインは個別のフォルダーにあります。 次に、受け取ったマニフェストの構造を見てみましょう

cat manifest.json | json_pp

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

あまりない。 に従って、どのマニフェストをロードする必要があるかを見てみましょう ドキュメンテーション.

HTTP リクエストを使用して Docker クライアントを使用せずに docker pull および docker Push コマンドを実装する

明らかに、既存のマニフェストは私たちには合わないので、ブラックジャックと遊女、ライフラインと構成を使用して独自のマニフェストを作成します。

常に少なくとも 2 つの構成ファイルと一連のライフラインが存在します。 スキーム バージョン XNUMX (執筆時点)、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 テンプレートを使用します。

{
         "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 で実行され、純粋な HTTP リクエストとは異なり、Docker クライアント リクエストのデータはあまり有益ではありません。

次に、移行により、Docker アップロードの仮想ユーザー数が約 150% 増加し、平均応答時間が 20 ~ 25% 高速化されました。 Docker のダウンロードでは、ユーザー数を 500% 増やすことができましたが、平均応答時間は約 60% 減少しました。

ご清聴ありがとうございました。

出所: habr.com

コメントを追加します