KĂ€ivitame Apache Spark'i Kubernetesel

Kallid lugejad, tere pÀevast. TÀna rÀÀgime natuke Apache Sparkist ja selle arenguvÔimalustest.

KĂ€ivitame Apache Spark'i Kubernetesel

TĂ€napĂ€eva Big Data maailmas on Apache Spark de facto standard andmete partii töötlemise ĂŒlesannete arendamisel. Lisaks sellele kasutatakse seda ka mikropartite kontseptsioonis töötavate voograkenduste loomiseks, mis töötlevad ja edastavad andmeid vĂ€ikeste portsjonitena (Spark Structured Streaming). Traditsiooniliselt on see olnud osa Hadoopi kogumist, kasutades ressursihaldurina YARNi (vĂ”i mĂ”nel juhul Apache Mesost). Aastaks 2020 on selle traditsiooniline kasutamine enamikus ettevĂ”tetes suurtes kahtlustes, kuna puuduvad head Hadoopi jaotused - HDP ja CDH arendamine on peatunud, CDH on ebapiisavalt arendatud ja tal on kĂ”rge hind, samas kui teised Hadoopi pakkujad on kas oma tegevuse lĂ”petanud vĂ”i nende tulevik on Ă€hmane. SeetĂ”ttu tĂ”mbab Apache Spark Kubernetesega kĂ€ivitamine ĂŒha rohkem huvi nii kogukonnas kui suurtes ettevĂ”tetes - olles konteinerite orkestreerimise ja ressursside haldamise standard eras ja avalikes pilvedes, lahendab see YARNil pĂ”hinevate Spark'i ĂŒlesannete ressursside plaani keerukuse ning pakub stabiilselt arenevat platvormi mitmesuguste kommertslike ja avatud jaotustega kĂ”igile ettevĂ”tetele. Eriti populaarseks saades on enamik juba suutnud luua paar kolmandikku oma installatsioonidest ja suurendada oma ekspertiisi selle kasutamisel, mis lihtsustab ĂŒleminekut.

Alates versioonist 2.3.0 on Apache Spark saanud ametliku toe Kubernetes klastris ĂŒlesannete kĂ€itamise jaoks ja tĂ€na rÀÀgime selle lĂ€henemise praegusest kĂŒpsusest, erinevatest kasutusvĂ”imalustest ja probleemidest, millega tuleb silmitsi seista juurutamisel.

Esiteks vaatame, kuidas luua ĂŒlesandeid ja rakendusi Apache Sparkil ning toome vĂ€lja tĂŒĂŒpilised juhtumid, kus on vajalik kĂ€ivitada ĂŒlesanne Kubernetes klastris. Selle postituse ettevalmistamisel kasutatakse jaotust OpenShift ja tuuakse vĂ€lja kĂ€sud, mis on vajalikud selle kĂ€surea tööriista (oc) jaoks. Teiste Kubernetes jaotuste puhul vĂ”ivad olla kasutatud vastavaid standardse Kubernetes kĂ€surea tööriista (kubectl) kĂ€ske vĂ”i nende analooge (nĂ€iteks oc adm policy).

Esimene kasutusvariant — spark-submit

Arendusprotsessis tuleb arendajal kĂ€ivitada ĂŒlesandeid andmete transformatsiooni tĂ”rkeotsimiseks. Teoreetiliselt vĂ”iks nende eesmĂ€rkide jaoks kasutada ka simulaatoreid, kuid arendamine reaalse (kuigi testimise) lĂ”pp-sĂŒsteemi eksemplaridega on selles klassis ĂŒlesannetes osutunud kiiremaks ja kvaliteetsemaks. Kui me teostame tĂ”rkeotsingut reaalses lĂ”pp-sĂŒsteemi eksemplaris, on vĂ”imalikud kaks tegevusskeemi:

  • arendaja kĂ€ivitab Spark ĂŒlesande kohalikult standalone-reĆŸiimis;

    KĂ€ivitame Apache Spark'i Kubernetesel

  • arendaja kĂ€ivitab Spark ĂŒlesande Kubernetes klastris testkeskkonnas.

    KĂ€ivitame Apache Spark'i Kubernetesel

Esimene variant on teostatav, kuid sellega kaasnevad mitmed puudused:

  • iga arendaja peab tagama juurdepÀÀsu kĂ”igile vajalikutele lĂ”pp-sĂŒsteemi eksemplaridele oma tööjaamast;
  • tööarvutis peavad olema piisavalt ressursse arendatava ĂŒlesande kĂ€itamiseks.

Teine variant on vabastatud nendest puudustest, kuna Kubernetes klastrite kasutamine vĂ”imaldab eraldada vajalikku ressursipooli ĂŒlesannete kĂ€itamiseks ja tagada juurdepÀÀsu lĂ”pp-sĂŒsteemi eksemplaridele, pakkudes sellele juurdepÀÀsu paindlikult Kubernetes'i rollipĂ”hise mudeli kaudu kĂ”igile arendustiimi liikmetele. RĂ”hutame seda kui esimest kasutusvarianti — Spark ĂŒlesannete kĂ€itamine arendaja kohalikust masinast Kubernetes klastris testkeskkonnas.

RÀÀgime lÀhemalt Spark seadistamise protsessist kohalikuks kÀitamiseks. Spark'i kasutamiseks tuleb see installida:

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

Koostame Kubernetes'e tööks vajalikud paketid:

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

TÀispakkumine vÔtab aega, ja Docker'i piltide loomine ja nende kÀitamine Kubernetes klastris vajab tegelikult ainult 'assembly/' kataloogist jar-faile, seega vÔime kokku panna ainult selle alaprojekti:

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

Spark'i ĂŒlesannete kĂ€itamiseks Kubernetes'is on vajalik luua Docker'i pilt, mida kasutatakse alusena. Siin on vĂ”imalikke kaks lĂ€henemist:

  • Loodud Docker'i pilt sisaldab Spark'i ĂŒlesande teostuskoodi;
  • Loodud pilt sisaldab ainult Spark'i ja vajalikke sĂ”ltuvusi, sealhulgas kĂ€ivitatavat koodi, mis on kaugserveris (nĂ€iteks HDFS).

Alustame Docker pildi loomist, mis sisaldab Spark'i testĂŒlesande nĂ€idet. Spark'il on Docker piltide loomiseks sobiv tööriist nimega „docker-image-tool“. Vaatame selle juhendit:

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

Selle abil saab luua Docker pilte ja laadida need kaugregistritesse, kuid vaikimisi on sellel mitmeid puudusi:

  • see nĂ”uab, et luuakse kohe 3 Docker pilti — Spark'i, PySpark'i ja R'i jaoks;
  • see ei vĂ”imalda mÀÀrata pildi nime.

SeetÔttu kasutame allpool toodud muudetud versiooni sellest tööriistast:

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

Selle abil koostame Spark'i baaspildi, mis sisaldab testĂŒlesannet Pi arvu arvutamiseks Spark'i abil (siin {docker-registry-url} — teie Docker pildi registri URL, {repo} — registri sees olev repo nimi, mis vastab OpenShift'i projektile, {image-name} — pildi nimi (kui kasutatakse kolmeastmelist pildi eraldamist, nĂ€iteks Red Hat OpenShift'i integreeritud pildiregistris), {tag} — selle pildi versiooni silt):

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

Logime sisse OKD klastrisse kĂ€surea tööriista abil (siin {OKD-API-URL} — OKD klastri API URL):

oc login {OKD-API-URL}

Saame praeguse kasutaja tokeni Docker Registry'ga autentimiseks:

oc whoami -t

Logime sisse OKD klastri sisemisse Docker Registry'sse (paroolina kasutame eelneva kÀsu kaudu saadud tokenit):

docker login {docker-registry-url}

Laeme koostatud Docker pildi OKD Docker Registry'sse:

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

Kontrollime, et loodud pilt on OKD-s saadaval. Selleks avame brauseris vastava projekti piltide loendi URL (siin {project} — OpenShift'i klastris asuva projekti nimi, {OKD-WEBUI-URL} — OpenShift'i veebi konsooli URL) — https:\/{OKD-WEBUI-URL}\/console\/project\/ {project}\/browse\/images\/ {image-name}.

Ülesannete kĂ€ivitamiseks peab olema loodud teenuse konto, millel on Ă”igused konteinerite kĂ€ivitamiseks root'is (arutame seda hiljem):

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

Kasutame spark-submit kĂ€sku Spark'i ĂŒlesande esitamiseks OKD klastris, mÀÀrates loodud teenuse konto ja Docker pildi:

 /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

Siin:

—name — ĂŒlesande nimi, mis osaleb Kubernetes'i konteinerite nimede koostamisel;

—klass — kĂ€ivitatava faili klass, mida kutsutakse ĂŒlesande kĂ€ivitamisel;

—conf — Spark'i konfiguratsiooniparametrid;

spark.executor.instances — Spark'i kĂ€ivitatavate executorite arv;

spark.kubernetes.authenticate.driver.serviceAccountName — Kubernetes'i teenuse konto nimi, mida kasutatakse podide kĂ€ivitamisel (turvakonteksti ja Ă”iguste mÀÀramiseks Kubernetes'i API'ga suhtlemisel);

spark.kubernetes.namespace — Kubernetes'i nimede ruum, kus kĂ€ivitatakse juhi ja executorite podid;

spark.submit.deployMode — Spark'i kĂ€ivitamise meetod (standardse spark-submit jaoks kasutatakse "cluster", Spark Operatori ja hilisemate Spark'i versioonide jaoks "client");

spark.kubernetes.container.image — Docker'i pilt, mida kasutatakse podide kĂ€ivitamiseks;

spark.master — Kubernetes'i API URL (mille kaudu toimub vĂ€liskutsumine lokaalsest masinast);

local:// — tee Spark'i kĂ€ivitatavale failile Docker'i pildis.

Liigume vastavasse OKD projekti ja uurime loodud pode — https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Arendamisprotsessi lihtsustamiseks vĂ”ib kasutada veel ĂŒhte varianti, mille puhul luuakse ĂŒhine pĂ”hikujundus Spark'i jaoks, mida kĂ”ik ĂŒlesanded kasutavad kĂ€ivitamiseks, ja kĂ€ivitatavate failide snapshots avaldatakse vĂ€listesse hoidlatest (nĂ€iteks Hadoop) ning osutatakse spark-submit'i kutsumisel linkidena. Sel juhul saab kĂ€ivitada erinevaid Spark'i ĂŒlesande versioone ilma Docker'i piltide taasloomiseta, kasutades pilte avaldamiseks nĂ€iteks WebHDFS'i. Saata eraldusloome faili loomise pĂ€ring (siin {host} — WebHDFS teenuse host, {port} — WebHDFS teenuse port, {path-to-file-on-hdfs} — soovitud tee faili kohta HDFS-is):

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

Sel juhul saadakse vastus sellise sisuga (siin {location} — see on URL, mida tuleb faili ĂŒleslaadimiseks kasutada):

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

Laeme Spark'i kĂ€ivitatava faili HDFS-i (siin {path-to-local-file} — tee Spark'i kĂ€ivitatavale failile praeguses hostis):

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

PĂ€rast seda saame teha spark-submit'i Spark'i failiga, mis on HDFS-i ĂŒles laaditud (siin {class-name} — klassi nimi, mida tuleb kĂ€ivitada ĂŒlesande tĂ€itmiseks):

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

Sel juhul tuleb mĂ€rkida, et HDFS-i juurdepÀÀsuks ja ĂŒlesande toimimise tagamiseks vĂ”ib olla vajalik muuta Dockerfile'i ja entrypoint.sh skripti — lisada Dockerfile'isse direktiiv sĂ”ltuvate teekide kopeerimiseks katalooge /opt/spark/jars ning HDFS'i konfiguratsioonifaili lisamiseks SPARK_CLASSPATH'sse entrypoint.sh-s.

Teine kasutusvariatsioon — Apache Livy

JĂ€rgmine, kui ĂŒlesanne on vĂ€lja töötatud ja saadud tulemuse testimine on vajalik, tekib kĂŒsimus selle kĂ€ivitamise kohta CI/CD protsessi raames ja selle tĂ€itmise staatuste jĂ€lgimise kohta. Loomulikult on vĂ”imalik seda kĂ€ivitada ka kohaliku spark-submit kutsumisega, kuid see keerukalt CI/CD infrastruktuuri, kuna see nĂ”uab Spark'i installimist ja konfigureerimist CI serveri agentides / jooksjates ning juurdepÀÀsu seadistamist Kubernetes API-le. Antud juhul on sihiks valitud Apache Livy kasutamine REST API-na Spark'i ĂŒlesannete kĂ€ivitamiseks, mis on paigutatud Kubernetes klastrisse. Selle abil saab kĂ€ivitada Spark'i ĂŒlesandeid Kubernetes klastris tavaliste cURL pĂ€ringute abil, mis on hĂ”lpsasti rakendatav igasuguste CI lahenduste alusel, ning selle paigutamine Kubernetes klastrisse lahendab autentimise kĂŒsimuse Kubernetes API-ga suhtlemisel.

KĂ€ivitame Apache Spark'i Kubernetesel

MÀÀrame selle teiseks kasutusvariatsiooniks — Spark'i ĂŒlesannete kĂ€itamine CI/CD protsessi raames Kubernetes klastris testimiskeskkonnas.

Veidi Apache Livy kohta — see töötab nagu HTTP server, pakkudes veebiliidest ja RESTful API-d, mis vĂ”imaldab kaugel kĂ€ivitada spark-submit, edastades vajalikud parameetrid. Traditsiooniliselt on see olnud osa HDP jaotusest, kuid seda saab ka kĂ€ivitada OKD-s vĂ”i mis tahes muus Kubernetesi installatsioonis vastava manifesti ja Docker'i piltide abil, nĂ€iteks see — github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Meie juhtumi jaoks on koostatud sarnane Docker'i pilt, mis sisaldab Spark'i versiooni 2.4.5 jĂ€rgmise Dockerfile'i alusel:

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

Loodud pilt saab koguda ja laadida teie olemasolevasse Docker'i registrisse, nĂ€iteks sisemine OKD registri. Selle juurutamiseks kasutatakse jĂ€rgmist manifesti ({registry-url} — Docker'i pildi registri URL, {image-name} — Docker'i pildi nimi, {tag} — Docker'i pildi silt, {livy-url} — soovitud URL, kust Livy server on saadaval; 'Route' manifesti rakendatakse juhul, kui kasutatakse Red Hat OpenShift'i Kubernetes'i jaotust, vastasel juhul kasutatakse sobivat Ingress vĂ”i NodePort tĂŒĂŒpi Service manifesti):

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

PĂ€rast selle rakendamist ja poda edukat kĂ€ivitamist on Livy graafiline liides saadaval lingil: http://{livy-url}/ui. Livy abil saame oma Spark ĂŒlesande avaldada, kasutades REST pĂ€ringut, nĂ€iteks Postmanist. Allpool on esitatud pĂ€ringute kogumi nĂ€ide (massiivis "args" saab edastada konfiguratsiooniargumente koos muutujaitega, mis on vajalikud kĂ€ivitatava ĂŒlesande toimimiseks):

{
    "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 Esita tööd jar'iga",
            "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 Esita tööd ilma jar'ita",
            "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": {}
}

Teeme esimese pĂ€ringu kogust, lĂ€heme OKD liidesesse ja kontrollime, et töö on edukalt kĂ€ivitatud — https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Samuti ilmub Livy liidesesse (http://{livy-url}/ui) seanss, mille raames saab kasutada Livy API-d vĂ”i graafilist liidest töö kĂ€igu jĂ€lgimiseks ja seansi logide uurimiseks.

NĂŒĂŒd tutvustame Livy töömehhanismi. Selleks vaatame Livy konteineri logisid Livy serveri podis — https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name}?tab=logs. Logidest selgub, et kui kutsuda vĂ€lja Livy REST API, siis konteineris nimega „livy“ kĂ€ivitatakse spark-submit, mis on sarnane sellele, mida kasutasime varem (siin {livy-pod-name} on loodud Livy serveri podi nimi). Kollektsioon sisaldab ka teist pĂ€ringut, mis vĂ”imaldab kĂ€ivitada ĂŒlesandeid kaugrakenduse Spark tĂ€itmise failiga Livy serveri kaudu.

Kolmas kasutusvariant — Spark Operator

NĂŒĂŒd, kui ĂŒlesanne on testitud, tekib kĂŒsimus selle regulaarse kĂ€ivitamise kohta. Kuberneteses ĂŒlesannete regulaarseks kĂ€ivitamiseks on natiivne mehhanism CronJob ja seda vĂ”ib kasutada, kuid hetkel on suurem populaarsus operaatoreid, mis hallata rakendusi Kuberneteses, ja Sparkile on olemas piisavalt kĂŒps operaator, mida kasutatakse ka ettevĂ”tte taseme lahendustes (nĂ€iteks Lightbend FastData Platform). Soovitame seda kasutada — praegune stabiilne Spark versioon (2.4.5) on ĂŒsna piiratud ĂŒlesannete konfigureerimise vĂ”imalustes Kuberneteses, samas kui jĂ€rgmises suuremas versioonis (3.0.0) on lubatud tĂ€ielik Kubernetes toe, kuid selle vĂ€ljaande kuupĂ€ev on endiselt teadmata. Spark Operator kompenseerib selle puuduse, lisades olulised konfigureerimise parameetrid (nĂ€iteks ConfigMapi mĂ€lu, mis sisaldab juurdepÀÀsu Hadoopile Spark podides) ja vĂ”imaluse regulaarseks ĂŒlesande kĂ€ivitamiseks graafiku alusel.

KĂ€ivitame Apache Spark'i Kubernetesel
RĂ”hutame seda kui kolmandat kasutusvarianti — Spark ĂŒlesannete regulaarne kĂ€ivitamine Kubernetes klastris tootmiskeskkonnas.

Spark Operator on avatud lĂ€htekoodiga ja seda arendatakse Google Cloud Platformi raames — github.com/GoogleCloudPlatform/spark-on-k8s-operator. Selle installimist saab teostada kolmel viisil:

  1. Lightbend FastData Platformi/Cloudflow raames;
  2. Kasutades Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Manifesteid rakendades ametlikust hoidlast (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Tasub mĂ€rkida, et Cloudflow's on operaatorkomplekt versiooniga API v1beta1. Kui seda tĂŒĂŒpi paigaldust kasutatakse, peavad Spark rakenduste manifeste olema koostatud vastavalt Git'i siltide nĂ€idistele koos vastava API versiooniga, nĂ€iteks 'v1beta1-0.9.0-2.4.0'. Operaatorkomplekti versiooni saab vaadata CRD kirjelduse kaudu, mis kuulub operaatorkomplekti sĂ”nastikus 'versions':
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Kui operaatorkomplekt on Ă”igesti paigaldatud, ilmub vastavasse projekti aktiivne Spark operaatori pod (nĂ€iteks cloudflow-fdp-sparkoperator Cloudflow' sĂŒsteemis) ja ilmub vastav Kubernetes'i ressursitĂŒĂŒp nimega 'sparkapplications'. Olemasolevaid Spark rakendusi saab uurida jĂ€rgmise kĂ€suga:

oc get sparkapplications -n {project}

Spark Operator'iga ĂŒlesannete kĂ€ivitamiseks tuleb teha 3 asja:

  • luua Docker'i pilt, mis sisaldab kĂ”iki vajalikke raamatukogusid, samuti konfigureerimis- ja tĂ€itmisfaile. Siin on tegemist pildiga, mis on loodud CI/CD etapis ja testitud testimisklastris;
  • publitseerida Docker'i pilt registrisse, mis on Kubernetese klastri poolt ligipÀÀsetav;
  • koostada 'SparkApplication' tĂŒĂŒpi manifest ja defineerida kĂ€ivitatav ĂŒlesanne. Manifeste nĂ€idised on saadaval ametlikus hoidlas (nĂ€iteks, github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Oluline on tĂ€hele panna manifeste puudutavaid peamisi punkte:
    1. sÔnastikus 'apiVersion' peab olema nÀidatud API versioon, mis vastab operaatorkomplekti versioonile;
    2. sÔnastikus 'metadata.namespace' peab olema nÀidatud ruum, kus rakendus kÀivitatakse;
    3. sÔnastikus 'spec.image' peab olema mÀrgitud loodud Docker'i pildi aadress ligipÀÀsetavas registris;
    4. sĂ”nastikus 'spec.mainClass' peab olema mĂ€rgitud Spark ĂŒlesande klass, mida tuleb kĂ€ivitamisel kĂ€ivitada;
    5. sÔnastikus 'spec.mainApplicationFile' peab olema mÀrgitud tee tÀidetava jar faili juurde;
    6. sÔnastikus 'spec.sparkVersion' peab olema nÀidatud kasutatav Spark'i versioon;
    7. sÔnastikus 'spec.driver.serviceAccount' peab olema nÀidatud teenuse konto vastavas Kubernetes'i ruumis, mida rakenduse kÀitamiseks kasutatakse;
    8. sÔnastikus 'spec.executor' peab olema mÀrgitud rakendusele eraldatud ressursside arv;
    9. SĂ”nastikus «spec.volumeMounts» peab olema mÀÀratud kohaliku katalooge, kus luuakse Spark'i ĂŒlesande kohalikud failid.

Maniifesti koostamise nĂ€ide (siin {spark-service-account} on teenusekonto Kubernetes'i klastri sees Spark'i ĂŒlesannete kĂ€ivitamiseks):

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"

Selles maniifestis on mÀÀratud teenusekonto, millele on enne maniifesti avaldamist vajalik luua vajalikud rollide sidumised, mis annavad Spark'i rakendusele vajalikud ligipÀÀsuÔigused Kubernetes'i API-ga (kui see on vajalik). Meie juhul vajab rakendus Ôigusi Pod'ide loomiseks. Loome vajaliku rolli sidumise:

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

Samuti tasub mĂ€rkida, et antud maniifesti spetsifikatsioonis vĂ”ib olla mÀÀratud parameeter «hadoopConfigMap», mis vĂ”imaldab mÀÀrata ConfigMap'i Hadoop'i konfiguratsiooniga ilma vastava faili eelneva paigutamiseta Docker'i pildile. Samuti sobib see regulaarsete ĂŒlesannete kĂ€itamiseks — parameetri «schedule» abil saab mÀÀrata antud ĂŒlesande kĂ€ivitamise ajakava.

PĂ€rast seda salvestame meie maniifesti failiks spark-pi.yaml ja rakendame selle meie Kubernetes'i klastrisse:

oc apply -f spark-pi.yaml

Sellega luuakse objekt tĂŒĂŒbiga «sparkapplications»:

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

Selle tulemuseks on pod koos rakendusega, mille olek kuvatakse loodud «sparkapplications». Seda saab vaadata jÀrgmise kÀsuga:

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

Ülesande lĂ”puleviimisel lĂ€heb POD olekusse «Completed», mis samuti vĂ€rskendatakse «sparkapplications»-is. Rakenduse logisid saab vaadata veebibrauseris vĂ”i jĂ€rgmise kĂ€suga (siin {sparkapplications-pod-name} on kĂ€ivitatud ĂŒlesande pod'i nimi):

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

Samuti saab Spark'i ĂŒlesannete haldamist teostada spetsialiseeritud utiliidiga sparkctl. Selle installimiseks kloonime selle lĂ€htekoodi reposti, paigaldame Go ja compileerime selle utiliidi:

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

Uurime kĂ€ivitatud Spark'i ĂŒlesannete nimekirja:

sparkctl list -n {project}

Loome kirjeldust Spark'i ĂŒlesande jaoks:

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"

KĂ€ivitame mÀÀratud ĂŒlesande sparkctl'i abil:

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

Uurime kĂ€ivitatud Spark'i ĂŒlesannete nimekirja:

sparkctl list -n {project}

Uurime kĂ€ivitatud Spark'i ĂŒlesande sĂŒndmuste nimekirja:

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

Uurime kĂ€ivitatud Spark'i ĂŒlesande staatust:

sparkctl status spark-pi -n {project}

KokkuvÔtteks tahaksime arutada avastatud miinuseid praeguse stabiilse Spark (2.4.5) versiooni kasutamisel Kuberneteses:

  1. Esimene ja vĂ”ib-olla kĂ”ige olulisem miinus on andmete paiknemise puudumine. Vaatamata kĂ”igile YARN-i puudustele olid selle kasutamisel ka eelised, nĂ€iteks koodi kohaletoimetamise printsiip andmetele (mitte andmete kohaletoimetamine koodile). TĂ€nu sellele tĂ€ideti Spark'i ĂŒlesanded sĂ”lmedes, kus asusid arvutustes osalevad andmed, vĂ€hendades mĂ€rgatavalt andmete edastamise aega vĂ”rgu kaudu. Kubernetes't kasutades seisame silmitsi vajadusega edastada ĂŒlesandes osalevaid andmeid, mis vĂ”ivad olla piisavalt suured, mistĂ”ttu ĂŒlesande tĂ€itmise aeg vĂ”ib oluliselt pikeneda ning nĂ”uda piisavalt suurt kettaruumi, mis on eraldatud Spark'i ĂŒlesande eksemplaridele nende ajutiseks salvestamiseks. Selle puuduse saab vĂ€hendada spetsialiseeritud tarkvaralahenduste abil, mis tagavad andmete paiknemise Kubernetes's (nĂ€iteks Alluxio), kuid see tĂ€hendab sisuliselt, et on vajalik andmete tĂ€ieliku koopia hoidmine Kubernetes'i klastrite sĂ”lmedes.
  2. Teine oluline miinus on turvalisus. Vaikimisi on Spark'i ĂŒlesannete turvalisuse tagamise funktsioonid keelatud, ja Kerberose kasutamise variant ametlikus dokumentatsioonis pole kaetud (kuigi vastavad parameetrid ilmusid versioonis 3.0.0, mis nĂ”uab tĂ€iendavat töötlust), ning Spark'i turvalisuse tagamise dokumentatsioonis (https://spark.apache.org/docs/2.4.5/security.html) on vĂ”tmehoidjatena nimetatud ainult YARN, Mesos ja Standalone Cluster. Samuti ei saa Spark'i ĂŒlesannete kĂ€ivitamisel kasutatavat kasutajat otseselt nĂ€idata — me mÀÀrame vaid teenuse konto, mille all töötab pod, ja kasutaja valitakse kehtestatud turvapoliitikate alusel. SeetĂ”ttu kas kasutatakse root-kasutajat, mis pole tootmiskeskkonnas ohutu, vĂ”i juhuslikku UID-ga kasutajat, mis on ebamugav andmete juurdepÀÀsu Ă”iguste jagamisel (lahendatav PodSecurityPolicies loomise ja nende sidumisega vastavate teenusekontodega). Praegu lahendatakse see kas kĂ”ikide vajalike failide paigutamisega otse Docker'i pildile vĂ”i Spark'i kĂ€ivitusskripti muutmisega, et kasutada teie organisatsioonis kehtestatud saladuste salvestamise ja saamise mehhanismi.
  3. Spark'i ĂŒlesannete kĂ€ivitamine Kubernetesega on endiselt ametlikult eksperimentaalses reĆŸiimis ja tulevikus vĂ”ib juhtuda mĂ€rkimisvÀÀrseid muutusi kasutatavates artefaktides (konfiguratsioonifailid, Docker'i pĂ”hikujud ja kĂ€ivituskrandid). TĂ”epoolest - materjali ettevalmistamisel testiti versioone 2.3.0 ja 2.4.5, mille kĂ€itumine erines oluliselt.

Ootame uuendusi - hiljuti ilmus uus Spark'i versioon (3.0.0), mis tĂ”i kaasa tuntavad muudatused Spark'i toimimises Kuberneteses, kuid jĂ€tnud selle eksperimentaalseks ressurshalduri toe staatuseks. VĂ”imalik, et jĂ€rgmised uuendused tĂ”epoolest vĂ”imaldavad soovitada loobuda YARN'ist ja kĂ€ivitada Spark'i ĂŒlesandeid Kuberneteses, kartmata teie sĂŒsteemi turvalisuse pĂ€rast ja ilma vajaduseta ise funktsionaalsete komponentide ĂŒle töötada.

Fin.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster