Mieliśmy 2 worki trawy, 75 tabletek meskaliny, środowisko uniksowe, repozytorium dokerów i zadanie implementacji poleceń docker pull i docker push bez klienta dockera.
UPD:
Pytanie: Po co to wszystko?
Odpowiedź: Testowanie obciążeniowe produktu (NIE przy użyciu basha, skrypty służą celom edukacyjnym). Zdecydowano się nie używać klienta dokowanego do redukcji dodatkowych warstw (w rozsądnych granicach) i odpowiednio emulować większe obciążenie. W efekcie usunięto wszystkie opóźnienia systemowe klienta Docker. Otrzymaliśmy stosunkowo czysty ładunek bezpośrednio na produkt.
W artykule wykorzystano wersje narzędzi GNU.
Najpierw zastanówmy się, co robią te polecenia.
Do czego więc służy docker pull? Według
„Wyciągnij obraz lub repozytorium z rejestru”.
Znajdujemy tam również link do
Stąd możemy zrozumieć, że obraz okna dokowanego to zestaw pewnych warstw, które zawierają informacje o najnowszych zmianach w obrazie, a to jest oczywiście to, czego potrzebujemy. Dalej patrzymy
Jest napisane co następuje:
„„ Obraz ” to połączenie manifestu JSON i plików poszczególnych warstw. Proces wyciągania > obrazu koncentruje się na pobraniu tych dwóch komponentów.
Zatem pierwszym krokiem zgodnie z dokumentacją jest „Wyciąganie manifestu obrazu".
Oczywiście nie będziemy go kręcić, ale potrzebujemy z niego danych. Poniżej znajduje się przykładowe żądanie: GET /v2/{name}/manifests/{reference}
„Nazwa i parametry odniesienia identyfikują obraz i są wymagane. Odniesienie może zawierać znacznik lub skrót.”
Nasze repozytorium dokerów jest wdrożone lokalnie, spróbujmy wykonać żądanie:
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/manifests/1.1.1" -H "header_if_needed"
W odpowiedzi otrzymujemy json, z którego aktualnie interesują nas tylko linie życia, a raczej ich skróty. Po ich otrzymaniu możemy przejrzeć każdy z nich i wykonać następujące żądanie: „GET /v2/{name}/blobs/{digest}”
„Dostęp do warstwy będzie ograniczony nazwą repozytorium, ale będzie jednoznacznie identyfikowany w rejestrze poprzez skrót”.
Digest w tym przypadku jest skrótem, który otrzymaliśmy.
Spróbujmy
curl -s -X GET "http://localhost:8081/link/to/docker/registry/v2/centos-11-10/blobs/sha256:f972d139738dfcd1519fd2461815651336ee25a8b54c358834c50af094bb262f" -H "header_if_needed" --output firstLayer
Zobaczmy, jaki plik w końcu otrzymaliśmy jako pierwsze ratunek.
file firstLayer
te. szyny to archiwa tar, rozpakowując je w odpowiedniej kolejności otrzymamy zawartość obrazu.
Napiszmy mały skrypt basha, aby wszystko to można było zautomatyzować
#!/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
Teraz możemy uruchomić go z żądanymi parametrami i uzyskać zawartość wymaganego obrazu
./script.sh dirName “http://localhost:8081/link/to/docker/registry” myAwesomeImage 1.0
Część 2 – wypychanie dokera
To będzie trochę bardziej skomplikowane.
Zacznijmy od nowa
Po przestudiowaniu dokumentacji proces pobierania możemy podzielić na kilka etapów:
- Inicjalizacja procesu — „POST /v2/{nazwa repo}/blobs/uploads/”
- Przesyłanie liny ratunkowej (użyjemy przesyłania monolitycznego, czyli wysyłamy każdą linę ratunkową w całości) - „PUT /v2/{repoName}/blobs/uploads/{uuid}?digest={digest}
Długość zawartości: {rozmiar warstwy}
Typ treści: aplikacja/strumień oktetowy
Warstwa danych binarnych”. - Ładowanie manifestu - „PUT /v2/{nazwa repo}/manifests/{reference}”.
Ale w dokumentacji brakuje jednego kroku, bez którego nic nie będzie działać. W przypadku ładowania monolitycznego, jak również częściowego (pofragmentowanego), przed załadowaniem szyny należy wykonać żądanie PATCH:
„PATCH /v2/{nazwa repo}/blobs/uploads/{uuid}
Długość zawartości: {rozmiar fragmentu}
Typ treści: aplikacja/strumień oktetowy
{Dane binarne fragmentu warstwy}”.
W przeciwnym razie nie będziesz mógł przejść poza pierwszy punkt, ponieważ... Zamiast oczekiwanego kodu odpowiedzi 202 otrzymasz 4xx.
Teraz algorytm wygląda następująco:
- Inicjalizacja
- Szyna łatkowa
- Ładowanie poręczy
- Ładowanie manifestu
Odpowiednio punkty 2 i 3 zostaną powtórzone tyle razy, ile wierszy należy załadować.
Po pierwsze potrzebujemy dowolnego obrazu. Użyję archlinux:latest
docker pull archlinux
Teraz zapiszmy to lokalnie do dalszej analizy
docker save c24fe13d37b9 -o savedArch
Rozpakuj powstałe archiwum do bieżącego katalogu
tar xvf savedArch
Jak widać, każda linia życia znajduje się w osobnym folderze. Przyjrzyjmy się teraz strukturze otrzymanego manifestu
cat manifest.json | json_pp
Niewiele. Zobaczmy, jaki manifest jest potrzebny do załadowania, zgodnie z
Oczywiście istniejący manifest nam nie odpowiada, więc stworzymy własny, wykorzystując blackjacka i kurtyzany, koła ratunkowe i konfiguracje.
Zawsze będziemy mieć co najmniej jeden plik konfiguracyjny i tablicę lin ratunkowych. Schemat w wersji 2 (aktualny w momencie pisania), mediaType pozostawimy bez zmian:
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
Po utworzeniu podstawowego manifestu należy wypełnić go poprawnymi danymi. W tym celu używamy szablonu json obiektu Rail:
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": ${layersSizes[$i]},
"digest": "sha256:${layersNames[$i]}"
},
Dodamy go do manifestu dla każdej szyny.
Następnie musimy sprawdzić rozmiar pliku konfiguracyjnego i zastąpić fragmenty w manifeście prawdziwymi danymi
sed -i "s/config_size/$configSize/g; s/config_hash/$configName/g" $manifestFile
Teraz możesz rozpocząć proces pobierania i zapisać identyfikator UUID, który powinien towarzyszyć wszystkim kolejnym żądaniom.
Kompletny skrypt wygląda mniej więcej tak:
#!/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
możemy skorzystać z gotowego skryptu:
./uploadImage.sh "~/path/to/saved/image" "http://localhost:8081/link/to/docker/registry" myRepoName 1.0
UPD:
Co w rezultacie otrzymaliśmy?
Po pierwsze, prawdziwe dane do analizy, ponieważ testy są uruchamiane w blazemetrze, a dane dotyczące żądań klientów dokowanych nie dostarczają zbyt wielu informacji, w przeciwieństwie do czystych żądań HTTP.
Po drugie, przejście pozwoliło nam zwiększyć liczbę wirtualnych użytkowników do przesyłania plików dockera o około 150% i uzyskać średni czas reakcji o 20-25% krótszy. W przypadku pobierania dockera udało nam się zwiększyć liczbę użytkowników o 500%, przy jednoczesnym skróceniu średniego czasu odpowiedzi o około 60%.
Dziękuję za uwagę.
Źródło: www.habr.com