Kubernetes-də Apache Spark işlədir

Əziz oxucular, günortanız xeyir. Bu gün biz Apache Spark və onun inkişaf perspektivləri haqqında bir az danışacağıq.

Kubernetes-də Apache Spark işlədir

Müasir Big Data dünyasında Apache Spark toplu məlumatların işlənməsi tapşırıqlarını hazırlamaq üçün faktiki standartdır. Bundan əlavə, o, mikro toplu konsepsiyasında işləyən axın proqramları yaratmaq, məlumatların kiçik hissələrdə işlənməsi və göndərilməsi (Spark Structured Streaming) üçün də istifadə olunur. Və ənənəvi olaraq o, resurs meneceri kimi YARN (və ya bəzi hallarda Apache Mesos) istifadə edərək ümumi Hadoop yığınının bir hissəsi olmuşdur. 2020-ci ilə qədər onun ənənəvi formada istifadəsi layiqli Hadoop paylamalarının olmaması səbəbindən əksər şirkətlər üçün sual altındadır - HDP və CDH-nin inkişafı dayanıb, CDH yaxşı inkişaf etməyib və yüksək qiymətə malikdir və qalan Hadoop təchizatçıları ya mövcud olmağı dayandırdı, ya da gələcəyi qaranlıq idi. Buna görə də, Kubernetes-dən istifadə edərək Apache Spark-ın işə salınması icma və böyük şirkətlər arasında artan maraq doğurur - özəl və ictimai buludlarda konteynerlərin təşkili və resursların idarə edilməsində standarta çevrilərək, YARN-də Spark tapşırıqlarının əlverişsiz resurs cədvəli ilə problemi həll edir və təmin edir. bütün ölçülü və zolaqlı şirkətlər üçün bir çox kommersiya və açıq paylamaları olan davamlı inkişaf edən platformadır. Bundan əlavə, populyarlıq ardınca çoxları artıq bir neçə öz qurğusunu əldə etməyi bacardılar və onun istifadəsində təcrübələrini artırdılar ki, bu da hərəkəti asanlaşdırır.

2.3.0 versiyasından başlayaraq, Apache Spark Kubernetes klasterində tapşırıqları yerinə yetirmək üçün rəsmi dəstək əldə etdi və bu gün biz bu yanaşmanın hazırkı yetkinliyi, istifadəsinin müxtəlif variantları və tətbiq zamanı qarşılaşacaq tələlər haqqında danışacağıq.

İlk növbədə, gəlin Apache Spark əsasında tapşırıqların və proqramların hazırlanması prosesinə nəzər salaq və Kubernetes klasterində tapşırığı yerinə yetirməli olduğunuz tipik halları vurğulayaq. Bu postu hazırlayarkən OpenShift paylama kimi istifadə olunur və onun komanda xətti yardım proqramına (oc) müvafiq əmrlər veriləcək. Digər Kubernetes paylamaları üçün standart Kubernetes komanda xətti yardım proqramından (kubectl) və ya onların analoqlarından (məsələn, oc adm siyasəti üçün) müvafiq əmrlər istifadə edilə bilər.

İlk istifadə halı - spark-submit

Tapşırıqların və tətbiqlərin inkişafı zamanı tərtibatçı məlumat transformasiyasını sazlamaq üçün tapşırıqları yerinə yetirməlidir. Nəzəri olaraq, bu məqsədlər üçün stublardan istifadə edilə bilər, lakin son sistemlərin real (test də olsa) nümunələrinin iştirakı ilə inkişaf bu sinif tapşırıqlarda daha sürətli və daha yaxşı olduğunu sübut etdi. Son sistemlərin real nümunələrində debug etdiyimiz halda, iki ssenari mümkündür:

  • tərtibatçı müstəqil rejimdə yerli olaraq Spark tapşırığını yerinə yetirir;

    Kubernetes-də Apache Spark işlədir

  • bir tərtibatçı test döngəsində Kubernetes klasterində Spark tapşırığını yerinə yetirir.

    Kubernetes-də Apache Spark işlədir

Birinci seçim mövcud olmaq hüququna malikdir, lakin bir sıra çatışmazlıqlara səbəb olur:

  • Hər bir tərtibatçı iş yerindən ehtiyac duyduğu son sistemlərin bütün nümunələrinə çıxışla təmin edilməlidir;
  • işlənən tapşırığı yerinə yetirmək üçün işləyən maşında kifayət qədər resurs tələb olunur.

İkinci seçimdə bu çatışmazlıqlar yoxdur, çünki Kubernetes klasterinin istifadəsi sizə tapşırıqlar üçün lazımi resurs hovuzunu ayırmağa və ona son sistem nümunələrinə lazımi girişi təmin etməyə imkan verir, Kubernetes rol modelindən istifadə edərək çevik şəkildə ona girişi təmin edir. inkişaf qrupunun bütün üzvləri. Gəlin bunu ilk istifadə halı kimi qeyd edək - sınaq dövrəsində Kubernetes klasterində yerli tərtibatçı maşından Spark tapşırıqlarını işə salaq.

Spark-ın yerli olaraq işləməsi üçün qurulması prosesi haqqında daha çox danışaq. Spark istifadə etməyə başlamaq üçün onu quraşdırmalısınız:

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 ilə işləmək üçün lazımi paketləri toplayırıq:

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

Tam qurma çox vaxt tələb edir və Docker şəkillərini yaratmaq və onları Kubernetes klasterində işə salmaq üçün sizə həqiqətən yalnız “assembly/” kataloqundan jar faylları lazımdır, ona görə də yalnız bu alt layihəni yarada bilərsiniz:

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

Kubernetes-də Spark işlərini işə salmaq üçün əsas şəkil kimi istifadə etmək üçün Docker şəkli yaratmalısınız. Burada 2 mümkün yanaşma var:

  • Yaradılmış Docker təsvirinə icra edilə bilən Spark tapşırıq kodu daxildir;
  • Yaradılmış təsvirə yalnız Spark və lazımi asılılıqlar daxildir, icra olunan kod uzaqdan yerləşdirilir (məsələn, HDFS-də).

Əvvəlcə Spark tapşırığının sınaq nümunəsini ehtiva edən Docker şəklini yaradaq. Docker şəkillərini yaratmaq üçün Spark-da "docker-image-tool" adlı köməkçi proqram var. Bununla bağlı yardımı öyrənək:

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

Onun köməyi ilə siz Docker şəkilləri yarada və onları uzaq registrlərə yükləyə bilərsiniz, lakin standart olaraq onun bir sıra çatışmazlıqları var:

  • mütləq bir anda 3 Docker təsviri yaradır - Spark, PySpark və R üçün;
  • təsvirin adını göstərməyə imkan vermir.

Buna görə də, biz aşağıda verilmiş bu yardım proqramının dəyişdirilmiş versiyasından istifadə edəcəyik:

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

Onun köməyi ilə biz Spark-dan istifadə edərək Pi-nin hesablanması üçün test tapşırığını ehtiva edən əsas Spark şəklini yığırıq (burada {docker-registr-url} Docker təsvir reyestrinizin URL-idir, {repo} reyestr daxilindəki deponun adıdır, OpenShift-də layihəyə uyğun gələn, {şəkil adı} - şəklin adı (əgər şəkillərin üç səviyyəli ayrılması istifadə olunursa, məsələn, Red Hat OpenShift şəkillərinin inteqrasiya olunmuş reyestrində olduğu kimi), {tag} - bunun etiketi şəklin versiyası):

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

Konsol yardım proqramından istifadə edərək OKD klasterinə daxil olun (burada {OKD-API-URL} OKD klasterinin API URL-idir):

oc login {OKD-API-URL}

Gəlin Docker Registry-də avtorizasiya üçün cari istifadəçinin işarəsini əldə edək:

oc whoami -t

OKD klasterinin daxili Docker Reyestrinə daxil olun (biz əvvəlki əmrdən istifadə edərək əldə edilmiş nişanı parol kimi istifadə edirik):

docker login {docker-registry-url}

Gəlin yığılmış Docker şəklini Docker Registry OKD-ə yükləyək:

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

Yığılmış şəklin OKD-də mövcud olduğunu yoxlayaq. Bunu etmək üçün brauzerdə müvafiq layihənin şəkillərinin siyahısı ilə URL-i açın (burada {project} OpenShift klasterindəki layihənin adıdır, {OKD-WEBUI-URL} OpenShift Veb konsolunun URL-dir. ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Tapşırıqları yerinə yetirmək üçün podları kök kimi işlətmək imtiyazları ilə xidmət hesabı yaradılmalıdır (bu məqamı daha sonra müzakirə edəcəyik):

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

Yaradılmış xidmət hesabını və Docker şəklini göstərərək, OKD klasterində Spark tapşırığını dərc etmək üçün spark-submit əmrini yerinə yetirək:

 /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

Burada:

—ad — Kubernetes podlarının adının formalaşmasında iştirak edəcək tapşırığın adı;

—class — tapşırıq işə salındıqda çağırılan icra olunan faylın sinfi;

—conf — Spark konfiqurasiya parametrləri;

spark.executor.instances — işə salınacaq Spark icraçılarının sayı;

spark.kubernetes.authenticate.driver.serviceAccountName - podları işə salarkən istifadə edilən Kubernetes xidmət hesabının adı (Kubernetes API ilə qarşılıqlı əlaqə zamanı təhlükəsizlik kontekstini və imkanlarını müəyyən etmək üçün);

spark.kubernetes.namespace — Sürücü və icraçı podların işə salınacağı Kubernetes ad sahəsi;

spark.submit.deployMode — Spark-ı işə salma üsulu (standart spark-submit “klaster” üçün, Spark Operator və Spark “client”in sonrakı versiyaları üçün istifadə olunur);

spark.kubernetes.container.image - podları işə salmaq üçün istifadə edilən Docker təsviri;

spark.master — Kubernetes API URL-i (xarici müəyyən edilir ki, giriş yerli maşından baş verir);

local:// Docker təsvirinin daxilində icra olunan Spark-a gedən yoldur.

Müvafiq OKD layihəsinə gedirik və yaradılmış podları öyrənirik - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

İnkişaf prosesini sadələşdirmək üçün başqa bir seçimdən istifadə edilə bilər ki, burada Spark-ın ümumi əsas təsviri yaradılır, bütün tapşırıqlar yerinə yetirmək üçün istifadə olunur və icra edilə bilən faylların görüntüləri xarici yaddaşda (məsələn, Hadoop) dərc edilir və zəng edərkən təyin olunur. spark-link kimi təqdim edin. Bu halda siz Spark tapşırıqlarının müxtəlif versiyalarını Docker şəkillərini yenidən qurmadan, məsələn, WebHDFS-dən istifadə edərək şəkilləri dərc edə bilərsiniz. Biz fayl yaratmaq üçün sorğu göndəririk (burada {host} WebHDFS xidmətinin hostudur, {port} WebHDFS xidmətinin portudur, {path-to-file-on-hdfs} fayla istədiyiniz yoldur HDFS haqqında):

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

Siz belə bir cavab alacaqsınız (burada {location} faylı yükləmək üçün istifadə edilməli olan URL-dir):

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

Spark icra edilə bilən faylını HDFS-ə yükləyin (burada {local fayla yol} cari hostda Spark icra edilə bilən faylına gedən yoldur):

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

Bundan sonra biz HDFS-ə yüklənmiş Spark faylından istifadə edərək spark-submit edə bilərik (burada {class-name} tapşırığı yerinə yetirmək üçün işə salınmalı olan sinfin adıdır):

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

Qeyd etmək lazımdır ki, HDFS-ə daxil olmaq və tapşırığın işləməsini təmin etmək üçün siz Dockerfile və entrypoint.sh skriptini dəyişməli ola bilərsiniz - asılı kitabxanaları /opt/spark/jars qovluğuna köçürmək üçün Dockerfile-ə direktiv əlavə edin və HDFS konfiqurasiya faylını giriş nöqtəsində SPARK_CLASSPATH-ə daxil edin.

İkinci istifadə halı - Apache Livy

Bundan əlavə, tapşırıq hazırlandıqda və nəticəni sınaqdan keçirmək lazım olduqda, onun CI/CD prosesinin bir hissəsi kimi işə salınması və onun icra vəziyyətinin izlənilməsi sualı yaranır. Əlbəttə ki, siz onu yerli qığılcım göndərmə zəngindən istifadə edərək işlədə bilərsiniz, lakin bu, CI/CD infrastrukturunu çətinləşdirir, çünki o, CI server agentləri/qaçışlarında Spark-ın quraşdırılmasını və konfiqurasiyasını və Kubernetes API-yə girişin qurulmasını tələb edir. Bu halda, hədəf tətbiq Kubernetes klasterində yerləşdirilən Spark tapşırıqlarını yerinə yetirmək üçün REST API kimi Apache Livy-dən istifadə etməyi seçdi. Onun köməyi ilə siz istənilən CI həlli əsasında asanlıqla həyata keçirilən müntəzəm cURL sorğularından istifadə edərək Kubernetes klasterində Spark tapşırıqlarını yerinə yetirə bilərsiniz və onun Kubernetes klasterində yerləşdirilməsi Kubernetes API ilə qarşılıqlı əlaqə zamanı autentifikasiya problemini həll edir.

Kubernetes-də Apache Spark işlədir

Gəlin bunu ikinci istifadə halı kimi vurğulayaq - sınaq dövrəsində Kubernetes klasterində CI/CD prosesinin bir hissəsi kimi Spark tapşırıqlarını yerinə yetirmək.

Apache Livy haqqında bir az - o, lazımi parametrləri ötürməklə spark-submit-i uzaqdan işə salmağa imkan verən Veb interfeysi və RESTful API təmin edən HTTP serveri kimi işləyir. Ənənəvi olaraq HDP paylanmasının bir hissəsi kimi göndərilir, lakin müvafiq manifest və bu kimi bir sıra Docker təsvirlərindən istifadə edərək OKD və ya hər hansı digər Kubernetes quraşdırmasına yerləşdirilə bilər - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Bizim vəziyyətimiz üçün aşağıdakı Dockerfile-dən Spark versiyası 2.4.5 daxil olmaqla oxşar Docker təsviri qurulmuşdur:

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

Yaradılan şəkil daxili OKD deposu kimi mövcud Docker repozitoriyanıza tikilə və yüklənə bilər. Onu yerləşdirmək üçün aşağıdakı manifestdən istifadə edin ({registr-url} - Docker təsvir reyestrinin URL-i, {image-name} - Docker image adı, {tag} - Docker image tag, {livy-url} - istədiyiniz URL-nin server Livy üçün əlçatan olacaq; Red Hat OpenShift Kubernetes paylanması kimi istifadə edilərsə, “Marşrut” manifestindən istifadə edilir, əks halda NodePort tipli müvafiq Giriş və ya Xidmət manifestindən istifadə olunur):

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

Onu tətbiq etdikdən və podu uğurla işə saldıqdan sonra Livy qrafik interfeysi linkdə mövcuddur: http://{livy-url}/ui. Livy ilə biz, məsələn, Postmandan gələn REST sorğusundan istifadə edərək Spark tapşırığımızı dərc edə bilərik. Sorğuları olan kolleksiyanın nümunəsi aşağıda təqdim olunur (başlanmış tapşırığın işləməsi üçün lazım olan dəyişənləri olan konfiqurasiya arqumentləri "args" massivində ötürülə bilər):

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

Kolleksiyadan ilk sorğunu yerinə yetirək, OKD interfeysinə keçək və tapşırığın uğurla işə salındığını yoxlayaq - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Eyni zamanda, Livy interfeysində (http://{livy-url}/ui) sessiya görünəcək, onun daxilində Livy API və ya qrafik interfeysdən istifadə edərək tapşırığın gedişatını izləyə və sessiyanı öyrənə bilərsiniz. loglar.

İndi Livinin necə işlədiyini göstərək. Bunu etmək üçün, Livy serveri ilə pod daxilindəki Livy konteynerinin qeydlərini araşdıraq - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Onlardan görə bilərik ki, Livy REST API-ni “livy” adlı konteynerdə çağırarkən yuxarıda istifadə etdiyimizə bənzər bir qığılcım göndərmə icra olunur (burada {livy-pod-name} yaradılmış podun adıdır. Livy serveri ilə). Kolleksiya həmçinin Livy serverindən istifadə edərək Spark icraedicisini uzaqdan yerləşdirən tapşırıqları yerinə yetirməyə imkan verən ikinci sorğu təqdim edir.

Üçüncü istifadə halı - Spark Operator

İndi tapşırığın sınaqdan keçirildiyi üçün onun müntəzəm icrası ilə bağlı sual yaranır. Kubernetes klasterində tapşırıqları müntəzəm yerinə yetirməyin yerli yolu CronJob varlığıdır və siz ondan istifadə edə bilərsiniz, lakin hazırda Kubernetes-də tətbiqləri idarə etmək üçün operatorlardan istifadə çox populyardır və Spark üçün kifayət qədər yetkin operator var, bu da Enterprise səviyyəli həllərdə istifadə olunur (məsələn, Lightbend FastData Platforması). Biz ondan istifadə etməyi tövsiyə edirik - Spark-ın (2.4.5) hazırkı stabil versiyası Kubernetes-də Spark tapşırıqlarını yerinə yetirmək üçün kifayət qədər məhdud konfiqurasiya seçimlərinə malikdir, növbəti əsas versiya (3.0.0) isə Kubernetes üçün tam dəstək elan edir, lakin onun buraxılış tarixi məlum deyil. . Spark Operator mühüm konfiqurasiya seçimləri (məsələn, Hadoop giriş konfiqurasiyası ilə ConfigMap-ın Spark podlarına quraşdırılması) və müntəzəm olaraq planlaşdırılan tapşırığı yerinə yetirmək imkanı əlavə etməklə bu çatışmazlığı kompensasiya edir.

Kubernetes-də Apache Spark işlədir
Gəlin bunu üçüncü istifadə halı kimi vurğulayaq - istehsal dövrəsində Kubernetes klasterində müntəzəm olaraq Spark tapşırıqlarını yerinə yetirmək.

Spark Operator açıq mənbədir və Google Bulud Platforması çərçivəsində hazırlanmışdır - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Onun quraşdırılması 3 yolla edilə bilər:

  1. Lightbend FastData Platform/Cloudflow quraşdırmasının bir hissəsi kimi;
  2. Dəbilqədən istifadə:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Rəsmi depodan manifestlərdən istifadə (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Aşağıdakıları qeyd etmək lazımdır - Cloudflow-a API v1beta1 versiyası olan operator daxildir. Bu tip quraşdırma istifadə edilərsə, Spark tətbiqinin manifest təsvirləri müvafiq API versiyası ilə Git-də nümunə teqlərə əsaslanmalıdır, məsələn, "v1beta1-0.9.0-2.4.0". Operatorun versiyasını "versiyalar" lüğətində operatora daxil olan CRD-nin təsvirində tapa bilərsiniz:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Operator düzgün quraşdırılıbsa, müvafiq layihədə Spark operatoru ilə aktiv pod görünəcək (məsələn, Cloudflow quraşdırılması üçün Cloudflow məkanında cloudflow-fdp-sparkoperator) və “sparkapplications” adlı müvafiq Kubernetes resurs növü görünəcək. . Mövcud Spark proqramlarını aşağıdakı əmrlə araşdıra bilərsiniz:

oc get sparkapplications -n {project}

Spark Operator istifadə edərək tapşırıqları yerinə yetirmək üçün 3 şeyi etməlisiniz:

  • bütün lazımi kitabxanaları, həmçinin konfiqurasiya və icra olunan faylları özündə cəmləşdirən Docker şəklini yaradın. Hədəf şəkildə bu, CI/CD mərhələsində yaradılmış və test klasterində sınaqdan keçirilmiş təsvirdir;
  • Docker şəklini Kubernetes klasterindən əldə edilə bilən reyestrdə dərc etmək;
  • “SparkApplication” növü və işə salınacaq tapşırığın təsviri ilə manifest yaradın. Nümunə manifestlər rəsmi depoda mövcuddur (məs. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Manifestlə bağlı qeyd edilməli vacib məqamlar var:
    1. “apiVersion” lüğətində operator versiyasına uyğun API versiyası göstərilməlidir;
    2. “metadata.namespace” lüğətində tətbiqin işə salınacağı ad sahəsi göstərilməlidir;
    3. “spec.image” lüğəti əlçatan reyestrdə yaradılmış Docker təsvirinin ünvanını ehtiva etməlidir;
    4. “spec.mainClass” lüğətində proses başlayanda işə salınmalı olan Spark tapşırıq sinfi olmalıdır;
    5. icra edilə bilən jar faylının yolu “spec.mainApplicationFile” lüğətində göstərilməlidir;
    6. “spec.sparkVersion” lüğətində istifadə olunan Spark versiyası göstərilməlidir;
    7. “spec.driver.serviceAccount” lüğəti tətbiqi işə salmaq üçün istifadə ediləcək müvafiq Kubernetes ad məkanında xidmət hesabını müəyyən etməlidir;
    8. “spec.executor” lüğətində tətbiqə ayrılmış resursların sayı göstərilməlidir;
    9. "spec.volumeMounts" lüğəti yerli Spark tapşırıq fayllarının yaradılacağı yerli kataloqu göstərməlidir.

Manifest yaratmaq nümunəsi (burada {spark-service-account}, Spark tapşırıqlarını yerinə yetirmək üçün Kubernetes klasterində xidmət hesabıdır):

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"

Bu manifest, manifest dərc etməzdən əvvəl, Kubernetes API ilə qarşılıqlı əlaqədə olmaq üçün Spark tətbiqi üçün zəruri giriş hüquqlarını təmin edən zəruri rol bağlamalarını yaratmalı olduğunuz xidmət hesabını müəyyən edir (lazım olduqda). Bizim vəziyyətimizdə tətbiqə Podlar yaratmaq hüququ lazımdır. Lazımi rol bağlamasını yaradaq:

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

Onu da qeyd etmək lazımdır ki, bu manifest spesifikasiyası “hadoopConfigMap” parametrini ehtiva edə bilər ki, bu da ilk olaraq müvafiq faylı Docker şəklinə yerləşdirmədən Hadoop konfiqurasiyası ilə ConfigMap təyin etməyə imkan verir. Tapşırıqları müntəzəm yerinə yetirmək üçün də uyğundur - "cədvəl" parametrindən istifadə edərək, verilmiş tapşırığı yerinə yetirmək üçün cədvəl müəyyən edilə bilər.

Bundan sonra manifestimizi spark-pi.yaml faylında saxlayırıq və onu Kubernetes klasterimizə tətbiq edirik:

oc apply -f spark-pi.yaml

Bu, "sparkapplications" tipli bir obyekt yaradacaq:

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

Bu vəziyyətdə, statusu yaradılmış "qığılcım tətbiqlərində" göstəriləcək bir tətbiqi olan bir pod yaradılacaq. Buna aşağıdakı əmrlə baxa bilərsiniz:

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

Tapşırığı yerinə yetirdikdən sonra POD "Tamamlandı" statusuna keçəcək, bu da "qığılcım tətbiqlərində" yenilənəcək. Tətbiq qeydlərinə brauzerdə və ya aşağıdakı əmrdən istifadə etməklə baxmaq olar (burada {sparkapplications-pod-name} işləyən tapşırığın podunun adıdır):

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

Spark tapşırıqları xüsusi sparkctl yardım proqramından istifadə etməklə də idarə oluna bilər. Onu quraşdırmaq üçün deponu mənbə kodu ilə klonlayın, Go quraşdırın və bu yardım proqramını qurun:

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

Çalışan Spark tapşırıqlarının siyahısını nəzərdən keçirək:

sparkctl list -n {project}

Spark tapşırığı üçün təsvir yaradaq:

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"

Təsvir edilən tapşırığı sparkctl istifadə edərək yerinə yetirək:

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

Çalışan Spark tapşırıqlarının siyahısını nəzərdən keçirək:

sparkctl list -n {project}

Başlanmış Spark tapşırığının hadisələrinin siyahısını nəzərdən keçirək:

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

Çalışan Spark tapşırığının vəziyyətini nəzərdən keçirək:

sparkctl status spark-pi -n {project}

Sonda Kubernetes-də Spark-ın (2.4.5) hazırkı sabit versiyasından istifadə etməyin aşkar edilmiş çatışmazlıqlarını nəzərdən keçirmək istərdim:

  1. Birinci və bəlkə də əsas çatışmazlıq Data Locality-nin olmamasıdır. YARN-in bütün çatışmazlıqlarına baxmayaraq, ondan istifadənin üstünlükləri də var idi, məsələn, kodun verilənlərə çatdırılması prinsipi (verilənlərin koduna deyil). Onun sayəsində hesablamalarda iştirak edən məlumatların yerləşdiyi qovşaqlarda Spark tapşırıqları yerinə yetirildi və məlumatların şəbəkə üzərindən çatdırılmasına sərf olunan vaxt xeyli azaldı. Kubernetes-dən istifadə edərkən, tapşırıqda iştirak edən məlumatları şəbəkə üzrə köçürmək ehtiyacı ilə üzləşirik. Əgər onlar kifayət qədər böyükdürsə, tapşırığın icra müddəti əhəmiyyətli dərəcədə arta bilər və həmçinin onların müvəqqəti saxlanması üçün Spark tapşırıq nümunələrinə ayrılmış kifayət qədər böyük disk sahəsi tələb olunur. Bu çatışmazlıq Kubernetes-də (məsələn, Alluxio) məlumatların yerləşdirilməsini təmin edən xüsusi proqram təminatından istifadə etməklə azalda bilər, lakin bu, əslində Kubernetes klasterinin qovşaqlarında məlumatların tam surətinin saxlanması zərurəti deməkdir.
  2. İkinci vacib çatışmazlıq təhlükəsizlikdir. Defolt olaraq, Spark tapşırıqlarının icrası ilə bağlı təhlükəsizliklə bağlı funksiyalar qeyri-aktivdir, Kerberos-un istifadəsi rəsmi sənədlərdə əhatə olunmur (baxmayaraq ki, müvafiq variantlar 3.0.0 versiyasında təqdim edilib və bu, əlavə iş tələb edir) və təhlükəsizlik sənədləri üçün Spark istifadə edərək (https ://spark.apache.org/docs/2.4.5/security.html) yalnız YARN, Mesos və Standalone Cluster əsas mağazalar kimi görünür. Eyni zamanda, Spark tapşırıqlarının işə salındığı istifadəçi birbaşa göstərilə bilməz - biz yalnız onun işləyəcəyi xidmət hesabını müəyyənləşdiririk və istifadəçi konfiqurasiya edilmiş təhlükəsizlik siyasətləri əsasında seçilir. Bununla əlaqədar olaraq, ya məhsuldar mühitdə təhlükəsiz olmayan kök istifadəçi, ya da məlumatlara giriş hüquqlarını bölüşdürərkən əlverişsiz olan təsadüfi UID-i olan istifadəçi istifadə olunur (bu, PodSecurityPolicies yaratmaq və onları şəbəkə ilə əlaqələndirməklə həll edilə bilər. müvafiq xidmət hesabları). Hal-hazırda, həll yolu bütün lazımi faylları birbaşa Docker şəklinə yerləşdirmək və ya təşkilatınızda qəbul edilmiş sirləri saxlamaq və əldə etmək üçün mexanizmdən istifadə etmək üçün Spark işə salma skriptini dəyişdirməkdir.
  3. Kubernetes istifadə edərək Spark işlərinin icrası rəsmi olaraq hələ də eksperimental rejimdədir və gələcəkdə istifadə olunan artefaktlarda (konfiqurasiya faylları, Docker əsas şəkilləri və işəsalma skriptləri) əhəmiyyətli dəyişikliklər ola bilər. Və həqiqətən də, material hazırlayarkən 2.3.0 və 2.4.5 versiyaları sınaqdan keçirildi, davranış əhəmiyyətli dərəcədə fərqli idi.

Gəlin yeniləmələri gözləyək - bu yaxınlarda Spark-ın (3.0.0) yeni versiyası buraxıldı, bu, Kubernetes-də Spark-ın işinə əhəmiyyətli dəyişikliklər gətirdi, lakin bu resurs meneceri üçün eksperimental dəstək statusunu saxladı. Ola bilsin ki, növbəti yeniləmələr həqiqətən də YARN-dən imtina etməyi və sisteminizin təhlükəsizliyi üçün qorxmadan və funksional komponentləri müstəqil şəkildə dəyişdirməyə ehtiyac olmadan Kubernetes-də Spark tapşırıqlarını yerinə yetirməyi tövsiyə etməyə imkan verəcək.

Son

Mənbə: www.habr.com

Добавить комментарий