Pokretanje Apache Spark na Kubernetesu

Dragi čitatelji, dobar dan. Danas ćemo govoriti malo o Apache Sparku i njegovim izgledima za razvoj.

Pokretanje Apache Spark na Kubernetesu

U modernom svijetu velikih podataka, Apache Spark je de facto standard za razvoj zadataka skupne obrade podataka. Osim toga, također se koristi za izradu aplikacija za strujanje koje rade u konceptu mikro serija, obrađujući i otpremajući podatke u malim dijelovima (Spark Structured Streaming). I tradicionalno je bio dio cjelokupnog Hadoop stoga, koristeći YARN (ili u nekim slučajevima Apache Mesos) kao upravitelja resursima. Do 2020. njegova je upotreba u tradicionalnom obliku upitna za većinu tvrtki zbog nedostatka pristojnih Hadoop distribucija – razvoj HDP-a i CDH-a je zaustavljen, CDH nije dobro razvijen i ima visoku cijenu, a preostali dobavljači Hadoop-a imaju ili su prestale postojati ili imaju nejasnu budućnost. Stoga je pokretanje Apache Sparka pomoću Kubernetesa od sve većeg interesa među zajednicom i velikim tvrtkama - postavši standard u orkestraciji spremnika i upravljanju resursima u privatnim i javnim oblacima, rješava problem s nezgodnim raspoređivanjem resursa Spark zadataka na YARN-u i pruža platforma koja se stalno razvija s mnogo komercijalnih i otvorenih distribucija za tvrtke svih veličina i vrsta. Osim toga, u jeku popularnosti, većina je već uspjela nabaviti nekoliko vlastitih instalacija i povećati svoju stručnost u korištenju, što pojednostavljuje selidbu.

Počevši od verzije 2.3.0, Apache Spark dobio je službenu podršku za izvršavanje zadataka u Kubernetes klasteru, a danas ćemo govoriti o trenutnoj zrelosti ovog pristupa, raznim opcijama za njegovu upotrebu i zamkama na koje ćemo naići tijekom implementacije.

Prije svega, pogledajmo proces razvoja zadataka i aplikacija temeljenih na Apache Sparku i istaknimo tipične slučajeve u kojima trebate pokrenuti zadatak na Kubernetes klasteru. U pripremi ovog posta, OpenShift se koristi kao distribucija i dat će se naredbe relevantne za njegov pomoćni program naredbenog retka (oc). Za druge Kubernetes distribucije mogu se koristiti odgovarajuće naredbe iz standardnog uslužnog programa Kubernetes naredbenog retka (kubectl) ili njihovi analozi (na primjer, za oc adm policy).

Prvi slučaj upotrebe - spark-submit

Tijekom razvoja zadataka i aplikacija, programer mora pokrenuti zadatke za otklanjanje pogrešaka transformacije podataka. Teoretski, stubovi se mogu koristiti u te svrhe, ali razvoj uz sudjelovanje stvarnih (iako testnih) instanci krajnjih sustava pokazao se bržim i boljim u ovoj klasi zadataka. U slučaju kada ispravljamo pogreške na stvarnim instancama krajnjih sustava, moguća su dva scenarija:

  • programer pokreće Spark zadatak lokalno u samostalnom načinu rada;

    Pokretanje Apache Spark na Kubernetesu

  • programer pokreće Spark zadatak na Kubernetes klasteru u testnoj petlji.

    Pokretanje Apache Spark na Kubernetesu

Prva opcija ima pravo postojati, ali uključuje niz nedostataka:

  • Svakom programeru mora biti omogućen pristup s radnog mjesta svim instancama krajnjih sustava koji su mu potrebni;
  • potrebna je dovoljna količina resursa na radnom stroju za izvođenje zadatka koji se razvija.

Druga opcija nema te nedostatke, budući da vam upotreba Kubernetes klastera omogućuje alociranje potrebnog skupa resursa za izvršavanje zadataka i pružanje potrebnog pristupa krajnjim instancama sustava, fleksibilno pružajući pristup njemu pomoću uzora Kubernetes za svi članovi razvojnog tima. Istaknimo ga kao prvi slučaj upotrebe - pokretanje Spark zadataka s lokalnog razvojnog stroja na Kubernetes klasteru u testnom krugu.

Razgovarajmo više o procesu postavljanja Spark-a za lokalno pokretanje. Da biste počeli koristiti Spark morate ga instalirati:

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

Prikupljamo potrebne pakete za rad s Kubernetesom:

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

Potpuna izrada oduzima puno vremena, a da biste izradili Docker slike i pokrenuli ih na Kubernetes klasteru, zapravo su vam potrebne samo jar datoteke iz direktorija "assembly/", tako da možete sastaviti samo ovaj potprojekt:

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

Za pokretanje Spark poslova na Kubernetesu morate izraditi Docker sliku koju ćete koristiti kao osnovnu sliku. Ovdje postoje 2 moguća pristupa:

  • Generirana Docker slika uključuje izvršni kod Spark zadatka;
  • Stvorena slika uključuje samo Spark i potrebne ovisnosti, izvršni kod hostiran je na daljinu (na primjer, u HDFS-u).

Najprije izradimo Docker sliku koja sadrži testni primjer Spark zadatka. Za stvaranje Docker slika, Spark ima uslužni program pod nazivom "docker-image-tool". Proučimo pomoć o tome:

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

Uz njegovu pomoć možete stvoriti Docker slike i učitati ih u udaljene registre, ali prema zadanim postavkama ima niz nedostataka:

  • bez greške stvara 3 Docker slike odjednom - za Spark, PySpark i R;
  • ne dopušta vam da odredite naziv slike.

Stoga ćemo koristiti modificiranu verziju ovog uslužnog programa danu u nastavku:

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

Uz njegovu pomoć sastavljamo osnovnu Spark sliku koja sadrži testni zadatak za izračunavanje Pi pomoću Sparka (ovdje {docker-registry-url} je URL vašeg registra Docker slike, {repo} je naziv repozitorija unutar registra, koji odgovara projektu u OpenShiftu, {ime-image} - naziv slike (ako se koristi odvajanje slika na tri razine, na primjer, kao u integriranom registru Red Hat OpenShift slika), {tag} - oznaka ovog verzija slike):

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

Prijavite se na OKD klaster pomoću uslužnog programa konzole (ovdje je {OKD-API-URL} API URL OKD klastera):

oc login {OKD-API-URL}

Uzmimo token trenutnog korisnika za autorizaciju u Docker registru:

oc whoami -t

Prijavite se u interni Docker registar OKD klastera (kao lozinku koristimo token dobiven prethodnom naredbom):

docker login {docker-registry-url}

Učitajmo sastavljenu Docker sliku u Docker registar OKD:

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

Provjerimo je li sastavljena slika dostupna u OKD-u. Da biste to učinili, otvorite URL u pregledniku s popisom slika odgovarajućeg projekta (ovdje je {project} naziv projekta unutar OpenShift klastera, {OKD-WEBUI-URL} je URL OpenShift web konzole ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{ime-slike}.

Za pokretanje zadataka mora se stvoriti račun usluge s privilegijama za pokretanje podova kao root (o tome ćemo raspravljati kasnije):

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

Pokrenimo naredbu spark-submit za objavljivanje Spark zadatka u OKD klasteru, navodeći kreirani servisni račun i Docker sliku:

 /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

ovdje:

—name — naziv zadatka koji će sudjelovati u formiranju imena Kubernetes podova;

—class — klasa izvršne datoteke, koja se poziva kada se zadatak pokrene;

—conf — konfiguracijski parametri Spark;

spark.executor.instances — broj Spark izvršitelja za pokretanje;

spark.kubernetes.authenticate.driver.serviceAccountName - naziv Kubernetes servisnog računa koji se koristi prilikom pokretanja podova (za definiranje sigurnosnog konteksta i mogućnosti prilikom interakcije s Kubernetes API-jem);

spark.kubernetes.namespace — Kubernetes imenski prostor u kojem će se pokretati upravljački i izvršni moduli;

spark.submit.deployMode — način pokretanja Spark-a (za standardni spark-submit koristi se “cluster”, za Spark Operator i novije verzije Spark-a “klijent”);

spark.kubernetes.container.image - Docker slika koja se koristi za pokretanje podova;

spark.master — Kubernetes API URL (naveden je vanjski tako da se pristupa s lokalnog stroja);

local:// je put do izvršne datoteke Spark unutar Docker slike.

Idemo na odgovarajući OKD projekt i proučavamo stvorene mahune - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Kako bi se pojednostavio razvojni proces, može se koristiti druga opcija, u kojoj se stvara zajednička osnovna slika Spark-a koju koriste svi zadaci za pokretanje, a snimke izvršnih datoteka objavljuju se u vanjskoj pohrani (na primjer, Hadoop) i navode se prilikom poziva spark-submit kao poveznica. U ovom slučaju možete pokrenuti različite verzije Spark zadataka bez ponovne izgradnje Docker slika, koristeći, na primjer, WebHDFS za objavljivanje slika. Šaljemo zahtjev za izradu datoteke (ovdje je {host} host usluge WebHDFS, {port} je luka usluge WebHDFS, {path-to-file-on-hdfs} je željena staza do datoteke na HDFS):

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

Dobit ćete ovakav odgovor (ovdje je {location} URL koji je potrebno koristiti za preuzimanje datoteke):

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

Učitajte izvršnu datoteku Spark u HDFS (ovdje je {path-to-local-file} staza do izvršne datoteke Spark na trenutnom hostu):

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

Nakon toga, možemo napraviti spark-submit pomoću Spark datoteke prenesene na HDFS (ovdje je {class-name} naziv klase koju je potrebno pokrenuti da bi se izvršio zadatak):

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

Treba imati na umu da ćete, kako biste pristupili HDFS-u i osigurali rad zadatka, možda morati promijeniti Dockerfile i skriptu entrypoint.sh - dodati direktivu u Dockerfile za kopiranje zavisnih biblioteka u direktorij /opt/spark/jars i uključi HDFS konfiguracijsku datoteku u SPARK_CLASSPATH u ulaznoj točki.

Drugi slučaj upotrebe - Apache Livy

Nadalje, kada je zadatak razvijen i rezultat treba testirati, postavlja se pitanje njegovog pokretanja kao dijela CI/CD procesa i praćenja statusa njegovog izvršenja. Naravno, možete ga pokrenuti koristeći lokalni spark-submit poziv, ali to komplicira CI/CD infrastrukturu budući da zahtijeva instalaciju i konfiguraciju Sparka na agentima/runerima CI poslužitelja i postavljanje pristupa Kubernetes API-ju. Za ovaj slučaj, ciljna implementacija je odabrala korištenje Apache Livy kao REST API-ja za izvođenje Spark zadataka smještenih unutar Kubernetes klastera. Uz njegovu pomoć možete pokrenuti Spark zadatke na Kubernetes klasteru koristeći regularne cURL zahtjeve, što se lako implementira na temelju bilo kojeg CI rješenja, a njegovo postavljanje unutar Kubernetes klastera rješava problem autentifikacije prilikom interakcije s Kubernetes API-jem.

Pokretanje Apache Spark na Kubernetesu

Istaknimo ga kao drugi slučaj upotrebe - pokretanje Spark zadataka kao dijela CI/CD procesa na Kubernetes klasteru u testnoj petlji.

Malo o Apache Livy - radi kao HTTP poslužitelj koji pruža web sučelje i RESTful API koji vam omogućuje daljinsko pokretanje spark-submita prosljeđivanjem potrebnih parametara. Tradicionalno se isporučuje kao dio HDP distribucije, ali se također može implementirati na OKD ili bilo koju drugu Kubernetes instalaciju koristeći odgovarajući manifest i skup Docker slika, kao što je ova - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Za naš slučaj izgrađena je slična Docker slika, uključujući Spark verziju 2.4.5 iz sljedeće Docker datoteke:

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

Generirana slika može se izgraditi i učitati u vaše postojeće Docker spremište, kao što je interno OKD spremište. Da biste ga implementirali, upotrijebite sljedeći manifest ({registry-url} - URL registra Docker slike, {image-name} - Naziv Docker slike, {tag} - Docker oznaka slike, {livy-url} - željeni URL gdje poslužitelj će biti dostupan Livy; manifest “Route” se koristi ako se Red Hat OpenShift koristi kao Kubernetes distribucija, inače se koristi odgovarajući Ingress ili Service manifest tipa NodePort):

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

Nakon primjene i uspješnog pokretanja modula, Livy grafičko sučelje dostupno je na poveznici: http://{livy-url}/ui. Uz Livy, možemo objaviti naš Spark zadatak koristeći REST zahtjev od, na primjer, Postmana. Primjer zbirke sa zahtjevima prikazan je u nastavku (konfiguracijski argumenti s varijablama potrebnim za rad pokrenutog zadatka mogu se proslijediti u polje “args”):

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

Izvršimo prvi zahtjev iz kolekcije, odemo na OKD sučelje i provjerimo je li zadatak uspješno pokrenut - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Istodobno će se u Livy sučelju (http://{livy-url}/ui) pojaviti sesija unutar koje pomoću Livy API-ja ili grafičkog sučelja možete pratiti napredak zadatka i proučavati sesiju cjepanice.

Sada pokažimo kako Livy radi. Da bismo to učinili, ispitajmo zapisnike Livy spremnika unutar pod-a s Livy poslužiteljem - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=dnevnici. Iz njih možemo vidjeti da se prilikom pozivanja Livy REST API-ja u spremniku pod nazivom “livy” izvršava spark-submit, sličan onom koji smo upotrijebili gore (ovdje je {livy-pod-name} naziv kreiranog modula s poslužiteljem Livy). Zbirka također predstavlja drugi upit koji vam omogućuje pokretanje zadataka koji daljinski ugošćuju izvršnu datoteku Spark pomoću Livy poslužitelja.

Treći slučaj upotrebe - Spark Operator

Sada kada je zadatak testiran, postavlja se pitanje njegovog redovitog izvođenja. Izvorni način redovitog pokretanja zadataka u Kubernetes klasteru je CronJob entitet i možete ga koristiti, ali trenutno je upotreba operatora za upravljanje aplikacijama u Kubernetesu vrlo popularna i za Spark postoji prilično zreo operator, koji je također koristi se u rješenjima na razini poduzeća (na primjer, Lightbend FastData Platform). Preporučamo da je koristite - trenutna stabilna verzija Spark (2.4.5) ima prilično ograničene mogućnosti konfiguracije za pokretanje Spark zadataka u Kubernetesu, dok sljedeća velika verzija (3.0.0) deklarira punu podršku za Kubernetes, ali datum njezina izlaska ostaje nepoznat . Spark Operator nadoknađuje ovaj nedostatak dodavanjem važnih opcija konfiguracije (na primjer, montiranje konfiguracije ConfigMap s Hadoop pristupom na Spark module) i mogućnošću pokretanja redovito planiranog zadatka.

Pokretanje Apache Spark na Kubernetesu
Istaknimo ga kao treći slučaj upotrebe - redovito pokretanje Spark zadataka na Kubernetes klasteru u produkcijskoj petlji.

Spark Operator je otvorenog koda i razvijen unutar Google Cloud Platforme - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Njegova instalacija se može izvršiti na 3 načina:

  1. Kao dio instalacije Lightbend FastData Platform/Cloudflow;
  2. Korištenje Helma:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Korištenje manifesta iz službenog repozitorija (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Vrijedno je napomenuti sljedeće - Cloudflow uključuje operatera s API verzijom v1beta1. Ako se koristi ova vrsta instalacije, opisi manifesta aplikacije Spark trebali bi se temeljiti na oznakama primjera u Gitu s odgovarajućom verzijom API-ja, na primjer, "v1beta1-0.9.0-2.4.0". Verziju operatora možete pronaći u opisu CRD-a uključenog u operator u rječniku "verzije":
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Ako je operator ispravno instaliran, aktivan pod s operatorom Spark pojavit će se u odgovarajućem projektu (na primjer, cloudflow-fdp-sparkoperator u prostoru Cloudflow za instalaciju Cloudflowa) i pojavit će se odgovarajuća vrsta Kubernetes resursa pod nazivom "sparkapplications". . Možete istražiti dostupne Spark aplikacije sljedećom naredbom:

oc get sparkapplications -n {project}

Za pokretanje zadataka pomoću Spark Operatora trebate učiniti 3 stvari:

  • izradite Docker sliku koja uključuje sve potrebne biblioteke, kao i konfiguracijske i izvršne datoteke. Na ciljnoj slici, ovo je slika stvorena u fazi CI/CD i testirana na testnom klasteru;
  • objaviti Docker sliku u registru dostupnom iz Kubernetes klastera;
  • generirajte manifest s tipom "SparkApplication" i opisom zadatka koji se pokreće. Primjeri manifesta dostupni su u službenom repozitoriju (npr. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Postoje važne točke koje treba imati na umu u vezi s manifestom:
    1. rječnik “apiVersion” mora navesti verziju API-ja koja odgovara verziji operatera;
    2. rječnik “metadata.namespace” mora označavati imenski prostor u kojem će se aplikacija pokrenuti;
    3. rječnik “spec.image” mora sadržavati adresu stvorene Docker slike u dostupnom registru;
    4. rječnik “spec.mainClass” mora sadržavati Spark klasu zadatka koju je potrebno pokrenuti kada se proces pokrene;
    5. rječnik “spec.mainApplicationFile” mora sadržavati put do izvršne jar datoteke;
    6. rječnik “spec.sparkVersion” mora naznačiti verziju Sparka koja se koristi;
    7. rječnik “spec.driver.serviceAccount” mora navesti servisni račun unutar odgovarajućeg Kubernetes imenskog prostora koji će se koristiti za pokretanje aplikacije;
    8. rječnik “spec.executor” mora naznačiti broj resursa dodijeljenih aplikaciji;
    9. rječnik "spec.volumeMounts" mora navesti lokalni direktorij u kojem će se kreirati lokalne datoteke Spark zadataka.

Primjer generiranja manifesta (ovdje je {spark-service-account} račun usluge unutar Kubernetes klastera za pokretanje Spark zadataka):

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"

Ovaj manifest specificira račun usluge za koji, prije objavljivanja manifesta, morate stvoriti potrebna povezivanja uloga koja pružaju potrebna prava pristupa za interakciju aplikacije Spark s Kubernetes API-jem (ako je potrebno). U našem slučaju, aplikaciji su potrebna prava za stvaranje Podova. Kreirajmo potrebno vezanje uloga:

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

Također je vrijedno napomenuti da ova specifikacija manifesta može uključivati ​​parametar "hadoopConfigMap", koji vam omogućuje da navedete ConfigMap s Hadoop konfiguracijom bez prethodnog postavljanja odgovarajuće datoteke u Docker sliku. Također je prikladan za redovito izvršavanje zadataka - korištenjem parametra "raspored" može se odrediti raspored za pokretanje određenog zadatka.

Nakon toga spremamo naš manifest u datoteku spark-pi.yaml i primjenjujemo ga na naš Kubernetes klaster:

oc apply -f spark-pi.yaml

Ovo će stvoriti objekt tipa "sparkapplications":

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

U tom slučaju kreirat će se pod s aplikacijom čiji će status biti prikazan u kreiranim “sparkapplications”. Možete ga vidjeti sljedećom naredbom:

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

Nakon završetka zadatka, POD će prijeći u status "Dovršeno", koji će se također ažurirati u "sparkapplications". Dnevnici aplikacije mogu se pregledati u pregledniku ili pomoću sljedeće naredbe (ovdje je {sparkapplications-pod-name} naziv modula zadatka koji se izvodi):

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

Spark zadacima također se može upravljati pomoću specijaliziranog uslužnog programa sparkctl. Da biste ga instalirali, klonirajte spremište s njegovim izvornim kodom, instalirajte Go i izradite ovaj uslužni program:

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

Pogledajmo popis pokrenutih Spark zadataka:

sparkctl list -n {project}

Kreirajmo opis za Spark zadatak:

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"

Pokrenimo opisani zadatak koristeći sparkctl:

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

Pogledajmo popis pokrenutih Spark zadataka:

sparkctl list -n {project}

Pogledajmo popis događaja pokrenutog Spark zadatka:

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

Ispitajmo status pokrenutog Spark zadatka:

sparkctl status spark-pi -n {project}

Zaključno, želio bih razmotriti otkrivene nedostatke korištenja trenutne stabilne verzije Spark (2.4.5) u Kubernetesu:

  1. Prvi i, možda, glavni nedostatak je nedostatak Lokalnosti podataka. Unatoč svim nedostacima YARN-a, postojale su i prednosti njegove uporabe, na primjer, princip isporuke koda u podatke (umjesto podataka u kod). Zahvaljujući njemu, Spark zadaci su se izvršavali na čvorovima gdje su se nalazili podaci uključeni u izračune, a vrijeme potrebno za isporuku podataka preko mreže značajno je smanjeno. Kada koristimo Kubernetes, suočeni smo s potrebom premještanja podataka uključenih u zadatak preko mreže. Ako su dovoljno velike, vrijeme izvršenja zadatka može se značajno povećati, a također zahtijevati prilično veliku količinu prostora na disku dodijeljenog instancama zadatka Spark za njihovu privremenu pohranu. Taj se nedostatak može ublažiti korištenjem specijaliziranog softvera koji osigurava lokalitet podataka u Kubernetesu (primjerice Alluxio), no to zapravo znači potrebu pohranjivanja potpune kopije podataka na čvorove Kubernetes klastera.
  2. Drugi važan nedostatak je sigurnost. Prema zadanim postavkama onemogućene su značajke povezane sa sigurnošću u vezi s izvođenjem Spark zadataka, upotreba Kerberosa nije pokrivena službenom dokumentacijom (iako su odgovarajuće opcije uvedene u verziji 3.0.0, što će zahtijevati dodatni rad), a sigurnosna dokumentacija za korištenjem Sparka (https ://spark.apache.org/docs/2.4.5/security.html) samo se YARN, Mesos i Samostalni klaster pojavljuju kao ključna spremišta. Istodobno, korisnik pod kojim se pokreću Spark zadaci ne može se izravno navesti - navodimo samo račun usluge pod kojim će raditi, a korisnik se odabire na temelju konfiguriranih sigurnosnih pravila. U tom smislu koristi se ili root korisnik, što nije sigurno u produktivnom okruženju, ili korisnik s nasumičnim UID-om, što je nezgodno pri raspodjeli prava pristupa podacima (ovo se može riješiti stvaranjem PodSecurityPolicies i njihovim povezivanjem s odgovarajući servisni računi). Trenutačno je rješenje smjestiti sve potrebne datoteke izravno u Docker sliku ili modificirati skriptu za pokretanje Spark-a da koristi mehanizam za pohranu i dohvaćanje tajni usvojen u vašoj organizaciji.
  3. Izvođenje Spark poslova pomoću Kubernetesa službeno je još uvijek u eksperimentalnom načinu rada i u budućnosti bi moglo doći do značajnih promjena u korištenim artefaktima (konfiguracijske datoteke, Docker osnovne slike i skripte za pokretanje). I doista, prilikom pripreme materijala testirane su verzije 2.3.0 i 2.4.5, ponašanje je bilo bitno drugačije.

Pričekajmo ažuriranja – nedavno je izašla nova verzija Sparka (3.0.0) koja je donijela značajne promjene u radu Sparka na Kubernetesu, ali je zadržala eksperimentalni status podrške za ovaj resource manager. Možda će sljedeća ažuriranja doista omogućiti potpunu preporuku napuštanja YARN-a i pokretanja Spark zadataka na Kubernetesu bez straha za sigurnost vašeg sustava i bez potrebe za neovisnim mijenjanjem funkcionalnih komponenti.

Peraje.

Izvor: www.habr.com

Dodajte komentar