Вклучување на Apache Spark на Kubernetes

Драги читатели, добро попладне. Денес ќе зборуваме малку за Apache Spark и неговите изгледи за развој.

Вклучување на Apache Spark на Kubernetes

Во современиот свет на големи податоци, Apache Spark е де факто стандард за развој на задачи за сериска обработка на податоци. Дополнително, се користи и за креирање апликации за стриминг кои работат во концептот на микро серија, обработка и испорака на податоци во мали делови (Spark Structured Streaming). И традиционално е дел од целокупниот стек Hadoop, користејќи YARN (или во некои случаи Apache Mesos) како менаџер на ресурси. До 2020 година, неговата употреба во неговата традиционална форма е доведена во прашање за повеќето компании поради недостатокот на пристојни дистрибуции на Hadoop - развојот на HDP и CDH е запрен, CDH не е добро развиен и има висока цена, а преостанатите добавувачи на Hadoop имаат или престана да постои или има мрачна иднина. Затоа, лансирањето на Apache Spark со користење на Kubernetes е од зголемен интерес кај заедницата и големите компании - станувајќи стандард во оркестрацијата на контејнери и управувањето со ресурси во приватни и јавни облаци, го решава проблемот со незгодното закажување ресурси на задачите Spark на YARN и обезбедува платформа која постојано се развива со многу комерцијални и отворени дистрибуции за компании од сите големини и ленти. Покрај тоа, во пресрет на популарноста, повеќето веќе успеаја да стекнат неколку сопствени инсталации и ја зголемија својата експертиза во неговата употреба, што го поедноставува потегот.

Почнувајќи од верзијата 2.3.0, Apache Spark доби официјална поддршка за извршување задачи во кластерот Kubernetes и денес ќе зборуваме за моменталната зрелост на овој пристап, различните опции за негова употреба и замките што ќе се сретнат при имплементацијата.

Пред сè, да го погледнеме процесот на развивање задачи и апликации засновани на Apache Spark и да ги истакнеме типичните случаи во кои треба да извршите задача на кластерот Kubernetes. При подготовката на овој пост, OpenShift се користи како дистрибуција и ќе бидат дадени команди релевантни за неговата алатка за командна линија (oc). За други дистрибуции на Kubernetes, може да се користат соодветните наредби од стандардната алатка за командна линија Kubernetes (kubectl) или нивните аналози (на пример, за oc adm политика).

Случај за прва употреба - искра-поднеси

За време на развојот на задачите и апликациите, развивачот треба да изврши задачи за дебагирање на трансформацијата на податоците. Теоретски, никулците може да се користат за овие цели, но развојот со учество на реални (иако тест) примери на крајните системи се покажа како побрз и подобар во оваа класа на задачи. Во случај кога дебагираме на реални примери на крајни системи, можни се две сценарија:

  • програмерот извршува задача Spark локално во самостоен режим;

    Вклучување на Apache Spark на Kubernetes

  • програмер извршува задача Spark на кластерот Kubernetes во тест циклус.

    Вклучување на Apache Spark на Kubernetes

Првата опција има право да постои, но повлекува голем број на недостатоци:

  • Секој развивач мора да има пристап од работното место до сите случаи на крајните системи што му се потребни;
  • потребно е доволно количество ресурси на работната машина за извршување на задачата што се развива.

Втората опција ги нема овие недостатоци, бидејќи употребата на кластерот Kubernetes ви овозможува да го распределите потребниот базен на ресурси за извршување задачи и да му го обезбедите потребниот пристап до крајните системски примероци, флексибилно обезбедувајќи пристап до него користејќи го моделот на Kubernetes за сите членови на тимот за развој. Ајде да го истакнеме како прв случај на употреба - започнување задачи на Spark од локална машина за развивачи на кластерот Kubernetes во тест циклус.

Ајде да разговараме повеќе за процесот на поставување на Spark да работи локално. За да започнете со користење на Spark, треба да го инсталирате:

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

Ги собираме потребните пакети за работа со Kubernetes:

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

Целосната изградба бара многу време, а за да креирате Docker слики и да ги извршите на кластерот Kubernetes, навистина ви требаат само jar-датотеки од директориумот „assembly/“, така што можете да го изградите само овој подпроект:

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

За да ги извршите Spark работните места на Kubernetes, треба да креирате Docker слика за да ја користите како основна слика. Постојат 2 можни пристапи овде:

  • Генерираната слика на Docker го вклучува извршниот код за задачата Spark;
  • Создадената слика вклучува само Spark и потребните зависности, извршниот код е хостиран од далечина (на пример, во HDFS).

Прво, ајде да изградиме слика на Docker што содржи тест пример за задача на Spark. За да креирате Docker слики, Spark има алатка наречена „docker-image-tool“. Ајде да ја проучиме помошта за тоа:

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

Со негова помош, можете да креирате Docker слики и да ги прикачите во оддалечени регистри, но по дифолт има голем број на недостатоци:

  • без неуспех создава 3 Docker слики одеднаш - за Spark, PySpark и R;
  • не дозволува да наведете име на сликата.

Затоа, ќе користиме изменета верзија на оваа алатка дадена подолу:

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

Со негова помош, составуваме основна слика на Spark која содржи тест задача за пресметување на Pi со помош на Spark (тука {docker-registry-url} е URL-то на вашиот регистар со слики на Docker, {repo} е името на складиштето во регистарот, што се совпаѓа со проектот во OpenShift , {image-name} — име на сликата (ако се користи раздвојување на слики на три нивоа, на пример, како во интегрираниот регистар на слики од Red Hat OpenShift), {tag} — ознака на оваа верзија на сликата):

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

Најавете се во кластерот OKD користејќи ја алатката за конзола (тука {OKD-API-URL} е URL-адресата на API на кластерот OKD):

oc login {OKD-API-URL}

Ајде да го добиеме токенот на тековниот корисник за овластување во регистарот Docker:

oc whoami -t

Пријавете се во внатрешниот Docker регистар на кластерот OKD (го користиме токенот добиен со користење на претходната команда како лозинка):

docker login {docker-registry-url}

Ајде да ја прикачиме собраната слика на Docker во Docker Registry OKD:

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

Ајде да провериме дали склопената слика е достапна во ОКД. За да го направите ова, отворете го URL-то во прелистувачот со листа на слики од соодветниот проект (тука {project} е името на проектот во кластерот OpenShift, {OKD-WEBUI-URL} е URL-адресата на веб-конзолата OpenShift ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

За извршување на задачите, мора да се креира сервисна сметка со привилегии да се извршуваат подлоги како root (ќе разговараме за оваа точка подоцна):

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

Ајде да ја извршиме командата spark-submit за да објавиме задача Spark во кластерот OKD, наведувајќи ја креираната сметка на услугата и сликата на Docker:

 /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

Тука:

—име — името на задачата што ќе учествува во формирањето на името на мешунките Кубернет;

—класа — класа на извршната датотека, повикана кога започнува задачата;

—conf — параметри за конфигурација на Spark;

spark.executor.instances — бројот на Spark извршители што треба да се стартуваат;

spark.kubernetes.authenticate.driver.serviceAccountName - името на сметката на услугата Kubernetes што се користи при стартување на pods (за дефинирање на безбедносниот контекст и можности при интеракција со Kubernetes API);

spark.kubernetes.namespace — именски простор на Kubernetes во кој ќе се активираат подлоги за двигатели и извршители;

spark.submit.deployMode — метод за лансирање на Spark (за стандарден spark-submit се користи „кластер“, за Spark Operator и подоцнежните верзии на Spark „клиент“);

spark.kubernetes.container.image - Докер-слика што се користи за лансирање на подлоги;

spark.master — URL-адреса на Kubernetes API (надворешна е одредена така што пристапот се јавува од локалната машина);

local:// е патеката до извршната датотека Spark во сликата на Docker.

Одиме во соодветниот OKD проект и ги проучуваме креираните подлоги - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

За да се поедностави процесот на развој, може да се користи друга опција, во која се креира заедничка основна слика на Spark, која се користи од сите задачи за извршување, а снимките од извршните датотеки се објавуваат на надворешно складирање (на пример, Hadoop) и се одредуваат при повикување искра-поднеси како врска. Во овој случај, можете да извршите различни верзии на задачите на Spark без да ги обновувате сликите на Docker, користејќи, на пример, WebHDFS за објавување слики. Испраќаме барање за создавање датотека (тука {host} е домаќин на услугата WebHDFS, {port} е пристаништето на услугата WebHDFS, {path-to-file-on-hdfs} е саканата патека до датотеката на HDFS):

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

Ќе добиете ваков одговор (тука {location} е URL-то што треба да се користи за преземање на датотеката):

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

Вчитајте ја извршната датотека Spark во HDFS (тука {path-to-local-file} е патеката до извршната датотека Spark на тековниот домаќин):

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

После ова, можеме да направиме spark-submit користејќи ја датотеката Spark поставена на HDFS (тука {class-name} е името на класата што треба да се стартува за да се заврши задачата):

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

Треба да се забележи дека за да пристапите до HDFS и да се осигурате дека задачата работи, можеби ќе треба да ги промените Dockerfile и скриптата entrypoint.sh - додадете директива во Dockerfile за да ги копирате зависните библиотеки во директориумот /opt/spark/jars и вклучете ја конфигурациската датотека HDFS во SPARK_CLASSPATH во влезната точка.

Случај за втора употреба - Apache Livy

Понатаму, кога задачата се развива и резултатот треба да се тестира, се поставува прашањето за нејзино лансирање како дел од процесот CI/CD и следење на статусот на неговото извршување. Се разбира, можете да го извршите користејќи локален повик за испраќање искра, но ова ја комплицира инфраструктурата CI/CD бидејќи бара инсталирање и конфигурирање на Spark на агентите/трчачите на серверот CI и поставување пристап до Kubernetes API. За овој случај, целната имплементација избра да користи Apache Livy како REST API за извршување задачи на Spark хостирани во кластерот Kubernetes. Со негова помош, можете да извршувате задачи на Spark на кластерот Kubernetes користејќи редовни барања за cURL, што лесно се спроведува врз основа на кое било CI решение, а неговото поставување во кластерот Kubernetes го решава проблемот со автентикацијата при интеракција со Kubernetes API.

Вклучување на Apache Spark на Kubernetes

Ајде да го истакнеме како втор случај на употреба - извршување задачи на Spark како дел од процесот CI/CD на кластерот Kubernetes во тест циклус.

Малку за Apache Livy - тој работи како HTTP сервер кој обезбедува веб-интерфејс и RESTful API што ви овозможува далечински да го стартувате spark-submit со пренесување на потребните параметри. Традиционално, тој е испорачан како дел од дистрибуција на HDP, но исто така може да се распореди на OKD или која било друга инсталација на Kubernetes користејќи го соодветниот манифест и збир на слики на Docker, како што е оваа - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. За нашиот случај, беше изградена слична слика на Docker, вклучително и Spark верзија 2.4.5 од следната 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"]

Генерираната слика може да се изгради и прикачи во вашето постоечко складиште на Docker, како што е внатрешното складиште на OKD. За да го распоредите, користете го следниов манифест ({registry-url} - URL на регистарот на слики на Docker, {image-name} - Docker image name, {tag} - Docker image ознака, {livy-url} - саканиот URL каде што серверот ќе биде достапен Livy; манифестот „Route“ се користи ако Red Hat OpenShift се користи како дистрибуција на Kubernetes, инаку се користи соодветниот Ingress или Service манифест од типот NodePort):

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

Откако ќе го примените и успешно го стартувате подлогот, графичкиот интерфејс Livy е достапен на врската: http://{livy-url}/ui. Со Livy, можеме да ја објавиме нашата Spark задача користејќи барање REST од, на пример, Postman. Пример за колекција со барања е претставен подолу (конфигурациските аргументи со променливи неопходни за функционирање на започнатата задача може да се пренесат во низата „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": {}
}

Ајде да го извршиме првото барање од колекцијата, да одиме во интерфејсот OKD и да провериме дали задачата е успешно стартувана - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Во исто време, ќе се појави сесија во интерфејсот Livy (http://{livy-url}/ui), во рамките на која, користејќи го Livy API или графичкиот интерфејс, можете да го следите напредокот на задачата и да ја проучувате сесијата трупци.

Сега да покажеме како работи Ливи. За да го направите ова, ајде да ги испитаме дневниците на контејнерот Livy внатре во подлогата со серверот Livy - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=дневници. Од нив можеме да видиме дека при повикување на Livy REST API во контејнер наречен „livy“, се извршува spark-submit, сличен на оној што го користевме погоре (тука {livy-pod-name} е името на креираниот pod со серверот Livy). Колекцијата, исто така, воведува второ барање што ви овозможува да извршувате задачи што од далечина хостираат извршна датотека Spark користејќи сервер Livy.

Случај за трета употреба - Spark Operator

Сега, кога задачата е тестирана, се поставува прашањето за нејзино редовно извршување. Природниот начин за редовно извршување на задачите во кластерот Kubernetes е ентитетот CronJob и можете да го користите, но во моментов употребата на оператори за управување со апликации во Kubernetes е многу популарна и за Spark има прилично зрел оператор, кој исто така е се користи во решенија на ниво на претпријатија (на пример, Lightbend FastData Platform). Препорачуваме да го користите - тековната стабилна верзија на Spark (2.4.5) има прилично ограничени опции за конфигурација за извршување задачи на Spark во Kubernetes, додека следната голема верзија (3.0.0) прогласува целосна поддршка за Kubernetes, но датумот на неговото објавување останува непознат . Spark Operator го компензира овој недостаток со додавање важни опции за конфигурација (на пример, монтирање на ConfigMap со конфигурација за пристап Hadoop на Spark pods) и можност за извршување на редовно закажана задача.

Вклучување на Apache Spark на Kubernetes
Ајде да го истакнеме како трет случај на употреба - редовно извршување на задачите на Spark на кластерот Kubernetes во производствен циклус.

Spark Operator е со отворен код и развиен во рамките на Google Cloud Platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Неговата инсталација може да се направи на 3 начини:

  1. Како дел од инсталацијата Lightbend FastData Platform/Cloudflow;
  2. Користење на кормилото:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Користење на манифести од официјалното складиште (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Вреди да се забележи следново - Cloudflow вклучува оператор со API верзија v1beta1. Ако се користи овој тип на инсталација, описите на манифестот на апликацијата Spark треба да се засноваат на пример ознаки во Git со соодветна верзија на API, на пример, „v1beta1-0.9.0-2.4.0“. Верзијата на операторот може да се најде во описот на CRD вклучена во операторот во речникот „верзии“:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Ако операторот е правилно инсталиран, во соодветниот проект ќе се појави активен pod со операторот Spark (на пример, cloudflow-fdp-sparkoperator во просторот Cloudflow за инсталацијата Cloudflow) и ќе се појави соодветен тип на ресурс Kubernetes наречен „sparkapplications“ . Можете да ги истражувате достапните апликации Spark со следнава команда:

oc get sparkapplications -n {project}

За да извршите задачи користејќи Spark Operator, треба да направите 3 работи:

  • креирајте Docker слика која ги вклучува сите потребни библиотеки, како и конфигурациските и извршните датотеки. Во целната слика, ова е слика создадена во фазата CI/CD и тестирана на тест кластер;
  • објави слика на Docker во регистар достапен од кластерот Kubernetes;
  • генерира манифест со типот „SparkApplication“ и опис на задачата што треба да се стартува. Примери манифестации се достапни во официјалното складиште (на пр. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Има важни точки што треба да се забележат за манифестот:
    1. речникот „apiVersion“ мора да ја означи верзијата на API што одговара на верзијата на операторот;
    2. речникот „metadata.namespace“ мора да го означи именскиот простор во кој ќе се активира апликацијата;
    3. речникот „spec.image“ мора да ја содржи адресата на креираната слика на Docker во достапен регистар;
    4. речникот „spec.mainClass“ мора да ја содржи класата на задачи Spark што треба да се изврши кога ќе започне процесот;
    5. патеката до извршната датотека jar мора да биде наведена во речникот „spec.mainApplicationFile“;
    6. речникот „spec.sparkVersion“ мора да ја означи верзијата на Spark што се користи;
    7. речникот „spec.driver.serviceAccount“ мора да ја наведе сметката на услугата во соодветниот именски простор на Kubernetes што ќе се користи за извршување на апликацијата;
    8. речникот „spec.executor“ мора да го означи бројот на ресурси доделени на апликацијата;
    9. речникот „spec.volumeMounts“ мора да го наведе локалниот директориум во кој ќе се креираат локалните датотеки со задачи Spark.

Пример за генерирање на манифест (тука {spark-service-account} е сервисна сметка во кластерот Kubernetes за извршување задачи на Spark):

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

Овој манифест одредува сервисна сметка за која, пред да го објавите манифестот, мора да ги креирате потребните врски за улоги кои ги обезбедуваат потребните права за пристап за апликацијата Spark да комуницира со Kubernetes API (ако е потребно). Во нашиот случај, на апликацијата и се потребни права за креирање Pods. Ајде да ја создадеме потребната обврзувачка улога:

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

Исто така, вреди да се напомене дека оваа спецификација на манифестот може да вклучува параметар „hadoopConfigMap“, кој ви овозможува да наведете ConfigMap со конфигурацијата Hadoop без претходно да ја поставите соодветната датотека во сликата на Docker. Погоден е и за редовно извршување на задачите - со помош на параметарот „распоред“, може да се одреди распоред за извршување на дадена задача.

После тоа, го зачувуваме нашиот манифест во датотеката spark-pi.yaml и го применуваме во нашиот кластер Kubernetes:

oc apply -f spark-pi.yaml

Ова ќе создаде објект од типот „sparkapplications“:

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

Во овој случај, ќе се создаде подлога со апликација, чиј статус ќе се прикаже во креираните „sparkapplications“. Можете да го видите со следнава команда:

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

По завршувањето на задачата, POD ќе се пресели во статусот „Завршен“, кој исто така ќе се ажурира во „sparkapplications“. Дневниците на апликациите може да се видат во прелистувачот или со помош на следнава команда (тука {sparkapplications-pod-name} е името на подлогата на тековната задача):

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

Задачите со Spark може да се управуваат и со помош на специјализираната алатка sparkctl. За да го инсталирате, клонирајте го складиштето со неговиот изворен код, инсталирајте го Go и изградете ја оваа алатка:

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

Ајде да го испитаме списокот со задачи на Spark:

sparkctl list -n {project}

Ајде да создадеме опис за задачата Spark:

vi spark-app.yaml

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

Ајде да ја извршиме опишаната задача користејќи sparkctl:

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

Ајде да го испитаме списокот со задачи на Spark:

sparkctl list -n {project}

Ајде да го испитаме списокот на настани на започнатата задача на Spark:

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

Ајде да го испитаме статусот на работната задача Spark:

sparkctl status spark-pi -n {project}

Како заклучок, би сакал да ги разгледам откриените недостатоци за користење на тековната стабилна верзија на Spark (2.4.5) во Kubernetes:

  1. Првиот и, можеби, главниот недостаток е недостатокот на локалитет на податоци. И покрај сите недостатоци на YARN, имаше и предности за користење, на пример, принципот на доставување код до податоци (наместо податоци до код). Благодарение на него, задачите на Spark беа извршени на јазлите каде што се наоѓаа податоците вклучени во пресметките, а времето потребно за доставување податоци преку мрежата беше значително намалено. Кога користиме Kubernetes, се соочуваме со потребата да ги преместиме податоците вклучени во задача низ мрежата. Ако се доволно големи, времето за извршување на задачата може значително да се зголеми, а исто така да бара прилично голема количина на простор на дискот доделен на примероците на задачи Spark за нивно привремено складирање. Овој недостаток може да се ублажи со користење на специјализиран софтвер кој обезбедува локација на податоци во Kubernetes (на пример, Alluxio), но тоа всушност значи потреба да се складира целосна копија од податоците на јазлите на кластерот Kubernetes.
  2. Вториот важен недостаток е безбедноста. Стандардно, функциите поврзани со безбедноста во врска со извршувањето на задачите на Spark се оневозможени, употребата на Kerberos не е опфатена во официјалната документација (иако соодветните опции беа воведени во верзијата 3.0.0, која ќе бара дополнителна работа), а безбедносната документација за користејќи Spark (https ://spark.apache.org/docs/2.4.5/security.html) само YARN, Mesos и самостоен кластер се појавуваат како складишта за клучеви. Во исто време, корисникот под кој се стартуваат задачите на Spark не може директно да се одреди - ние ја одредуваме само сметката на услугата под која ќе работи, а корисникот се избира врз основа на конфигурираните безбедносни политики. Во овој поглед, или се користи root корисникот, кој не е безбеден во продуктивна средина, или корисник со случаен UID, што е незгодно кога се дистрибуираат правата за пристап до податоците (ова може да се реши со креирање PodSecurityPolicies и нивно поврзување со соодветните сметки за услуги). Во моментов, решението е или да ги поставите сите потребни датотеки директно во сликата на Docker или да ја измените скриптата за стартување Spark за да го користите механизмот за складирање и преземање тајни усвоени во вашата организација.
  3. Извршувањето на Spark jobs со користење на Kubernetes е официјално сè уште во експериментален режим и може да има значителни промени во користените артефакти (датотеки за конфигурација, слики од базата на Docker и скрипти за лансирање) во иднина. И навистина, при подготовката на материјалот, беа тестирани верзиите 2.3.0 и 2.4.5, однесувањето беше значително различно.

Да почекаме ажурирања - неодамна беше објавена нова верзија на Spark (3.0.0), која донесе значителни промени во работата на Spark на Kubernetes, но го задржа експерименталниот статус на поддршка за овој менаџер на ресурси. Можеби следните ажурирања навистина ќе овозможат целосно да препорачате напуштање на YARN и извршување на задачите на Spark на Kubernetes без страв за безбедноста на вашиот систем и без потреба од независно менување на функционалните компоненти.

Заврши.

Извор: www.habr.com

Додадете коментар