Apache Sparki käivitamine Kubernetesis

Kallid lugejad, tere pärastlõunal. Täna räägime veidi Apache Sparkist ja selle arenguväljavaadetest.

Apache Sparki käivitamine Kubernetesis

Kaasaegses suurandmete maailmas on Apache Spark de facto standard andmepakettide töötlemise ülesannete arendamiseks. Lisaks kasutatakse seda ka voogedastusrakenduste loomiseks, mis töötavad mikropartii kontseptsioonis, töötlevad ja edastavad andmeid väikeste portsjonitena (Spark Structured Streaming). Ja traditsiooniliselt on see olnud osa üldisest Hadoopi pinust, kasutades ressursihaldurina YARNi (või mõnel juhul Apache Mesost). Aastaks 2020 on selle kasutamine traditsioonilisel kujul enamiku ettevõtete jaoks küsimärgi all korralike Hadoopi distributsioonide puudumise tõttu – HDP ja CDH arendamine on peatunud, CDH pole hästi arenenud ja kulukas ning ülejäänud Hadoopi tarnijad kas lakkas olemast või neil on hämar tulevik. Seetõttu pakub Kubernetese abil Apache Sparki käivitamine kogukonna ja suurettevõtete seas üha suuremat huvi – saades privaatsete ja avalike pilvede konteinerite orkestreerimise ja ressursside haldamise standardiks, lahendab see probleemi, mis on seotud Sparki ülesannete ebamugava ressursside planeerimisega YARN-is ja pakub pidevalt arenev platvorm, kus on palju kommerts- ja avatud distributsioone igas suuruses ja igas suuruses ettevõtetele. Lisaks on enamikul populaarsuse kiiluvees juba õnnestunud paar oma installatsiooni soetada ja oma teadmisi selle kasutamises suurendanud, mis lihtsustab kolimist.

Alates versioonist 2.3.0 omandas Apache Spark ametliku toe ülesannete käitamiseks Kubernetese klastris ja täna räägime selle lähenemisviisi praegusest küpsusest, selle kasutamise erinevatest võimalustest ja rakendamisel ilmnevatest lõkse.

Kõigepealt vaatame Apache Sparkil põhinevate ülesannete ja rakenduste arendamise protsessi ning toome välja tüüpilised juhtumid, mil peate Kubernetese klastris ülesande käivitama. Selle postituse ettevalmistamisel kasutatakse OpenShifti distributsioonina ja antakse selle käsurea utiliidi (oc) jaoks asjakohaseid käske. Teiste Kubernetese distributsioonide puhul saab kasutada vastavaid käske tavalisest Kubernetese käsurea utiliidist (kubectl) või nende analooge (näiteks oc adm poliitika jaoks).

Esimene kasutusjuhtum – säde-esita

Ülesannete ja rakenduste arendamise ajal peab arendaja andmete teisendamise silumiseks käivitama ülesandeid. Teoreetiliselt saab nendel eesmärkidel kasutada stubsid, kuid arendus lõppsüsteemide reaalsete (ehkki test) eksemplaride osalusel on selles ülesannete klassis osutunud kiiremaks ja paremaks. Kui silume lõppsüsteemide tegelikel eksemplaridel, on võimalikud kaks stsenaariumi:

  • arendaja käivitab Sparki ülesande lokaalselt eraldiseisvas režiimis;

    Apache Sparki käivitamine Kubernetesis

  • arendaja käivitab Sparki ülesande Kubernetese klastris testtsüklis.

    Apache Sparki käivitamine Kubernetesis

Esimesel variandil on õigus eksisteerida, kuid sellel on mitmeid puudusi:

  • Igale arendajale peab olema tagatud juurdepääs töökohalt kõikidele lõppsüsteemide eksemplaridele, mida ta vajab;
  • arendatava ülesande täitmiseks on töömasinal vaja piisavalt ressursse.

Teisel variandil neid puudusi pole, kuna Kubernetese klastri kasutamine võimaldab eraldada tööülesannete jaoks vajaliku ressursikogumi ja pakkuda sellele vajalikku juurdepääsu lõppsüsteemi eksemplaridele, pakkudes sellele paindlikult juurdepääsu Kubernetese rollimudeli abil. kõik arendusmeeskonna liikmed. Toome selle esile kui esimest kasutusjuhtumit – Sparki ülesannete käivitamine kohalikust arendajamasinast Kubernetese klastris testitsüklis.

Räägime lähemalt Sparki kohalikuks tööks seadistamise protsessist. Sparki kasutamise alustamiseks peate selle installima:

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

Kogume Kubernetesega töötamiseks vajalikud paketid:

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

Täielik ehitamine võtab palju aega ning Dockeri piltide loomiseks ja Kubernetese klastris käitamiseks vajate tõesti ainult jar-faile kataloogist „assembly/”, nii et saate ehitada ainult selle alamprojekti:

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

Sparki tööde käitamiseks Kubernetesis peate looma Dockeri kujutise, mida kasutada baaspildina. Siin on 2 võimalikku lähenemist:

  • Loodud Dockeri kujutis sisaldab käivitatavat Sparki ülesandekoodi;
  • Loodud pilt sisaldab ainult Sparki ja vajalikke sõltuvusi, käivitatavat koodi majutatakse eemalt (näiteks HDFS-is).

Esmalt loome Dockeri kujutise, mis sisaldab Sparki ülesande testnäidet. Dockeri piltide loomiseks on Sparkil utiliit nimega "docker-image-tool". Uurime selle kohta abi:

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

Selle abiga saate luua Dockeri pilte ja laadida need kaugregistritesse, kuid vaikimisi on sellel mitmeid puudusi:

  • loob tõrgeteta 3 Dockeri pilti korraga - Sparki, PySparki ja R jaoks;
  • ei võimalda määrata pildi nime.

Seetõttu kasutame selle utiliidi muudetud versiooni, mis on toodud allpool:

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 abiga koostame Sparki põhikujutise, mis sisaldab testülesannet Pi arvutamiseks Sparki abil (siin on {docker-registry-url} teie Dockeri piltide registri URL, {repo} on registris oleva hoidla nimi, mis sobib projektiga OpenShiftis , {image-name} - pildi nimi (kui kasutatakse piltide kolmetasandilist eraldamist, näiteks nagu Red Hat OpenShifti piltide integreeritud registris), {tag} - selle silt pildi versioon):

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

Logige OKD-klastrisse sisse konsooliutiliidi abil (siin on {OKD-API-URL} OKD-klastri API URL):

oc login {OKD-API-URL}

Hankime Dockeri registris autoriseerimiseks praeguse kasutaja märgi:

oc whoami -t

Logige sisse OKD klastri sisemisse Dockeri registrisse (kasutame paroolina eelmise käsuga saadud luba):

docker login {docker-registry-url}

Laadime kokkupandud Dockeri pildi üles Dockeri registri OKD-sse:

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

Kontrollime, kas kokkupandud pilt on OKD-s saadaval. Selleks avage brauseris URL koos vastava projekti piltide loendiga (siin on {project} projekti nimi OpenShifti klastri sees, {OKD-WEBUI-URL} on OpenShifti veebikonsooli URL ) – https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Ülesannete käitamiseks tuleb luua teenusekonto, millel on õigused käitada podeid administraatorina (sellest punktist räägime hiljem):

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

Käivitame käsku spark-submit, et avaldada Sparki ülesanne OKD klastris, täpsustades loodud teenusekonto ja Dockeri kujutise:

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

—nimi — ülesande nimi, mis osaleb Kubernetese kaunade nime moodustamisel;

—class — käivitatava faili klass, mida kutsutakse ülesande käivitamisel;

—conf — Sparki konfiguratsiooniparameetrid;

spark.executor.instances — käivitatavate Sparki täitjate arv;

spark.kubernetes.authenticate.driver.serviceAccountName – Podide käivitamisel kasutatav Kubernetese teenusekonto nimi (turbekonteksti ja võimaluste määratlemiseks Kubernetese API-ga suhtlemisel);

spark.kubernetes.namespace — Kubernetese nimeruum, milles käivitatakse draiveri- ja täiturikomplektid;

spark.submit.deployMode — Sparki käivitamise meetod (standardse spark-submit jaoks kasutatakse "klastrit", Spark Operatori ja Sparki "kliendi" hilisemate versioonide jaoks);

spark.kubernetes.container.image – Dockeri kujutis, mida kasutatakse kaunade käivitamiseks;

spark.master — Kubernetes API URL (väline on määratud, nii et juurdepääs toimub kohalikust masinast);

local:// on Sparki käivitatava faili tee Dockeri pildi sees.

Läheme vastava OKD projekti juurde ja uurime loodud kaustasid – https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Arendusprotsessi lihtsustamiseks saab kasutada teist võimalust, kus luuakse Sparki ühine baaspilt, mida kasutavad kõik tööülesanded ning käivitatavate failide hetktõmmised avaldatakse välismällu (näiteks Hadoop) ja täpsustatakse helistamisel. spark-submit lingina. Sel juhul saate käitada Sparki ülesannete erinevaid versioone ilma Dockeri kujutisi uuesti üles ehitamata, kasutades piltide avaldamiseks näiteks WebHDFS-i. Saadame faili loomise taotluse (siin on {host} WebHDFS-teenuse host, {port} on WebHDFS-teenuse port, {path-to-file-on-hdfs} on faili soovitud tee HDFS-is):

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

Saate sellise vastuse (siin on {location} URL, mida tuleb faili allalaadimiseks kasutada):

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

Laadige Sparki käivitatav fail HDFS-i (siin {path-to-local-file} on Sparki käivitatava faili tee praeguses hostis):

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

Pärast seda saame teha spark-submit, kasutades HDFS-i üles laaditud Spark-faili (siin on {class-name} klassi nimi, mis tuleb ülesande täitmiseks käivitada):

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

Tuleb märkida, et HDFS-ile juurdepääsuks ja ülesande toimimise tagamiseks peate võib-olla muutma Dockerfile'i ja enterpoint.sh skripti – lisama Dockerfile'i käskkirja sõltuvate teekide kopeerimiseks kataloogi /opt/spark/jars ja kaasa HDFS-i konfiguratsioonifail sisestuspunktis SPARK_CLASSPATH. sh.

Teine kasutusjuht – Apache Livy

Peale selle, kui ülesanne on välja töötatud ja tulemust on vaja testida, tekib küsimus selle käivitamisest CI/CD protsessi osana ja selle täitmise oleku jälgimisest. Muidugi saate seda käivitada kohaliku sädeme esitamise väljakutsega, kuid see muudab CI/CD infrastruktuuri keerulisemaks, kuna nõuab Sparki installimist ja konfigureerimist CI-serveri agentidele/käijatele ning juurdepääsu seadistamist Kubernetes API-le. Sel juhul on sihtrakendus valinud Apache Livy kasutamise REST API-na Kubernetese klastris hostitud Sparki ülesannete käitamiseks. Selle abiga saate Sparki ülesandeid käivitada Kubernetese klastris, kasutades tavalisi cURL-i päringuid, mida saab hõlpsasti rakendada mis tahes CI-lahenduse põhjal ja selle paigutamine Kubernetese klastris lahendab Kubernetese API-ga suhtlemisel autentimise probleemi.

Apache Sparki käivitamine Kubernetesis

Toome selle esile kui teist kasutusjuhtumit – Sparki ülesannete käitamine CI/CD protsessi osana Kubernetese klastris testtsüklis.

Natuke Apache Livy kohta - see töötab HTTP-serverina, mis pakub veebiliidest ja RESTful API-d, mis võimaldab vajalike parameetrite edastamisega kaugkäivitada spark-submit. Traditsiooniliselt on see tarnitud HDP distributsiooni osana, kuid seda saab juurutada ka OKD-sse või mis tahes muusse Kubernetese installisse, kasutades sobivat manifesti ja Dockeri kujutiste komplekti, nagu see - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Meie puhul ehitati sarnane Dockeri pilt, sealhulgas järgmisest Dockerfile'ist pärit Sparki versioon 2.4.5:

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 pildi saab luua ja üles laadida olemasolevasse Dockeri hoidlasse, näiteks sisemisse OKD hoidlasse. Selle juurutamiseks kasutage järgmist manifesti ({registry-url} – Dockeri kujutiste registri URL, {image-name} – Dockeri kujutise nimi, {tag} – Dockeri pildimärgend, {livy-url} – soovitud URL, kus server on ligipääsetav Livy; kui Kubernetese distributsioonina kasutatakse Red Hat OpenShiftit, kasutatakse manifesti "Marsruut", vastasel juhul kasutatakse vastavat NodePort tüüpi sissepääsu või teenuse 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 podi edukat käivitamist on Livy graafiline liides saadaval lingil: http://{livy-url}/ui. Livy abil saame oma Sparki ülesande avaldada kasutades REST päringut näiteks Postimehelt. Allpool on toodud näide päringutega kogust (käivitatud ülesande toimimiseks vajalike muutujatega konfiguratsiooniargumente saab edastada massiivis “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": {}
}

Täitkem kollektsiooni esimese päringu, minge OKD liidesesse ja kontrollime, kas ülesanne on edukalt käivitatud – https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Samal ajal ilmub Livy liidesesse (http://{livy-url}/ui) seanss, mille raames saate Livy API või graafilise liidese abil jälgida ülesande edenemist ja seanssi uurida palgid.

Nüüd näitame, kuidas Livy töötab. Selleks uurime Livy serveriga tasku sees oleva Livy konteineri logisid - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Nende põhjal näeme, et Livy REST API kutsumisel konteineris nimega “livy”, käivitatakse säde-submit, mis on sarnane ülalpool kasutatud rakendusega (siin on {livy-pod-name} loodud podi nimi Livy serveriga). Kogumik tutvustab ka teist päringut, mis võimaldab teil Livy serveri abil käivitada ülesandeid, mis hostivad kaugkäivitusfaili Spark.

Kolmas kasutusjuht – Spark Operator

Nüüd, kui ülesanne on testitud, kerkib küsimus selle korrapärase täitmise kohta. Kubernetese klastris ülesannete regulaarseks käitamiseks on tavaline viis CronJob ja saate seda kasutada, kuid praegu on operaatorite kasutamine Kubernetese rakenduste haldamiseks väga populaarne ja Sparki jaoks on olemas üsna küps operaator, mis on ka kasutatakse ettevõtte tasemel lahendustes (näiteks Lightbend FastData Platform). Soovitame seda kasutada – Sparki praegusel stabiilsel versioonil (2.4.5) on Kuberneteses Sparki ülesannete käitamiseks üsna piiratud konfiguratsioonivõimalused, samas kui järgmine suurem versioon (3.0.0) deklareerib Kubernetese täielikku toetust, kuid selle väljalaskekuupäev jääb teadmata. . Spark Operator kompenseerib selle puuduse, lisades olulisi konfiguratsioonivalikuid (näiteks Hadoopi juurdepääsu konfiguratsiooniga ConfigMapi paigaldamine Spark podsidele) ja võimaluse käitada regulaarselt ajastatud toiminguid.

Apache Sparki käivitamine Kubernetesis
Toome selle esile kolmanda kasutusjuhtumina – Sparki ülesannete regulaarne käitamine Kubernetese klastris tootmistsüklis.

Spark Operator on avatud lähtekoodiga ja välja töötatud Google'i pilveplatvormis - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Selle paigaldamist saab teha kolmel viisil:

  1. Lightbend FastData platvormi/Cloudflow installi osana;
  2. Helmi kasutamine:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Kasutades ametliku hoidla manifeste (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Tähelepanu väärib järgmine – Cloudflow sisaldab operaatorit API versiooniga v1beta1. Kui seda tüüpi installi kasutatakse, peaksid Sparki rakenduse manifesti kirjeldused põhinema sobiva API versiooniga Giti näidismärgenditel, näiteks "v1beta1-0.9.0-2.4.0". Operaatori versiooni leiate sõnastikus "Versioonid" olevast operaatoris sisalduvast CRD kirjeldusest:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Kui operaator on õigesti installitud, ilmub vastavasse projekti Spark-operaatoriga aktiivne pod (nt Cloudflow installi jaoks Cloudflow ruumis cloudflow-fdp-sparkoperator) ja kuvatakse vastav Kubernetese ressursitüüp nimega "sparkapplications". . Saadaolevaid Sparki rakendusi saate uurida järgmise käsuga:

oc get sparkapplications -n {project}

Spark Operatori abil toimingute käivitamiseks peate tegema 3 asja.

  • luua Dockeri pilt, mis sisaldab kõiki vajalikke teeke, aga ka konfiguratsiooni- ja käivitatavaid faile. Sihtpildil on see pilt, mis on loodud CI/CD etapis ja testitud testklastris;
  • Dockeri kujutise avaldamine Kubernetese klastrist juurdepääsetavasse registrisse;
  • genereerida manifest tüübiga "SparkApplication" ja käivitatava ülesande kirjeldus. Näidismanifestid on saadaval ametlikus hoidlas (nt. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Manifesti puhul tuleb märkida olulisi punkte:
    1. sõnastik “apiVersion” peab näitama operaatori versioonile vastava API versiooni;
    2. sõnastik “metadata.namespace” peab näitama nimeruumi, milles rakendus käivitatakse;
    3. sõnastik "spec.image" peab sisaldama loodud Dockeri kujutise aadressi juurdepääsetavas registris;
    4. sõnastik “spec.mainClass” peab sisaldama Sparki ülesandeklassi, mis tuleb protsessi käivitamisel käivitada;
    5. sõnastik "spec.mainApplicationFile" peab sisaldama käivitatava jar-faili teed;
    6. sõnastik „spec.sparkVersion” peab näitama kasutatava Sparki versiooni;
    7. sõnastik „spec.driver.serviceAccount” peab määrama vastavas Kubernetese nimeruumis teenusekonto, mida rakenduse käitamiseks kasutatakse;
    8. "spetsiif.täitja" sõnastikus peab olema märgitud taotlusele eraldatud ressursside arv;
    9. sõnastik "spec.volumeMounts" peab määrama kohaliku kataloogi, kuhu Sparki kohalikud ülesandefailid luuakse.

Manifesti loomise näide (siin {spark-service-account} on Kubernetese klastris asuv teenusekonto Sparki ülesannete käitamiseks):

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"

See manifest määrab teenusekonto, mille jaoks peate enne manifesti avaldamist looma vajalikud rollide sidumised, mis pakuvad Sparki rakendusele Kubernetes API-ga suhtlemiseks vajalikud juurdepääsuõigused (vajadusel). Meie puhul vajab rakendus Podide loomiseks õigusi. Loome vajaliku rollide sidumise:

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

Samuti väärib märkimist, et see manifesti spetsifikatsioon võib sisaldada parameetrit "hadoopConfigMap", mis võimaldab teil määrata ConfigMapi koos Hadoopi konfiguratsiooniga, ilma et peaksite esmalt Dockeri kujutisele vastavat faili paigutama. See sobib ka ülesannete regulaarseks täitmiseks – parameetri “schedule” abil saab määrata antud ülesande täitmise ajakava.

Pärast seda salvestame manifesti faili spark-pi.yaml ja rakendame selle meie Kubernetese klastris:

oc apply -f spark-pi.yaml

See loob objekti tüüpi "sparkapplications":

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

Sel juhul luuakse rakendusega pod, mille olek kuvatakse loodud “säderakendustes”. Saate seda vaadata järgmise käsuga:

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

Pärast ülesande täitmist liigub POD olekusse "Lõpetatud", mida värskendatakse ka jaotises "Sparkapplications". Rakenduste logisid saab vaadata brauseris või kasutades järgmist käsku (siin on {sparkapplications-pod-name} töötava ülesande paketi nimi):

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

Sparki ülesandeid saab hallata ka spetsiaalse sparkctl utiliidi abil. Selle installimiseks kloonige hoidla koos selle lähtekoodiga, installige Go ja looge see utiliit:

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 Sparki töötavate ülesannete loendit:

sparkctl list -n {project}

Loome Sparki ülesande kirjelduse:

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 kirjeldatud ülesande sparkctl abil:

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

Uurime Sparki töötavate ülesannete loendit:

sparkctl list -n {project}

Uurime käivitatud Sparki ülesande sündmuste loendit:

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

Uurime töötava Sparki ülesande olekut:

sparkctl status spark-pi -n {project}

Kokkuvõtteks tahaksin kaaluda Sparki praeguse stabiilse versiooni (2.4.5) Kubernetesis kasutamise avastatud puudusi:

  1. Esimene ja võib-olla ka peamine puudus on andmete lokaalsuse puudumine. Vaatamata kõigile YARNi puudustele oli selle kasutamisel ka eeliseid, näiteks koodi andmetesse toimetamise põhimõte (mitte andmetest koodini). Tänu sellele täideti Sparki ülesandeid sõlmedes, kus paiknesid arvutustega seotud andmed, ning andmete üle võrgu edastamiseks kuluv aeg vähenes oluliselt. Kubernetese kasutamisel seisame silmitsi vajadusega teisaldada ülesandega seotud andmeid üle võrgu. Kui need on piisavalt suured, võib ülesande täitmise aeg märkimisväärselt pikeneda ja nõuda ka Sparki tegumieksemplaridele nende ajutiseks salvestamiseks üsna palju kettaruumi. Seda puudust saab leevendada spetsiaalse tarkvara abil, mis tagab andmete asukoha Kuberneteses (näiteks Alluxio), kuid tegelikult tähendab see vajadust salvestada andmete täielik koopia Kubernetese klastri sõlmedesse.
  2. Teine oluline puudus on turvalisus. Vaikimisi on Sparki ülesannete käitamise turvalisusega seotud funktsioonid keelatud, ametlik dokumentatsioon ei käsitle Kerberose kasutamist (kuigi vastavaid valikuid tutvustati versioonis 3.0.0, mis nõuab täiendavat tööd) ja turvadokumentatsioon Sparki (https://spark.apache.org/docs/2.4.5/security.html) kasutades kuvatakse võtmepoodidena ainult YARN, Mesos ja Standalone Cluster. Samas ei saa otse määrata kasutajat, kelle alt Sparki ülesanded käivitatakse – määrame vaid teenusekonto, mille all see töötab, ning kasutaja valitakse konfigureeritud turvapoliitikate alusel. Sellega seoses kasutatakse kas juurkasutajat, mis pole produktiivses keskkonnas turvaline, või juhusliku UID-ga kasutajat, mis on andmetele juurdepääsuõiguste jagamisel ebamugav (selle saab lahendada, luues PodSecurityPolicies ja sidudes need vastavad teenusekontod). Praegu on lahenduseks kas paigutada kõik vajalikud failid otse Dockeri kujutisele või muuta Sparki käivitusskripti, et kasutada teie organisatsioonis kasutusele võetud saladuste salvestamise ja toomise mehhanismi.
  3. Sparki tööde käitamine Kubernetese abil on ametlikult endiselt katserežiimis ja kasutatavates artefaktides (konfiguratsioonifailid, Dockeri baaspildid ja käivitusskriptid) võib tulevikus toimuda olulisi muudatusi. Ja tõepoolest, materjali ettevalmistamisel testiti versioone 2.3.0 ja 2.4.5, käitumine oli oluliselt erinev.

Ootame värskendusi – hiljuti ilmus Sparki uus versioon (3.0.0), mis tõi Kubernetesis Sparki töösse olulisi muudatusi, kuid säilitas selle ressursihalduri toe eksperimentaalse staatuse. Võib-olla võimaldavad järgmised värskendused tõesti soovitada täielikult loobuda YARN-ist ja käivitada Sparki ülesanded Kubernetesis, kartmata oma süsteemi turvalisuse pärast ja ilma vajaduseta iseseisvalt muuta funktsionaalseid komponente.

Lõpp.

Allikas: www.habr.com

Lisa kommentaar