Izvajanje Apache Spark na Kubernetesu

Dragi bralci, dober dan. Danes bomo nekaj govorili o Apache Spark in njegovih možnostih razvoja.

Izvajanje Apache Spark na Kubernetesu

V sodobnem svetu velikih podatkov je Apache Spark de facto standard za razvoj nalog paketne obdelave podatkov. Poleg tega se uporablja tudi za ustvarjanje pretočnih aplikacij, ki delujejo v konceptu mikro paketov, obdelujejo in pošiljajo podatke v majhnih delih (Spark Structured Streaming). In tradicionalno je bil del celotnega sklada Hadoop z uporabo YARN (ali v nekaterih primerih Apache Mesos) kot upravitelja virov. Do leta 2020 je njegova uporaba v tradicionalni obliki za večino podjetij pod vprašajem zaradi pomanjkanja dostojnih distribucij Hadoop - razvoj HDP in CDH se je ustavil, CDH ni dobro razvit in ima visoke stroške, preostali dobavitelji Hadoop pa so bodisi prenehali obstajati bodisi imajo nejasno prihodnost. Zato je zagon Apache Spark z uporabo Kubernetesa vse bolj zanimiv v skupnosti in velikih podjetjih - postaja standard pri orkestraciji vsebnikov in upravljanju virov v zasebnih in javnih oblakih, rešuje težavo z neprimernim razporejanjem virov za naloge Spark na YARN in zagotavlja vztrajno razvijajoča se platforma s številnimi komercialnimi in odprtimi distribucijami za podjetja vseh velikosti in razlik. Poleg tega je večina zaradi priljubljenosti že uspela pridobiti nekaj lastnih inštalacij in povečati svoje znanje o uporabi, kar poenostavi selitev.

Z različico 2.3.0 je Apache Spark pridobil uradno podporo za izvajanje nalog v gruči Kubernetes, danes pa bomo govorili o trenutni zrelosti tega pristopa, različnih možnostih njegove uporabe in pasteh, na katere bomo naleteli pri implementaciji.

Najprej si poglejmo proces razvoja nalog in aplikacij, ki temeljijo na Apache Spark, in osvetlimo tipične primere, v katerih je potrebno zagnati nalogo v gruči Kubernetes. Pri pripravi te objave se OpenShift uporablja kot distribucija in podani bodo ukazi, ki se nanašajo na njegov pripomoček ukazne vrstice (oc). Za druge distribucije Kubernetes je mogoče uporabiti ustrezne ukaze iz standardnega pripomočka ukazne vrstice Kubernetes (kubectl) ali njihove analoge (na primer za politiko oc adm).

Prvi primer uporabe - spark-submit

Med razvojem nalog in aplikacij mora razvijalec izvajati naloge za odpravljanje napak pri transformaciji podatkov. Teoretično je za te namene mogoče uporabiti škrbine, vendar se je razvoj s sodelovanjem realnih (čeprav testnih) primerkov končnih sistemov v tem razredu nalog izkazal za hitrejšega in boljšega. V primeru, ko razhroščevanje izvajamo na realnih primerkih končnih sistemov, sta možna dva scenarija:

  • razvijalec zažene nalogo Spark lokalno v samostojnem načinu;

    Izvajanje Apache Spark na Kubernetesu

  • razvijalec zažene nalogo Spark v gruči Kubernetes v testni zanki.

    Izvajanje Apache Spark na Kubernetesu

Prva možnost ima pravico do obstoja, vendar vključuje številne pomanjkljivosti:

  • Vsakemu razvijalcu mora biti zagotovljen dostop z delovnega mesta do vseh primerkov končnih sistemov, ki jih potrebuje;
  • na delovnem stroju je potrebna zadostna količina virov za izvajanje naloge, ki se razvija.

Druga možnost nima teh pomanjkljivosti, saj vam uporaba gruče Kubernetes omogoča, da dodelite potrebno skupino virov za izvajanje nalog in ji zagotovite potreben dostop do instanc končnega sistema, pri čemer fleksibilno zagotovite dostop do nje z uporabo vzornega modela Kubernetes za vsi člani razvojne ekipe. Poudarimo ga kot prvi primer uporabe – zagon nalog Spark iz lokalnega razvijalskega stroja v gruči Kubernetes v testnem vezju.

Pogovorimo se več o postopku nastavitve Spark za lokalno delovanje. Če želite začeti uporabljati Spark, ga morate namestiti:

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

Zbiramo potrebne pakete za delo s Kubernetesom:

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

Popolna izdelava traja veliko časa in za ustvarjanje slik Docker in njihovo izvajanje v gruči Kubernetes v resnici potrebujete samo datoteke jar iz imenika »assembly/«, tako da lahko zgradite samo ta podprojekt:

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

Če želite izvajati opravila Spark v Kubernetesu, morate ustvariti sliko Docker, ki jo boste uporabili kot osnovno sliko. Tukaj sta možna dva pristopa:

  • Ustvarjena slika Dockerja vključuje izvedljivo kodo naloge Spark;
  • Ustvarjena slika vključuje samo Spark in potrebne odvisnosti, izvedljiva koda gostuje na daljavo (na primer v HDFS).

Najprej zgradimo Dockerjevo sliko, ki vsebuje testni primer naloge Spark. Za ustvarjanje slik Docker ima Spark pripomoček, imenovan "docker-image-tool". Preučimo pomoč o tem:

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

Z njegovo pomočjo lahko ustvarite slike Docker in jih naložite v oddaljene registre, vendar ima privzeto številne pomanjkljivosti:

  • brez napak ustvari 3 Docker slike hkrati - za Spark, PySpark in R;
  • ne dovoljuje določitve imena slike.

Zato bomo uporabili spremenjeno različico tega pripomočka, navedeno spodaj:

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

Z njegovo pomočjo sestavimo osnovno sliko Spark, ki vsebuje testno nalogo za izračun Pi s pomočjo Spark (tukaj je {docker-registry-url} URL vašega registra slik Docker, {repo} je ime repozitorija znotraj registra, ki se ujema s projektom v OpenShift , {ime-image} - ime slike (če se uporablja trinivojsko ločevanje slik, na primer kot v integriranem registru slik Red Hat OpenShift), {tag} - oznaka tega različica 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 v gručo OKD s pripomočkom konzole (tukaj je {OKD-API-URL} URL API-ja gruče OKD):

oc login {OKD-API-URL}

Pridobimo žeton trenutnega uporabnika za avtorizacijo v registru Docker:

oc whoami -t

Prijavite se v notranji register Docker Registry gruče OKD (kot geslo uporabimo žeton, pridobljen s prejšnjim ukazom):

docker login {docker-registry-url}

Naložimo sestavljeno sliko Dockerja v register Docker Registry OKD:

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

Preverimo, ali je sestavljena slika na voljo v OKD. Če želite to narediti, v brskalniku odprite URL s seznamom slik ustreznega projekta (tukaj je {project} ime projekta znotraj gruče OpenShift, {OKD-WEBUI-URL} je URL spletne konzole OpenShift ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{ime-slike}.

Za zagon nalog je treba ustvariti račun storitve s privilegiji za zagon podov kot root (o tej točki bomo razpravljali pozneje):

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

Zaženimo ukaz spark-submit, da objavimo nalogo Spark v gručo OKD, pri čemer podamo ustvarjen račun storitve in sliko Dockerja:

 /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

Tukaj:

—name — ime naloge, ki bo sodelovala pri oblikovanju imena podov Kubernetes;

—class — razred izvršljive datoteke, ki se kliče ob zagonu naloge;

—conf — konfiguracijski parametri Spark;

spark.executor.instances — število izvajalcev Spark za zagon;

spark.kubernetes.authenticate.driver.serviceAccountName – ime storitvenega računa Kubernetes, uporabljenega pri zagonu podov (za določitev varnostnega konteksta in zmogljivosti pri interakciji z API-jem Kubernetes);

spark.kubernetes.namespace — imenski prostor Kubernetes, v katerem bodo zagnani gonilniki in izvajalci;

spark.submit.deployMode — način zagona Spark (za standardno spark-submit se uporablja »cluster«, za Spark Operator in novejše različice Spark »client«);

spark.kubernetes.container.image – Dockerjeva slika, ki se uporablja za zagon podov;

spark.master — URL Kubernetes API (naveden je zunanji, tako da se dostop izvede z lokalnega računalnika);

local:// je pot do izvedljive datoteke Spark znotraj slike Docker.

Gremo na ustrezen projekt OKD in preučimo ustvarjene pode - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Za poenostavitev razvojnega procesa je mogoče uporabiti drugo možnost, pri kateri se ustvari skupna osnovna slika Spark, ki jo uporabljajo vsa opravila za izvajanje, posnetki izvedljivih datotek pa so objavljeni v zunanjem pomnilniku (na primer Hadoop) in določeni ob klicu spark-submit kot povezavo. V tem primeru lahko zaženete različne različice nalog Spark brez ponovne izdelave slik Docker z uporabo na primer WebHDFS za objavo slik. Pošljemo zahtevo za ustvarjanje datoteke (tukaj je {host} gostitelj storitve WebHDFS, {port} je vrata storitve WebHDFS, {path-to-file-on-hdfs} je želena pot do datoteke na HDFS):

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

Prejeli boste tak odgovor (tukaj je {location} URL, ki ga morate uporabiti za prenos datoteke):

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

Naložite izvršljivo datoteko Spark v HDFS (tukaj je {path-to-local-file} pot do izvršljive datoteke Spark na trenutnem gostitelju):

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

Po tem lahko izvedemo spark-submit z uporabo datoteke Spark, naložene v HDFS (tukaj je {class-name} ime razreda, ki ga je treba zagnati za dokončanje naloge):

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

Upoštevati je treba, da boste za dostop do HDFS in zagotovitev delovanja naloge morda morali spremeniti datoteko Dockerfile in skript entrypoint.sh – datoteki Dockerfile dodajte direktivo za kopiranje odvisnih knjižnic v imenik /opt/spark/jars in vključi konfiguracijsko datoteko HDFS v SPARK_CLASSPATH v vstopni točki.

Drugi primer uporabe - Apache Livy

Nadalje, ko je naloga razvita in je treba rezultat preizkusiti, se pojavi vprašanje njenega zagona kot dela procesa CI/CD in sledenja statusu njenega izvajanja. Seveda ga lahko zaženete z uporabo lokalnega klica spark-submit, vendar to zaplete infrastrukturo CI/CD, saj zahteva namestitev in konfiguracijo Spark na agentih/izvajalcih strežnika CI in nastavitev dostopa do API-ja Kubernetes. V tem primeru se je ciljna izvedba odločila za uporabo Apache Livy kot REST API za izvajanje nalog Spark, ki gostujejo znotraj gruče Kubernetes. Z njegovo pomočjo lahko izvajate naloge Spark v gruči Kubernetes z običajnimi zahtevami cURL, kar je enostavno implementirano na podlagi katere koli rešitve CI, njena umestitev znotraj gruče Kubernetes pa rešuje vprašanje avtentikacije pri interakciji s Kubernetes API.

Izvajanje Apache Spark na Kubernetesu

Poudarimo ga kot drugi primer uporabe – izvajanje nalog Spark kot del procesa CI/CD v gruči Kubernetes v testni zanki.

Nekaj ​​o Apache Livy - deluje kot strežnik HTTP, ki ponuja spletni vmesnik in RESTful API, ki vam omogoča oddaljeni zagon spark-submit s posredovanjem potrebnih parametrov. Običajno je bila poslana kot del distribucije HDP, vendar jo je mogoče namestiti tudi v OKD ali katero koli drugo namestitev Kubernetes z uporabo ustreznega manifesta in nabora slik Docker, kot je ta - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Za naš primer je bila zgrajena podobna slika Dockerja, vključno z različico Spark 2.4.5 iz naslednje datoteke Docker:

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

Ustvarjeno sliko je mogoče zgraditi in naložiti v vaš obstoječi repozitorij Docker, kot je notranji repozitorij OKD. Če ga želite razmestiti, uporabite naslednji manifest ({registry-url} – URL registra slik Docker, {ime-image} – ime slike Docker, {tag} – oznaka slike Docker, {livy-url} – želeni URL, kjer strežnik bo dostopen Livyju; manifest »Route« se uporablja, če se Red Hat OpenShift uporablja kot distribucija Kubernetes, sicer se uporablja ustrezen manifest Ingress ali Service 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

Po uporabi in uspešnem zagonu poda je grafični vmesnik Livy na voljo na povezavi: http://{livy-url}/ui. Z Livy lahko objavimo našo nalogo Spark z uporabo zahteve REST od, na primer, Postmana. Spodaj je predstavljen primer zbirke z zahtevami (konfiguracijski argumenti s spremenljivkami, potrebnimi za delovanje zagnane naloge, se lahko posredujejo v matriki »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": {}
}

Izvedemo prvo zahtevo iz zbirke, pojdimo na vmesnik OKD in preverimo, ali je bila naloga uspešno zagnana - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Hkrati se bo pojavila seja v vmesniku Livy (http://{livy-url}/ui), znotraj katere lahko z uporabo Livy API ali grafičnega vmesnika spremljate potek naloge in preučujete sejo dnevniki.

Zdaj pa pokažimo, kako deluje Livy. Če želite to narediti, preglejmo dnevnike vsebnika Livy znotraj poda s strežnikom Livy - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=dnevniki. Iz njih lahko vidimo, da se pri klicu API-ja Livy REST v vsebniku z imenom »livy« izvede spark-submit, podoben tistemu, ki smo ga uporabili zgoraj (tukaj je {livy-pod-name} ime ustvarjenega sklopa s strežnikom Livy). Zbirka uvaja tudi drugo poizvedbo, ki vam omogoča izvajanje nalog, ki na daljavo gostijo izvršljivo datoteko Spark s strežnikom Livy.

Tretji primer uporabe - Spark Operator

Zdaj, ko je naloga preizkušena, se pojavi vprašanje, ali jo je treba redno izvajati. Izvorni način za redno izvajanje nalog v gruči Kubernetes je entiteta CronJob in jo lahko uporabite, vendar je trenutno uporaba operaterjev za upravljanje aplikacij v Kubernetesu zelo priljubljena in za Spark obstaja dokaj zrel operater, ki je tudi ki se uporablja v rešitvah na ravni podjetja (na primer Lightbend FastData Platform). Priporočamo, da jo uporabite - trenutna stabilna različica Spark (2.4.5) ima precej omejene konfiguracijske možnosti za izvajanje nalog Spark v Kubernetesu, medtem ko naslednja večja različica (3.0.0) napoveduje popolno podporo za Kubernetes, vendar datum njene izdaje ostaja neznan . Spark Operator kompenzira to pomanjkljivost z dodajanjem pomembnih konfiguracijskih možnosti (na primer namestitev ConfigMap s konfiguracijo dostopa do Hadoop v sklope Spark) in zmožnostjo izvajanja redno načrtovanega opravila.

Izvajanje Apache Spark na Kubernetesu
Naj ga izpostavimo kot tretji primer uporabe – redno izvajanje nalog Spark v gruči Kubernetes v produkcijski zanki.

Spark Operator je odprtokoden in razvit znotraj platforme Google Cloud Platform – github.com/GoogleCloudPlatform/spark-on-k8s-operator. Namestitev je možna na 3 načine:

  1. Kot del namestitve Lightbend FastData Platform/Cloudflow;
  2. Uporaba Helma:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Uporaba manifestov iz uradnega repozitorija (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Omeniti velja naslednje - Cloudflow vključuje operaterja z API različico v1beta1. Če se uporabi ta vrsta namestitve, morajo opisi manifesta aplikacije Spark temeljiti na vzorčnih oznakah v Gitu z ustrezno različico API-ja, na primer »v1beta1-0.9.0-2.4.0«. Različico operaterja lahko najdete v opisu CRD, vključenega v operator v slovarju »različice«:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Če je operater pravilno nameščen, se bo v ustreznem projektu prikazal aktivni pod z operaterjem Spark (na primer cloudflow-fdp-sparkoperator v prostoru Cloudflow za namestitev Cloudflow) in pojavila se bo ustrezna vrsta vira Kubernetes z imenom »sparkapplications«. . Razpoložljive aplikacije Spark lahko raziščete z naslednjim ukazom:

oc get sparkapplications -n {project}

Če želite zagnati opravila z uporabo Spark Operator, morate narediti 3 stvari:

  • ustvarite sliko Dockerja, ki vključuje vse potrebne knjižnice ter konfiguracijske in izvršljive datoteke. Na ciljni sliki je to slika, ustvarjena na stopnji CI/CD in preizkušena na testni gruči;
  • objavite Dockerjevo sliko v registru, ki je dostopen iz gruče Kubernetes;
  • ustvarite manifest z vrsto »SparkApplication« in opisom opravila, ki ga želite zagnati. Primeri manifestov so na voljo v uradnem repozitoriju (npr. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). O manifestu je treba upoštevati pomembne točke:
    1. slovar “apiVersion” mora navesti različico API-ja, ki ustreza različici operaterja;
    2. slovar “metadata.namespace” mora navesti imenski prostor, v katerem bo aplikacija zagnana;
    3. slovar “spec.image” mora vsebovati naslov ustvarjene slike Docker v dostopnem registru;
    4. slovar “spec.mainClass” mora vsebovati razred opravil Spark, ki ga je treba zagnati ob zagonu procesa;
    5. slovar “spec.mainApplicationFile” mora vsebovati pot do izvršljive datoteke jar;
    6. slovar “spec.sparkVersion” mora navajati uporabljeno različico Spark;
    7. slovar “spec.driver.serviceAccount” mora podati storitveni račun znotraj ustreznega imenskega prostora Kubernetes, ki bo uporabljen za izvajanje aplikacije;
    8. slovar “spec.executor” mora navajati število sredstev, dodeljenih aplikaciji;
    9. slovar "spec.volumeMounts" mora podati lokalni imenik, v katerem bodo ustvarjene lokalne datoteke opravil Spark.

Primer generiranja manifesta (tukaj je {spark-service-account} storitveni račun znotraj gruče Kubernetes za izvajanje nalog Spark):

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

Ta manifest določa storitveni račun, za katerega morate pred objavo manifesta ustvariti potrebne vezave vlog, ki zagotavljajo potrebne pravice dostopa za aplikacijo Spark za interakcijo z API-jem Kubernetes (če je potrebno). V našem primeru aplikacija potrebuje pravice za ustvarjanje podov. Ustvarimo potrebno vezavo vlog:

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

Omeniti velja tudi, da lahko ta specifikacija manifesta vključuje parameter "hadoopConfigMap", ki vam omogoča, da določite ConfigMap s konfiguracijo Hadoop, ne da bi morali najprej postaviti ustrezno datoteko v sliko Docker. Primeren je tudi za redno izvajanje opravil - s parametrom “razpored” lahko določite urnik izvajanja danega opravila.

Po tem shranimo naš manifest v datoteko spark-pi.yaml in ga uporabimo v naši gruči Kubernetes:

oc apply -f spark-pi.yaml

To bo ustvarilo objekt tipa "sparkapplications":

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

V tem primeru bo ustvarjen pod z aplikacijo, katere status bo prikazan v ustvarjenih “sparkapplications”. Ogledate si ga lahko z naslednjim ukazom:

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

Po zaključku opravila se POD premakne v status »Dokončano«, ki se bo posodobil tudi v »sparkapplications«. Dnevnike aplikacij si lahko ogledate v brskalniku ali z uporabo naslednjega ukaza (tukaj je {sparkapplications-pod-name} ime sklopa izvajajočega se opravila):

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

Naloge Spark lahko upravljate tudi s posebnim pripomočkom sparkctl. Če ga želite namestiti, klonirajte repozitorij z njegovo izvorno kodo, namestite Go in zgradite ta pripomoček:

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

Oglejmo si seznam izvajajočih se nalog Spark:

sparkctl list -n {project}

Ustvarimo opis za nalogo Spark:

vi spark-app.yaml

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

Zaženimo opisano nalogo s pomočjo sparkctl:

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

Oglejmo si seznam izvajajočih se nalog Spark:

sparkctl list -n {project}

Oglejmo si seznam dogodkov zagnanega opravila Spark:

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

Oglejmo si status izvajajočega se opravila Spark:

sparkctl status spark-pi -n {project}

Na koncu bi rad razmislil o odkritih pomanjkljivostih uporabe trenutne stabilne različice Spark (2.4.5) v Kubernetesu:

  1. Prva in morda glavna pomanjkljivost je pomanjkanje podatkovne lokalnosti. Kljub vsem pomanjkljivostim YARN-a so bile pri njegovi uporabi tudi prednosti, na primer načelo dostave kode v podatke (namesto podatkov v kodo). Zahvaljujoč njej so se naloge Spark izvajale na vozliščih, kjer so bili podatki, vključeni v izračune, in čas, potreben za dostavo podatkov po omrežju, se je bistveno zmanjšal. Ko uporabljamo Kubernetes, se soočamo s potrebo po premikanju podatkov, vključenih v nalogo, po omrežju. Če so dovolj veliki, se lahko čas izvajanja opravil znatno poveča in zahteva tudi precej veliko prostora na disku, dodeljenega primerkom opravil Spark za njihovo začasno shranjevanje. To pomanjkljivost lahko omilimo z uporabo specializirane programske opreme, ki zagotavlja lokalnost podatkov v Kubernetesu (na primer Alluxio), vendar to dejansko pomeni, da je treba celotno kopijo podatkov shranjevati na vozliščih gruče Kubernetes.
  2. Druga pomembna pomanjkljivost je varnost. Privzeto so varnostne funkcije v zvezi z izvajanjem nalog Spark onemogočene, uporaba Kerberosa ni zajeta v uradni dokumentaciji (čeprav so bile ustrezne možnosti uvedene v različici 3.0.0, kar bo zahtevalo dodatno delo) in varnostna dokumentacija za z uporabo Spark (https://spark.apache.org/docs/2.4.5/security.html) so samo YARN, Mesos in Standalone Cluster prikazani kot ključne shrambe. Hkrati pa uporabnika, pod katerim se zaganjajo naloge Spark, ne more neposredno določiti - določimo le servisni račun, pod katerim bo deloval, uporabnika pa izberemo na podlagi konfiguriranih varnostnih politik. Pri tem se uporabi bodisi root uporabnik, kar v produktivnem okolju ni varno, bodisi uporabnik z naključnim UID, kar je neprijetno pri razdeljevanju pravic dostopa do podatkov (to je mogoče rešiti tako, da ustvarite PodSecurityPolicies in jih povežete z ustrezne storitvene račune). Trenutno je rešitev, da bodisi postavite vse potrebne datoteke neposredno v sliko Docker ali spremenite skript za zagon Spark tako, da bo uporabljal mehanizem za shranjevanje in pridobivanje skrivnosti, sprejet v vaši organizaciji.
  3. Izvajanje opravil Spark z uporabo Kubernetesa je uradno še vedno v poskusnem načinu in v prihodnosti lahko pride do znatnih sprememb v uporabljenih artefaktih (konfiguracijske datoteke, osnovne slike Docker in skripti za zagon). In res, pri pripravi materiala sta bili testirani različici 2.3.0 in 2.4.5, obnašanje je bilo bistveno drugačno.

Počakajmo na posodobitve - pred kratkim je izšla nova različica Spark (3.0.0), ki je prinesla pomembne spremembe v delu Spark na Kubernetesu, vendar je ohranila eksperimentalni status podpore za tega upravitelja virov. Morda bodo naslednje posodobitve res omogočile popolno priporočilo opustitve YARN in izvajanja nalog Spark na Kubernetesu brez strahu za varnost vašega sistema in brez potrebe po neodvisnem spreminjanju funkcionalnih komponent.

Konec

Vir: www.habr.com

Dodaj komentar