Lafen Apache Spark op Kubernetes

Léif Lieser, Gudde Mëtteg. Haut schwätze mir e bëssen iwwer Apache Spark a seng Entwécklungsperspektiven.

Lafen Apache Spark op Kubernetes

An der moderner Welt vu Big Data ass Apache Spark den de facto Standard fir Batchdatenveraarbechtungsaufgaben z'entwéckelen. Zousätzlech gëtt et och benotzt fir Streaming Uwendungen ze kreéieren déi am Micro Batch Konzept schaffen, Veraarbechtung a Versanddaten a klenge Portiounen (Spark Structured Streaming). An traditionell war et Deel vum Gesamthadoop Stack, mat YARN (oder an e puer Fäll Apache Mesos) als Ressource Manager. Bis 2020 ass seng Benotzung a senger traditioneller Form a Fro fir déi meescht Firmen wéinst dem Mangel u anstännegen Hadoop Verdeelungen - d'Entwécklung vun HDP an CDH ass gestoppt, CDH ass net gutt entwéckelt an huet héich Käschten, an déi verbleiwen Hadoop Fournisseuren hunn entweder opgehalen ze existéieren oder eng däischter Zukunft hunn. Dofir ass de Start vum Apache Spark mat Kubernetes vun ëmmer méi Interesse tëscht der Gemeinschaft a grousse Firmen - gëtt e Standard an der Containerorchestratioun a Ressourceverwaltung a privaten an ëffentleche Wolleken, et léist de Problem mat onbequem Ressourceplang vun Spark Aufgaben op YARN a bitt eng stänneg Entwécklungsplattform mat ville kommerziellen an oppe Verdeelunge fir Firmen vun alle Gréissten a Sträifen. Zousätzlech, no der Popularitéit, hunn déi meescht et scho fäerdeg bruecht e puer eegen Installatiounen ze kréien an hunn hir Expertise a senger Benotzung erhéicht, wat d'Beweegung vereinfacht.

Ugefaange mat der Versioun 2.3.0, huet Apache Spark offiziell Ënnerstëtzung fir Aufgaben an engem Kubernetes Cluster opkaf an haut wäerte mir iwwer déi aktuell Reife vun dëser Approche schwätzen, verschidde Méiglechkeeten fir seng Notzung a Fallfäegkeeten déi während der Ëmsetzung begéint ginn.

Als éischt kucke mer de Prozess vun der Entwécklung vun Aufgaben an Uwendungen baséiert op Apache Spark a markéiert typesch Fäll an deenen Dir eng Aufgab op engem Kubernetes Cluster muss lafen. Beim Virbereedung vun dësem Post gëtt OpenShift als Verdeelung benotzt a Befehle relevant fir säi Kommandozeil Utility (oc) ginn uginn. Fir aner Kubernetes Verdeelunge kënnen déi entspriechend Kommandoen aus dem Standard Kubernetes Kommandozeil Utility (kubectl) oder hir Analoga (zum Beispill fir oc adm Politik) benotzt ginn.

Éischt Benotzung Fall - Spark-submit

Wärend der Entwécklung vun Aufgaben an Uwendungen muss den Entwéckler Aufgaben ausféieren fir Datentransformatioun ze debuggen. Theoretesch kënne Stécker fir dës Zwecker benotzt ginn, awer d'Entwécklung mat der Participatioun vun realen (och wann Test) Instanzen vun Endsystemer huet sech méi séier a besser an dëser Klass vun Aufgaben bewisen. Am Fall wa mir op realen Instanzen vun Endsystemer debuggen, sinn zwee Szenarie méiglech:

  • den Entwéckler leeft eng Spark Aufgab lokal am Standalone Modus;

    Lafen Apache Spark op Kubernetes

  • en Entwéckler leeft eng Spark Task op engem Kubernetes Cluster an enger Testschleife.

    Lafen Apache Spark op Kubernetes

Déi éischt Optioun huet e Recht ze existéieren, awer enthält eng Rei Nodeeler:

  • All Entwéckler muss Zougang vun der Aarbechtsplaz op all Instanzen vun den Endsystemer ginn, déi hien brauch;
  • eng genuch Betrag u Ressourcen ass op der Aarbechtsmaschinn erfuerderlech fir d'Aufgab ze lafen déi entwéckelt gëtt.

Déi zweet Optioun huet dës Nodeeler net, well d'Benotzung vun engem Kubernetes Cluster erlaabt Iech den néidege Ressource Pool fir d'Lafen Aufgaben ze verdeelen an et den néidegen Zougang zu Enn System Instanzen ze bidden, flexibel Zougang dozou ze bidden mat dem Kubernetes Rollmodell fir all Member vun der Entwécklung Equipe. Loosst eis et als den éischte Gebrauchsfall markéieren - Spark Aufgaben vun enger lokaler Entwécklermaschinn op engem Kubernetes-Cluster an engem Testkrees starten.

Loosst eis méi iwwer de Prozess schwätzen fir Spark opzestellen fir lokal ze lafen. Fir Spark ze benotzen, musst Dir se installéieren:

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

Mir sammelen déi néideg Packagen fir mat Kubernetes ze schaffen:

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

E komplette Bau brauch vill Zäit, a fir Docker-Biller ze kreéieren an se op engem Kubernetes-Cluster auszeféieren, braucht Dir wierklech nëmmen Jar-Dateien aus dem "Assemblée/" Verzeichnis, sou datt Dir nëmmen dësen Ënnerprojet bauen kann:

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

Fir Spark Jobs op Kubernetes ze lafen, musst Dir en Docker-Bild erstellen fir als Basisbild ze benotzen. Et ginn 2 méiglech Approche hei:

  • Dat generéiert Docker-Bild enthält den ausführbare Spark Taskcode;
  • Dat erstallt Bild enthält nëmmen Spark an déi néideg Ofhängegkeeten, den ausführbare Code gëtt op afstand gehost (zum Beispill an HDFS).

Als éischt, loosst eis en Docker-Bild bauen mat engem Testbeispiel vun enger Spark Task. Fir Docker Biller ze kreéieren, huet Spark en Utility mam Numm "docker-image-tool". Loosst eis d'Hëllef doriwwer studéieren:

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

Mat senger Hëllef kënnt Dir Docker Biller erstellen an se op Fernregistréiere eroplueden, awer par défaut huet et eng Rei Nodeeler:

  • ouni Feeler erstellt 3 Docker Biller op eemol - fir Spark, PySpark a R;
  • erlaabt Iech net e Bild Numm ze uginn.

Dofir benotze mir eng modifizéiert Versioun vun dësem Utility hei ënnendrënner:

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

Mat senger Hëllef versammele mir e Basis Spark Bild mat enger Testaufgab fir Pi mat Spark ze berechnen (hei {docker-registry-url} ass d'URL vun Ärem Docker Bildregister, {repo} ass den Numm vum Repository am Registry, déi mam Projet an OpenShift entsprécht, {image-name} - Numm vum Bild (wann d'Drei-Niveau Trennung vu Biller benotzt gëtt, zum Beispill, wéi an der integréierter Registry vu Red Hat OpenShift Biller), {tag} - Tag vun dësem Versioun vum Bild):

./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

Mellt Iech an den OKD Cluster mat der Konsol Utility un (hei {OKD-API-URL} ass d'OKD Cluster API URL):

oc login {OKD-API-URL}

Loosst eis den aktuelle Benotzer Token fir Autorisatioun an der Docker Registry kréien:

oc whoami -t

Loggt Iech an d'intern Docker Registry vum OKD Cluster un (mir benotzen den Token, dee mam fréiere Kommando als Passwuert kritt gëtt):

docker login {docker-registry-url}

Loosst eis dat versammelt Docker-Bild op den Docker Registry OKD eropluede:

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

Loosst eis kucken ob dat gesammelt Bild an OKD verfügbar ass. Fir dëst ze maachen, öffnen d'URL am Browser mat enger Lëscht vun de Biller vum entspriechende Projet (hei ass {project} den Numm vum Projet am OpenShift-Cluster, {OKD-WEBUI-URL} ass d'URL vun der OpenShift Webkonsole ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Fir Aufgaben auszeféieren, muss e Servicekonto erstallt ginn mat de Privilegien fir Pods als Root ze lafen (mir diskutéieren dëse Punkt méi spéit):

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

Loosst eis de Spark-Submit Kommando ausféieren fir eng Spark Task op den OKD Cluster ze publizéieren, de erstallt Service Kont an Docker Bild ze spezifizéieren:

 /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

Hei:

—Numm — den Numm vun der Aufgab, déi un der Formation vum Numm vun de Kubernetes Pods deelhëllt;

-class - Klass vun der ausführbarer Datei, genannt wann d'Aufgab ufänkt;

-conf - Spark Konfiguratiounsparameter;

spark.executor.instances - d'Zuel vun de Spark Exekutoren fir ze starten;

spark.kubernetes.authenticate.driver.serviceAccountName - den Numm vum Kubernetes Service Kont deen benotzt gëtt wann Dir Pods lancéiert (fir de Sécherheetskontext a Fäegkeeten ze definéieren wann Dir mat der Kubernetes API interagéiert);

spark.kubernetes.namespace - Kubernetes Nummraum an deem Chauffeur an Exekutor Pods lancéiert ginn;

spark.submit.deployMode - Methode fir Spark ze lancéieren (fir Standard Spark-submit "Cluster" gëtt benotzt, fir Spark Operator a spéider Versioune vum Spark "Client");

spark.kubernetes.container.image - Docker Bild benotzt fir Pods ze starten;

spark.master - Kubernetes API URL (extern ass spezifizéiert sou datt den Zougang vun der lokaler Maschinn geschitt);

local:// ass de Wee zum Spark ausführbar am Docker Bild.

Mir ginn op den entspriechende OKD Projet a studéiert déi erstallt Pods - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Fir den Entwécklungsprozess ze vereinfachen, kann eng aner Optioun benotzt ginn, an där e gemeinsamt Basisbild vu Spark erstallt gëtt, vun all Aufgaben benotzt fir ze lafen, a Snapshots vun ausführbare Dateie ginn op extern Späichere publizéiert (zum Beispill Hadoop) a spezifizéiert wann Dir rufft spark-submit als Link. An dësem Fall kënnt Dir verschidde Versioune vu Spark Aufgaben lafen ouni Docker Biller opzebauen, zum Beispill WebHDFS benotzt fir Biller ze publizéieren. Mir schécken eng Ufro fir e Fichier ze kreéieren (hei ass {host} den Host vum WebHDFS Service, {port} ass den Hafen vum WebHDFS Service, {path-to-file-on-hdfs} ass de gewënschten Wee fir d'Datei op HDFS):

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

Dir kritt eng Äntwert wéi dës (hei {Location} ass d'URL déi benotzt muss ginn fir d'Datei erofzelueden):

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

Lued d'Spark ausführbar Datei an HDFS (hei {path-to-local-file} ass de Wee op d'Spark ausführbar Datei um aktuellen Host):

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

Duerno kënne mir Spark-Submit maachen mat der Spark Datei déi op HDFS eropgelueden ass (hei ass {class-name} den Numm vun der Klass déi lancéiert muss ginn fir d'Aufgab ofzeschléissen):

/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}

Et sollt bemierkt datt fir Zougang zu HDFS ze kréien an ze garantéieren datt d'Aufgab funktionnéiert, musst Dir d'Dockerfile an den entrypoint.sh Skript änneren - eng Direktiv an d'Dockerfile addéieren fir ofhängeg Bibliothéiken an den /opt/spark/jars Verzeichnis ze kopéieren an enthält d'HDFS Konfiguratiounsdatei an SPARK_CLASSPATH am Entréespunkt. sh.

Zweet Benotzungsfall - Apache Livy

Weider, wann eng Aufgab entwéckelt gëtt an d'Resultat muss getest ginn, stellt sech d'Fro op, et als Deel vum CI / CD Prozess ze lancéieren an de Status vu senger Ausféierung ze verfolgen. Natierlech kënnt Dir et mat engem lokalen Spark-Sender Uruff lafen, awer dëst komplizéiert d'CI / CD Infrastruktur well et erfuerdert Spark op den CI Server Agenten / Leefer z'installéieren an ze konfiguréieren an den Zougang zu der Kubernetes API opzestellen. Fir dëse Fall huet d'Zilimplementatioun gewielt Apache Livy als REST API ze benotzen fir Spark Aufgaben ze lafen, déi an engem Kubernetes Cluster gehost ginn. Mat senger Hëllef kënnt Dir Spark Aufgaben op engem Kubernetes Stärekoup mat regelméissege cURL Ufroen lafen, déi einfach op all CI Léisung implementéiert gëtt, a seng Plaz am Kubernetes Cluster léist d'Fro vun der Authentifikatioun beim Interaktioun mat der Kubernetes API.

Lafen Apache Spark op Kubernetes

Loosst eis et als zweete Benotzungsfall ervirhiewen - Lafen Spark Aufgaben als Deel vun engem CI / CD Prozess op engem Kubernetes Cluster an enger Testschleife.

E bëssen iwwer Apache Livy - et funktionnéiert als HTTP-Server deen e Web-Interface an e RESTful API ubitt, deen Iech erlaabt Spark-Submit op afstand ze starten andeems Dir déi néideg Parameteren passéiert. Traditionell ass et als Deel vun enger HDP Verdeelung verschéckt ginn, awer kann och op OKD oder all aner Kubernetes Installatioun ofgesat ginn mat dem passenden Manifest an engem Set vun Docker Biller, sou wéi dësen - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Fir eise Fall gouf en ähnlecht Docker Bild gebaut, dorënner Spark Versioun 2.4.5 vun der folgender 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"]

Dat generéiert Bild kann gebaut an eropgeluede ginn op Ärem existente Docker Repository, sou wéi den internen OKD Repository. Fir et z'installéieren, benotzt de folgende Manifest ({registry-url} - URL vum Docker-Bildregistrierung, {image-name} - Docker-Bildnumm, {tag} - Docker-Bildtag, {livy-url} - gewënschte URL wou de Server gëtt zougänglech Livy; de "Route" Manifest gëtt benotzt wann Red Hat OpenShift als Kubernetes Verdeelung benotzt gëtt, soss gëtt de entspriechende Ingress oder Service Manifest vum Typ NodePort benotzt):

---
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

Nodeems Dir et ugewannt hutt an de Pod erfollegräich lancéiert huet, ass de Livy grafeschen Interface um Link verfügbar: http://{livy-url}/ui. Mat Livy kënne mir eis Spark Task verëffentlechen mat enger REST Ufro vum zum Beispill Postman. E Beispill vun enger Sammlung mat Ufroe gëtt hei ënnen presentéiert (Konfiguratiounsargumenter mat Variabelen déi néideg sinn fir d'Operatioun vun der lancéierter Aufgab kënnen an der Array "args" weidergeleet ginn):

{
    "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": {}
}

Loosst eis déi éischt Ufro vun der Sammlung ausféieren, gitt op d'OKD Interface a kontrolléiert datt d'Aufgab erfollegräich gestart gouf - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Zur selwechter Zäit erschéngt eng Sessioun an der Livy Interface (http://{livy-url}/ui), an där Dir mat der Livy API oder grafescher Interface de Fortschrëtt vun der Aufgab verfolgt an d'Sessioun studéiert Logbicher.

Loosst eis elo weisen wéi de Livy funktionnéiert. Fir dëst ze maachen, loosst eis d'Logbicher vum Livy Container am Pod mam Livy Server ënnersichen - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=Logbicher. Vun hinnen kënne mir gesinn datt wann Dir de Livy REST API an engem Container mam Numm "livy" rufft, e Spark-Submit ausgefouert gëtt, ähnlech wéi dee mir hei uewen benotzt hunn (hei {livy-pod-name} ass den Numm vum erstallten Pod mam Livy Server). D'Sammlung stellt och eng zweet Ufro vir, déi Iech erlaabt Aufgaben auszeféieren, déi e Spark ausféierbar op afstand mat engem Livy Server hosten.

Drëtt Benotzungsfall - Spark Operator

Elo datt d'Aufgab getest gouf, stellt sech d'Fro fir se regelméisseg ze lafen. Déi gebierteg Manéier fir regelméisseg Aufgaben an engem Kubernetes Cluster ze lafen ass d'CronJob Entitéit an Dir kënnt et benotzen, awer de Moment ass d'Benotzung vun den Opérateuren fir Applikatiounen a Kubernetes ze managen ganz populär a fir Spark gëtt et e relativ reife Bedreiwer, deen och benotzt an Enterprise-Niveau Léisungen (zum Beispill, Lightbend FastData Plattform). Mir recommandéieren et ze benotzen - déi aktuell stabil Versioun vu Spark (2.4.5) huet zimlech limitéiert Konfiguratiounsoptioune fir Spark Aufgaben a Kubernetes ze lafen, während déi nächst grouss Versioun (3.0.0) voll Ënnerstëtzung fir Kubernetes deklaréiert, awer säi Verëffentlechungsdatum bleift onbekannt . Spark Operator kompenséiert dës Defizit andeems Dir wichteg Konfiguratiounsoptiounen bäidréit (zum Beispill eng ConfigMap mat Hadoop Zougangskonfiguratioun op Spark Pods montéieren) an d'Fäegkeet fir eng regelméisseg geplangte Aufgab auszeféieren.

Lafen Apache Spark op Kubernetes
Loosst eis et als drëtten Benotzungsfall markéieren - regelméisseg Lafen Spark Aufgaben op engem Kubernetes Cluster an enger Produktiounsschleife.

Spark Operator ass Open Source an entwéckelt bannent der Google Cloud Plattform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Seng Installatioun kann op 3 Weeër gemaach ginn:

  1. Als Deel vun der Installatioun vu Lightbend FastData Plattform / Cloudflow;
  2. Benotzt Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Mat Manifestatiounen aus dem offiziellen Repository (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Et ass derwäert déi folgend ze notéieren - Cloudflow enthält en Bedreiwer mat der API Versioun v1beta1. Wann dës Zort Installatioun benotzt gëtt, sollten d'Spark Applikatioun Manifest Beschreiwunge op Beispill Tags am Git mat der entspriechender API Versioun baséieren, zum Beispill "v1beta1-0.9.0-2.4.0". D'Bedreiwer Versioun kann an der Beschreiwung vum CRD fonnt ginn, deen am Bedreiwer am "Versiounen" Wierderbuch abegraff ass:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Wann de Bedreiwer richteg installéiert ass, erschéngt en aktive Pod mam Spark Bedreiwer am entspriechende Projet (zum Beispill Cloudflow-fdp-sparkoperator am Cloudflow Raum fir d'Cloudflow Installatioun) an e entspriechende Kubernetes Ressourcentyp mam Numm "sparkpplications" erschéngt . Dir kënnt verfügbar Spark Uwendungen mat dem folgenden Kommando entdecken:

oc get sparkapplications -n {project}

Fir Aufgaben mam Spark Operator auszeféieren, musst Dir 3 Saache maachen:

  • en Docker-Bild erstellen deen all déi néideg Bibliothéiken enthält, souwéi Konfiguratiouns- an ausführbar Dateien. Am Zilbild ass dëst e Bild erstallt op der CI/CD-Bühn an op engem Testcluster getest;
  • Verëffentlechen en Docker Image zu engem Registry zougänglech aus dem Kubernetes Cluster;
  • Generéiere engem Manifest mat der "SparkApplication" Typ an eng Beschreiwung vun der Aufgab lancéiert ginn. Beispill Manifestatiounen sinn am offiziellen Repository verfügbar (z. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Et gi wichteg Punkten iwwer de Manifest ze notéieren:
    1. d'Wörterbuch "apiVersion" muss d'API Versioun uginn, déi der Bedreiwer Versioun entsprécht;
    2. de Wierderbuch "metadata.namespace" muss den Nummraum uginn an deem d'Applikatioun lancéiert gëtt;
    3. de "spec.image" Wierderbuch muss d'Adress vum erstallten Docker-Bild an engem zougänglechen Registry enthalen;
    4. der "spec.mainClass" Wierderbuch muss der Spark Aufgab Klass enthalen datt lafen muss wann de Prozess fänkt;
    5. de Wee op d'ausführbar Jar-Datei muss am "spec.mainApplicationFile" Wierderbuch spezifizéiert ginn;
    6. de "spec.sparkVersion" Wierderbuch muss d'Versioun vum Spark uginn, déi benotzt gëtt;
    7. de "spec.driver.serviceAccount" Wierderbuch muss de Servicekonto am entspriechende Kubernetes Nummraum spezifizéieren deen benotzt gëtt fir d'Applikatioun ze lafen;
    8. d'Wierderbuch "spec.executor" muss d'Zuel vun de Ressourcen un der Applikatioun zougewisen uginn;
    9. de "spec.volumeMounts" Wierderbuch muss de lokalen Dossier spezifizéieren an deem déi lokal Spark Taskdateien erstallt ginn.

E Beispill fir e Manifest ze generéieren (hei {spark-service-account} ass e Servicekonto am Kubernetes Cluster fir Spark Aufgaben ze lafen):

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"

Dëse Manifest spezifizéiert e Servicekonto fir deen, ier Dir de Manifest verëffentlechen, déi néideg Rollebindungen erstellen, déi déi néideg Zougangsrechter fir d'Spark-Applikatioun ubidden fir mat der Kubernetes API ze interagéieren (wann néideg). An eisem Fall brauch d'Applikatioun Rechter fir Pods ze kreéieren. Loosst eis déi néideg Rollverbindung erstellen:

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

Et ass och derwäert ze notéieren datt dës Manifest Spezifizéierung e "hadoopConfigMap" Parameter enthält, deen Iech erlaabt eng ConfigMap mat der Hadoop Konfiguratioun ze spezifizéieren ouni déi entspriechend Datei als éischt am Docker Bild ze placéieren. Et ass och gëeegent fir regelméisseg Aufgaben ze lafen - mam "Schedule" Parameter kann e Zäitplang fir eng bestëmmten Aufgab ausféieren.

Duerno späichere mir eise Manifest an d'Spark-pi.yaml Datei an applizéieren se op eise Kubernetes Cluster:

oc apply -f spark-pi.yaml

Dëst wäert en Objet vum Typ "Sparkpplications" erstellen:

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

An dësem Fall gëtt e Pod mat enger Applikatioun erstallt, de Status vun deem an den erstallten "Sparkpplications" ugewise gëtt. Dir kënnt et mat dem folgenden Kommando kucken:

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

Nom Ofschloss vun der Aufgab wäert de POD op de Status "Fäerdeg" réckelen, deen och an "Sparkpplications" aktualiséiert gëtt. Applikatioun Logbicher kënnen am Browser gekuckt ginn oder de folgende Kommando benotzen (hei {sparkapplications-pod-name} ass den Numm vum Pod vun der lafender Aufgab):

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

Spark Aufgaben kënnen och mat dem spezialiséierte sparkctl Utility verwalt ginn. Fir et z'installéieren, klon de Repository mat sengem Quellcode, installéiert Go a baut dësen Utility:

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

Loosst eis d'Lëscht vun de lafende Spark Aufgaben ënnersichen:

sparkctl list -n {project}

Loosst eis eng Beschreiwung fir d'Spark Aufgab erstellen:

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"

Loosst eis déi beschriwwe Aufgab mat sparkctl ausféieren:

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

Loosst eis d'Lëscht vun de lafende Spark Aufgaben ënnersichen:

sparkctl list -n {project}

Loosst eis d'Lëscht vun den Eventer vun enger lancéierter Spark Task ënnersichen:

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

Loosst eis de Status vun der lafender Spark Task ënnersichen:

sparkctl status spark-pi -n {project}

Als Conclusioun géif ech gär d'entdeckte Nodeeler vun der Benotzung vun der aktueller stabiler Versioun vu Spark (2.4.5) an Kubernetes betruechten:

  1. Den éischten a vläicht Haaptnodeel ass de Mangel u Datenlokalitéit. Trotz all de Mängel vum YARN, waren et och Virdeeler fir et ze benotzen, zum Beispill de Prinzip fir Code op Daten ze liwweren (anstatt Daten ze Code). Dank et goufen Spark Aufgaben op den Noden ausgefouert, wou d'Donnéeën, déi an de Berechnungen involvéiert sinn, lokaliséiert goufen, an d'Zäit déi et gedauert huet fir Daten iwwer d'Netz ze liwweren ass wesentlech reduzéiert. Wann Dir Kubernetes benotzt, si mir konfrontéiert mat der Bedierfnes fir Daten, déi an enger Aufgab iwwer d'Netz involvéiert sinn, ze plënneren. Wa se grouss genuch sinn, kann d'Task Ausféierungszäit erheblech eropgoen, an och eng zimlech grouss Quantitéit un Disk Space erfuerderen, déi un Spark Task Instanzen zougewisen ass fir hir temporär Späichere. Dësen Nodeel kann ofgeschaaft ginn andeems Dir spezialiséiert Software benotzt, déi d'Datenlokalitéit an Kubernetes garantéiert (zum Beispill Alluxio), awer dat heescht eigentlech d'Noutwendegkeet fir eng komplett Kopie vun den Donnéeën op de Wirbelen vum Kubernetes-Cluster ze späicheren.
  2. Déi zweet wichteg Nodeel ass Sécherheet. Par défaut sinn d'Sécherheetsbezunnen Features betreffend Spark Aufgaben ausgeschalt, d'Benotzung vu Kerberos ass net an der offizieller Dokumentatioun ofgedeckt (obwuel déi entspriechend Optiounen an der Versioun 3.0.0 agefouert goufen, déi zousätzlech Aarbecht erfuerderen), an d'Sécherheetsdokumentatioun fir benotzt Spark (https://spark.apache.org/docs/2.4.5/security.html) nëmmen YARN, Mesos a Standalone Cluster erschéngen als Schlësselgeschäfter. Zur selwechter Zäit kann de Benotzer ënner deem Spark Aufgaben lancéiert ginn, net direkt spezifizéiert ginn - mir spezifizéieren nëmmen de Servicekonto ënner deem et funktionnéiert, an de Benotzer gëtt ausgewielt op Basis vun der konfiguréierter Sécherheetspolitik. An dëser Hisiicht gëtt entweder de Root-Benotzer benotzt, deen net sécher ass an engem produktiven Ëmfeld, oder e Benotzer mat enger zoufälleger UID, wat onbequem ass wann Dir Zougangsrechter op Daten verdeelt (dëst kann geléist ginn andeems Dir PodSecurityPolicies erstellt an se mat der Verknëppung vun der entspriechend Servicekonten). De Moment ass d'Léisung entweder all déi néideg Dateien direkt an d'Docker-Bild ze placéieren, oder de Spark-Startskript ze änneren fir de Mechanismus ze benotzen fir Geheimnisser ze späicheren an zréckzezéien, déi an Ärer Organisatioun ugeholl goufen.
  3. Spark Jobs mat Kubernetes lafen ass offiziell nach ëmmer am experimentellen Modus an et kënne bedeitend Ännerungen an den benotzten Artefakte ginn (Konfiguratiounsdateien, Docker Base Biller, a Startskripter) an der Zukunft. An tatsächlech, wann d'Material virbereet, Versiounen 2.3.0 an 2.4.5 getest goufen, war d'Verhalen wesentlech anescht.

Loosst eis op Updates waarden - eng nei Versioun vu Spark (3.0.0) gouf viru kuerzem verëffentlecht, wat bedeitend Ännerungen an der Aarbecht vu Spark op Kubernetes bruecht huet, awer den experimentellen Status vun der Ënnerstëtzung fir dëse Ressourcemanager behalen. Vläicht wäerten déi nächst Updates et wierklech maachen et méiglech ze recommandéieren d'YARN opzeginn an d'Spark Aufgaben op Kubernetes ze lafen ouni Angscht fir d'Sécherheet vun Ärem System an ouni d'Notzung fir onofhängeg funktionell Komponenten z'änneren.

Fin.

Source: will.com

Setzt e Commentaire