Kubernetes-da Apache Spark-ni ishga tushirish

Hurmatli o'quvchilar, xayrli kun. Bugun biz Apache Spark va uning rivojlanish istiqbollari haqida bir oz gaplashamiz.

Kubernetes-da Apache Spark-ni ishga tushirish

Katta ma'lumotlarning zamonaviy dunyosida Apache Spark ommaviy ma'lumotlarni qayta ishlash vazifalarini ishlab chiqish uchun de-fakto standartdir. Bundan tashqari, u mikro partiya kontseptsiyasida ishlaydigan oqimli ilovalarni yaratish, ma'lumotlarni kichik qismlarda qayta ishlash va jo'natish uchun ham ishlatiladi (Spark Structured Streaming). Va an'anaviy ravishda u YARN (yoki ba'zi hollarda Apache Mesos) dan manba menejeri sifatida foydalangan holda umumiy Hadoop stekining bir qismi bo'lgan. 2020 yilga kelib, uni an'anaviy shaklda qo'llash ko'pchilik kompaniyalar uchun munosib Hadoop taqsimotlarining yo'qligi sababli so'roq ostida - HDP va CDH rivojlanishi to'xtadi, CDH yaxshi rivojlanmagan va yuqori narxga ega, qolgan Hadoop etkazib beruvchilari esa yo mavjud bo'lishni to'xtatdi yoki xira kelajakka ega. Shu sababli, Kubernetes-dan foydalangan holda Apache Spark-ning ishga tushirilishi hamjamiyat va yirik kompaniyalar orasida tobora ortib borayotgan qiziqish uyg'otmoqda - xususiy va ommaviy bulutlarda konteynerlarni boshqarish va resurslarni boshqarishda standart bo'lib, YARN-da Spark vazifalarini noqulay resurslarni rejalashtirish muammosini hal qiladi va taqdim etadi. barcha o'lchamdagi va chiziqlardagi kompaniyalar uchun ko'plab tijorat va ochiq tarqatishlarga ega bo'lgan barqaror rivojlanayotgan platforma. Bundan tashqari, mashhurlikka erishgandan so'ng, ko'pchilik allaqachon o'zlarining bir nechta qurilmalarini sotib olishga muvaffaq bo'lishdi va undan foydalanish bo'yicha tajribalarini oshirdilar, bu esa harakatni soddalashtiradi.

2.3.0 versiyasidan boshlab, Apache Spark Kubernetes klasteridagi vazifalarni bajarish uchun rasmiy yordamga ega bo'ldi va bugun biz ushbu yondashuvning hozirgi etukligi, uni qo'llashning turli xil variantlari va amalga oshirish jarayonida duch keladigan tuzoqlar haqida gaplashamiz.

Avvalo, keling, Apache Spark-ga asoslangan vazifalar va ilovalarni ishlab chiqish jarayonini ko'rib chiqamiz va Kubernetes klasterida vazifani bajarishingiz kerak bo'lgan odatiy holatlarni ajratib ko'rsatamiz. Ushbu postni tayyorlashda OpenShift tarqatish sifatida ishlatiladi va uning buyruq qatori yordam dasturiga (oc) tegishli buyruqlar beriladi. Boshqa Kubernetes tarqatishlari uchun standart Kubernetes buyruq qatori yordam dasturidan (kubectl) yoki ularning analoglaridan (masalan, oc adm siyosati uchun) tegishli buyruqlardan foydalanish mumkin.

Birinchi foydalanish holati - spark-submit

Vazifalar va ilovalarni ishlab chiqish jarayonida ishlab chiquvchi ma'lumotlar transformatsiyasini disk raskadrovka qilish uchun vazifalarni bajarishi kerak. Nazariy jihatdan, bu maqsadlar uchun stublardan foydalanish mumkin, ammo oxirgi tizimlarning haqiqiy (sinov bo'lsa ham) misollari ishtirokida ishlab chiqish ushbu vazifalar sinfida tezroq va yaxshiroq ekanligini isbotladi. Yakuniy tizimlarning haqiqiy misollarida disk raskadrovka qilingan taqdirda, ikkita stsenariy bo'lishi mumkin:

  • ishlab chiquvchi Spark vazifasini mahalliy sifatida mustaqil rejimda bajaradi;

    Kubernetes-da Apache Spark-ni ishga tushirish

  • ishlab chiquvchi sinov siklida Kubernetes klasterida Spark vazifasini bajaradi.

    Kubernetes-da Apache Spark-ni ishga tushirish

Birinchi variant mavjud bo'lish huquqiga ega, ammo bir qator kamchiliklarni o'z ichiga oladi:

  • Har bir ishlab chiquvchiga ish joyidan unga kerak bo'lgan oxirgi tizimlarning barcha nusxalariga kirish imkoniyati ta'minlanishi kerak;
  • ishlab chiqilayotgan vazifani bajarish uchun ishchi mashinada etarli miqdorda resurslar talab qilinadi.

Ikkinchi variantda bunday kamchiliklar mavjud emas, chunki Kubernetes klasteridan foydalanish sizga vazifalarni bajarish uchun kerakli resurs jamg'armasini ajratish va tizimning oxirgi nusxalariga kerakli kirishni ta'minlash imkonini beradi, bu esa Kubernetes rol modelidan foydalangan holda moslashuvchan tarzda unga kirishni ta'minlaydi. rivojlanish guruhining barcha a'zolari. Keling, buni birinchi foydalanish misoli sifatida ta'kidlab o'tamiz - sinov tsiklida Kubernetes klasteridagi mahalliy ishlab chiquvchi mashinasidan Spark vazifalarini ishga tushirish.

Keling, Spark-ni mahalliy sifatida ishga tushirish jarayoni haqida ko'proq gapiraylik. Spark-dan foydalanishni boshlash uchun uni o'rnatishingiz kerak:

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

Biz Kubernetes bilan ishlash uchun kerakli paketlarni yig'amiz:

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

To'liq qurish juda ko'p vaqtni oladi va Docker tasvirlarini yaratish va ularni Kubernetes klasterida ishga tushirish uchun sizga faqat "assembly/" katalogidagi jar fayllari kerak, shuning uchun siz faqat ushbu kichik loyihani yaratishingiz mumkin:

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

Kubernetes-da Spark ishlarini bajarish uchun siz asosiy tasvir sifatida foydalanish uchun Docker tasvirini yaratishingiz kerak. Bu erda 2 ta yondashuv mavjud:

  • Yaratilgan Docker tasviri bajariladigan Spark vazifa kodini o'z ichiga oladi;
  • Yaratilgan tasvir faqat Spark va kerakli bog'liqliklarni o'z ichiga oladi, bajariladigan kod masofadan turib (masalan, HDFSda) joylashtirilgan.

Birinchidan, Spark topshirig'ining sinov namunasini o'z ichiga olgan Docker tasvirini yarataylik. Docker tasvirlarini yaratish uchun Sparkda "docker-image-tool" deb nomlangan yordamchi dastur mavjud. Keling, bu boradagi yordamni o'rganamiz:

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

Uning yordami bilan siz Docker tasvirlarini yaratishingiz va ularni masofaviy registrlarga yuklashingiz mumkin, ammo sukut bo'yicha uning bir qator kamchiliklari bor:

  • bir vaqtning o'zida 3 ta Docker tasvirini yaratadi - Spark, PySpark va R uchun;
  • rasm nomini belgilashga ruxsat bermaydi.

Shuning uchun biz quyida keltirilgan ushbu yordam dasturining o'zgartirilgan versiyasidan foydalanamiz:

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

Uning yordami bilan biz Spark yordamida Pi ni hisoblash uchun test topshirig'ini o'z ichiga olgan asosiy Spark tasvirini yig'amiz (bu yerda {docker-registry-url} Docker tasvirlar registrining URL manzili, {repo} - registr ichidagi ombor nomi, OpenShift-dagi loyihaga mos keladigan, {image-name} - tasvir nomi (agar tasvirlarni uch darajali ajratish ishlatilsa, masalan, Red Hat OpenShift tasvirlarining o'rnatilgan registridagi kabi), {tag} - buning tegi rasmning versiyasi):

./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 yordam dasturidan foydalanib OKD klasteriga kiring (bu yerda {OKD-API-URL} - OKD klasterining API URL manzili):

oc login {OKD-API-URL}

Keling, Docker registrida avtorizatsiya uchun joriy foydalanuvchi tokenini olamiz:

oc whoami -t

OKD klasterining ichki Docker registriga kiring (biz oldingi buyruq yordamida olingan tokendan parol sifatida foydalanamiz):

docker login {docker-registry-url}

Keling, yig'ilgan Docker tasvirini Docker Registry OKD ga yuklaymiz:

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

Keling, yig'ilgan tasvirning OKD da mavjudligini tekshirib ko'raylik. Buning uchun brauzerda URL-manzilni tegishli loyihaning rasmlari roʻyxati bilan oching (bu yerda {loyiha} - OpenShift klasteridagi loyiha nomi, {OKD-WEBUI-URL} - OpenShift veb-konsolining URL manzili ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Vazifalarni bajarish uchun podkalarni ildiz sifatida ishga tushirish imtiyozlari bilan xizmat hisobi yaratilishi kerak (bu nuqtani keyinroq muhokama qilamiz):

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

Yaratilgan xizmat hisobi va Docker tasvirini belgilab, OKD klasteriga Spark topshirig‘ini nashr qilish uchun spark-submit buyrug‘ini ishga tushiramiz:

 /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

Bu erda:

—nom — Kubernetes ko‘zalari nomini shakllantirishda ishtirok etadigan vazifaning nomi;

—class — vazifa boshlanganda chaqiriladigan bajariladigan fayl sinfi;

—conf — Spark konfiguratsiya parametrlari;

spark.executor.instances — ishga tushiriladigan Spark ijrochilari soni;

spark.kubernetes.authenticate.driver.serviceAccountName - podlarni ishga tushirishda foydalaniladigan Kubernetes xizmat hisobining nomi (Kubernetes API bilan ishlashda xavfsizlik konteksti va imkoniyatlarini aniqlash uchun);

spark.kubernetes.namespace — Kubernetes nom maydoni, unda haydovchi va ijrochi pods ishga tushiriladi;

spark.submit.deployMode — Sparkni ishga tushirish usuli (standart spark-submit “klaster” uchun, Spark Operator va Spark “mijoz” ning keyingi versiyalari uchun ishlatiladi);

spark.kubernetes.container.image - podkalarni ishga tushirish uchun ishlatiladigan Docker tasviri;

spark.master — Kubernetes API URL manzili (tashqi belgilangan, shuning uchun kirish mahalliy mashinadan amalga oshiriladi);

local:// - Docker tasviridagi Spark bajariladigan faylga yo'l.

Biz tegishli OKD loyihasiga o'tamiz va yaratilgan podslarni o'rganamiz - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Rivojlanish jarayonini soddalashtirish uchun boshqa variantdan foydalanish mumkin, unda Spark-ning umumiy asosiy tasviri yaratiladi, barcha vazifalarni bajarish uchun foydalaniladi va bajariladigan fayllarning oniy tasvirlari tashqi xotiraga (masalan, Hadoop) nashr etiladi va qo'ng'iroq qilishda ko'rsatiladi. havola sifatida spark-submit. Bunday holda, siz Spark vazifalarining turli versiyalarini Docker tasvirlarini qayta tiklamasdan, masalan, WebHDFS yordamida tasvirlarni nashr etishingiz mumkin. Biz fayl yaratish uchun soʻrov yuboramiz (bu yerda {host} — WebHDFS xizmatining xosti, {port} — WebHDFS xizmatining porti, {path-to-file-on-hdfs} — faylga kerakli yoʻl. HDFSda):

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

Siz shunday javob olasiz (bu yerda {location} faylni yuklab olish uchun ishlatilishi kerak boʻlgan URL manzili):

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

Spark bajariladigan faylini HDFS-ga yuklang (bu yerda {local-faylga yo'l} joriy xostdagi Spark bajariladigan fayliga yo'l):

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

Shundan so'ng biz HDFS-ga yuklangan Spark fayli yordamida spark-submitni amalga oshirishimiz mumkin (bu erda {sinf nomi} vazifani bajarish uchun ishga tushirilishi kerak bo'lgan sinf nomi):

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

Shuni ta'kidlash kerakki, HDFS-ga kirish va vazifaning ishlashini ta'minlash uchun siz Dockerfile va entrypoint.sh skriptini o'zgartirishingiz kerak bo'lishi mumkin - Dockerfile-ga bog'liq kutubxonalarni /opt/spark/jars katalogiga nusxalash uchun direktiv qo'shing va HDFS konfiguratsiya faylini kirish nuqtasiga SPARK_CLASSPATH ichiga qo'shing.

Ikkinchi foydalanish holati - Apache Livy

Bundan tashqari, vazifa ishlab chiqilganda va natijani sinab ko'rish kerak bo'lganda, uni CI/CD jarayonining bir qismi sifatida ishga tushirish va uning bajarilishi holatini kuzatish haqida savol tug'iladi. Albatta, siz uni mahalliy spark-submit qo‘ng‘irog‘i yordamida ishga tushirishingiz mumkin, ammo bu CI/CD infratuzilmasini murakkablashtiradi, chunki u CI server agentlari/yuguruvchilarida Spark-ni o‘rnatish va sozlashni va Kubernetes API-ga kirishni sozlashni talab qiladi. Bu holatda, maqsadli dastur Kubernetes klasterida joylashtirilgan Spark vazifalarini bajarish uchun REST API sifatida Apache Livy-dan foydalanishni tanladi. Uning yordami bilan siz Kubernetes klasterida Spark vazifalarini oddiy cURL so'rovlari yordamida bajarishingiz mumkin, bu har qanday CI yechimi asosida osonlik bilan amalga oshiriladi va uning Kubernetes klasteriga joylashtirilishi Kubernetes API bilan o'zaro aloqada autentifikatsiya muammosini hal qiladi.

Kubernetes-da Apache Spark-ni ishga tushirish

Keling, buni ikkinchi foydalanish misoli sifatida ta'kidlab o'tamiz - sinov tsiklidagi Kubernetes klasterida CI/CD jarayonining bir qismi sifatida Spark vazifalarini bajarish.

Apache Livy haqida bir oz - u veb-interfeys va RESTful API bilan ta'minlovchi HTTP serveri sifatida ishlaydi, bu sizga kerakli parametrlarni o'tkazish orqali spark-submitni masofadan ishga tushirish imkonini beradi. An'anaga ko'ra, u HDP tarqatishning bir qismi sifatida yuborilgan, ammo tegishli manifest va Docker tasvirlari to'plamidan foydalangan holda OKD yoki boshqa Kubernetes o'rnatishlariga ham joylashtirilishi mumkin, masalan, bu - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Bizning holatlarimiz uchun shunga o'xshash Docker tasviri yaratilgan, jumladan, quyidagi Docker faylidan Spark 2.4.5 versiyasi:

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

Yaratilgan rasm ichki OKD ombori kabi mavjud Docker omboringizga tuzilishi va yuklanishi mumkin. Uni joylashtirish uchun quyidagi manifestdan foydalaning ({registry-url} - Docker tasvirlar registrining URL manzili, {image-name} - Docker tasvir nomi, {tag} - Docker image yorlig'i, {livy-url} - kerakli URL manzili serverga Livy kirish mumkin; Agar Red Hat OpenShift Kubernetes tarqatish sifatida ishlatilsa, “Marshrut” manifestidan foydalaniladi, aks holda NodePort tipidagi tegishli kirish yoki Xizmat manifestidan foydalaniladi):

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

Uni qo'llash va podkastni muvaffaqiyatli ishga tushirgandan so'ng, Livy grafik interfeysi havolada mavjud: http://{livy-url}/ui. Livi bilan biz Spark vazifamizni REST so'rovi yordamida nashr qilishimiz mumkin, masalan, Postman. So'rovlar bilan to'plamning namunasi quyida keltirilgan (boshlangan vazifaning ishlashi uchun zarur bo'lgan o'zgaruvchilar bilan konfiguratsiya argumentlari "args" massivida o'tkazilishi mumkin):

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

To'plamdan birinchi so'rovni bajaramiz, OKD interfeysiga o'tamiz va vazifa muvaffaqiyatli ishga tushirilganligini tekshiramiz - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Shu bilan birga, Livy interfeysida (http://{livy-url}/ui) sessiya paydo bo'ladi, uning ichida Livy API yoki grafik interfeysdan foydalanib, siz vazifaning borishini kuzatishingiz va sessiyani o'rganishingiz mumkin. jurnallar.

Keling, Livi qanday ishlashini ko'rsatamiz. Buning uchun Livy serveri yordamida pod ichidagi Livy konteynerining jurnallarini ko'rib chiqamiz - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Ulardan biz Livy REST API-ni "livy" nomli konteynerda chaqirganda, yuqorida biz ishlatganga o'xshash spark-submit amalga oshirilishini ko'rishimiz mumkin (bu erda {livy-pod-name} - yaratilgan podning nomi. Livy serveri bilan). To'plam, shuningdek, Livy serveri yordamida Spark bajariladigan faylni masofadan joylashtiradigan vazifalarni bajarishga imkon beruvchi ikkinchi so'rovni taqdim etadi.

Uchinchi foydalanish holati - Spark Operator

Vazifa sinovdan o'tgandan so'ng, uni muntazam ravishda bajarish haqida savol tug'iladi. Kubernetes klasterida vazifalarni muntazam ravishda bajarishning asosiy usuli bu CronJob ob'ektidir va siz undan foydalanishingiz mumkin, ammo hozirda Kubernetes-da ilovalarni boshqarish uchun operatorlardan foydalanish juda mashhur va Spark uchun juda etuk operator mavjud, bu ham Korxona darajasidagi yechimlarda qo'llaniladi (masalan, Lightbend FastData Platformasi). Biz undan foydalanishni tavsiya qilamiz - Spark ning joriy barqaror versiyasi (2.4.5) Kubernetes-da Spark vazifalarini bajarish uchun juda cheklangan konfiguratsiya imkoniyatlariga ega, keyingi asosiy versiya (3.0.0) esa Kubernetes-ni to'liq qo'llab-quvvatlashini e'lon qiladi, ammo uning chiqish sanasi noma'lumligicha qolmoqda. . Spark Operator bu kamchilikni muhim konfiguratsiya opsiyalarini (masalan, Spark podslariga Hadoop kirish konfiguratsiyasi bilan ConfigMapni o‘rnatish) va muntazam ravishda rejalashtirilgan vazifani bajarish qobiliyatini qo‘shish orqali qoplaydi.

Kubernetes-da Apache Spark-ni ishga tushirish
Keling, buni uchinchi foydalanish holati sifatida ajratib ko'rsatamiz - ishlab chiqarish tsiklida Kubernetes klasterida Spark vazifalarini muntazam ravishda bajarish.

Spark Operator ochiq manba hisoblanadi va Google Cloud Platformasida ishlab chiqilgan - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Uni o'rnatish 3 usulda amalga oshirilishi mumkin:

  1. Lightbend FastData Platform/Cloudflow o'rnatilishining bir qismi sifatida;
  2. Helm-dan foydalanish:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Rasmiy ombordagi manifestlardan foydalanish (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Shuni ta'kidlash kerakki, Cloudflow API v1beta1 versiyasiga ega operatorni o'z ichiga oladi. Agar ushbu turdagi o'rnatish ishlatilsa, Spark ilovasi manifest tavsiflari tegishli API versiyasiga ega Git-dagi misol teglariga asoslangan bo'lishi kerak, masalan, "v1beta1-0.9.0-2.4.0". Operatorning versiyasini "versiyalar" lug'atida operatorga kiritilgan CRD tavsifida topish mumkin:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Agar operator to'g'ri o'rnatilgan bo'lsa, tegishli loyihada Spark operatori bilan faol pod paydo bo'ladi (masalan, Cloudflow o'rnatilishi uchun Cloudflow maydonida cloudflow-fdp-sparkoperator) va tegishli Kubernetes resurs turi "sparkapplications" paydo bo'ladi. . Mavjud Spark ilovalarini quyidagi buyruq bilan o'rganishingiz mumkin:

oc get sparkapplications -n {project}

Spark Operator yordamida vazifalarni bajarish uchun siz 3 ta narsani qilishingiz kerak:

  • barcha kerakli kutubxonalarni, shuningdek, konfiguratsiya va bajariladigan fayllarni o'z ichiga olgan Docker tasvirini yarating. Maqsadli rasmda bu CI/CD bosqichida yaratilgan va test klasterida sinovdan o'tgan tasvir;
  • Docker tasvirini Kubernetes klasteridan foydalanish mumkin bo'lgan ro'yxatga olish kitobiga nashr qilish;
  • “SparkApplication” turi va ishga tushiriladigan vazifa tavsifi bilan manifest yarating. Namuna manifestlar rasmiy omborda mavjud (masalan, github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Manifestda e'tiborga olish kerak bo'lgan muhim fikrlar mavjud:
    1. "apiVersion" lug'ati operator versiyasiga mos keladigan API versiyasini ko'rsatishi kerak;
    2. "metadata.namespace" lug'atida ilova ishga tushiriladigan nomlar maydoni ko'rsatilishi kerak;
    3. "spec.image" lug'ati mavjud registrda yaratilgan Docker tasvirining manzilini o'z ichiga olishi kerak;
    4. "spec.mainClass" lug'ati jarayon boshlanganda ishga tushirilishi kerak bo'lgan Spark vazifa sinfini o'z ichiga olishi kerak;
    5. "spec.mainApplicationFile" lug'ati bajariladigan jar fayliga yo'lni o'z ichiga olishi kerak;
    6. "spec.sparkVersion" lug'atida Sparkning qo'llanilayotgan versiyasi ko'rsatilishi kerak;
    7. "spec.driver.serviceAccount" lug'ati ilovani ishga tushirish uchun ishlatiladigan tegishli Kubernetes nom maydonidagi xizmat hisobini ko'rsatishi kerak;
    8. "spec.executor" lug'ati ilovaga ajratilgan resurslar sonini ko'rsatishi kerak;
    9. "spec.volumeMounts" lug'ati mahalliy Spark vazifa fayllari yaratiladigan mahalliy katalogni ko'rsatishi kerak.

Manifestni yaratishga misol (bu yerda {spark-service-account} - Spark vazifalarini bajarish uchun Kubernetes klasteridagi xizmat hisobi):

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 xizmat hisobini belgilaydi, uning uchun manifestni nashr etishdan oldin, Spark ilovasining Kubernetes API bilan oʻzaro ishlashi uchun zarur boʻlgan kirish huquqlarini taʼminlaydigan kerakli rol bogʻlamalarini yaratishingiz kerak (agar kerak boʻlsa). Bizning holatda, dasturga Podlarni yaratish huquqi kerak. Keling, kerakli rolni bog'lashni yaratamiz:

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

Shuni ham ta'kidlash joizki, ushbu manifest spetsifikatsiyasi "hadoopConfigMap" parametrini o'z ichiga olishi mumkin, bu sizga birinchi navbatda tegishli faylni Docker tasviriga joylashtirmasdan Hadoop konfiguratsiyasi bilan ConfigMapni belgilash imkonini beradi. Vazifalarni muntazam bajarish uchun ham javob beradi - "jadval" parametridan foydalanib, berilgan vazifani bajarish jadvalini belgilash mumkin.

Shundan so'ng biz manifestimizni spark-pi.yaml fayliga saqlaymiz va uni Kubernetes klasterimizga qo'llaymiz:

oc apply -f spark-pi.yaml

Bu "sparkapplications" tipidagi ob'ektni yaratadi:

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

Bunday holda, ilova bilan podkast yaratiladi, uning holati yaratilgan "sparkapplications" da ko'rsatiladi. Siz uni quyidagi buyruq bilan ko'rishingiz mumkin:

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

Vazifani tugatgandan so'ng, POD "Bajarildi" holatiga o'tadi, u "sparkapplications" da yangilanadi. Ilova jurnallarini brauzerda yoki quyidagi buyruq yordamida ko'rish mumkin (bu erda {sparkapplications-pod-name} - bajarilayotgan vazifa podining nomi):

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

Spark vazifalarini ixtisoslashtirilgan sparkctl yordam dasturi yordamida ham boshqarish mumkin. Uni o'rnatish uchun omborni manba kodi bilan klonlang, Go'ni o'rnating va ushbu yordam dasturini yarating:

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

Ishlayotgan Spark vazifalari ro'yxatini ko'rib chiqamiz:

sparkctl list -n {project}

Spark vazifasi uchun tavsif yaratamiz:

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"

Keling, sparkctl yordamida tasvirlangan vazifani bajaramiz:

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

Ishlayotgan Spark vazifalari ro'yxatini ko'rib chiqamiz:

sparkctl list -n {project}

Keling, ishga tushirilgan Spark topshirig'ining voqealar ro'yxatini ko'rib chiqaylik:

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

Ishlayotgan Spark vazifasining holatini ko'rib chiqamiz:

sparkctl status spark-pi -n {project}

Xulosa qilib, Kubernetes-da Spark (2.4.5) ning joriy barqaror versiyasidan foydalanishning aniqlangan kamchiliklarini ko'rib chiqmoqchiman:

  1. Birinchi va, ehtimol, asosiy kamchilik - bu ma'lumotlarning joylashuvi yo'qligi. YARN ning barcha kamchiliklariga qaramay, undan foydalanishning afzalliklari ham mavjud edi, masalan, kodni ma'lumotlarga etkazish printsipi (ma'lumotlardan kodga emas). Uning yordamida hisob-kitoblarga jalb qilingan ma’lumotlar joylashgan tugunlarda Spark vazifalari bajarildi va tarmoq orqali ma’lumotlarni yetkazish vaqti sezilarli darajada qisqardi. Kubernetes-dan foydalanganda, biz tarmoq bo'ylab vazifaga jalb qilingan ma'lumotlarni ko'chirish zarurligiga duch kelamiz. Agar ular etarlicha katta bo'lsa, vazifani bajarish vaqti sezilarli darajada oshishi mumkin, shuningdek ularni vaqtincha saqlash uchun Spark topshiriq namunalariga ajratilgan juda katta hajmdagi disk maydonini talab qiladi. Ushbu kamchilikni Kubernetes-da ma'lumotlarning joylashishini ta'minlaydigan maxsus dasturiy ta'minot yordamida kamaytirish mumkin (masalan, Alluxio), lekin bu aslida Kubernetes klasteri tugunlarida ma'lumotlarning to'liq nusxasini saqlash zarurligini anglatadi.
  2. Ikkinchi muhim kamchilik - bu xavfsizlik. Odatiy bo'lib, Spark vazifalarini bajarish bilan bog'liq xavfsizlik bilan bog'liq xususiyatlar o'chirib qo'yilgan, Kerberosdan foydalanish rasmiy hujjatlarda yoritilgan (garchi tegishli variantlar 3.0.0 versiyasida kiritilgan bo'lsa-da, bu qo'shimcha ishlarni talab qiladi) va xavfsizlik hujjatlari Spark (https://spark.apache.org/docs/2.4.5/security.html) yordamida faqat YARN, Mesos va Standalone Cluster asosiy doʻkonlar sifatida koʻrinadi. Shu bilan birga, Spark vazifalari ishga tushirilgan foydalanuvchini to'g'ridan-to'g'ri ko'rsatib bo'lmaydi - biz faqat u ishlaydigan xizmat hisobini belgilaymiz va foydalanuvchi sozlangan xavfsizlik siyosati asosida tanlanadi. Shu munosabat bilan, samarali muhitda xavfsiz bo'lmagan ildiz foydalanuvchi yoki ma'lumotlarga kirish huquqlarini tarqatishda noqulay bo'lgan tasodifiy UID foydalanuvchi ishlatiladi (buni PodSecurityPolicies yaratish va ularni bog'lash orqali hal qilish mumkin). tegishli xizmat hisoblari). Hozirgi vaqtda yechim barcha kerakli fayllarni to'g'ridan-to'g'ri Docker tasviriga joylashtirish yoki tashkilotingizda qabul qilingan sirlarni saqlash va olish mexanizmidan foydalanish uchun Spark ishga tushirish skriptini o'zgartirishdir.
  3. Kubernetes yordamida Spark ishlarini ishga tushirish rasman hali ham eksperimental rejimda va kelajakda foydalaniladigan artefaktlarda (konfiguratsiya fayllari, Docker asosiy tasvirlari va ishga tushirish skriptlari) sezilarli oʻzgarishlar boʻlishi mumkin. Va haqiqatan ham, materialni tayyorlashda 2.3.0 va 2.4.5 versiyalari sinovdan o'tkazildi, xatti-harakatlar sezilarli darajada farq qildi.

Yangilanishlarni kutamiz - yaqinda Spark (3.0.0) ning yangi versiyasi chiqdi, u Kubernetesdagi Spark ishiga sezilarli o'zgarishlar kiritdi, ammo ushbu resurs menejerini qo'llab-quvvatlashning eksperimental holatini saqlab qoldi. Ehtimol, keyingi yangilanishlar haqiqatan ham YARN-dan voz kechishni va tizimingiz xavfsizligi uchun qo'rqmasdan va funktsional komponentlarni mustaqil ravishda o'zgartirishni talab qilmasdan Kubernetes-da Spark vazifalarini bajarishni to'liq tavsiya qilish imkonini beradi.

Oxiri.

Manba: www.habr.com

a Izoh qo'shish