我们有 2 袋草、75 片麦斯卡林片剂、unix 环境、一个 docker 存储库,以及在没有 docker 客户端的情况下实现 docker pull 和 docker push 命令的任务。
UPD:
问:这一切是为了什么?
答案:产品的负载测试(不使用 bash,提供的脚本用于教育目的)。 决定不使用 docker 客户端来减少额外的层(在合理的限制内),并相应地模拟更高的负载。 这样一来,Docker 客户端的所有系统延迟都被消除了。 我们直接在产品上收到了相对干净的负载。
本文使用了 GNU 版本的工具。
首先,让我们弄清楚这些命令的作用。
那么 docker pull 是用来做什么的呢? 根据
“从注册表中提取图像或存储库”。
在那里我们还找到了一个链接
从这里我们可以理解,docker镜像是一组某些层,其中包含镜像最新变化的信息,这显然是我们所需要的。 接下来我们看看
它说如下:
“‘镜像’是 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"
作为响应,我们收到 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
让我们看看我们最终收到什么样的文件作为第一条生命线。
file firstLayer
那些。 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 部分 - docker 推送
这会稍微复杂一些。
让我们重新开始
研究完文档后,我们可以将下载过程分为几个步骤:
- 进程初始化 - “POST /v2/{repoName}/blob/uploads/”
- 上传生命线(我们将使用整体上传,即我们完整地发送每个生命线)-“PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
内容长度:{层的大小}
Content-Type:application / octet-stream
层二进制数据”。 - 加载清单 - “PUT /v2/{repoName}/manifests/{reference}”。
但该文档遗漏了一步,没有这一步骤,一切都将不起作用。 对于整体加载以及部分(分块)加载,在加载导轨之前,您必须执行 PATCH 请求:
“补丁/v2/{repoName}/blob/uploads/{uuid}
内容长度:{块的大小}
Content-Type:application / octet-stream
{层块二进制数据}”。
否则,你将无法超越第一点,因为…… 您将收到 202xx,而不是预期的响应代码 4。
现在算法看起来像:
- 初始化
- 补轨
- 装载扶手
- 加载清单
第 2 点和第 3 点将分别根据需要加载的行数重复多次。
首先,我们需要任何图像。 我将使用 archlinux:latest
docker pull archlinux
现在我们将其保存在本地以供进一步分析
docker save c24fe13d37b9 -o savedArch
将生成的存档解压到当前目录
tar xvf savedArch
如您所见,每条生命线都位于单独的文件夹中。 现在让我们看看我们收到的清单的结构
cat manifest.json | json_pp
不多。 让我们看看需要加载什么清单,根据
显然,现有的宣言不适合我们,所以我们将用二十一点和妓女、生命线和配置来制作我们自己的宣言。
我们将始终拥有至少一个配置文件和一组生命线。 方案版本 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
创建基本清单后,您需要填充有效数据。 为此,我们使用 Rail 对象的 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,该 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 upload 的虚拟用户数量增加约 150%,并将平均响应时间加快 20-25%。 对于 docker download,我们成功地将用户数量增加了 500%,而平均响应时间减少了约 60%。
感谢您的关注。
来源: habr.com