Τρέχοντας το Apache Spark στο Kubernetes

Αγαπητοί αναγνώστες, καλησπέρα. Σήμερα θα μιλήσουμε λίγο για το Apache Spark και τις προοπτικές ανάπτυξής του.

Τρέχοντας το Apache Spark στο Kubernetes

Στον σύγχρονο κόσμο των Big Data, το Apache Spark είναι το de facto πρότυπο για την ανάπτυξη εργασιών επεξεργασίας δεδομένων παρτίδας. Επιπλέον, χρησιμοποιείται επίσης για τη δημιουργία εφαρμογών ροής που λειτουργούν στην έννοια της μικροπαρτίδας, την επεξεργασία και την αποστολή δεδομένων σε μικρές μερίδες (Spark Structured Streaming). Και παραδοσιακά ήταν μέρος της συνολικής στοίβας Hadoop, χρησιμοποιώντας το YARN (ή σε ορισμένες περιπτώσεις το Apache Mesos) ως διαχειριστή πόρων. Μέχρι το 2020, η χρήση του στην παραδοσιακή του μορφή αμφισβητείται για τις περισσότερες εταιρείες λόγω της έλλειψης αξιοπρεπών διανομών Hadoop - η ανάπτυξη του HDP και του CDH έχει σταματήσει, το CDH δεν έχει αναπτυχθεί καλά και έχει υψηλό κόστος και οι υπόλοιποι προμηθευτές Hadoop έχουν είτε έπαψε να υπάρχει είτε έχουν ένα αμυδρό μέλλον. Ως εκ τούτου, η κυκλοφορία του Apache Spark χρησιμοποιώντας Kubernetes παρουσιάζει αυξανόμενο ενδιαφέρον μεταξύ της κοινότητας και των μεγάλων εταιρειών - καθιστώντας πρότυπο στην ενορχήστρωση κοντέινερ και τη διαχείριση πόρων σε ιδιωτικά και δημόσια σύννεφα, λύνει το πρόβλημα με τον άβολο προγραμματισμό πόρων των εργασιών Spark στο YARN και παρέχει μια σταθερά αναπτυσσόμενη πλατφόρμα με πολλές εμπορικές και ανοιχτές διανομές για εταιρείες όλων των μεγεθών και λωρίδων. Επιπλέον, στον απόηχο της δημοτικότητας, οι περισσότεροι έχουν ήδη καταφέρει να αποκτήσουν μερικές δικές τους εγκαταστάσεις και έχουν αυξήσει την τεχνογνωσία τους στη χρήση του, γεγονός που απλοποιεί τη μετακόμιση.

Ξεκινώντας με την έκδοση 2.3.0, το Apache Spark απέκτησε επίσημη υποστήριξη για την εκτέλεση εργασιών σε ένα σύμπλεγμα Kubernetes και σήμερα, θα μιλήσουμε για την τρέχουσα ωριμότητα αυτής της προσέγγισης, διάφορες επιλογές για τη χρήση της και παγίδες που θα συναντηθούν κατά την υλοποίηση.

Πρώτα απ 'όλα, ας δούμε τη διαδικασία ανάπτυξης εργασιών και εφαρμογών που βασίζονται στο Apache Spark και ας επισημάνουμε τυπικές περιπτώσεις στις οποίες πρέπει να εκτελέσετε μια εργασία σε ένα σύμπλεγμα Kubernetes. Κατά την προετοιμασία αυτής της ανάρτησης, το OpenShift χρησιμοποιείται ως διανομή και θα δοθούν εντολές σχετικές με το βοηθητικό πρόγραμμα γραμμής εντολών (oc). Για άλλες διανομές Kubernetes, μπορούν να χρησιμοποιηθούν οι αντίστοιχες εντολές από το τυπικό βοηθητικό πρόγραμμα γραμμής εντολών Kubernetes (kubectl) ή τα ανάλογα τους (για παράδειγμα, για την πολιτική oc adm).

Πρώτη περίπτωση χρήσης - spark-submit

Κατά την ανάπτυξη εργασιών και εφαρμογών, ο προγραμματιστής πρέπει να εκτελέσει εργασίες για τον εντοπισμό σφαλμάτων του μετασχηματισμού δεδομένων. Θεωρητικά, τα στελέχη μπορούν να χρησιμοποιηθούν για αυτούς τους σκοπούς, αλλά η ανάπτυξη με τη συμμετοχή πραγματικών (αν και δοκιμαστικών) περιπτώσεων τελικών συστημάτων έχει αποδειχθεί ταχύτερη και καλύτερη σε αυτήν την κατηγορία εργασιών. Στην περίπτωση που πραγματοποιούμε εντοπισμό σφαλμάτων σε πραγματικές παρουσίες τελικών συστημάτων, είναι δυνατά δύο σενάρια:

  • ο προγραμματιστής εκτελεί μια εργασία Spark τοπικά σε αυτόνομη λειτουργία.

    Τρέχοντας το Apache Spark στο Kubernetes

  • ένας προγραμματιστής εκτελεί μια εργασία Spark σε ένα σύμπλεγμα Kubernetes σε έναν βρόχο δοκιμής.

    Τρέχοντας το Apache Spark στο Kubernetes

Η πρώτη επιλογή έχει δικαίωμα ύπαρξης, αλλά συνεπάγεται μια σειρά από μειονεκτήματα:

  • Κάθε προγραμματιστής πρέπει να έχει πρόσβαση από τον χώρο εργασίας σε όλες τις περιπτώσεις των τελικών συστημάτων που χρειάζεται.
  • Απαιτείται επαρκής ποσότητα πόρων στη μηχανή εργασίας για την εκτέλεση της εργασίας που αναπτύσσεται.

Η δεύτερη επιλογή δεν έχει αυτά τα μειονεκτήματα, καθώς η χρήση ενός συμπλέγματος Kubernetes σάς επιτρέπει να εκχωρήσετε το απαραίτητο σύνολο πόρων για την εκτέλεση εργασιών και να του παρέχετε την απαραίτητη πρόσβαση σε τελικές παρουσίες συστήματος, παρέχοντας ευέλικτα πρόσβαση σε αυτό χρησιμοποιώντας το πρότυπο Kubernetes για όλα τα μέλη της ομάδας ανάπτυξης. Ας το επισημάνουμε ως την πρώτη περίπτωση χρήσης - εκκίνηση εργασιών Spark από μια τοπική μηχανή προγραμματιστών σε ένα σύμπλεγμα Kubernetes σε ένα δοκιμαστικό κύκλωμα.

Ας μιλήσουμε περισσότερο για τη διαδικασία ρύθμισης του Spark για τοπική εκτέλεση. Για να ξεκινήσετε να χρησιμοποιείτε το Spark, πρέπει να το εγκαταστήσετε:

mkdir /opt/spark
cd /opt/spark
wget http://mirror.linux-ia64.org/apache/spark/spark-2.4.5/spark-2.4.5.tgz
tar zxvf spark-2.4.5.tgz
rm -f spark-2.4.5.tgz

Συλλέγουμε τα απαραίτητα πακέτα για εργασία με την Kubernetes:

cd spark-2.4.5/
./build/mvn -Pkubernetes -DskipTests clean package

Μια πλήρης κατασκευή απαιτεί πολύ χρόνο και για να δημιουργήσετε εικόνες Docker και να τις εκτελέσετε σε ένα σύμπλεγμα Kubernetes, χρειάζεστε πραγματικά μόνο αρχεία jar από τον κατάλογο "assembly/", ώστε να μπορείτε να δημιουργήσετε μόνο αυτό το υποέργο:

./build/mvn -f ./assembly/pom.xml -Pkubernetes -DskipTests clean package

Για να εκτελέσετε εργασίες Spark στο Kubernetes, πρέπει να δημιουργήσετε μια εικόνα Docker για να τη χρησιμοποιήσετε ως βασική εικόνα. Υπάρχουν 2 πιθανές προσεγγίσεις εδώ:

  • Η παραγόμενη εικόνα Docker περιλαμβάνει τον εκτελέσιμο κώδικα εργασίας Spark.
  • Η δημιουργημένη εικόνα περιλαμβάνει μόνο το Spark και τις απαραίτητες εξαρτήσεις, ο εκτελέσιμος κώδικας φιλοξενείται από απόσταση (για παράδειγμα, σε HDFS).

Αρχικά, ας δημιουργήσουμε μια εικόνα Docker που περιέχει ένα δοκιμαστικό παράδειγμα μιας εργασίας Spark. Για τη δημιουργία εικόνων Docker, το Spark διαθέτει ένα βοηθητικό πρόγραμμα που ονομάζεται "docker-image-tool". Ας μελετήσουμε τη βοήθεια σε αυτό:

./bin/docker-image-tool.sh --help

Με τη βοήθειά του, μπορείτε να δημιουργήσετε εικόνες Docker και να τις ανεβάσετε σε απομακρυσμένα μητρώα, αλλά από προεπιλογή έχει μια σειρά από μειονεκτήματα:

  • χωρίς αποτυχία δημιουργεί 3 εικόνες Docker ταυτόχρονα - για Spark, PySpark και R.
  • δεν σας επιτρέπει να καθορίσετε όνομα εικόνας.

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

vi bin/docker-image-tool-upd.sh

#!/usr/bin/env bash

function error {
  echo "$@" 1>&2
  exit 1
}

if [ -z "${SPARK_HOME}" ]; then
  SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi
. "${SPARK_HOME}/bin/load-spark-env.sh"

function image_ref {
  local image="$1"
  local add_repo="${2:-1}"
  if [ $add_repo = 1 ] && [ -n "$REPO" ]; then
    image="$REPO/$image"
  fi
  if [ -n "$TAG" ]; then
    image="$image:$TAG"
  fi
  echo "$image"
}

function build {
  local BUILD_ARGS
  local IMG_PATH

  if [ ! -f "$SPARK_HOME/RELEASE" ]; then
    IMG_PATH=$BASEDOCKERFILE
    BUILD_ARGS=(
      ${BUILD_PARAMS}
      --build-arg
      img_path=$IMG_PATH
      --build-arg
      datagram_jars=datagram/runtimelibs
      --build-arg
      spark_jars=assembly/target/scala-$SPARK_SCALA_VERSION/jars
    )
  else
    IMG_PATH="kubernetes/dockerfiles"
    BUILD_ARGS=(${BUILD_PARAMS})
  fi

  if [ -z "$IMG_PATH" ]; then
    error "Cannot find docker image. This script must be run from a runnable distribution of Apache Spark."
  fi

  if [ -z "$IMAGE_REF" ]; then
    error "Cannot find docker image reference. Please add -i arg."
  fi

  local BINDING_BUILD_ARGS=(
    ${BUILD_PARAMS}
    --build-arg
    base_img=$(image_ref $IMAGE_REF)
  )
  local BASEDOCKERFILE=${BASEDOCKERFILE:-"$IMG_PATH/spark/docker/Dockerfile"}

  docker build $NOCACHEARG "${BUILD_ARGS[@]}" 
    -t $(image_ref $IMAGE_REF) 
    -f "$BASEDOCKERFILE" .
}

function push {
  docker push "$(image_ref $IMAGE_REF)"
}

function usage {
  cat <<EOF
Usage: $0 [options] [command]
Builds or pushes the built-in Spark Docker image.

Commands:
  build       Build image. Requires a repository address to be provided if the image will be
              pushed to a different registry.
  push        Push a pre-built image to a registry. Requires a repository address to be provided.

Options:
  -f file               Dockerfile to build for JVM based Jobs. By default builds the Dockerfile shipped with Spark.
  -p file               Dockerfile to build for PySpark Jobs. Builds Python dependencies and ships with Spark.
  -R file               Dockerfile to build for SparkR Jobs. Builds R dependencies and ships with Spark.
  -r repo               Repository address.
  -i name               Image name to apply to the built image, or to identify the image to be pushed.  
  -t tag                Tag to apply to the built image, or to identify the image to be pushed.
  -m                    Use minikube's Docker daemon.
  -n                    Build docker image with --no-cache
  -b arg      Build arg to build or push the image. For multiple build args, this option needs to
              be used separately for each build arg.

Using minikube when building images will do so directly into minikube's Docker daemon.
There is no need to push the images into minikube in that case, they'll be automatically
available when running applications inside the minikube cluster.

Check the following documentation for more information on using the minikube Docker daemon:

  https://kubernetes.io/docs/getting-started-guides/minikube/#reusing-the-docker-daemon

Examples:
  - Build image in minikube with tag "testing"
    $0 -m -t testing build

  - Build and push image with tag "v2.3.0" to docker.io/myrepo
    $0 -r docker.io/myrepo -t v2.3.0 build
    $0 -r docker.io/myrepo -t v2.3.0 push
EOF
}

if [[ "$@" = *--help ]] || [[ "$@" = *-h ]]; then
  usage
  exit 0
fi

REPO=
TAG=
BASEDOCKERFILE=
NOCACHEARG=
BUILD_PARAMS=
IMAGE_REF=
while getopts f:mr:t:nb:i: option
do
 case "${option}"
 in
 f) BASEDOCKERFILE=${OPTARG};;
 r) REPO=${OPTARG};;
 t) TAG=${OPTARG};;
 n) NOCACHEARG="--no-cache";;
 i) IMAGE_REF=${OPTARG};;
 b) BUILD_PARAMS=${BUILD_PARAMS}" --build-arg "${OPTARG};;
 esac
done

case "${@: -1}" in
  build)
    build
    ;;
  push)
    if [ -z "$REPO" ]; then
      usage
      exit 1
    fi
    push
    ;;
  *)
    usage
    exit 1
    ;;
esac

Με τη βοήθειά του, συγκεντρώνουμε μια βασική εικόνα Spark που περιέχει μια δοκιμαστική εργασία για τον υπολογισμό του Pi χρησιμοποιώντας το Spark (εδώ {docker-registry-url} είναι η διεύθυνση URL του μητρώου εικόνων Docker, {repo} είναι το όνομα του αποθετηρίου μέσα στο μητρώο, που ταιριάζει με το έργο στο OpenShift , {image-name} - όνομα της εικόνας (εάν χρησιμοποιείται διαχωρισμός εικόνων σε τρία επίπεδα, για παράδειγμα, όπως στο ενσωματωμένο μητρώο εικόνων Red Hat OpenShift), {tag} - ετικέτα αυτού έκδοση της εικόνας):

./bin/docker-image-tool-upd.sh -f resource-managers/kubernetes/docker/src/main/dockerfiles/spark/Dockerfile -r {docker-registry-url}/{repo} -i {image-name} -t {tag} build

Συνδεθείτε στο σύμπλεγμα OKD χρησιμοποιώντας το βοηθητικό πρόγραμμα κονσόλας (εδώ το {OKD-API-URL} είναι το URL του API του συμπλέγματος OKD):

oc login {OKD-API-URL}

Ας πάρουμε το διακριτικό του τρέχοντος χρήστη για εξουσιοδότηση στο Μητρώο Docker:

oc whoami -t

Συνδεθείτε στο εσωτερικό Μητρώο Docker του συμπλέγματος OKD (χρησιμοποιούμε ως κωδικό πρόσβασης το διακριτικό που αποκτήσαμε χρησιμοποιώντας την προηγούμενη εντολή):

docker login {docker-registry-url}

Ας ανεβάσουμε τη συναρμολογημένη εικόνα Docker στο μητρώο Docker OKD:

./bin/docker-image-tool-upd.sh -r {docker-registry-url}/{repo} -i {image-name} -t {tag} push

Ας ελέγξουμε ότι η συναρμολογημένη εικόνα είναι διαθέσιμη σε ΟΚΔ. Για να το κάνετε αυτό, ανοίξτε τη διεύθυνση URL στο πρόγραμμα περιήγησης με μια λίστα εικόνων του αντίστοιχου έργου (εδώ {project} είναι το όνομα του έργου μέσα στο σύμπλεγμα OpenShift, {OKD-WEBUI-URL} είναι η διεύθυνση URL της κονσόλας Web OpenShift ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Για την εκτέλεση εργασιών, πρέπει να δημιουργηθεί ένας λογαριασμός υπηρεσίας με τα δικαιώματα εκτέλεσης pods ως root (θα συζητήσουμε αυτό το σημείο αργότερα):

oc create sa spark -n {project}
oc adm policy add-scc-to-user anyuid -z spark -n {project}

Ας εκτελέσουμε την εντολή spark-submit για να δημοσιεύσουμε μια εργασία Spark στο σύμπλεγμα OKD, προσδιορίζοντας τον λογαριασμό υπηρεσίας που δημιουργήθηκε και την εικόνα Docker:

 /opt/spark/bin/spark-submit --name spark-test --class org.apache.spark.examples.SparkPi --conf spark.executor.instances=3 --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark --conf spark.kubernetes.namespace={project} --conf spark.submit.deployMode=cluster --conf spark.kubernetes.container.image={docker-registry-url}/{repo}/{image-name}:{tag} --conf spark.master=k8s://https://{OKD-API-URL}  local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.4.5.jar

Εδώ:

—όνομα — το όνομα της εργασίας που θα συμμετάσχει στον σχηματισμό του ονόματος των λοβών Kubernetes.

—class — κλάση του εκτελέσιμου αρχείου, που καλείται όταν ξεκινά η εργασία.

—conf — Παράμετροι διαμόρφωσης Spark.

spark.executor.instances — ο αριθμός των εκτελεστών Spark προς εκκίνηση.

spark.kubernetes.authenticate.driver.serviceAccountName - το όνομα του λογαριασμού υπηρεσίας Kubernetes που χρησιμοποιείται κατά την εκκίνηση pods (για τον καθορισμό του περιβάλλοντος ασφαλείας και των δυνατοτήτων κατά την αλληλεπίδραση με το Kubernetes API).

spark.kubernetes.namespace — Χώρος ονομάτων Kubernetes στον οποίο θα εκκινηθούν οι ομάδες προγραμμάτων οδήγησης και εκτελεστών.

spark.submit.deployMode — μέθοδος εκκίνησης του Spark (για τυπικό spark-submit χρησιμοποιείται το "cluster", για Spark Operator και νεότερες εκδόσεις του Spark "client").

spark.kubernetes.container.image - Εικόνα Docker που χρησιμοποιείται για την εκκίνηση pods.

spark.master — URL API Kubernetes (το εξωτερικό καθορίζεται έτσι ώστε η πρόσβαση πραγματοποιείται από τον τοπικό υπολογιστή).

local:// είναι η διαδρομή προς το εκτελέσιμο Spark μέσα στην εικόνα Docker.

Πηγαίνουμε στο αντίστοιχο έργο OKD και μελετάμε τα pods που δημιουργήθηκαν - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Για να απλοποιηθεί η διαδικασία ανάπτυξης, μπορεί να χρησιμοποιηθεί μια άλλη επιλογή, στην οποία δημιουργείται μια κοινή βασική εικόνα του Spark, χρησιμοποιείται από όλες τις εργασίες για εκτέλεση και στιγμιότυπα των εκτελέσιμων αρχείων δημοσιεύονται σε εξωτερικό χώρο αποθήκευσης (για παράδειγμα, Hadoop) και καθορίζονται κατά την κλήση spark-υποβολή ως σύνδεσμος. Σε αυτήν την περίπτωση, μπορείτε να εκτελέσετε διαφορετικές εκδόσεις εργασιών Spark χωρίς να δημιουργήσετε ξανά εικόνες Docker, χρησιμοποιώντας, για παράδειγμα, το WebHDFS για τη δημοσίευση εικόνων. Στέλνουμε ένα αίτημα για τη δημιουργία ενός αρχείου (εδώ {host} είναι ο κεντρικός υπολογιστής της υπηρεσίας WebHDFS, {port} είναι η θύρα της υπηρεσίας WebHDFS, {path-to-file-on-hdfs} είναι η επιθυμητή διαδρομή προς το αρχείο σε HDFS):

curl -i -X PUT "http://{host}:{port}/webhdfs/v1/{path-to-file-on-hdfs}?op=CREATE

Θα λάβετε μια απάντηση όπως αυτή (εδώ {location} είναι η διεύθυνση URL που πρέπει να χρησιμοποιηθεί για τη λήψη του αρχείου):

HTTP/1.1 307 TEMPORARY_REDIRECT
Location: {location}
Content-Length: 0

Φορτώστε το εκτελέσιμο αρχείο Spark στο HDFS (εδώ {path-to-local-file} είναι η διαδρομή προς το εκτελέσιμο αρχείο Spark στον τρέχοντα κεντρικό υπολογιστή):

curl -i -X PUT -T {path-to-local-file} "{location}"

Μετά από αυτό, μπορούμε να κάνουμε spark-submit χρησιμοποιώντας το αρχείο Spark που μεταφορτώθηκε στο HDFS (εδώ {class-name} είναι το όνομα της τάξης που πρέπει να εκκινηθεί για να ολοκληρωθεί η εργασία):

/opt/spark/bin/spark-submit --name spark-test --class {class-name} --conf spark.executor.instances=3 --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark --conf spark.kubernetes.namespace={project} --conf spark.submit.deployMode=cluster --conf spark.kubernetes.container.image={docker-registry-url}/{repo}/{image-name}:{tag} --conf spark.master=k8s://https://{OKD-API-URL}  hdfs://{host}:{port}/{path-to-file-on-hdfs}

Θα πρέπει να σημειωθεί ότι για να αποκτήσετε πρόσβαση στο HDFS και να διασφαλίσετε ότι η εργασία λειτουργεί, ίσως χρειαστεί να αλλάξετε το σενάριο Dockerfile και το σενάριο entrypoint.sh - να προσθέσετε μια οδηγία στο Dockerfile για να αντιγράψετε εξαρτημένες βιβλιοθήκες στον κατάλογο /opt/spark/jars και συμπεριλάβετε το αρχείο διαμόρφωσης HDFS στο SPARK_CLASSPATH στο σημείο εισόδου.

Δεύτερη περίπτωση χρήσης - Apache Livy

Περαιτέρω, όταν μια εργασία αναπτύσσεται και το αποτέλεσμα πρέπει να δοκιμαστεί, τίθεται το ερώτημα της εκκίνησης της ως μέρος της διαδικασίας CI/CD και παρακολούθησης της κατάστασης εκτέλεσής της. Φυσικά, μπορείτε να το εκτελέσετε χρησιμοποιώντας μια τοπική κλήση spark-submit, αλλά αυτό περιπλέκει την υποδομή CI/CD, καθώς απαιτεί εγκατάσταση και διαμόρφωση του Spark στους πράκτορες/διακομιστές διακομιστή CI και ρύθμιση πρόσβασης στο Kubernetes API. Για αυτήν την περίπτωση, η υλοποίηση στόχου επέλεξε να χρησιμοποιήσει το Apache Livy ως REST API για την εκτέλεση εργασιών Spark που φιλοξενούνται μέσα σε ένα σύμπλεγμα Kubernetes. Με τη βοήθειά του, μπορείτε να εκτελέσετε εργασίες Spark σε ένα σύμπλεγμα Kubernetes χρησιμοποιώντας κανονικά αιτήματα cURL, τα οποία υλοποιούνται εύκολα με βάση οποιαδήποτε λύση CI, και η τοποθέτησή του μέσα στο σύμπλεγμα Kubernetes λύνει το ζήτημα του ελέγχου ταυτότητας κατά την αλληλεπίδραση με το Kubernetes API.

Τρέχοντας το Apache Spark στο Kubernetes

Ας το επισημάνουμε ως δεύτερη περίπτωση χρήσης - την εκτέλεση εργασιών Spark ως μέρος μιας διαδικασίας CI/CD σε ένα σύμπλεγμα Kubernetes σε έναν βρόχο δοκιμής.

Λίγα λόγια για το Apache Livy - λειτουργεί ως διακομιστής HTTP που παρέχει μια διεπαφή Ιστού και ένα RESTful API που σας επιτρέπει να εκκινήσετε εξ αποστάσεως το spark-submit περνώντας τις απαραίτητες παραμέτρους. Παραδοσιακά έχει αποσταλεί ως μέρος μιας διανομής HDP, αλλά μπορεί επίσης να αναπτυχθεί στο OKD ή σε οποιαδήποτε άλλη εγκατάσταση Kubernetes χρησιμοποιώντας το κατάλληλο μανιφέστο και ένα σύνολο εικόνων Docker, όπως αυτό - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Για την περίπτωσή μας, δημιουργήθηκε μια παρόμοια εικόνα Docker, συμπεριλαμβανομένης της έκδοσης Spark 2.4.5 από το ακόλουθο Dockerfile:

FROM java:8-alpine

ENV SPARK_HOME=/opt/spark
ENV LIVY_HOME=/opt/livy
ENV HADOOP_CONF_DIR=/etc/hadoop/conf
ENV SPARK_USER=spark

WORKDIR /opt

RUN apk add --update openssl wget bash && 
    wget -P /opt https://downloads.apache.org/spark/spark-2.4.5/spark-2.4.5-bin-hadoop2.7.tgz && 
    tar xvzf spark-2.4.5-bin-hadoop2.7.tgz && 
    rm spark-2.4.5-bin-hadoop2.7.tgz && 
    ln -s /opt/spark-2.4.5-bin-hadoop2.7 /opt/spark

RUN wget http://mirror.its.dal.ca/apache/incubator/livy/0.7.0-incubating/apache-livy-0.7.0-incubating-bin.zip && 
    unzip apache-livy-0.7.0-incubating-bin.zip && 
    rm apache-livy-0.7.0-incubating-bin.zip && 
    ln -s /opt/apache-livy-0.7.0-incubating-bin /opt/livy && 
    mkdir /var/log/livy && 
    ln -s /var/log/livy /opt/livy/logs && 
    cp /opt/livy/conf/log4j.properties.template /opt/livy/conf/log4j.properties

ADD livy.conf /opt/livy/conf
ADD spark-defaults.conf /opt/spark/conf/spark-defaults.conf
ADD entrypoint.sh /entrypoint.sh

ENV PATH="/opt/livy/bin:${PATH}"

EXPOSE 8998

ENTRYPOINT ["/entrypoint.sh"]
CMD ["livy-server"]

Η παραγόμενη εικόνα μπορεί να κατασκευαστεί και να μεταφορτωθεί στο υπάρχον αποθετήριο Docker, όπως το εσωτερικό αποθετήριο OKD. Για να το αναπτύξετε, χρησιμοποιήστε την ακόλουθη δήλωση ({registry-url} - URL του μητρώου εικόνας Docker, {image-name} - Όνομα εικόνας Docker, {tag} - Ετικέτα εικόνας Docker, {livy-url} - επιθυμητή διεύθυνση URL όπου η Ο διακομιστής θα είναι προσβάσιμος Livy. Το μανιφέστο "Δρομολόγηση" χρησιμοποιείται εάν χρησιμοποιείται το Red Hat OpenShift ως διανομή Kubernetes, διαφορετικά χρησιμοποιείται το αντίστοιχο μανιφέστο εισόδου ή υπηρεσίας τύπου NodePort):

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    component: livy
  name: livy
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      component: livy
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        component: livy
    spec:
      containers:
        - command:
            - livy-server
          env:
            - name: K8S_API_HOST
              value: localhost
            - name: SPARK_KUBERNETES_IMAGE
              value: 'gnut3ll4/spark:v1.0.14'
          image: '{registry-url}/{image-name}:{tag}'
          imagePullPolicy: Always
          name: livy
          ports:
            - containerPort: 8998
              name: livy-rest
              protocol: TCP
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          volumeMounts:
            - mountPath: /var/log/livy
              name: livy-log
            - mountPath: /opt/.livy-sessions/
              name: livy-sessions
            - mountPath: /opt/livy/conf/livy.conf
              name: livy-config
              subPath: livy.conf
            - mountPath: /opt/spark/conf/spark-defaults.conf
              name: spark-config
              subPath: spark-defaults.conf
        - command:
            - /usr/local/bin/kubectl
            - proxy
            - '--port'
            - '8443'
          image: 'gnut3ll4/kubectl-sidecar:latest'
          imagePullPolicy: Always
          name: kubectl
          ports:
            - containerPort: 8443
              name: k8s-api
              protocol: TCP
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: spark
      serviceAccountName: spark
      terminationGracePeriodSeconds: 30
      volumes:
        - emptyDir: {}
          name: livy-log
        - emptyDir: {}
          name: livy-sessions
        - configMap:
            defaultMode: 420
            items:
              - key: livy.conf
                path: livy.conf
            name: livy-config
          name: livy-config
        - configMap:
            defaultMode: 420
            items:
              - key: spark-defaults.conf
                path: spark-defaults.conf
            name: livy-config
          name: spark-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: livy-config
data:
  livy.conf: |-
    livy.spark.deploy-mode=cluster
    livy.file.local-dir-whitelist=/opt/.livy-sessions/
    livy.spark.master=k8s://http://localhost:8443
    livy.server.session.state-retain.sec = 8h
  spark-defaults.conf: 'spark.kubernetes.container.image        "gnut3ll4/spark:v1.0.14"'
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: livy
  name: livy
spec:
  ports:
    - name: livy-rest
      port: 8998
      protocol: TCP
      targetPort: 8998
  selector:
    component: livy
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: route.openshift.io/v1
kind: Route
metadata:
  labels:
    app: livy
  name: livy
spec:
  host: {livy-url}
  port:
    targetPort: livy-rest
  to:
    kind: Service
    name: livy
    weight: 100
  wildcardPolicy: None

Μετά την εφαρμογή του και την επιτυχή εκκίνηση του pod, η γραφική διεπαφή Livy είναι διαθέσιμη στον σύνδεσμο: http://{livy-url}/ui. Με το Livy, μπορούμε να δημοσιεύσουμε την εργασία μας στο Spark χρησιμοποιώντας ένα αίτημα REST από, για παράδειγμα, τον Postman. Παρακάτω παρουσιάζεται ένα παράδειγμα συλλογής με αιτήματα (ορίσματα διαμόρφωσης με μεταβλητές που είναι απαραίτητες για τη λειτουργία της εκκινούμενης εργασίας μπορούν να περάσουν στον πίνακα "args"):

{
    "info": {
        "_postman_id": "be135198-d2ff-47b6-a33e-0d27b9dba4c8",
        "name": "Spark Livy",
        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
    },
    "item": [
        {
            "name": "1 Submit job with jar",
            "request": {
                "method": "POST",
                "header": [
                    {
                        "key": "Content-Type",
                        "value": "application/json"
                    }
                ],
                "body": {
                    "mode": "raw",
                    "raw": "{nt"file": "local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.4.5.jar", nt"className": "org.apache.spark.examples.SparkPi",nt"numExecutors":1,nt"name": "spark-test-1",nt"conf": {ntt"spark.jars.ivy": "/tmp/.ivy",ntt"spark.kubernetes.authenticate.driver.serviceAccountName": "spark",ntt"spark.kubernetes.namespace": "{project}",ntt"spark.kubernetes.container.image": "{docker-registry-url}/{repo}/{image-name}:{tag}"nt}n}"
                },
                "url": {
                    "raw": "http://{livy-url}/batches",
                    "protocol": "http",
                    "host": [
                        "{livy-url}"
                    ],
                    "path": [
                        "batches"
                    ]
                }
            },
            "response": []
        },
        {
            "name": "2 Submit job without jar",
            "request": {
                "method": "POST",
                "header": [
                    {
                        "key": "Content-Type",
                        "value": "application/json"
                    }
                ],
                "body": {
                    "mode": "raw",
                    "raw": "{nt"file": "hdfs://{host}:{port}/{path-to-file-on-hdfs}", nt"className": "{class-name}",nt"numExecutors":1,nt"name": "spark-test-2",nt"proxyUser": "0",nt"conf": {ntt"spark.jars.ivy": "/tmp/.ivy",ntt"spark.kubernetes.authenticate.driver.serviceAccountName": "spark",ntt"spark.kubernetes.namespace": "{project}",ntt"spark.kubernetes.container.image": "{docker-registry-url}/{repo}/{image-name}:{tag}"nt},nt"args": [ntt"HADOOP_CONF_DIR=/opt/spark/hadoop-conf",ntt"MASTER=k8s://https://kubernetes.default.svc:8443"nt]n}"
                },
                "url": {
                    "raw": "http://{livy-url}/batches",
                    "protocol": "http",
                    "host": [
                        "{livy-url}"
                    ],
                    "path": [
                        "batches"
                    ]
                }
            },
            "response": []
        }
    ],
    "event": [
        {
            "listen": "prerequest",
            "script": {
                "id": "41bea1d0-278c-40c9-ad42-bf2e6268897d",
                "type": "text/javascript",
                "exec": [
                    ""
                ]
            }
        },
        {
            "listen": "test",
            "script": {
                "id": "3cdd7736-a885-4a2d-9668-bd75798f4560",
                "type": "text/javascript",
                "exec": [
                    ""
                ]
            }
        }
    ],
    "protocolProfileBehavior": {}
}

Ας εκτελέσουμε το πρώτο αίτημα από τη συλλογή, μεταβούμε στη διεπαφή OKD και ελέγξουμε ότι η εργασία έχει ξεκινήσει με επιτυχία - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Ταυτόχρονα, θα εμφανιστεί μια περίοδος σύνδεσης στη διεπαφή Livy (http://{livy-url}/ui), εντός της οποίας, χρησιμοποιώντας το Livy API ή τη γραφική διεπαφή, μπορείτε να παρακολουθείτε την πρόοδο της εργασίας και να μελετήσετε τη συνεδρία κούτσουρα.

Τώρα ας δείξουμε πώς λειτουργεί ο Livy. Για να το κάνετε αυτό, ας εξετάσουμε τα αρχεία καταγραφής του κοντέινερ Livy μέσα στο pod με τον διακομιστή Livy - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Από αυτά μπορούμε να δούμε ότι όταν καλούμε το Livy REST API σε ένα κοντέινερ που ονομάζεται "livy", εκτελείται ένα spark-submit, παρόμοιο με αυτό που χρησιμοποιήσαμε παραπάνω (εδώ {livy-pod-name} είναι το όνομα του δημιουργημένου pod με τον διακομιστή Livy). Η συλλογή εισάγει επίσης ένα δεύτερο ερώτημα που σας επιτρέπει να εκτελείτε εργασίες που φιλοξενούν εξ αποστάσεως ένα εκτελέσιμο Spark χρησιμοποιώντας έναν διακομιστή Livy.

Περίπτωση τρίτης χρήσης - Spark Operator

Τώρα που η εργασία έχει δοκιμαστεί, τίθεται το ζήτημα της τακτικής εκτέλεσης της. Ο εγγενής τρόπος για την τακτική εκτέλεση εργασιών σε ένα σύμπλεγμα Kubernetes είναι η οντότητα CronJob και μπορείτε να τη χρησιμοποιήσετε, αλλά αυτή τη στιγμή η χρήση τελεστών για τη διαχείριση εφαρμογών στο Kubernetes είναι πολύ δημοφιλής και για το Spark υπάρχει ένας αρκετά ώριμος τελεστής, ο οποίος είναι επίσης χρησιμοποιείται σε λύσεις σε επίπεδο επιχείρησης (για παράδειγμα, Lightbend FastData Platform). Συνιστούμε τη χρήση του - η τρέχουσα σταθερή έκδοση του Spark (2.4.5) έχει μάλλον περιορισμένες επιλογές διαμόρφωσης για την εκτέλεση εργασιών Spark στο Kubernetes, ενώ η επόμενη κύρια έκδοση (3.0.0) δηλώνει πλήρη υποστήριξη για το Kubernetes, αλλά η ημερομηνία κυκλοφορίας παραμένει άγνωστη . Το Spark Operator αντισταθμίζει αυτό το μειονέκτημα προσθέτοντας σημαντικές επιλογές διαμόρφωσης (για παράδειγμα, τοποθέτηση ενός ConfigMap με διαμόρφωση πρόσβασης Hadoop σε Spark pod) και τη δυνατότητα εκτέλεσης μιας τακτικά προγραμματισμένης εργασίας.

Τρέχοντας το Apache Spark στο Kubernetes
Ας το επισημάνουμε ως τρίτη περίπτωση χρήσης - την τακτική εκτέλεση εργασιών Spark σε ένα σύμπλεγμα Kubernetes σε έναν βρόχο παραγωγής.

Το Spark Operator είναι ανοιχτού κώδικα και αναπτύχθηκε στο Google Cloud Platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Η εγκατάστασή του μπορεί να γίνει με 3 τρόπους:

  1. Ως μέρος της εγκατάστασης Lightbend FastData Platform/Cloudflow.
  2. Χρησιμοποιώντας το τιμόνι:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Χρήση δηλώσεων από το επίσημο αποθετήριο (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Αξίζει να σημειωθεί το εξής - Το Cloudflow περιλαμβάνει έναν τελεστή με έκδοση API v1beta1. Εάν χρησιμοποιείται αυτός ο τύπος εγκατάστασης, οι περιγραφές του μανιφέστου εφαρμογής Spark θα πρέπει να βασίζονται σε παραδείγματα ετικετών στο Git με την κατάλληλη έκδοση API, για παράδειγμα, "v1beta1-0.9.0-2.4.0". Η έκδοση του χειριστή βρίσκεται στην περιγραφή του CRD που περιλαμβάνεται στον χειριστή στο λεξικό «εκδόσεις»:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Εάν ο χειριστής έχει εγκατασταθεί σωστά, θα εμφανιστεί ένα ενεργό pod με τον τελεστή Spark στο αντίστοιχο έργο (για παράδειγμα, cloudflow-fdp-sparkoperator στο χώρο Cloudflow για την εγκατάσταση του Cloudflow) και θα εμφανιστεί ένας αντίστοιχος τύπος πόρου Kubernetes με το όνομα "sparkapplications" . Μπορείτε να εξερευνήσετε διαθέσιμες εφαρμογές Spark με την ακόλουθη εντολή:

oc get sparkapplications -n {project}

Για να εκτελέσετε εργασίες χρησιμοποιώντας το Spark Operator, πρέπει να κάνετε 3 πράγματα:

  • δημιουργήστε μια εικόνα Docker που περιλαμβάνει όλες τις απαραίτητες βιβλιοθήκες, καθώς και ρυθμίσεις παραμέτρων και εκτελέσιμα αρχεία. Στην εικόνα στόχο, αυτή είναι μια εικόνα που δημιουργήθηκε στο στάδιο CI/CD και δοκιμάστηκε σε μια δοκιμαστική ομάδα.
  • Δημοσιεύστε μια εικόνα Docker σε ένα μητρώο προσβάσιμο από το σύμπλεγμα Kubernetes.
  • δημιουργήστε μια δήλωση με τον τύπο "SparkApplication" και μια περιγραφή της εργασίας που πρόκειται να ξεκινήσει. Παραδείγματα δηλώσεων είναι διαθέσιμα στο επίσημο αποθετήριο (π.χ. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Υπάρχουν σημαντικά σημεία που πρέπει να σημειωθούν σχετικά με το μανιφέστο:
    1. το λεξικό "apiVersion" πρέπει να υποδεικνύει την έκδοση API που αντιστοιχεί στην έκδοση χειριστή.
    2. το λεξικό "metadata.namespace" πρέπει να υποδεικνύει τον χώρο ονομάτων στον οποίο θα εκκινηθεί η εφαρμογή.
    3. το λεξικό "spec.image" πρέπει να περιέχει τη διεύθυνση της εικόνας Docker που δημιουργήθηκε σε ένα προσβάσιμο μητρώο.
    4. το λεξικό "spec.mainClass" πρέπει να περιέχει την κλάση εργασιών Spark που πρέπει να εκτελεστεί όταν ξεκινήσει η διαδικασία.
    5. το λεξικό "spec.mainApplicationFile" πρέπει να περιέχει τη διαδρομή προς το εκτελέσιμο αρχείο jar.
    6. το λεξικό "spec.sparkVersion" πρέπει να υποδεικνύει την έκδοση του Spark που χρησιμοποιείται.
    7. το λεξικό "spec.driver.serviceAccount" πρέπει να προσδιορίζει τον λογαριασμό υπηρεσίας στον αντίστοιχο χώρο ονομάτων Kubernetes που θα χρησιμοποιηθεί για την εκτέλεση της εφαρμογής.
    8. το λεξικό "spec.executor" πρέπει να υποδεικνύει τον αριθμό των πόρων που έχουν διατεθεί στην εφαρμογή.
    9. το λεξικό "spec.volumeMounts" πρέπει να προσδιορίζει τον τοπικό κατάλογο στον οποίο θα δημιουργηθούν τα τοπικά αρχεία εργασιών Spark.

Ένα παράδειγμα δημιουργίας δήλωσης (εδώ το {spark-service-account} είναι ένας λογαριασμός υπηρεσίας μέσα στο σύμπλεγμα Kubernetes για την εκτέλεση εργασιών Spark):

apiVersion: "sparkoperator.k8s.io/v1beta1"
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: {project}
spec:
  type: Scala
  mode: cluster
  image: "gcr.io/spark-operator/spark:v2.4.0"
  imagePullPolicy: Always
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.0.jar"
  sparkVersion: "2.4.0"
  restartPolicy:
    type: Never
  volumes:
    - name: "test-volume"
      hostPath:
        path: "/tmp"
        type: Directory
  driver:
    cores: 0.1
    coreLimit: "200m"
    memory: "512m"
    labels:
      version: 2.4.0
    serviceAccount: {spark-service-account}
    volumeMounts:
      - name: "test-volume"
        mountPath: "/tmp"
  executor:
    cores: 1
    instances: 1
    memory: "512m"
    labels:
      version: 2.4.0
    volumeMounts:
      - name: "test-volume"
        mountPath: "/tmp"

Αυτό το μανιφέστο καθορίζει έναν λογαριασμό υπηρεσίας για τον οποίο, πριν δημοσιεύσετε το μανιφέστο, πρέπει να δημιουργήσετε τις απαραίτητες δεσμεύσεις ρόλων που παρέχουν τα απαραίτητα δικαιώματα πρόσβασης στην εφαρμογή Spark για αλληλεπίδραση με το Kubernetes API (εάν είναι απαραίτητο). Στην περίπτωσή μας, η εφαρμογή χρειάζεται δικαιώματα για τη δημιουργία Pods. Ας δημιουργήσουμε το απαραίτητο δεσμευτικό ρόλο:

oc adm policy add-role-to-user edit system:serviceaccount:{project}:{spark-service-account} -n {project}

Αξίζει επίσης να σημειωθεί ότι αυτή η προδιαγραφή δήλωσης μπορεί να περιλαμβάνει μια παράμετρο "hadoopConfigMap", η οποία σας επιτρέπει να καθορίσετε ένα ConfigMap με τη διαμόρφωση Hadoop χωρίς να χρειάζεται να τοποθετήσετε πρώτα το αντίστοιχο αρχείο στην εικόνα Docker. Είναι επίσης κατάλληλο για την τακτική εκτέλεση εργασιών - χρησιμοποιώντας την παράμετρο "χρονοδιάγραμμα", μπορεί να καθοριστεί ένα χρονοδιάγραμμα για την εκτέλεση μιας δεδομένης εργασίας.

Μετά από αυτό, αποθηκεύουμε το μανιφέστο μας στο αρχείο spark-pi.yaml και το εφαρμόζουμε στο σύμπλεγμα Kubernetes:

oc apply -f spark-pi.yaml

Αυτό θα δημιουργήσει ένα αντικείμενο τύπου "sparkapplications":

oc get sparkapplications -n {project}
> NAME       AGE
> spark-pi   22h

Σε αυτήν την περίπτωση, θα δημιουργηθεί ένα pod με μια εφαρμογή, η κατάσταση του οποίου θα εμφανίζεται στις δημιουργημένες «sparkapplications». Μπορείτε να το δείτε με την ακόλουθη εντολή:

oc get sparkapplications spark-pi -o yaml -n {project}

Με την ολοκλήρωση της εργασίας, το POD θα μετακινηθεί στην κατάσταση "Ολοκληρώθηκε", η οποία θα ενημερώνεται επίσης στο "sparkapplications". Τα αρχεία καταγραφής εφαρμογών μπορούν να προβληθούν στο πρόγραμμα περιήγησης ή χρησιμοποιώντας την ακόλουθη εντολή (εδώ {sparkapplications-pod-name} είναι το όνομα του pod της εργασίας που εκτελείται):

oc logs {sparkapplications-pod-name} -n {project}

Μπορείτε επίσης να διαχειριστείτε τις εργασίες Spark χρησιμοποιώντας το εξειδικευμένο βοηθητικό πρόγραμμα sparkctl. Για να το εγκαταστήσετε, κλωνοποιήστε το αποθετήριο με τον πηγαίο κώδικα του, εγκαταστήστε το Go και δημιουργήστε αυτό το βοηθητικό πρόγραμμα:

git clone https://github.com/GoogleCloudPlatform/spark-on-k8s-operator.git
cd spark-on-k8s-operator/
wget https://dl.google.com/go/go1.13.3.linux-amd64.tar.gz
tar -xzf go1.13.3.linux-amd64.tar.gz
sudo mv go /usr/local
mkdir $HOME/Projects
export GOROOT=/usr/local/go
export GOPATH=$HOME/Projects
export PATH=$GOPATH/bin:$GOROOT/bin:$PATH
go -version
cd sparkctl
go build -o sparkctl
sudo mv sparkctl /usr/local/bin

Ας εξετάσουμε τη λίστα των εργασιών Spark που εκτελούνται:

sparkctl list -n {project}

Ας δημιουργήσουμε μια περιγραφή για την εργασία Spark:

vi spark-app.yaml

apiVersion: "sparkoperator.k8s.io/v1beta1"
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: {project}
spec:
  type: Scala
  mode: cluster
  image: "gcr.io/spark-operator/spark:v2.4.0"
  imagePullPolicy: Always
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.11-2.4.0.jar"
  sparkVersion: "2.4.0"
  restartPolicy:
    type: Never
  volumes:
    - name: "test-volume"
      hostPath:
        path: "/tmp"
        type: Directory
  driver:
    cores: 1
    coreLimit: "1000m"
    memory: "512m"
    labels:
      version: 2.4.0
    serviceAccount: spark
    volumeMounts:
      - name: "test-volume"
        mountPath: "/tmp"
  executor:
    cores: 1
    instances: 1
    memory: "512m"
    labels:
      version: 2.4.0
    volumeMounts:
      - name: "test-volume"
        mountPath: "/tmp"

Ας εκτελέσουμε την περιγραφόμενη εργασία χρησιμοποιώντας sparkctl:

sparkctl create spark-app.yaml -n {project}

Ας εξετάσουμε τη λίστα των εργασιών Spark που εκτελούνται:

sparkctl list -n {project}

Ας εξετάσουμε τη λίστα συμβάντων μιας εργασίας Spark που ξεκίνησε:

sparkctl event spark-pi -n {project} -f

Ας εξετάσουμε την κατάσταση της εργασίας Spark που εκτελείται:

sparkctl status spark-pi -n {project}

Εν κατακλείδι, θα ήθελα να εξετάσω τα ανακαλυφθέντα μειονεκτήματα της χρήσης της τρέχουσας σταθερής έκδοσης του Spark (2.4.5) στο Kubernetes:

  1. Το πρώτο και, ίσως, κύριο μειονέκτημα είναι η έλλειψη Τοπικότητας Δεδομένων. Παρά όλες τις αδυναμίες του YARN, υπήρχαν επίσης πλεονεκτήματα στη χρήση του, για παράδειγμα, η αρχή της παράδοσης κώδικα σε δεδομένα (και όχι από δεδομένα σε κώδικα). Χάρη σε αυτό, οι εργασίες Spark εκτελέστηκαν στους κόμβους όπου βρίσκονταν τα δεδομένα που εμπλέκονται στους υπολογισμούς και ο χρόνος που χρειάστηκε για την παράδοση δεδομένων μέσω του δικτύου μειώθηκε σημαντικά. Όταν χρησιμοποιούμε το Kubernetes, αντιμετωπίζουμε την ανάγκη να μετακινήσουμε δεδομένα που εμπλέκονται σε μια εργασία στο δίκτυο. Εάν είναι αρκετά μεγάλα, ο χρόνος εκτέλεσης της εργασίας μπορεί να αυξηθεί σημαντικά και, επίσης, απαιτείται αρκετά μεγάλος χώρος στο δίσκο που εκχωρείται σε στιγμιότυπα εργασιών Spark για την προσωρινή τους αποθήκευση. Αυτό το μειονέκτημα μπορεί να μετριαστεί χρησιμοποιώντας εξειδικευμένο λογισμικό που διασφαλίζει την εντοπιότητα δεδομένων στο Kubernetes (για παράδειγμα, Alluxio), αλλά αυτό σημαίνει στην πραγματικότητα την ανάγκη αποθήκευσης ενός πλήρους αντιγράφου των δεδομένων στους κόμβους του συμπλέγματος Kubernetes.
  2. Το δεύτερο σημαντικό μειονέκτημα είναι η ασφάλεια. Από προεπιλογή, οι λειτουργίες που σχετίζονται με την ασφάλεια σχετικά με την εκτέλεση εργασιών Spark είναι απενεργοποιημένες, η χρήση του Kerberos δεν καλύπτεται στην επίσημη τεκμηρίωση (αν και οι αντίστοιχες επιλογές εισήχθησαν στην έκδοση 3.0.0, η οποία θα απαιτήσει πρόσθετη εργασία) και η τεκμηρίωση ασφαλείας για χρησιμοποιώντας το Spark (https ://spark.apache.org/docs/2.4.5/security.html) μόνο το YARN, το Mesos και το Standalone Cluster εμφανίζονται ως καταστήματα κλειδιών. Ταυτόχρονα, ο χρήστης υπό τον οποίο εκκινούνται οι εργασίες Spark δεν μπορεί να καθοριστεί άμεσα - καθορίζουμε μόνο τον λογαριασμό υπηρεσίας με τον οποίο θα λειτουργεί και ο χρήστης επιλέγεται με βάση τις διαμορφωμένες πολιτικές ασφαλείας. Από αυτή την άποψη, είτε χρησιμοποιείται ο χρήστης root, ο οποίος δεν είναι ασφαλής σε ένα παραγωγικό περιβάλλον, είτε ένας χρήστης με τυχαίο UID, το οποίο δεν είναι βολικό κατά τη διανομή δικαιωμάτων πρόσβασης σε δεδομένα (αυτό μπορεί να επιλυθεί δημιουργώντας PodSecurityPolicies και συνδέοντάς τα με το αντίστοιχους λογαριασμούς υπηρεσιών). Επί του παρόντος, η λύση είναι είτε να τοποθετήσετε όλα τα απαραίτητα αρχεία απευθείας στην εικόνα του Docker είτε να τροποποιήσετε το σενάριο εκκίνησης Spark για να χρησιμοποιήσετε τον μηχανισμό αποθήκευσης και ανάκτησης μυστικών που έχει υιοθετηθεί στον οργανισμό σας.
  3. Η εκτέλεση εργασιών Spark χρησιμοποιώντας το Kubernetes είναι επίσημα ακόμα σε πειραματική λειτουργία και ενδέχεται να υπάρξουν σημαντικές αλλαγές στα τεχνουργήματα που χρησιμοποιούνται (αρχεία διαμόρφωσης, εικόνες βάσης Docker και σενάρια εκκίνησης) στο μέλλον. Και πράγματι, κατά την προετοιμασία του υλικού, δοκιμάστηκαν οι εκδόσεις 2.3.0 και 2.4.5, η συμπεριφορά ήταν σημαντικά διαφορετική.

Ας περιμένουμε ενημερώσεις - κυκλοφόρησε πρόσφατα μια νέα έκδοση του Spark (3.0.0), η οποία έφερε σημαντικές αλλαγές στο έργο του Spark στο Kubernetes, αλλά διατήρησε την πειραματική κατάσταση υποστήριξης για αυτόν τον διαχειριστή πόρων. Ίσως οι επόμενες ενημερώσεις θα επιτρέψουν πραγματικά να προτείνουμε πλήρως την εγκατάλειψη του YARN και την εκτέλεση εργασιών Spark στο Kubernetes χωρίς φόβο για την ασφάλεια του συστήματός σας και χωρίς την ανάγκη να τροποποιήσετε ανεξάρτητα λειτουργικά στοιχεία.

Πτερύγιο.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο