Kurante Apache Spark sur Kubernetes

Karaj legantoj, bonan posttagmezon. Hodiaŭ ni parolos iomete pri Apache Spark kaj ĝiaj evoluperspektivoj.

Kurante Apache Spark sur Kubernetes

En la moderna mondo de Granda Datumo, Apache Spark estas la fakta normo por disvolvi taskojn pri grupaj datumtraktadoj. Krome, ĝi ankaŭ estas uzata por krei fluajn aplikojn, kiuj funkcias en la mikrobatch-koncepto, prilaborado kaj sendado de datumoj en malgrandaj partoj (Spark Structured Streaming). Kaj tradicie ĝi estis parto de la ĝenerala Hadoop-stako, uzante YARN (aŭ en iuj kazoj Apache Mesos) kiel la rimedomanaĝeron. Ĝis 2020, ĝia uzo en sia tradicia formo estas pridubita por la plej multaj kompanioj pro la manko de decaj Hadoop-distribuoj - la evoluo de HDP kaj CDH ĉesis, CDH ne estas bone evoluinta kaj havas altan koston, kaj la ceteraj Hadoop-provizantoj havas aŭ ĉesis ekzisti aŭ havas malklaran estontecon. Tial, la lanĉo de Apache Spark uzanta Kubernetes estas de kreskanta intereso inter la komunumo kaj grandaj kompanioj - iĝante normo en kontenera orkestrado kaj administrado de rimedoj en privataj kaj publikaj nuboj, ĝi solvas la problemon kun maloportuna planado de rimedoj de Spark-taskoj sur YARN kaj provizas konstante evoluanta platformo kun multaj komercaj kaj malfermaj distribuoj por kompanioj de ĉiuj grandecoj kaj strioj. Krome, post la populareco, plej multaj jam sukcesis akiri kelkajn proprajn instalaĵojn kaj pliigis sian kompetentecon pri ĝia uzo, kio simpligas la movon.

Komencante de la versio 2.3.0, Apache Spark akiris oficialan subtenon por ruli taskojn en Kubernetes-areo kaj hodiaŭ ni parolos pri la nuna matureco de ĉi tiu aliro, diversaj opcioj por ĝia uzo kaj malfacilaĵoj, kiujn oni renkontos dum efektivigo.

Antaŭ ĉio, ni rigardu la procezon de disvolvado de taskoj kaj aplikoj bazitaj sur Apache Spark kaj reliefigu tipajn kazojn, en kiuj vi devas ruli taskon sur Kubernetes-areto. En la preparado de ĉi tiu afiŝo, OpenShift estas uzata kiel distribuo kaj komandoj rilataj al ĝia komandlinia utileco (oc) estos donitaj. Por aliaj Kubernetes-distribuoj, la respondaj komandoj de la norma Kubernetes komandlinia utileco (kubectl) aŭ iliaj analogoj (ekzemple, por oc adm politiko) povas esti uzataj.

Unua uzokazo - spark-submit

Dum la disvolviĝo de taskoj kaj aplikoj, la programisto bezonas plenumi taskojn por sencimigi datuman transformon. Teorie, ĝermoj povas esti uzitaj por tiuj celoj, sed evoluo kun la partopreno de realaj (kvankam testaj) kazoj de finsistemoj pruvis esti pli rapida kaj pli bona en tiu klaso de taskoj. En la kazo kiam ni elpurigas realajn kazojn de finsistemoj, du scenaroj estas eblaj:

  • la programisto prizorgas Spark-taskon loke en memstara reĝimo;

    Kurante Apache Spark sur Kubernetes

  • programisto prizorgas Spark-taskon sur Kubernetes-areto en prova buklo.

    Kurante Apache Spark sur Kubernetes

La unua opcio rajtas ekzisti, sed kunportas kelkajn malavantaĝojn:

  • Ĉiu programisto devas esti provizita per aliro de la laborejo al ĉiuj kazoj de la finsistemoj kiujn li bezonas;
  • sufiĉa kvanto de rimedoj estas postulata sur la labormaŝino por ruli la taskon estantan evoluigita.

La dua opcio ne havas ĉi tiujn malavantaĝojn, ĉar la uzo de Kubernetes-grupo ebligas al vi asigni la necesan rimedon por ruli taskojn kaj provizi al ĝi la necesan aliron por fini sistemajn petskribojn, flekseble disponigante aliron al ĝi uzante la Kubernetes rolmodelon por ĉiuj membroj de la evolua teamo. Ni reliefigu ĝin kiel la unua uzkazo - lanĉante Spark-taskojn de loka programmaŝino sur Kubernetes-areto en prova cirkvito.

Ni parolu pli pri la procezo agordi Spark por funkcii loke. Por komenci uzi Spark, vi devas instali ĝin:

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

Ni kolektas la necesajn pakaĵojn por labori kun Kubernetes:

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

Plena konstruo prenas multan tempon, kaj por krei Docker-bildojn kaj ruli ilin sur Kubernetes-areo, vi vere bezonas nur jardosierojn el la dosierujo "asembleo/", do vi nur povas konstrui ĉi tiun subprojekton:

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

Por ruli Spark-laborojn sur Kubernetes, vi devas krei Docker-bildon por uzi kiel bazan bildon. Estas 2 eblaj aliroj ĉi tie:

  • La generita Docker-bildo inkluzivas la plenumeblan Spark-taskan kodon;
  • La kreita bildo inkluzivas nur Spark kaj la necesajn dependecojn, la plenumebla kodo estas gastigita malproksime (ekzemple en HDFS).

Unue, ni konstruu Docker-bildon enhavantan provan ekzemplon de Spark-tasko. Por krei Docker-bildojn, Spark havas ilon nomatan "docker-image-tool". Ni studu la helpon pri ĝi:

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

Kun ĝia helpo, vi povas krei Docker-bildojn kaj alŝuti ilin al foraj registroj, sed defaŭlte ĝi havas kelkajn malavantaĝojn:

  • senmanke kreas 3 Docker-bildojn samtempe - por Spark, PySpark kaj R;
  • ne permesas al vi specifi bildonomon.

Tial, ni uzos modifitan version de ĉi tiu utileco donita sube:

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

Kun ĝia helpo, ni kunvenas bazan Spark-bildon enhavantan testan taskon por kalkuli Pi per Spark (ĉi tie {docker-registry-url} estas la URL de via Docker-bildregistro, {repo} estas la nomo de la deponejo ene de la registro, kiu kongruas kun la projekto en OpenShift , {bildo-nomo} - nomo de la bildo (se oni uzas trinivelan apartigon de bildoj, ekzemple, kiel en la integra registro de Red Hat OpenShift-bildoj), {tag} - etikedo de ĉi tiu versio de la bildo):

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

Ensalutu al la OKD-grupo per la konzola ilo (ĉi tie {OKD-API-URL} estas la OKD-grupo API URL):

oc login {OKD-API-URL}

Ni ricevu la ĵetonon de la nuna uzanto por rajtigo en la Docker-Registro:

oc whoami -t

Ensalutu al la interna Docker-Registro de la OKD-areto (ni uzas la ĵetonon akiritan uzante la antaŭan komandon kiel pasvorton):

docker login {docker-registry-url}

Ni alŝutu la kunvenitan Docker-bildon al la Docker Registry OKD:

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

Ni kontrolu, ke la kunmetita bildo disponeblas en OKD. Por fari tion, malfermu la URL en la retumilo kun listo de bildoj de la responda projekto (ĉi tie {projekto} estas la nomo de la projekto ene de la OpenShift-grupo, {OKD-WEBUI-URL} estas la URL de la OpenShift Web-konzolo ) - https://{OKD-WEBUI-URL}/console /project/{projekto}/browse/images/{bildo-nomo}.

Por ruli taskojn, servokonto devas esti kreita kun la privilegioj ruli podojn kiel radiko (ni diskutos ĉi tiun punkton poste):

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

Ni rulu la komandon spark-submit por publikigi Spark-taskon al la OKD-areto, specifante la kreitan servokonton kaj Docker-bildon:

 /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

Jen:

—name — la nomo de la tasko, kiu partoprenos en la formado de la nomo de la Kubernetes-podoj;

—class — klaso de la plenumebla dosiero, vokita kiam la tasko komenciĝas;

—conf — Spark-agordaj parametroj;

spark.executor.instances — la nombro da Spark-ekzekutintoj lanĉendaj;

spark.kubernetes.authenticate.driver.serviceAccountName - la nomo de la serva konto de Kubernetes uzata dum lanĉo de podoj (por difini la sekurecan kuntekston kaj kapablojn dum interagado kun la Kubernetes API);

spark.kubernetes.namespace — Kubernetes nomspaco en kiu ŝoforaj kaj ekzekutistoj estos lanĉitaj;

spark.submit.deployMode — metodo de lanĉo de Spark (por norma spark-submetado "areto" estas uzata, por Spark Operator kaj postaj versioj de Spark "kliento");

spark.kubernetes.container.image - Docker-bildo uzata por lanĉi podojn;

spark.master — Kubernetes API URL (ekstera estas specifita tiel aliro okazas de la loka maŝino);

local:// estas la vojo al la efektivigebla Spark ene de la bildo de Docker.

Ni iras al la responda OKD-projekto kaj studas la kreitajn podojn - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Por simpligi la disvolvan procezon, alia opcio povas esti uzata, en kiu estas kreita komuna baza bildo de Spark, uzata de ĉiuj taskoj por ruliĝi, kaj momentfotoj de ruleblaj dosieroj estas publikigitaj al ekstera stokado (ekzemple Hadoop) kaj specifitaj dum vokado. spark-submeti kiel ligilon. En ĉi tiu kazo, vi povas ruli malsamajn versiojn de Spark-taskoj sen rekonstrui Docker-bildojn, uzante, ekzemple, WebHDFS por publikigi bildojn. Ni sendas peton por krei dosieron (ĉi tie {gastiganto} estas la gastiganto de la servo WebHDFS, {port} estas la haveno de la servo WebHDFS, {path-to-file-on-hdfs} estas la dezirata vojo al la dosiero sur HDFS):

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

Vi ricevos respondon tian (ĉi tie {location} estas la URL, kiu devas esti uzata por elŝuti la dosieron):

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

Ŝarĝu la ruleblan dosieron de Spark en HDFS (ĉi tie {path-to-local-file} estas la vojo al la rulebla dosiero de Spark sur la nuna gastiganto):

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

Post ĉi tio, ni povas fari spark-submetadon uzante la Spark-dosieron alŝutitan al HDFS (ĉi tie {class-name} estas la nomo de la klaso, kiu devas esti lanĉita por plenumi la taskon):

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

Oni devas rimarki, ke por aliri HDFS kaj certigi, ke la tasko funkcias, vi eble bezonos ŝanĝi la Dockerfile kaj la entrypoint.sh-skripton - aldonu direktivon al la Dockerfile por kopii dependajn bibliotekojn al la dosierujo /opt/spark/jars kaj inkluzivi la HDFS-agordan dosieron en SPARK_CLASSPATH en enirpunkto.

Dua uzokazo - Apache Livy

Plue, kiam tasko estas evoluigita kaj la rezulto devas esti provita, la demando ekestas lanĉi ĝin kiel parto de la CI/CD-procezo kaj spuri la statuson de ĝia ekzekuto. Kompreneble, vi povas ruli ĝin uzante lokan spark-submetadon, sed ĉi tio malfaciligas la CI/KD-infrastrukturon ĉar ĝi postulas instali kaj agordi Spark sur la CI-servilaj agentoj/kuristoj kaj agordi aliron al la Kubernetes API. Por ĉi tiu kazo, la cela efektivigo elektis uzi Apache Livy kiel REST-API por ruli Spark-taskojn gastigitajn ene de Kubernetes-areto. Kun ĝia helpo, vi povas ruli Spark-taskojn sur Kubernetes-areo uzante regulajn cURL-petojn, kiuj estas facile efektivigitaj surbaze de iu ajn CI-solvo, kaj ĝia lokigo ene de la Kubernetes-areo solvas la problemon de aŭtentigo interagante kun la Kubernetes-API.

Kurante Apache Spark sur Kubernetes

Ni reliefigu ĝin kiel duan uzkazon - ruli Spark-taskojn kiel parto de CI/KD-procezo sur Kubernetes-areto en prova buklo.

Iom pri Apache Livy - ĝi funkcias kiel HTTP-servilo, kiu provizas Retan interfacon kaj RESTful-API, kiu ebligas al vi malproksime lanĉi spark-submit pasante la necesajn parametrojn. Tradicie ĝi estis ekspedita kiel parto de HDP-distribuo, sed ankaŭ povas esti deplojita al OKD aŭ iu ajn alia instalado de Kubernetes uzante la taŭgan manifeston kaj aron de Docker-bildoj, kiel ĉi tiu - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Por nia kazo, simila Docker-bildo estis konstruita, inkluzive de Spark-versio 2.4.5 de la sekva Dockerfile:

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

La generita bildo povas esti konstruita kaj alŝutita al via ekzistanta Docker-deponejo, kiel la interna OKD-deponejo. Por disfaldi ĝin, uzu la sekvan manifeston ({registry-url} - URL de la bildregistro de Docker, {image-name} - Docker-bildnomo, {tag} - Docker-bilda etikedo, {livy-url} - dezirata URL kie la servilo estos alirebla Livy; la manifesto "Itinero" estas uzata se Red Hat OpenShift estas uzata kiel Kubernetes-distribuo, alie la responda Eniro aŭ Servo manifesto de tipo NodePort estas uzata):

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

Post apliki ĝin kaj sukcese lanĉi la podon, la grafika interfaco de Livy disponeblas ĉe la ligilo: http://{livy-url}/ui. Kun Livy, ni povas publikigi nian Spark-taskon uzante REST-peton de, ekzemple, Postman. Ekzemplo de kolekto kun petoj estas prezentita sube (agordaj argumentoj kun variabloj necesaj por la funkciado de la lanĉita tasko povas esti pasitaj en la tabelo "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": {}
}

Ni plenumu la unuan peton de la kolekto, iru al la OKD-interfaco kaj kontrolu, ke la tasko estas lanĉita sukcese - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Samtempe aperos sesio en la interfaco de Livy (http://{livy-url}/ui), ene de kiu, uzante la API de Livy aŭ grafikan interfacon, vi povas spuri la progreson de la tasko kaj studi la sesion. ŝtipoj.

Nun ni montru kiel Livy funkcias. Por fari tion, ni ekzamenu la protokolojn de la Livy-ujo ene de la pod kun la Livy-servilo - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=protokoloj. El ili ni povas vidi, ke kiam oni vokas la Livy REST API en ujo nomita "livy", oni ekzekutas spark-submetadon, simile al tiu, kiun ni uzis supre (ĉi tie {livy-pod-name} estas la nomo de la kreita pod. kun la servilo Livy). La kolekto ankaŭ enkondukas duan demandon, kiu ebligas al vi ruli taskojn, kiuj malproksime gastigas Spark-efikeblan uzante Livy-servilon.

Tria uzokazo - Spark Operator

Nun kiam la tasko estis provita, la demando pri funkcii ĝin regule ekestas. La denaska maniero regule ruli taskojn en Kubernetes-grupo estas la ento CronJob kaj vi povas uzi ĝin, sed nuntempe la uzo de funkciigistoj por administri aplikojn en Kubernetes estas tre populara kaj por Spark ekzistas sufiĉe matura operatoro, kiu ankaŭ estas uzata en Enterprise-nivelaj solvoj (ekzemple, Lightbend FastData Platform). Ni rekomendas uzi ĝin - la nuna stabila versio de Spark (2.4.5) havas sufiĉe limigitajn agordajn eblojn por ruli Spark-taskojn en Kubernetes, dum la sekva grava versio (3.0.0) deklaras plenan subtenon por Kubernetes, sed ĝia eldondato restas nekonata. . Spark Operator kompensas ĉi tiun mankon aldonante gravajn agordajn elektojn (ekzemple, muntante ConfigMap kun Hadoop-alira agordo al Spark-podoj) kaj la kapablo plenumi regule planitan taskon.

Kurante Apache Spark sur Kubernetes
Ni reliefigu ĝin kiel trian uzkazon - regule rulante Spark-taskojn sur Kubernetes-areto en produktadbuklo.

Spark Operator estas malfermkoda kaj disvolvita ene de la Google Cloud Platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Ĝia instalado povas esti farita en 3 manieroj:

  1. Kiel parto de la instalado de Lightbend FastData Platform/Cloudflow;
  2. Uzante Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Uzante manifestojn de la oficiala deponejo (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Indas noti la jenon - Cloudflow inkluzivas funkciigiston kun API-versio v1beta1. Se ĉi tiu tipo de instalado estas uzata, Spark-aplikaj manifestpriskriboj devus esti bazitaj sur ekzemplaj etikedoj en Git kun la taŭga API-versio, ekzemple "v1beta1-0.9.0-2.4.0". La versio de la funkciigisto troveblas en la priskribo de la CRD inkluzivita en la funkciigisto en la "versioj" vortaro:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Se la funkciigisto estas instalita ĝuste, aktiva pod kun la Spark-funkciigisto aperos en la responda projekto (ekzemple, cloudflow-fdp-sparkoperator en la Cloudflow-spaco por la instalado de Cloudflow) kaj aperos responda rimeda tipo de Kubernetes nomita "sparkapplications". . Vi povas esplori disponeblajn Spark-aplikojn per la sekva komando:

oc get sparkapplications -n {project}

Por ruli taskojn per Spark Operator vi devas fari 3 aferojn:

  • kreu Docker-bildon, kiu inkluzivas ĉiujn necesajn bibliotekojn, same kiel agordajn kaj ruleblajn dosierojn. En la celbildo, ĉi tio estas bildo kreita ĉe la CI/KD-stadio kaj provita sur testa areto;
  • publikigi Docker-bildon al registro alirebla de la Kubernetes-grupo;
  • generi manifeston kun la tipo "SparkApplication" kaj priskribo de la lanĉota tasko. Ekzemplaj manifestoj estas haveblaj en la oficiala deponejo (ekz. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Estas gravaj punktoj por noti pri la manifesto:
    1. la vortaro "apiVersion" devas indiki la API-version respondan al la operaciista versio;
    2. la vortaro “metadata.namespace” devas indiki la nomspacon en kiu la aplikaĵo estos lanĉita;
    3. la vortaro “spec.image” devas enhavi la adreson de la kreita Docker-bildo en alirebla registro;
    4. la vortaro "spec.mainClass" devas enhavi la Spark-taskan klason, kiu devas esti rulita kiam la procezo komenciĝas;
    5. la vortaro “spec.mainApplicationFile” devas enhavi la vojon al la plenumebla jardosiero;
    6. la vortaro “spec.sparkVersion” devas indiki la version de Spark uzata;
    7. la vortaro "spec.driver.serviceAccount" devas specifi la servokonton ene de la responda nomspaco de Kubernetes, kiu estos uzata por ruli la aplikaĵon;
    8. la vortaro "spec.executor" devas indiki la nombron da rimedoj asignitaj al la aplikaĵo;
    9. la vortaro "spec.volumeMounts" devas specifi la lokan dosierujon en kiu la lokaj Spark-taskaj dosieroj estos kreitaj.

Ekzemplo de generado de manifesto (ĉi tie {spark-service-account} estas servokonto ene de la Kubernetes-areo por ruli Spark-taskojn):

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"

Ĉi tiu manifesto precizigas servokonton por kiu, antaŭ ol publikigi la manifeston, vi devas krei la necesajn rolligojn, kiuj provizas la necesajn alirrajtojn por la Spark-aplikaĵo por interagi kun la Kubernetes API (se necese). En nia kazo, la aplikaĵo bezonas rajtojn por krei Pods. Ni kreu la necesan rolligadon:

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

Ankaŭ indas noti, ke ĉi tiu manifesta specifo povas inkluzivi parametron "hadoopConfigMap", kiu ebligas al vi specifi ConfigMap kun la agordo Hadoop sen devi unue meti la respondan dosieron en la bildon de Docker. Ĝi ankaŭ taŭgas por plenumi taskojn regule - uzante la parametron "horaro", oni povas specifi horaron por ekzekuti difinitan taskon.

Post tio, ni konservas nian manifeston al la dosiero spark-pi.yaml kaj aplikas ĝin al nia Kubernetes-grupo:

oc apply -f spark-pi.yaml

Ĉi tio kreos objekton de tipo "sparkapplications":

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

En ĉi tiu kazo, pod kun aplikaĵo estos kreita, kies stato estos montrita en la kreitaj "sparkapplications". Vi povas vidi ĝin per la sekva komando:

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

Post la kompletiĝo de la tasko, la POD moviĝos al la statuso "Kompletita", kiu ankaŭ ĝisdatigos en "sparkaplikoj". Aplikaj protokoloj povas esti viditaj en la retumilo aŭ uzante la jenan komandon (ĉi tie {sparkapplications-pod-name} estas la nomo de la podo de la funkcianta tasko):

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

Spark-taskoj ankaŭ povas esti administritaj uzante la specialecan sparkctl-servaĵon. Por instali ĝin, klonu la deponejon per ĝia fontkodo, instalu Go kaj konstruu ĉi tiun ilon:

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

Ni ekzamenu la liston de rulantaj Spark-taskoj:

sparkctl list -n {project}

Ni kreu priskribon por la Spark-tasko:

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"

Ni rulu la priskribitan taskon uzante sparkctl:

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

Ni ekzamenu la liston de rulantaj Spark-taskoj:

sparkctl list -n {project}

Ni ekzamenu la liston de eventoj de lanĉita Spark-tasko:

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

Ni ekzamenu la staton de la funkcianta Spark-tasko:

sparkctl status spark-pi -n {project}

Konklude, mi ŝatus konsideri la malkovritajn malavantaĝojn uzi la nunan stabilan version de Spark (2.4.5) en Kubernetes:

  1. La unua kaj, eble, ĉefa malavantaĝo estas la manko de Datuma Loko. Malgraŭ ĉiuj mankoj de YARN, estis ankaŭ avantaĝoj uzi ĝin, ekzemple, la principo de liverado de kodo al datumoj (prefere ol datumoj al kodo). Danke al ĝi, Spark-taskoj estis efektivigitaj sur la nodoj, kie troviĝis la datumoj implikitaj en la kalkuloj, kaj la tempo necesa por liveri datumojn tra la reto estis signife reduktita. Kiam ni uzas Kubernetes, ni alfrontas la bezonon movi datumojn implikitajn en tasko tra la reto. Se ili estas sufiĉe grandaj, la taska ekzekuttempo povas pliiĝi signife, kaj ankaŭ postuli sufiĉe grandan kvanton da diskospaco asignita al Spark-taskaj okazoj por ilia provizora stokado. Ĉi tiu malavantaĝo povas esti mildigita per uzado de speciala programaro, kiu certigas datuman lokon en Kubernetes (ekzemple, Alluxio), sed tio fakte signifas la bezonon stoki kompletan kopion de la datumoj sur la nodoj de la Kubernetes-areo.
  2. La dua grava malavantaĝo estas sekureco. Defaŭlte, sekurec-rilataj funkcioj pri funkciado de Spark-taskoj estas malŝaltitaj, la uzo de Kerberos ne estas kovrita en la oficiala dokumentaro (kvankam la respondaj opcioj estis lanĉitaj en versio 3.0.0, kiu postulos plian laboron), kaj la sekureca dokumentaro por uzante Spark (https ://spark.apache.org/docs/2.4.5/security.html) nur YARN, Mesos kaj Standalone Cluster aperas kiel ŝlosilaj vendejoj. Samtempe, la uzanto sub kiu Spark-taskoj estas lanĉitaj ne povas esti specifita rekte - ni nur specifas la servokonton sub kiu ĝi funkcios, kaj la uzanto estas elektita surbaze de la agordita sekureca politiko. Ĉi-rilate, aŭ la radika uzanto estas uzata, kiu ne estas sekura en produktiva medio, aŭ uzanto kun hazarda UID, kio estas maloportuna dum distribuado de alirrajtoj al datumoj (ĉi tio povas esti solvita kreante PodSecurityPolicies kaj ligante ilin al la respondaj servaj kontoj). Nuntempe, la solvo estas aŭ meti ĉiujn necesajn dosierojn rekte en la bildon de Docker, aŭ modifi la lanĉan skripton de Spark por uzi la mekanismon por konservi kaj retrovi sekretojn adoptitajn en via organizo.
  3. Ruli Spark-laborpostenojn per Kubernetes estas oficiale ankoraŭ en eksperimenta reĝimo kaj eble estos gravaj ŝanĝoj en la uzataj artefaktoj (agordaj dosieroj, Docker-bazaj bildoj kaj lanĉaj skriptoj) estonte. Kaj efektive, preparinte la materialon, versioj 2.3.0 kaj 2.4.5 estis provitaj, la konduto estis signife malsama.

Ni atendu ĝisdatigojn - lastatempe estis publikigita nova versio de Spark (3.0.0), kiu alportis signifajn ŝanĝojn al la laboro de Spark ĉe Kubernetes, sed konservis la eksperimentan statuson de subteno por ĉi tiu rimeda administranto. Eble la venontaj ĝisdatigoj vere ebligos plene rekomendi forlasi YARN kaj ruli Spark-taskojn sur Kubernetes sen timo pri la sekureco de via sistemo kaj sen neceso sendepende modifi funkciajn komponantojn.

Fino.

fonto: www.habr.com

Aldoni komenton