Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Είχαμε 2 σάκους με γρασίδι, 75 ταμπλέτες μεσκαλίνης περιβάλλον unix, ένα αποθετήριο docker και την εργασία υλοποίησης των εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker.

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

UPD:
Ερώτηση: Προς τι όλα αυτά;
Απάντηση: Δοκιμή φόρτωσης του προϊόντος (ΟΧΙ χρησιμοποιώντας bash, τα σενάρια παρέχονται για εκπαιδευτικούς σκοπούς). Αποφασίστηκε να μην χρησιμοποιηθεί το πρόγραμμα-πελάτης docker για τη μείωση πρόσθετων επιπέδων (μέσα σε λογικά όρια) και, κατά συνέπεια, την εξομοίωση υψηλότερου φορτίου. Ως αποτέλεσμα, καταργήθηκαν όλες οι καθυστερήσεις συστήματος του προγράμματος-πελάτη Docker. Λάβαμε ένα σχετικά καθαρό φορτίο απευθείας στο προϊόν.
Το άρθρο χρησιμοποιούσε εκδόσεις εργαλείων GNU.

Αρχικά, ας καταλάβουμε τι κάνουν αυτές οι εντολές.

Σε τι χρησιμοποιείται λοιπόν το docker pull; Σύμφωνα με τεκμηρίωση:

"Τραβήξτε μια εικόνα ή ένα αποθετήριο από ένα μητρώο".

Εκεί βρίσκουμε επίσης έναν σύνδεσμο προς κατανοούν εικόνες, κοντέινερ και προγράμματα οδήγησης αποθήκευσης.

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Από εδώ μπορούμε να καταλάβουμε ότι μια εικόνα docker είναι ένα σύνολο συγκεκριμένων επιπέδων που περιέχουν πληροφορίες σχετικά με τις τελευταίες αλλαγές στην εικόνα, κάτι που προφανώς είναι αυτό που χρειαζόμαστε. Στη συνέχεια εξετάζουμε μητρώου API.

Λέει τα εξής:

"Μια "εικόνα" είναι ένας συνδυασμός μιας δήλωσης 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"

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Ως απάντηση, λαμβάνουμε json από το οποίο προς το παρόν μας ενδιαφέρουν μόνο οι γραμμές ζωής, ή μάλλον οι κατακερματισμοί τους. Αφού τα λάβουμε, μπορούμε να εξετάσουμε το καθένα και να εκτελέσουμε το ακόλουθο αίτημα: "GET /v2/{name}/blobs/{digest}"

"Η πρόσβαση σε ένα επίπεδο θα περιορίζεται από το όνομα του αποθετηρίου, αλλά προσδιορίζεται μοναδικά στο μητρώο από την ανασκόπηση."

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

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Ας δούμε τι είδους αρχείο λάβαμε τελικά ως πρώτη σανίδα σωτηρίας.

file firstLayer

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

εκείνοι. Οι ράγες είναι αρχεία πίσσας, αποσυσκευάζοντάς τα με την κατάλληλη σειρά θα λάβουμε τα περιεχόμενα της εικόνας.

Ας γράψουμε ένα μικρό σενάριο 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: {size of layer}
    Τύπος περιεχομένου: εφαρμογή/ροή οκτάδας
    Δυαδικά δεδομένα επιπέδου».
  • Φόρτωση της δήλωσης - "PUT /v2/{repoName}/manifests/{reference}".

Αλλά η τεκμηρίωση χάνει ένα βήμα, χωρίς το οποίο τίποτα δεν θα λειτουργήσει. Για μονολιθική φόρτωση, καθώς και για μερική (κομμένη), πριν από τη φόρτωση της ράγας, πρέπει να εκτελέσετε αίτημα PATCH:

"PATCH /v2/{repoName}/blobs/uploads/{uuid}
Content-Length: {size of chunk}
Τύπος περιεχομένου: εφαρμογή/ροή οκτάδας
{Layer Chunk Binary Data}".

Διαφορετικά, δεν θα μπορείτε να προχωρήσετε πέρα ​​από το πρώτο σημείο, γιατί... Αντί για τον αναμενόμενο κωδικό απάντησης 202, θα λάβετε 4xx.

Τώρα ο αλγόριθμος μοιάζει με:

  • Αρχικοποίηση
  • Patch rail
  • Φόρτωση της κουπαστής
  • Φόρτωση της δήλωσης
    Τα σημεία 2 και 3, αντίστοιχα, θα επαναληφθούν όσες φορές χρειάζεται να φορτωθεί ο αριθμός των γραμμών.

Πρώτον, χρειαζόμαστε οποιαδήποτε εικόνα. Θα χρησιμοποιήσω το archlinux:latest

docker pull archlinux

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Τώρα ας το αποθηκεύσουμε τοπικά για περαιτέρω ανάλυση

docker save c24fe13d37b9 -o savedArch

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Αποσυσκευάστε το αρχείο που προκύπτει στον τρέχοντα κατάλογο

tar xvf savedArch

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Όπως μπορείτε να δείτε, κάθε γραμμή ζωής βρίσκεται σε ξεχωριστό φάκελο. Τώρα ας δούμε τη δομή του μανιφέστου που λάβαμε

cat manifest.json | json_pp

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Οχι πολύ. Ας δούμε τι δήλωση χρειάζεται για τη φόρτωση, σύμφωνα με τεκμηρίωση.

Εφαρμογή εντολών Docker pull και docker push χωρίς πρόγραμμα-πελάτη docker χρησιμοποιώντας αιτήματα HTTP

Προφανώς, το υπάρχον μανιφέστο δεν μας ταιριάζει, οπότε θα φτιάξουμε το δικό μας με blackjack και εταίρες, σωσίβια και διαμορφώσεις.

Θα έχουμε πάντα τουλάχιστον ένα αρχείο ρυθμίσεων και μια σειρά από γραμμές ζωής. Σχέδιο έκδοση 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

Αφού δημιουργήσετε το βασικό μανιφέστο, πρέπει να το συμπληρώσετε με έγκυρα δεδομένα. Για να το κάνουμε αυτό, χρησιμοποιούμε το πρότυπο json του αντικειμένου rail:

{
         "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 και τα δεδομένα για τα αιτήματα των πελατών docker δεν είναι πολύ ενημερωτικά, σε αντίθεση με τα αιτήματα καθαρά HTTP.

Δεύτερον, η μετάβαση μας επέτρεψε να αυξήσουμε τον αριθμό των εικονικών χρηστών για μεταφόρτωση με docker κατά περίπου 150% και να λάβουμε τον μέσο χρόνο απόκρισης 20-25% πιο γρήγορα. Για λήψη docker, καταφέραμε να αυξήσουμε τον αριθμό των χρηστών κατά 500%, ενώ ο μέσος χρόνος απόκρισης μειώθηκε κατά περίπου 60%.

Σας ευχαριστώ για την προσοχή σας.

Πηγή: www.habr.com

Αγοράστε αξιόπιστη φιλοξενία για ιστότοπους με προστασία DDoS, διακομιστές VPS VDS 🔥 Αγοράστε αξιόπιστη φιλοξενία ιστοσελίδων με προστασία DDoS, διακομιστές VPS VDS | ProHoster