Apache Spark exekutatzen Kubernetes-en

Irakurle agurgarriak, arratsalde on. Gaur Apache Spark-i eta bere garapen-aukerei buruz hitz egingo dugu.

Apache Spark exekutatzen Kubernetes-en

Big Dataren mundu modernoan, Apache Spark batch datuak prozesatzeko zereginak garatzeko de facto estandarra da. Horrez gain, mikro batch kontzeptuan lan egiten duten streaming aplikazioak sortzeko ere erabiltzen da, zati txikietan datuak prozesatu eta bidaltzeko (Spark Structured Streaming). Eta tradizionalki Hadoop pila osoaren parte izan da, YARN (edo zenbait kasutan Apache Mesos) baliabideen kudeatzaile gisa erabiliz. 2020rako, bere forma tradizionalean erabiltzea zalantzan dago enpresa gehienentzat Hadoop banaketa duinen faltagatik - HDP eta CDHren garapena gelditu egin da, CDH ez dago ondo garatuta eta kostu handia du, eta gainerako Hadoop hornitzaileek dute. edo existitzeari utzi edo etorkizun iluna izan. Hori dela eta, Kubernetes erabiliz Apache Spark abiarazteak gero eta interes handiagoa du komunitatearen eta enpresa handien artean - edukiontzien orkestrazioan eta baliabideen kudeaketan estandar bihurtuz hodei pribatu eta publikoetan, Spark-en zereginen programazio deserosoaren arazoa konpontzen du YARN-en eta eskaintzen du. Etengabe garatzen ari den plataforma bat, banaketa komertzial eta ireki ugari dituena tamaina eta marra guztietako enpresentzat. Horrez gain, ospearen ondorioz, gehienek instalazio pare bat eskuratzea lortu dute dagoeneko eta erabileran esperientzia handitu dute, eta horrek mugimendua errazten du.

2.3.0 bertsioarekin hasita, Apache Spark-ek Kubernetes kluster batean zereginak exekutatzeko euskarria ofiziala eskuratu zuen eta gaur, ikuspegi honen egungo heldutasunaz, erabilerarako hainbat aukeraz eta inplementazioan zehar izango diren hutsez hitz egingo dugu.

Lehenik eta behin, ikus ditzagun Apache Spark-en oinarritutako zereginak eta aplikazioak garatzeko prozesua eta nabarmendu ditzagun Kubernetes kluster batean zeregin bat exekutatu behar duzun kasu tipikoak. Post hau prestatzerakoan, OpenShift banaketa gisa erabiltzen da eta komando-lerroko erabilgarritasunarekin (oc) garrantzitsuak diren komandoak emango dira. Kubernetes beste banaketa batzuetarako, Kubernetes komando-lerroaren utilitate estandarraren (kubectl) edo haien analogoak (adibidez, oc adm politikarako) dagozkion komandoak erabil daitezke.

Lehen erabilera kasua - spark-bidaltzea

Zereginen eta aplikazioen garapenean, garatzaileak zereginak exekutatu behar ditu datuen eraldaketa arazteko. Teorian, zirriborroak erabil daitezke helburu horietarako, baina amaierako sistemen instantzia errealak (probak izan arren) parte hartzearekin garatzea bizkorragoa eta hobea dela frogatu da zeregin klase honetan. Amaierako sistemen instantzia errealetan arazketa egiten dugunean, bi eszenatoki posible dira:

  • garatzaileak Spark zeregin bat exekutatzen du lokalean modu autonomoan;

    Apache Spark exekutatzen Kubernetes-en

  • garatzaile batek Spark zeregin bat exekutatzen du Kubernetes kluster batean proba-begizta batean.

    Apache Spark exekutatzen Kubernetes-en

Lehen aukerak existitzeko eskubidea du, baina hainbat desabantaila dakartza:

  • Garatzaile bakoitzari lantokitik sarbidea eman behar zaio behar dituen amaierako sistemen instantzia guztietara;
  • garatzen ari den ataza exekutatzeko lan-makinan baliabide nahikoa behar da.

Bigarren aukerak ez ditu desabantaila hauek, Kubernetes kluster bat erabiltzeak zereginak exekutatzeko beharrezko baliabide-multzoa esleitu eta sistemaren amaierako instantzietarako beharrezko sarbidea ematea ahalbidetzen baitu, horretarako sarbidea malgutasunez emanez Kubernetes eredua erabiliz. garapen-taldeko kide guztiak. Azpimarra dezagun lehen erabilera-kasu gisa: Spark zereginak abiaraziz tokiko garatzaile-makina batetik Kubernetes kluster batean proba-zirkuitu batean.

Hitz egin dezagun Spark lokalean exekutatzeko konfiguratzeko prozesuari buruz. Spark erabiltzen hasteko instalatu behar duzu:

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

Kubernetesekin lan egiteko beharrezko paketeak biltzen ditugu:

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

Eraikuntza oso batek denbora asko behar du, eta Docker irudiak sortzeko eta Kubernetes kluster batean exekutatzeko, "muntaia/" direktorioko jar fitxategiak soilik behar dituzu, beraz, azpiproiektu hau bakarrik eraiki dezakezu:

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

Spark lanak Kubernetesen exekutatzeko, Docker irudi bat sortu behar duzu oinarrizko irudi gisa erabiltzeko. Hemen 2 planteamendu posible daude:

  • Sortutako Docker irudiak Spark ataza-kode exekutagarria dakar;
  • Sortutako irudiak Spark eta beharrezko menpekotasunak bakarrik biltzen ditu, kode exekutagarria urrunetik ostatatzen da (adibidez, HDFSn).

Lehenik eta behin, eraiki dezagun Spark ataza baten proba-adibide bat duen Docker irudia. Docker irudiak sortzeko, Spark-ek "docker-image-tool" izeneko utilitatea du. Azter dezagun horren inguruko laguntza:

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

Bere laguntzarekin, Docker irudiak sor ditzakezu eta urruneko erregistroetara igo ditzakezu, baina lehenespenez hainbat desabantaila ditu:

  • hutsik egin gabe 3 Docker irudi sortzen ditu aldi berean - Spark, PySpark eta R-rentzat;
  • ez du uzten irudi-izen bat zehazten.

Hori dela eta, behean ematen den erabilgarritasun honen bertsio aldatu bat erabiliko dugu:

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

Bere laguntzarekin, Spark erabiliz Pi kalkulatzeko proba-zeregin bat duen oinarrizko Spark irudi bat muntatzen dugu (hemen {docker-registry-url} zure Docker irudi-erregistroaren URLa da, {repo} erregistroaren barruan dagoen biltegiaren izena da, OpenShift-en proiektuarekin bat datorrena, {image-izena} - irudiaren izena (irudien hiru-mailako bereizketa erabiltzen bada, adibidez, Red Hat OpenShift irudien erregistro integratuan bezala), {tag} - honen etiketa irudiaren bertsioa):

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

Hasi saioa OKD klusterrean kontsolaren utilitatea erabiliz (hemen {OKD-API-URL} OKD klusterreko API URLa da):

oc login {OKD-API-URL}

Lor dezagun uneko erabiltzailearen tokena Docker Erregistroan baimentzeko:

oc whoami -t

Hasi saioa OKD klusterraren barneko Docker Erregistroan (aurreko komandoa erabiliz lortutako tokena erabiltzen dugu pasahitz gisa):

docker login {docker-registry-url}

Karga dezagun muntatutako Docker irudia Docker Registry OKD-ra:

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

Egiazta dezagun muntatutako irudia OKDn eskuragarri dagoela. Horretarako, ireki URLa arakatzailean dagokion proiektuaren irudien zerrenda batekin (hemen {project} OpenShift kluster barruko proiektuaren izena da, {OKD-WEBUI-URL} OpenShift Web kontsolaren URLa da. ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Zereginak exekutatzeko, zerbitzu-kontu bat sortu behar da pods root gisa exekutatzeko pribilegioekin (puntu hau aurrerago eztabaidatuko dugu):

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

Exekutatu dezagun spark-submit komandoa Spark zeregin bat OKD klusterrean argitaratzeko, sortutako zerbitzu-kontua eta Docker-eko irudia zehaztuz:

 /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

hemen:

—izena — Kubernetes poden izenaren eraketan parte hartuko duen zereginaren izena;

—class — fitxategi exekutagarriaren klasea, ataza hasten denean deitua;

—conf — Spark konfigurazio-parametroak;

spark.executor.instances — abiarazteko Spark exekutatzaileen kopurua;

spark.kubernetes.authenticate.driver.serviceAccountName - pods abiarazteko erabiltzen den Kubernetes zerbitzu-kontuaren izena (Kubernetes APIarekin elkarreraginean segurtasun-testuingurua eta gaitasunak definitzeko);

spark.kubernetes.namespace — Kubernetes izen-espazioa zeinetan kontrolatzaile eta exekutatzaile-podak abiaraziko diren;

spark.submit.deployMode — Spark abiarazteko metodoa (spark-bidaltzeko "kluster" estandarra erabiltzen da, Spark Operator eta Spark-en "bezero" bertsio geroago);

spark.kubernetes.container.image - Podak abiarazteko erabiltzen den Docker irudia;

spark.master — Kubernetes API URLa (kanpokoa zehazten da, beraz, sarbidea tokiko makinatik gertatzen da);

local:// Docker irudiaren barruan dagoen Spark exekutagarrirako bidea da.

Dagokion OKD proiektura joango gara eta sortutako podak aztertzen ditugu - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Garapen-prozesua sinplifikatzeko, beste aukera bat erabil daiteke, Spark-en oinarrizko irudi komun bat sortzen den, zeregin guztiek exekutatzeko erabiltzen dutena, eta fitxategi exekutagarrien argazkiak kanpoko biltegian argitaratzen dira (adibidez, Hadoop) eta deitzean zehaztu. spark-bidali esteka gisa. Kasu honetan, Spark zereginen bertsio desberdinak exekutatu ditzakezu Docker irudiak berreraiki gabe, adibidez, WebHDFS erabiliz irudiak argitaratzeko. Fitxategi bat sortzeko eskaera bidaltzen dugu (hemen {host} WebHDFS zerbitzuaren ostalaria da, {port} WebHDFS zerbitzuaren ataka da, {path-to-file-on-hdfs} fitxategirako nahi duzun bidea da HDFSn):

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

Horrelako erantzuna jasoko duzu (hemen {location} fitxategia deskargatzeko erabili behar den URLa):

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

Kargatu Spark fitxategi exekutagarria HDFS-n (hemen {path-to-local-file} uneko ostalariaren Spark fitxategi exekutagarriaren bidea da):

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

Honen ondoren, spark-bidalketa egin dezakegu HDFSra kargatutako Spark fitxategia erabiliz (hemen {class-name} ataza burutzeko abiarazi behar den klasearen izena da):

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

Kontuan izan behar da HDFSra sartzeko eta zereginak funtzionatzen duela ziurtatzeko, Dockerfile eta entrypoint.sh script-a aldatu beharko dituzula - gehitu zuzentarau bat Dockerfile-ra menpeko liburutegiak /opt/spark/jars direktoriora kopiatzeko eta sartu HDFS konfigurazio fitxategia SPARK_CLASSPATH-en sarrera-puntuan.

Bigarren erabilera kasua - Apache Livy

Gainera, zeregin bat garatzen denean eta emaitza probatu behar denean, galdera sortzen da CI/CD prozesuko zati gisa abiarazteko eta bere exekuzioaren egoeraren jarraipena egitea. Jakina, tokiko spark-bidalketa dei bat erabiliz exekutatu dezakezu, baina horrek CI/CD azpiegitura zailtzen du, Spark CI zerbitzariko agente/exekutatuetan instalatu eta konfiguratu eta Kubernetes APIrako sarbidea konfiguratu behar baitu. Kasu honetarako, xede-inplementazioak Apache Livy REST API gisa erabiltzea aukeratu du Kubernetes kluster batean ostatatutako Spark zereginak exekutatzeko. Bere laguntzarekin, Spark zereginak exekutatu ditzakezu Kubernetes kluster batean ohiko cURL eskaerak erabiliz, zeina erraz inplementatzen den edozein CI irtenbidetan oinarrituta, eta Kubernetes klusterraren barruan jartzeak autentifikazio-arazoa konpontzen du Kubernetes APIarekin elkarreraginean.

Apache Spark exekutatzen Kubernetes-en

Azpimarra dezagun bigarren erabilera-kasu gisa: Spark zereginak Kubernetes kluster batean CI/CD prozesu baten zati gisa exekutatzen ditu proba-begizta batean.

Apache Livy-ri buruz apur bat - Web interfazea eta RESTful API bat eskaintzen duen HTTP zerbitzari gisa funtzionatzen du, beharrezkoak diren parametroak pasatuz spark-submit urrunetik abiarazteko aukera ematen duena. Tradizionalki HDP banaketa baten zati gisa bidali izan da, baina OKD-ra edo Kubernetes-en beste edozein instalaziotara ere inplementa daiteke manifestu egokia eta Docker-en irudi multzo bat erabiliz, hala nola hau - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Gure kasuan, Dockerren antzeko irudi bat eraiki zen, Dockerfile honetatik Spark 2.4.5 bertsioa barne:

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

Sortutako irudia zure Docker biltegira eraiki eta kargatu daiteke, hala nola, barneko OKD biltegian. Inplementatzeko, erabili manifestu hau ({registry-url} - Docker irudi-erregistroaren URLa, {image-name} - Docker irudiaren izena, {tag} - Docker irudiaren etiketa, {livy-url} - nahi duzun URLa non zerbitzaria eskuragarri izango da Livy; "Route" manifestua erabiltzen da Red Hat OpenShift Kubernetes banaketa gisa erabiltzen bada, bestela NodePort motako Ingress edo Service manifestua erabiltzen da):

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

Aplikatu eta pod-a behar bezala abiarazi ondoren, Livy-ren interfaze grafikoa eskuragarri dago estekan: http://{livy-url}/ui. Livy-rekin, gure Spark zeregina argitaratu dezakegu, adibidez, Postman-en REST eskaera bat erabiliz. Behean eskaerak dituen bilduma baten adibide bat aurkezten da (abiarazitako zereginaren funtzionamendurako beharrezkoak diren aldagaiak dituzten konfigurazio-argumentuak "args" array-ra pasa daitezke):

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

Exekutatu dezagun bildumako lehen eskaera, joan OKD interfazera eta egiaztatu zeregina behar bezala abiarazi dela - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Aldi berean, saio bat agertuko da Livy interfazean (http://{livy-url}/ui), eta horren barruan, Livy APIa edo interfaze grafikoa erabiliz, zereginaren aurrerapena egin eta saioa aztertu dezakezu. erregistroak.

Orain ikus dezagun nola funtzionatzen duen Livyk. Horretarako, azter ditzagun Livy edukiontziaren erregistroak pod barruan Livy zerbitzariarekin - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=erregistroak. Horietatik ikus dezakegu "livy" izeneko edukiontzi batean Livy REST API-ra deitzean, spark-submit bat exekutatzen dela, goian erabili dugunaren antzekoa (hemen {livy-pod-name} sortutako podaren izena da. Livy zerbitzariarekin). Bildumak bigarren kontsulta bat ere aurkezten du, Livy zerbitzari bat erabiliz Spark exekutagarri bat urrunetik ostatatzen duten zereginak exekutatzeko aukera ematen duena.

Hirugarren erabilera kasua - Spark Operator

Orain zeregina probatu ondoren, aldizka exekutatzeko galdera sortzen da. Kubernetes kluster batean zereginak aldian-aldian exekutatzeko jatorrizko modua CronJob entitatea da eta erabil dezakezu, baina momentuz Kubernetes-en aplikazioak kudeatzeko operadoreen erabilera oso ezaguna da eta Sparkentzat nahiko operadore heldua dago, hau ere Enpresa mailako soluzioetan erabiltzen da (adibidez, Lightbend FastData Platform). Erabiltzea gomendatzen dugu - Spark-en (2.4.5) egungo bertsio egonkorrak konfigurazio-aukera nahiko mugatuak ditu Spark-en zereginak Kubernetesen exekutatzeko, eta hurrengo bertsio nagusiak (3.0.0) Kubernetes-en guztizko laguntza deklaratzen du, baina bere kaleratze data ezezaguna izaten jarraitzen du. . Spark Operator-ek hutsune hori konpentsatzen du konfigurazio-aukera garrantzitsuak gehituz (adibidez, ConfigMap bat Hadoop sarbide-konfigurazioarekin Spark podetan muntatzea) eta aldizka programatutako zeregin bat exekutatzeko gaitasunarekin.

Apache Spark exekutatzen Kubernetes-en
Azpimarra dezagun hirugarren erabilera-kasu gisa: Spark zereginak aldizka exekutatzen ditu Kubernetes kluster batean produkzio-begizta batean.

Spark Operator kode irekia da eta Google Cloud Platform-en garatua - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Bere instalazioa 3 modutan egin daiteke:

  1. Lightbend FastData Platform/Cloudflow instalazioaren barruan;
  2. Helm erabiliz:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Biltegi ofizialeko manifestuak erabiliz (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Aipatzekoa da honako hau: Cloudflow-ek API bertsioa v1beta1 duen operadorea barne hartzen du. Instalazio mota hau erabiltzen bada, Spark aplikazioaren manifestuaren deskribapenak Git-en etiketa adibideetan oinarrituta egon beharko luke APIaren bertsio egokiarekin, adibidez, "v1beta1-0.9.0-2.4.0". Operadorearen bertsioa "bertsioak" hiztegian operadorearen barnean dagoen CRDren deskribapenean aurki daiteke:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Eragilea behar bezala instalatuta badago, Spark operadorea duen pod aktibo bat agertuko da dagokion proiektuan (adibidez, cloudflow-fdp-sparkoperator Cloudflow espazioan Cloudflow instalaziorako) eta dagokion Kubernetes baliabide mota bat agertuko da "sparkapplications" izenekoa. . Eskuragarri dauden Spark aplikazioak arakatu ditzakezu komando honekin:

oc get sparkapplications -n {project}

Spark Operator erabiliz zereginak exekutatzeko 3 gauza egin behar dituzu:

  • sortu Docker irudi bat, beharrezko liburutegi guztiak barne hartzen dituena, baita konfigurazio eta fitxategi exekutagarriak ere. Xede-irudian, CI/CD fasean sortutako irudia da eta proba-kluster batean probatua;
  • argitaratu Docker-en irudi bat Kubernetes klusterretik eskura daitekeen erregistro batean;
  • sortu "SparkApplication" motako manifestua eta abiarazi beharreko zereginaren deskribapena. Manifestu adibideak biltegi ofizialean daude eskuragarri (adibidez. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Manifestuari buruzko puntu garrantzitsuak nabarmendu behar dira:
    1. “apiVersion” hiztegian operadorearen bertsioari dagokion API bertsioa adierazi behar da;
    2. “metadata.namespace” hiztegian aplikazioa abiaraziko den izen-espazioa adierazi behar da;
    3. “spec.image” hiztegian sortutako Docker irudiaren helbidea izan behar da erregistro eskuragarri batean;
    4. "spec.mainClass" hiztegian prozesua hasten denean exekutatu behar den Spark ataza-klasea eduki behar da;
    5. jar fitxategi exekutagarriaren bidea "spec.mainApplicationFile" hiztegian zehaztu behar da;
    6. “spec.sparkVersion” hiztegian erabiltzen ari den Spark-en bertsioa adierazi behar da;
    7. "spec.driver.serviceAccount" hiztegiak aplikazioa exekutatzeko erabiliko den Kubernetes izen-espazioaren barruan zerbitzu-kontua zehaztu behar du;
    8. “spec.executor” hiztegian aplikazioari esleitutako baliabide kopurua adierazi behar da;
    9. "spec.volumeMounts" hiztegian Spark ataza fitxategiak sortuko diren tokiko direktorioak zehaztu behar du.

Manifestu bat sortzeko adibide bat (hemen {spark-service-account} Kubernetes klusterreko zerbitzu-kontu bat da Spark zereginak exekutatzeko):

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"

Manifestu honek zerbitzu-kontu bat zehazten du eta horretarako, manifestua argitaratu aurretik, Spark aplikazioak Kubernetes APIarekin elkarreragiteko beharrezkoak diren atzipen-eskubideak ematen dituzten beharrezko rol-loturak sortu behar dituzu (beharrezkoa bada). Gure kasuan, aplikazioak Pods sortzeko eskubideak behar ditu. Sortu dezagun beharrezko rol-lotura:

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

Aipatzekoa da, halaber, manifestuaren zehaztapen honek "hadoopConfigMap" parametro bat izan dezakeela, eta horri esker, Hadoop konfigurazioarekin ConfigMap bat zehaz dezakezu, dagokion fitxategia Docker irudian lehenik jarri beharrik gabe. Zereginak aldian-aldian exekutatzeko ere egokia da - "programazioa" parametroa erabiliz, zeregin jakin bat exekutatzeko egutegia zehaztu daiteke.

Horren ostean, gure manifestua spark-pi.yaml fitxategian gordetzen dugu eta gure Kubernetes klusterean aplikatzen dugu:

oc apply -f spark-pi.yaml

Honek "sparkapplications" motako objektu bat sortuko du:

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

Kasu honetan, aplikazio bat duen pod bat sortuko da, eta horren egoera sortutako "sparkapplications"-n bistaratuko da. Komando honekin ikus dezakezu:

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

Ataza amaitzean, POD-a "Osatuta" egoerara pasatuko da, eta "sparkapplications"-en ere eguneratuko da. Aplikazioen erregistroak arakatzailean ikus daitezke edo komando hau erabiliz (hemen {sparkapplications-pod-name} da exekutatzen ari den zereginaren podaren izena):

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

Spark zereginak sparkctl utilitate espezializatua erabiliz ere kudeatu daitezke. Instalatzeko, klonatu biltegia bere iturburu-kodearekin, instalatu Go eta eraiki utilitate hau:

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

Azter dezagun Spark exekutatzen ari diren zereginen zerrenda:

sparkctl list -n {project}

Sortu dezagun Spark zereginaren deskribapen bat:

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"

Exekutatu dezagun deskribatutako zeregina sparkctl erabiliz:

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

Azter dezagun Spark exekutatzen ari diren zereginen zerrenda:

sparkctl list -n {project}

Azter dezagun abiarazitako Spark zeregin baten gertaeren zerrenda:

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

Azter dezagun martxan dagoen Spark zereginaren egoera:

sparkctl status spark-pi -n {project}

Amaitzeko, Kubernetesen Spark-en (2.4.5) egungo bertsio egonkorra erabiltzeak aurkitutako desabantailak kontuan hartu nahiko nituzke:

  1. Lehenengo eta, beharbada, desabantaila nagusia Datu-lokalitatearen falta da. YARN-en gabezia guztiak izan arren, abantailak ere bazeuden erabiltzeak, adibidez, kodea datuei (datuei kodeari baino) emateko printzipioa. Horri esker, kalkuluetan parte hartzen zuten datuak kokatzen ziren nodoetan exekutatu ziren Spark zereginak, eta sarean datuak emateko behar zen denbora nabarmen murriztu zen. Kubernetes erabiltzean, ataza batean parte hartzen duten datuak sarean zehar mugitzeko beharraren aurrean gaude. Nahikoa handiak badira, zereginen exekuzio-denbora nabarmen handitu daiteke eta, gainera, Spark ataza-instantziari esleitutako disko-espazio nahiko handia behar du bere aldi baterako biltegiratzeko. Desabantaila hori Kubernetesen datuen lokalizazioa ziurtatzen duen software espezializatua erabiliz arindu daiteke (adibidez, Alluxio), baina horrek esan nahi du datuen kopia osoa Kubernetes klusterreko nodoetan gordetzeko beharra.
  2. Bigarren desabantaila garrantzitsua segurtasuna da. Lehenespenez, Spark zereginak exekutatzeko segurtasunarekin lotutako eginbideak desgaituta daude, Kerberos-en erabilera ez dago dokumentazio ofizialean (nahiz eta 3.0.0 bertsioan dagozkion aukerak sartu ziren, lan gehigarria eskatuko duena), eta segurtasun dokumentazioan. Spark erabiliz (https ://spark.apache.org/docs/2.4.5/security.html) YARN, Mesos eta Standalone Cluster bakarrik agertzen dira gako denda gisa. Aldi berean, Spark zereginak abiarazten dituen erabiltzailea ezin da zuzenean zehaztu - funtzionatuko duen zerbitzu-kontua bakarrik zehazten dugu eta erabiltzailea konfiguratutako segurtasun-politiken arabera hautatzen da. Ildo horretan, edo root erabiltzailea erabiltzen da, ingurune produktibo batean segurua ez dena, edo ausazko UID bat duen erabiltzailea, eta hori deserosoa da datuetarako sarbide-eskubideak banatzerakoan (hau konpon daiteke PodSecurityPolicies sortuz eta hauekin lotuz). dagozkion zerbitzu-kontuak). Gaur egun, irtenbidea da beharrezkoak diren fitxategi guztiak zuzenean Docker irudian jartzea, edo Spark abiarazteko gidoia aldatzea zure erakundean hartutako sekretuak gordetzeko eta berreskuratzeko mekanismoa erabiltzeko.
  3. Kubernetes erabiliz Spark lanak exekutatzen dira ofizialki modu esperimentalean oraindik eta baliteke etorkizunean aldaketa handiak egotea erabilitako artefaktuetan (konfigurazio-fitxategiak, Docker-eko oinarri-irudiak eta abiarazteko script-ak). Eta hain zuzen ere, materiala prestatzerakoan, 2.3.0 eta 2.4.5 bertsioak probatu ziren, portaera nabarmen ezberdina zen.

Itxaron ditzagun eguneratzeak - Spark-en (3.0.0) bertsio berri bat kaleratu zen duela gutxi, eta horrek aldaketa handiak ekarri zituen Spark-en Kubernetes-en lanean, baina baliabide-kudeatzaile honen laguntza-egoera esperimentala mantendu zuen. Agian hurrengo eguneratzeek YARN uztea eta Kubernetesen Spark zereginak exekutatzeko guztiz gomendatzea ahalbidetuko dute zure sistemaren segurtasunaren beldurrik gabe eta osagai funtzionalak modu independentean aldatu beharrik gabe.

Amaiera

Iturria: www.habr.com

Gehitu iruzkin berria