Как загрузить образ докера с помощью HTTP API, используя учетные данные концентратора докеров
Итак, у меня есть частный репозиторий в Docker Hub, и я пытаюсь загрузить изображение (blobs) вручную с помощью HTTP API.
Теперь есть некоторые проблемы
- Я пробовал https://hub.docker.com/support/doc/how-do-i-authenticate-with-the-v2-api , и этот скрипт работает, и я вижу свои теги.
Но в Docker HUB API нет API для получения списка больших двоичных объектов из тега, а затем для его загрузки.
Есть апи реестра докеров, но там мой логин-пароль не работает. Что делать?
2 ответа
Для изображения вы сначала извлекаете манифест и анализируете его для списка больших двоичных объектов, которые вам нужно извлечь. Всем этим API нужны одни и те же заголовки авторизации, используемые для перечисления тегов. Я собираюсь использовать regctl из моего проекта regclient для запросов к локальному реестру, но вы также можете использовать curl для Hub, как показано ниже.
$ regctl tag ls localhost:5000/library/alpine
3
3-bkup-20210904
3.10
3.11
3.12
3.13
3.14
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
latest
$ regctl manifest get localhost:5000/library/alpine:latest --format body | jq .
{
"manifests": [
{
"digest": "sha256:14b55f5bb845c7b810283290ce057f175de87838be56f49060e941580032c60c",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "linux"
},
"size": 528
},
{
"digest": "sha256:40f396779ba29da16f29f780963bd4ad5b7719e3eb5dec04516d583713256aa8",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v6"
},
"size": 528
},
{
"digest": "sha256:392d9d85dff31e34d756be33579f05ef493cb1b0edccc36a11b3295365553bfd",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
},
"size": 528
},
{
"digest": "sha256:4fb53f12d2ec18199f16d7c305a12c54cda68cc622484bfc3b7346a44d5024ac",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
},
"size": 528
},
{
"digest": "sha256:e8d9cf28250078f08e890a3466efbefda68a8feac03cc4076d3ada3397370d6e",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "386",
"os": "linux"
},
"size": 528
},
{
"digest": "sha256:d860569a59af627dafee0b0f2b8069e31b07fbdaebe552904dbaec28047ccf64",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "ppc64le",
"os": "linux"
},
"size": 528
},
{
"digest": "sha256:6640b198347e5bf1e9a9dc5fc864e927154275dc31f3d26193b74350a5c94c9f",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "s390x",
"os": "linux"
},
"size": 528
}
],
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"schemaVersion": 2
}
$ regctl manifest get localhost:5000/library/alpine@sha256:14b55f5bb845c7b810283290ce057f175de87838be56f49060e941580032c60c --format body | jq .
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1472,
"digest": "sha256:e9adb5357e84d853cc3eb08cd4d3f9bd6cebdb8a67f0415cc884be7b0202416d"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 2812636,
"digest": "sha256:3d243047344378e9b7136d552d48feb7ea8b6fe14ce0990e0cc011d5e369626a"
}
]
}
$ regctl blob get localhost:5000/library/alpine sha256:e9adb5357e84d853cc3eb08cd4d3f9bd6cebdb8a67f0415cc884be7b0202416d | jq .
{
"architecture": "amd64",
"config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh"
],
"Image": "sha256:e211ac20c5c7aaa4ed30d5553654d4679082ec48efcb4d164bac6d50d62653fd",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"container": "b6ba94212561a8075e1d324fb050db160e25035ffcfbbe5b410e411e2b7000e2",
"container_config": {
"Hostname": "b6ba94212561",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/sh\"]"
],
"Image": "sha256:e211ac20c5c7aaa4ed30d5553654d4679082ec48efcb4d164bac6d50d62653fd",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"created": "2022-03-17T04:01:59.188838147Z",
"docker_version": "20.10.12",
"history": [
{
"created": "2022-03-17T04:01:58.883733237Z",
"created_by": "/bin/sh -c #(nop) ADD file:cf4b631a115c2bbfbd81cad2d3041bceb64a8136aac92ba8a63b6c51d60af764 in / "
},
{
"created": "2022-03-17T04:01:59.188838147Z",
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
"empty_layer": true
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:5e03d8cae8773cb694fff1d55da34a40d23c2349087ed15ce68476395d33753c"
]
}
}
$ regctl blob get localhost:5000/library/alpine sha256:3d243047344378e9b7136d552d48feb7ea8b6fe14ce0990e0cc011d5e369626a | tar -tvzf - | head
drwxr-xr-x 0/0 0 2022-03-16 16:15 bin/
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/arch -> /bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/ash -> /bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/base64 -> /bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/bbconfig -> /bin/busybox
-rwxr-xr-x 0/0 824984 2022-02-02 13:21 bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/cat -> /bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/chgrp -> /bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/chmod -> /bin/busybox
lrwxrwxrwx 0/0 0 2022-03-16 16:15 bin/chown -> /bin/busybox
...
Несколько примеров, если вы попытаетесь сделать это с помощью curl:
Получите токен (конкретный для Docker Hub, каждый реестр может иметь разные методы аутентификации и серверы):
token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
| jq -r '.token')
Теги:
curl -H "Authorization: Bearer $token" \
-s "https://registry-1.docker.io/v2/${repo}/tags/list" | jq .
Манифесты:
api="application/vnd.docker.distribution.manifest.v2+json"
apil="application/vnd.docker.distribution.manifest.list.v2+json"
curl -H "Accept: ${api}" -H "Accept: ${apil}" \
-H "Authorization: Bearer $token" \
-s "https://registry-1.docker.io/v2/${repo}/manifests/${sha:-$tag}" | jq .
И капли:
curl -H "Authorization: Bearer $token" \
-s -L -o - "https://registry-1.docker.io/v2/${repo}/blobs/${digest}"
Если вы хотите экспортировать все, чтобы иметь возможность позже импортировать,
regctl image export
преобразует изображение в смолу манифестов и больших двоичных объектов. Это выводится в формате OCI Layout, и если вы извлечете только один манифест изображения (а не мультиплатформенный манифест), он также будет включать необходимые файлы для
docker load
импортировать смолу.
Есть апи реестра докеров, но там мой логин-пароль не работает. Что делать?
Вероятно, это зависит от того, как вы их снабжаете. Я не пробовал это с частным репозиторием в Docker Hub, но вот скрипт, который загружает общедоступные образы из Docker Hub и образы из частных реестров:
#!/usr/bin/env bash
set -eu
image=$1
creds=${2-}
# https://github.com/moby/moby/blob/v20.10.18/vendor/github.com/docker/distribution/reference/normalize.go#L29-L57
# https://github.com/moby/moby/blob/v20.10.18/vendor/github.com/docker/distribution/reference/normalize.go#L88-L105
registry=${image%%/*}
if [ "$registry" = "$image" ] \
|| { [ "`expr index "$registry" .:`" = 0 ] && [ "$registry" != localhost ]; }; then
registry=docker.io
else
image=${image#*/}
fi
if [ "$registry" = docker.io ] && [ "`expr index "$image" /`" = 0 ]; then
image=library/$image
fi
if [ "`expr index "$image" :`" = 0 ]; then
tag=latest
else
tag=${image#*:}
image=${image%:*}
fi
if [ "$registry" = docker.io ]; then
registry=https://registry-1.docker.io
elif ! [[ "$registry" =~ ^localhost(:[0-9]+)$ ]]; then
registry=https://$registry
fi
r=`curl -sS "$registry/v2/" \
-o /dev/null \
-w '%{http_code}:%header{www-authenticate}'`
http_code=`echo "$r" | cut -d: -f1`
curl_args=(-sS -H 'Accept: application/vnd.docker.distribution.manifest.v2+json')
if [ "$http_code" = 401 ]; then
if [ "$registry" = https://registry-1.docker.io ]; then
header_www_authenticate=`echo "$r" | cut -d: -f2-`
header_www_authenticate=`echo "$header_www_authenticate" | sed -E 's/^Bearer +//'`
split_into_lines() {
sed -Ee :1 -e 's/^(([^",]|"([^"]|\")*")*),/\1\n/; t1'
}
header_www_authenticate=`echo "$header_www_authenticate" | split_into_lines`
extract_value() {
sed -E 's/^[^=]+="(([^"]|\")*)"$/\1/; s/\\(.)/\1/g'
}
realm=$(echo "$header_www_authenticate" | grep '^realm=' | extract_value)
service=$(echo "$header_www_authenticate" | grep '^service=' | extract_value)
scope=repository:$image:pull
token=`curl -sS "$realm?service=$service&scope=$scope" | jq -r .token`
curl_args+=(-H "Authorization: Bearer $token")
else
curl_args+=(-u "$creds")
fi
fi
manifest=`curl "${curl_args[@]}" "$registry/v2/$image/manifests/$tag"`
config_digest=`echo "$manifest" | jq -r .config.digest`
config=`curl "${curl_args[@]}" -L "$registry/v2/$image/blobs/$config_digest"`
layers=`echo "$manifest" | jq -r '.layers[] | .digest'`
echo "$layers" | \
while IFS= read -r digest; do
curl "${curl_args[@]}" -L "$registry/v2/$image/blobs/$digest" | wc -c
done
Использование:
$ ./download-image.sh hello-world
$ ./download-image.sh library/hello-world
$ ./download-image.sh docker.io/library/hello-world
$ ./download-image.sh myregistry.com/hello-world testuser:testpassword
$ ./download-image.sh localhost:5000/hello-world
Или, если быть более точным, он извлекает конфигурацию изображения и слои. Дело в том, что ты собираешься с ними делать? Вы можете попробовать создать tar-архив, похожий на созданный . Который является то, чтов основном делает. Но в идеале для этого надо знать, что именно и делать.
Чтобы дать вам несколько ссылок на исходный код,docker pull
(серверная часть) более или менее начинается здесь:
https://github.com/moby/moby/blob/v20.10.18/api/server/router/image/image.go#L37
https://github.com/moby/moby/blob/v20.10.18/api/server/router/image/image_routes.go#L78
https://github.com/moby/moby/blob/v20.10.18/daemon/images/image_pull.go#L54
https://github.com/moby/moby/blob/v20.10.18/daemon/images/image_pull.go#L130
https://github.com/moby/moby/blob/v20.10.18/distribution/pull.go#L52
Большая часть соответствующего кода находится вdistribution/pull_v2.go
(высокоуровневая часть).
Код, выполняющий HTTP-запросы, находится вvendor/github.com/docker/distribution/registry/client/auth/session.go
иvendor/github.com/docker/distribution/registry/client/repository.go
.
docker save
:
https://github.com/moby/moby/blob/v20.10.18/api/server/router/image/image.go#L31
https://github.com/moby/moby/blob/v20.10.18/api/server/router/image/image_routes.go#L160
https://github.com/moby/moby/blob/v20.10.18/daemon/images/image_exporter.go#L16
https://github.com/moby/moby/blob/v20.10.18/изображение/tarexport/save.go#L187
Большая часть кода находится вimage/tarexport/save.go
.
Но видимо разработчик этим не заморачивался, и это (docker-drag
) вроде работает.
В случае частных репозиториев Docker Hub вам, вероятно, потребуется добавить-u user:password
к$realm?service=$service&scope=$scope
запрос.
Пара замечаний по использованию API:
- Реестр представляет собой набор репозиториев. Подумайте о Гитхабе. Обычно имена репозиториев имеют форму
user/name
(напримерnginxproxy/nginx-proxy
). А вот с официальными репозиториями (которые все на самом деле начинаются сlibrary/
) первый сегмент может быть опущен (library/ruby
->ruby
). Также может быть только один сегмент (например, в частных реестрах), и иногда только первая часть называется репозиторием (пример ). Или второй. - Репозиторий — это набор изображений. Некоторые из них помечены. Изображения без тегов обычно появляются, когда вы отправляете новую версию изображения с тем же тегом.
- Старая, но, может быть, несколько актуальная актуальная статья (по крайней мере, начало).
- URL-адрес реестра Docker Hub см. в этом ответе .
- Не ожидайте, что все в спецификации будет работать. Например, Docker Hub не реализует
/v2/_catalog
маршрут.