Hardloop Apache Spark op Kubernetes

Beste lesers, goeie middag. Vandag sal ons 'n bietjie praat oor Apache Spark en sy ontwikkelingsvooruitsigte.

Hardloop Apache Spark op Kubernetes

In die moderne wêreld van Big Data is Apache Spark die de facto standaard vir die ontwikkeling van bondeldataverwerkingstake. Daarbenewens word dit ook gebruik om stromingstoepassings te skep wat in die mikro-joernaalkonsep werk, verwerking en versending van data in klein porsies (Spark Structured Streaming). En tradisioneel was dit deel van die algehele Hadoop-stapel, met YARN (of in sommige gevalle Apache Mesos) as die hulpbronbestuurder. Teen 2020 is die gebruik daarvan in sy tradisionele vorm 'n groot vraagteken vir die meeste maatskappye weens die gebrek aan ordentlike Hadoop-verspreidings - die ontwikkeling van HDP en CDH het gestop, CDH is onvoldoende ontwikkel en het 'n hoë koste, en die oorblywende Hadoop-verskaffers óf ophou bestaan ​​óf ’n dowwe toekoms het. Daarom is die bekendstelling van Apache Spark met Kubernetes van toenemende belangstelling onder die gemeenskap en groot maatskappye - dit word 'n standaard in houerorkestrasie en hulpbronbestuur in private en publieke wolke, dit los die probleem van ongerieflike hulpbronskedulering van Spark-take op YARN op en bied 'n voortdurend ontwikkelende platform met baie kommersiële en oop verspreidings vir maatskappye van alle groottes en strepe. Daarbenewens, in die nasleep van gewildheid, het die meeste reeds daarin geslaag om 'n paar installasies van hul eie te bekom en het hul kundigheid in die gebruik daarvan vergroot, wat die skuif vergemaklik.

Begin met weergawe 2.3.0, het Apache Spark amptelike ondersteuning verkry vir die uitvoer van take in 'n Kubernetes-kluster en vandag sal ons praat oor die huidige volwassenheid van hierdie benadering, verskeie opsies vir die gebruik daarvan en slaggate wat tydens implementering teëgekom sal word.

Kom ons kyk eerstens na die proses van die ontwikkeling van take en toepassings gebaseer op Apache Spark en beklemtoon tipiese gevalle waarin u 'n taak op 'n Kubernetes-kluster moet uitvoer. In die voorbereiding van hierdie pos word OpenShift as 'n verspreiding gebruik en opdragte wat relevant is vir sy opdragreëlnuts (oc) sal gegee word. Vir ander Kubernetes-verspreidings kan die ooreenstemmende opdragte van die standaard Kubernetes-opdragreëlnutsmiddel (kubectl) of hul analoë (byvoorbeeld vir oc adm-beleid) gebruik word.

Eerste gebruik geval - vonk-indien

Tydens die ontwikkeling van take en toepassings moet die ontwikkelaar take uitvoer om datatransformasie te ontfout. Teoreties kan stompe vir hierdie doeleindes gebruik word, maar ontwikkeling met die deelname van werklike (alhoewel toets) gevalle van eindstelsels het bewys dat dit vinniger en beter is in hierdie klas take. In die geval wanneer ons op werklike gevalle van eindstelsels ontfout, is twee scenario's moontlik:

  • die ontwikkelaar voer 'n Spark-taak plaaslik in selfstandige modus uit;

    Hardloop Apache Spark op Kubernetes

  • 'n ontwikkelaar hardloop 'n Spark-taak op 'n Kubernetes-kluster in 'n toetslus.

    Hardloop Apache Spark op Kubernetes

Die eerste opsie het 'n bestaansreg, maar hou 'n aantal nadele in:

  • Elke ontwikkelaar moet vanaf die werkplek van toegang voorsien word tot alle gevalle van die eindstelsels wat hy benodig;
  • 'n voldoende hoeveelheid hulpbronne word op die werkende masjien benodig om die taak wat ontwikkel word, uit te voer.

Die tweede opsie het nie hierdie nadele nie, aangesien die gebruik van 'n Kubernetes-kluster jou in staat stel om die nodige hulpbronpoel toe te wys vir lopende take en dit van die nodige toegang tot eindstelselgevalle te voorsien, wat buigsaam toegang daartoe verskaf deur die Kubernetes-rolmodel vir alle lede van die ontwikkelingspan. Kom ons beklemtoon dit as die eerste gebruiksgeval – die bekendstelling van Spark-take vanaf 'n plaaslike ontwikkelaarmasjien op 'n Kubernetes-kluster in 'n toetslus.

Kom ons praat meer oor die proses om Spark op te stel om plaaslik te werk. Om Spark te begin gebruik, moet jy dit installeer:

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

Ons versamel die nodige pakkette om met Kubernetes te werk:

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

'N Volledige bou neem baie tyd, en om Docker-beelde te skep en dit op 'n Kubernetes-kluster te laat loop, benodig jy eintlik net jar-lêers van die "assembly/"-gids, so jy kan net hierdie subprojek bou:

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

Om Spark-take op Kubernetes te laat loop, moet jy 'n Docker-prent skep om as 'n basisprent te gebruik. Daar is 2 moontlike benaderings hier:

  • Die gegenereerde Docker-beeld bevat die uitvoerbare Spark-taakkode;
  • Die geskepte beeld bevat slegs Spark en die nodige afhanklikhede, die uitvoerbare kode word op afstand gehuisves (byvoorbeeld in HDFS).

Kom ons bou eers 'n Docker-beeld wat 'n toetsvoorbeeld van 'n Spark-taak bevat. Om Docker-beelde te skep, het Spark 'n nut genaamd "docker-image-tool". Kom ons bestudeer die hulp daarop:

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

Met sy hulp kan u Docker-beelde skep en dit na afgeleë registers oplaai, maar dit het standaard 'n aantal nadele:

  • skep sonder versuim 3 Docker-beelde gelyktydig - vir Spark, PySpark en R;
  • laat jou nie toe om 'n prentnaam te spesifiseer nie.

Daarom sal ons 'n gewysigde weergawe van hierdie hulpmiddel gebruik wat hieronder gegee word:

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

Met die hulp daarvan stel ons 'n basiese Spark-beeld saam wat 'n toetstaak bevat vir die berekening van Pi met Spark (hier is {docker-registry-url} die URL van jou Docker-beeldregister, {repo} is die naam van die bewaarplek binne die register, wat ooreenstem met die projek in OpenShift , {image-name} - naam van die beeld (as drie-vlak skeiding van beelde gebruik word, byvoorbeeld, soos in die geïntegreerde register van Red Hat OpenShift beelde), {tag} - tag van hierdie weergawe van die prent):

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

Meld aan by die OKD-groepering met behulp van die konsole-hulpmiddel (hier is {OKD-API-URL} die OKD-kluster-API-URL):

oc login {OKD-API-URL}

Kom ons kry die huidige gebruiker se teken vir magtiging in die Docker-register:

oc whoami -t

Meld aan by die interne Docker-register van die OKD-groepering (ons gebruik die teken wat verkry is met die vorige opdrag as die wagwoord):

docker login {docker-registry-url}

Kom ons laai die saamgestelde Docker-beeld op na die Docker Registry OKD:

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

Kom ons kyk of die saamgestelde prent in OKD beskikbaar is. Om dit te doen, maak die URL in die blaaier oop met 'n lys prente van die ooreenstemmende projek (hier is {project} die naam van die projek binne die OpenShift-groepering, {OKD-WEBUI-URL} is die URL van die OpenShift-webkonsole ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Om take uit te voer, moet 'n diensrekening geskep word met die voorregte om peule as wortel te laat loop (ons sal hierdie punt later bespreek):

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

Kom ons voer die vonk-indien-opdrag uit om 'n Spark-taak na die OKD-kluster te publiseer, en spesifiseer die geskepde diensrekening en Docker-beeld:

 /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

hier:

—naam — die naam van die taak wat sal deelneem aan die vorming van die naam van die Kubernetes-peule;

—klas — klas van die uitvoerbare lêer, genoem wanneer die taak begin;

—conf — Spark-konfigurasieparameters;

spark.executor.instances — die aantal Spark-eksekuteurs om te begin;

spark.kubernetes.authenticate.driver.serviceAccountName - die naam van die Kubernetes-diensrekening wat gebruik word wanneer peule begin word (om die sekuriteitskonteks en vermoëns te definieer wanneer daar met die Kubernetes API interaksie is);

spark.kubernetes.namespace — Kubernetes naamruimte waarin drywer- en eksekuteurspeule geloods sal word;

spark.submit.deployMode — metode om Spark te begin (vir standaard vonk-indien word “cluster” gebruik, vir Spark Operator en latere weergawes van Spark “kliënt”);

spark.kubernetes.container.image - Docker-beeld wat gebruik word om peule te begin;

spark.master - Kubernetes API URL (ekstern word gespesifiseer sodat toegang vanaf die plaaslike masjien plaasvind);

local:// is die pad na die Spark-uitvoerbare in die Docker-beeld.

Ons gaan na die ooreenstemmende OKD-projek en bestudeer die geskepde peule - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Om die ontwikkelingsproses te vereenvoudig, kan 'n ander opsie gebruik word, waarin 'n algemene basisbeeld van Spark geskep word, wat deur alle take gebruik word om uit te voer, en momentopnames van uitvoerbare lêers na eksterne berging (byvoorbeeld Hadoop) gepubliseer word en gespesifiseer word wanneer dit gebel word vonk-submit as 'n skakel. In hierdie geval kan jy verskillende weergawes van Spark-take laat loop sonder om Docker-beelde te herbou, deur byvoorbeeld WebHDFS te gebruik om beelde te publiseer. Ons stuur 'n versoek om 'n lêer te skep (hier is {host} die gasheer van die WebHDFS-diens, {port} is die poort van die WebHDFS-diens, {path-to-file-on-hdfs} is die verlangde pad na die lêer op HDFS):

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

Jy sal 'n antwoord soos hierdie ontvang (hier {location} is die URL wat gebruik moet word om die lêer af te laai):

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

Laai die Spark-uitvoerbare lêer in HDFS (hier is {path-to-local-file} die pad na die Spark-uitvoerbare lêer op die huidige gasheer):

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

Hierna kan ons vonk-indiening doen met die Spark-lêer wat na HDFS opgelaai is (hier is {klasnaam} die naam van die klas wat geloods moet word om die taak te voltooi):

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

Daar moet kennis geneem word dat om toegang tot HDFS te verkry en te verseker dat die taak werk, jy dalk die Dockerfile en die entrypoint.sh script moet verander - voeg 'n opdrag by die Dockerfile om afhanklike biblioteke na die /opt/spark/jars gids te kopieer en sluit die HDFS-konfigurasielêer in SPARK_CLASSPATH by toegangspunt in. sh.

Tweede gebruik geval - Apache Livy

Verder, wanneer 'n taak ontwikkel word en die resultaat getoets moet word, ontstaan ​​die vraag om dit as deel van die CI/CD-proses te begin en die status van die uitvoering daarvan op te spoor. Natuurlik kan jy dit laat loop deur 'n plaaslike vonk-indien-oproep te gebruik, maar dit bemoeilik die CI/CD-infrastruktuur aangesien dit die installering en konfigurasie van Spark op die CI-bedieneragente/hardlopers vereis en toegang tot die Kubernetes API opstel. Vir hierdie geval het die teikenimplementering gekies om Apache Livy as 'n REST API te gebruik om Spark-take uit te voer wat binne 'n Kubernetes-kluster aangebied word. Met sy hulp kan jy Spark-take op 'n Kubernetes-kluster laat loop deur gereelde cURL-versoeke te gebruik, wat maklik geïmplementeer word op grond van enige CI-oplossing, en die plasing daarvan binne die Kubernetes-kluster los die kwessie van verifikasie op wanneer daar met die Kubernetes API interaksie is.

Hardloop Apache Spark op Kubernetes

Kom ons lig dit uit as 'n tweede gebruiksgeval - hardloop Spark-take as deel van 'n CI/CD-proses op 'n Kubernetes-kluster in 'n toetslus.

'n Bietjie oor Apache Livy - dit werk as 'n HTTP-bediener wat 'n webkoppelvlak en 'n RESTful API bied wat jou in staat stel om spark-submit op afstand te begin deur die nodige parameters deur te gee. Tradisioneel is dit as deel van 'n HDP-verspreiding gestuur, maar kan ook na OKD of enige ander Kubernetes-installasie ontplooi word deur die toepaslike manifes en 'n stel Docker-beelde te gebruik, soos hierdie een - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Vir ons geval is 'n soortgelyke Docker-beeld gebou, insluitend Spark weergawe 2.4.5 vanaf die volgende 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"]

Die gegenereerde beeld kan gebou en opgelaai word na u bestaande Docker-bewaarplek, soos die interne OKD-bewaarplek. Om dit te ontplooi, gebruik die volgende manifes ({registry-url} - URL van die Docker-beeldregister, {image-name} - Docker-prentnaam, {tag} - Docker-beeldmerker, {livy-url} - gewenste URL waar die bediener sal toeganklik wees Livy; die "Route"-manifes word gebruik as Red Hat OpenShift as die Kubernetes-verspreiding gebruik word, anders word die ooreenstemmende Ingress- of Service-manifes van tipe NodePort gebruik):

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

Nadat u dit toegepas het en die pod suksesvol begin het, is die Livy-grafiese koppelvlak beskikbaar by die skakel: http://{livy-url}/ui. Met Livy kan ons ons Spark-taak publiseer deur 'n REST-versoek van, byvoorbeeld, Postman. 'n Voorbeeld van 'n versameling met versoeke word hieronder aangebied (konfigurasie-argumente met veranderlikes wat nodig is vir die werking van die geloodsde taak kan in die "args"-skikking deurgegee word):

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

Kom ons voer die eerste versoek uit die versameling uit, gaan na die OKD-koppelvlak en maak seker dat die taak suksesvol van stapel gestuur is - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Terselfdertyd sal 'n sessie in die Livy-koppelvlak (http://{livy-url}/ui) verskyn, waarbinne jy, met behulp van die Livy API of grafiese koppelvlak, die vordering van die taak kan naspoor en die sessie kan bestudeer logs.

Kom ons wys nou hoe Livy werk. Om dit te doen, kom ons ondersoek die logs van die Livy-houer binne-in die pod met die Livy-bediener - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Van hulle kan ons sien dat wanneer die Livy REST API in 'n houer genaamd "livy" geroep word, 'n vonk-indiening uitgevoer word, soortgelyk aan die een wat ons hierbo gebruik het (hier is {livy-pod-name} die naam van die geskepde peul met die Livy-bediener). Die versameling stel ook 'n tweede navraag bekend wat jou toelaat om take uit te voer wat 'n Spark-uitvoerbare op afstand huisves met 'n Livy-bediener.

Derde gebruik geval - Spark Operator

Noudat die taak getoets is, ontstaan ​​die vraag om dit gereeld uit te voer. Die inheemse manier om take gereeld in 'n Kubernetes-kluster uit te voer is die CronJob-entiteit en jy kan dit gebruik, maar op die oomblik is die gebruik van operateurs om toepassings in Kubernetes te bestuur baie gewild en vir Spark is daar 'n redelik volwasse operateur, wat ook gebruik in oplossings op ondernemingsvlak (byvoorbeeld Lightbend FastData Platform). Ons beveel aan om dit te gebruik - die huidige stabiele weergawe van Spark (2.4.5) het taamlik beperkte konfigurasie-opsies om Spark-take in Kubernetes uit te voer, terwyl die volgende groot weergawe (3.0.0) volle ondersteuning vir Kubernetes verklaar, maar die vrystellingsdatum daarvan bly onbekend . Spark Operator vergoed vir hierdie tekortkoming deur belangrike konfigurasie-opsies by te voeg (byvoorbeeld om 'n ConfigMap met Hadoop-toegangkonfigurasie by Spark-peule te monteer) en die vermoë om 'n gereelde geskeduleerde taak uit te voer.

Hardloop Apache Spark op Kubernetes
Kom ons beklemtoon dit as 'n derde gebruiksgeval - gereelde loop van Spark-take op 'n Kubernetes-kluster in 'n produksielus.

Spark Operator is oopbron en ontwikkel binne die Google Wolk-platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Die installasie daarvan kan op 3 maniere gedoen word:

  1. As deel van die installering van Lightbend FastData Platform/Cloudflow;
  2. Gebruik Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Gebruik manifeste van die amptelike bewaarplek (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Dit is opmerklik op die volgende - Cloudflow bevat 'n operateur met API-weergawe v1beta1. As hierdie tipe installasie gebruik word, moet Spark-toepassingmanifesbeskrywings gebaseer wees op voorbeeldmerkers in Git met die toepaslike API-weergawe, byvoorbeeld, "v1beta1-0.9.0-2.4.0". Die weergawe van die operateur kan gevind word in die beskrywing van die CRD ingesluit in die operateur in die "weergawes" woordeboek:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

As die operateur korrek geïnstalleer is, sal 'n aktiewe peul met die Spark-operateur in die ooreenstemmende projek verskyn (byvoorbeeld, cloudflow-fdp-sparkoperator in die Cloudflow-spasie vir die Cloudflow-installasie) en 'n ooreenstemmende Kubernetes-hulpbrontipe genaamd "sparkapplications" sal verskyn . Jy kan beskikbare Spark-toepassings verken met die volgende opdrag:

oc get sparkapplications -n {project}

Om take met Spark Operator uit te voer, moet jy 3 dinge doen:

  • skep 'n Docker-beeld wat al die nodige biblioteke insluit, sowel as konfigurasie- en uitvoerbare lêers. In die teikenprent is dit 'n beeld wat by die CI/CD-stadium geskep is en op 'n toetsgroep getoets is;
  • publiseer 'n Docker-beeld na 'n register wat toeganklik is vanaf die Kubernetes-kluster;
  • genereer 'n manifes met die tipe "SparkApplication" en 'n beskrywing van die taak wat van stapel gestuur moet word. Voorbeeldmanifes is beskikbaar in die amptelike bewaarplek (bv. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Daar is belangrike punte om op te let oor die manifes:
    1. die "apiVersion" woordeboek moet die API weergawe aandui wat ooreenstem met die operateur weergawe;
    2. die “metadata.namespace”-woordeboek moet die naamruimte aandui waarin die toepassing geloods gaan word;
    3. die "spec.image" woordeboek moet die adres van die geskep Docker beeld in 'n toeganklike register bevat;
    4. die "spec.mainClass"-woordeboek moet die Spark-taakklas bevat wat uitgevoer moet word wanneer die proses begin;
    5. die pad na die uitvoerbare jar-lêer moet in die "spec.mainApplicationFile"-woordeboek gespesifiseer word;
    6. die "spec.sparkVersion" woordeboek moet die weergawe van Spark wat gebruik word, aandui;
    7. die “spec.driver.serviceAccount”-woordeboek moet die diensrekening spesifiseer binne die ooreenstemmende Kubernetes-naamruimte wat gebruik sal word om die toepassing te laat loop;
    8. die “spec.executor”-woordeboek moet die aantal hulpbronne wat aan die aansoek toegeken is, aandui;
    9. die "spec.volumeMounts" woordeboek moet die plaaslike gids spesifiseer waarin die plaaslike Spark taaklêers geskep sal word.

'n Voorbeeld van die generering van 'n manifes (hier is {spark-service-account} 'n diensrekening binne die Kubernetes-groepering om Spark-take uit te voer):

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"

Hierdie manifes spesifiseer 'n diensrekening waarvoor jy, voordat jy die manifes publiseer, die nodige rolbindings moet skep wat die nodige toegangsregte verskaf vir die Spark-toepassing om met die Kubernetes API te kommunikeer (indien nodig). In ons geval het die toepassing regte nodig om Pods te skep. Kom ons skep die nodige rolbinding:

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

Dit is ook opmerklik dat hierdie manifesspesifikasie 'n "hadoopConfigMap"-parameter kan insluit, wat jou toelaat om 'n ConfigMap met die Hadoop-konfigurasie te spesifiseer sonder om eers die ooreenstemmende lêer in die Docker-beeld te plaas. Dit is ook geskik om take gereeld uit te voer - deur die "skedule" parameter te gebruik, kan 'n skedule vir die uitvoer van 'n gegewe taak gespesifiseer word.

Daarna stoor ons ons manifes in die spark-pi.yaml-lêer en pas dit toe op ons Kubernetes-kluster:

oc apply -f spark-pi.yaml

Dit sal 'n voorwerp van tipe "sparkapplications" skep:

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

In hierdie geval sal 'n pod met 'n toepassing geskep word, waarvan die status in die geskepte "sparkapplications" vertoon sal word. Jy kan dit sien met die volgende opdrag:

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

Na voltooiing van die taak, sal die POD na die "Voltooid"-status beweeg, wat ook in "sparkapplications" sal opdateer. Toepassingsloglêers kan in die blaaier bekyk word of met die volgende opdrag (hier is {sparkapplications-pod-name} die naam van die pod van die lopende taak):

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

Spark-take kan ook bestuur word met behulp van die gespesialiseerde sparkctl-nutsding. Om dit te installeer, kloon die bewaarplek met sy bronkode, installeer Go en bou hierdie nutsprogram:

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

Kom ons ondersoek die lys van lopende Spark-take:

sparkctl list -n {project}

Kom ons skep 'n beskrywing vir die Spark-taak:

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"

Kom ons voer die beskryf taak uit met sparkctl:

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

Kom ons ondersoek die lys van lopende Spark-take:

sparkctl list -n {project}

Kom ons ondersoek die lys gebeure van 'n geloodsde Spark-taak:

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

Kom ons ondersoek die status van die lopende Spark-taak:

sparkctl status spark-pi -n {project}

Ten slotte wil ek die ontdekte nadele van die gebruik van die huidige stabiele weergawe van Spark (2.4.5) in Kubernetes oorweeg:

  1. Die eerste en miskien grootste nadeel is die gebrek aan datalokaliteit. Ten spyte van al die tekortkominge van YARN, was daar ook voordele daaraan verbonde om dit te gebruik, byvoorbeeld die beginsel om kode aan data te lewer (eerder as data na kode). Danksy dit is Spark-take uitgevoer op die nodusse waar die data betrokke by die berekeninge geleë is, en die tyd wat dit geneem het om data oor die netwerk te lewer, is aansienlik verminder. Wanneer ons Kubernetes gebruik, word ons gekonfronteer met die behoefte om data betrokke by 'n taak oor die netwerk te skuif. As hulle groot genoeg is, kan die taakuitvoeringstyd aansienlik toeneem, en vereis ook 'n redelike groot hoeveelheid skyfspasie wat aan Spark-taakgevalle toegewys is vir hul tydelike berging. Hierdie nadeel kan versag word deur gespesialiseerde sagteware te gebruik wat datalokaliteit in Kubernetes verseker (byvoorbeeld Alluxio), maar dit beteken eintlik die behoefte om 'n volledige kopie van die data op die nodusse van die Kubernetes-kluster te stoor.
  2. Die tweede belangrike nadeel is sekuriteit. By verstek is sekuriteitverwante kenmerke met betrekking tot die uitvoer van Spark-take gedeaktiveer, die gebruik van Kerberos word nie in die amptelike dokumentasie gedek nie (hoewel die ooreenstemmende opsies in weergawe 3.0.0 bekendgestel is, wat bykomende werk sal verg), en die sekuriteitsdokumentasie vir deur Spark (https://spark.apache.org/docs/2.4.5/security.html) te gebruik, verskyn slegs YARN, Mesos en Standalone Cluster as sleutelwinkels. Terselfdertyd kan die gebruiker onder wie Spark-take geloods word nie direk gespesifiseer word nie - ons spesifiseer slegs die diensrekening waaronder dit sal werk, en die gebruiker word gekies op grond van die gekonfigureerde sekuriteitsbeleide. In hierdie verband word óf die wortelgebruiker gebruik, wat nie veilig is in 'n produktiewe omgewing nie, óf 'n gebruiker met 'n ewekansige UID, wat ongerieflik is wanneer toegangsregte tot data versprei word (dit kan opgelos word deur PodSecurityPolicies te skep en dit te koppel aan die ooreenstemmende diensrekeninge). Tans is die oplossing om óf al die nodige lêers direk in die Docker-beeld te plaas, óf die Spark-bekendstellingskrip te verander om die meganisme te gebruik vir die stoor en herwinning van geheime wat in jou organisasie aangeneem is.
  3. Die uitvoering van Spark-take met Kubernetes is amptelik nog in eksperimentele modus en daar kan in die toekoms aansienlike veranderinge wees in die artefakte wat gebruik word (konfigurasielêers, Docker-basisbeelde en bekendstellingsskrifte). En inderdaad, wanneer die materiaal voorberei is, is weergawes 2.3.0 en 2.4.5 getoets, die gedrag was aansienlik anders.

Kom ons wag vir opdaterings - 'n nuwe weergawe van Spark (3.0.0) is onlangs vrygestel, wat aansienlike veranderinge aan die werk van Spark op Kubernetes gebring het, maar die eksperimentele status van ondersteuning vir hierdie hulpbronbestuurder behou het. Miskien sal die volgende opdaterings dit regtig moontlik maak om ten volle aan te beveel om YARN te laat vaar en Spark-take op Kubernetes te laat loop sonder om te vrees vir die sekuriteit van u stelsel en sonder dat u funksionele komponente onafhanklik hoef te verander.

Die einde.

Bron: will.com

Voeg 'n opmerking