Иҷрои Apache Spark дар Kubernetes

Хонандагони азиз, субҳ ба хайр. Имрӯз мо каме дар бораи Apache Spark ва дурнамои рушди он сӯҳбат хоҳем кард.

Иҷрои Apache Spark дар Kubernetes

Дар ҷаҳони муосири Big Data, 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-submit

Ҳангоми таҳияи вазифаҳо ва барномаҳо, таҳиякунанда бояд вазифаҳоро барои ислоҳи табдили додаҳо иҷро кунад. Аз ҷиҳати назариявӣ, нотаҳоро барои ин мақсадҳо истифода бурдан мумкин аст, аммо таҳия бо иштироки мисолҳои воқеии (ҳарчанд санҷишӣ) системаҳои ниҳоӣ дар ин синфи вазифаҳо тезтар ва беҳтар аст. Дар ҳолате, ки мо дар мисолҳои воқеии системаҳои ниҳоӣ ислоҳ мекунем, ду сенария имконпазир аст:

  • таҳиякунанда вазифаи 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-и кластери OKD API аст):

oc login {OKD-API-URL}

Биёед аломати корбари ҷорӣро барои авторизатсия дар Реестри Docker гирем:

oc whoami -t

Ба Реестри дохилии Docker кластери OKD ворид шавед (мо токенеро, ки бо истифода аз фармони қаблӣ ҳамчун парол гирифта шудааст, истифода мебарем):

docker login {docker-registry-url}

Биёед тасвири ҷамъшудаи Docker-ро ба Registry Docker OKD бор кунем:

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

Биёед тафтиш кунем, ки тасвири ҷамъшуда дар OKD дастрас аст. Барои ин URL-ро дар браузер бо рӯйхати тасвирҳои лоиҳаи мувофиқ кушоед (дар ин ҷо {project} номи лоиҳа дар дохили кластери OpenShift, {OKD-WEBUI-URL} URL-и консоли веби OpenShift мебошад. ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Барои иҷро кардани вазифаҳо, ҳисоби хидматрасонӣ бояд бо имтиёзҳо барои иҷро кардани pods ҳамчун реша эҷод карда шавад (мо ин нуктаро баъдтар баррасӣ хоҳем кард):

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

Дар ин ҷо:

—ном — номи вазифадое, ки дар ташаккули номи поддадои Кубернетес иштирок мекунанд;

—class — синфи файли иҷрошаванда, ки ҳангоми оғози вазифа даъват карда мешавад;

—conf — Параметрҳои конфигуратсияи Spark;

spark.executor.instances — шумораи иҷрокунандагони Spark барои оғоз;

spark.kubernetes.authenticate.driver.serviceAccountName - номи ҳисоби хидматрасонии Kubernetes, ки ҳангоми оғоз кардани pods истифода мешавад (барои муайян кардани контексти амният ва қобилиятҳо ҳангоми ҳамкорӣ бо API Kubernetes);

spark.kubernetes.namespace — Фазои номи Kubernetes, ки дар он подкладҳои ронанда ва иҷрокунанда ба кор андохта мешаванд;

spark.submit.deployMode — усули ба кор андохтани Spark (барои стандарти spark-submit “cluster” истифода мешавад, барои Spark Operator ва версияҳои охирини Spark “client”);

spark.kubernetes.container.image - Тасвири Docker барои оғоз кардани pods истифода мешавад;

spark.master — URL-и API Kubernetes (берунӣ муайян карда шудааст, то дастрасӣ аз мошини маҳаллӣ сурат гирад);

local:// ин роҳи иҷрошавандаи Spark дар дохили тасвири Docker мебошад.

Мо ба лоиҳаи мувофиқи OKD меравем ва подкҳои сохташударо меомӯзем - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Барои содда кардани раванди таҳия, варианти дигарро метавон истифода бурд, ки дар он тасвири умумии Spark сохта мешавад, ки онро ҳама вазифаҳо иҷро мекунанд ва аксҳои файлҳои иҷрошаванда дар нигаҳдории беруна (масалан, Hadoop) нашр карда мешаванд ва ҳангоми занг задан муайян карда мешаванд. шарора-супурдан ҳамчун пайванд. Дар ин ҳолат, шумо метавонед версияҳои гуногуни вазифаҳои Spark-ро бидуни барқарор кардани тасвирҳои Docker иҷро кунед, масалан, WebHDFS барои интишори тасвирҳо. Мо дархост барои эҷоди файл мефиристем (дар ин ҷо {хост} мизбони хидмати 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, ки ба HDFS бор карда шудааст, spark-submit -ро анҷом диҳем (дар ин ҷо {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 ва пайгирии ҳолати иҷрои он ба миён меояд. Албатта, шумо метавонед онро бо истифода аз занги маҳаллии spark-submit иҷро кунед, аммо ин инфрасохтори CI/CD-ро мушкил мекунад, зеро он насб ва конфигуратсияи Spark-ро дар агентҳо/давонҳои сервери CI ва танзими дастрасӣ ба Kubernetes API талаб мекунад. Дар ин ҳолат, татбиқи мақсаднок истифода бурдани Apache Livy -ро ҳамчун REST API барои иҷрои вазифаҳои Spark, ки дар дохили кластери Kubernetes ҷойгир шудааст, интихоб кардааст. Бо кӯмаки он, шумо метавонед вазифаҳои Spark-ро дар кластери Kubernetes бо истифода аз дархостҳои муқаррарии cURL иҷро кунед, ки дар асоси ҳама гуна ҳалли CI ба осонӣ амалӣ карда мешавад ва ҷойгиркунии он дар дохили кластери Kubernetes масъалаи аутентификатсияро ҳангоми ҳамкорӣ бо API Kubernetes ҳал мекунад.

Иҷрои Apache Spark дар Kubernetes

Биёед онро ҳамчун ҳолати дуюми истифода таъкид кунем - иҷро кардани вазифаҳои Spark ҳамчун як қисми раванди CI/CD дар кластери Kubernetes дар ҳалқаи санҷиш.

Каме дар бораи Apache Livy - он ҳамчун сервери HTTP кор мекунад, ки интерфейси веб ва API-и RESTful-ро таъмин мекунад, ки ба шумо имкон медиҳад, ки тавассути интиқоли параметрҳои зарурӣ 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, {tag} - Теги тасвири Docker, {livy-url} - URL-и дилхоҳ, ки дар он ҷо сервер Livy дастрас хоҳад шуд; манифести "Масир" истифода мешавад, агар Red Hat OpenShift ҳамчун тақсимоти Kubernetes истифода шавад, дар акси ҳол манифести мувофиқи Ingress ё хидмати навъи 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

Пас аз татбиқи он ва бомуваффақият оғоз кардани pod, интерфейси графикии Livy дар истиноди дастрас аст: http://{livy-url}/ui. Бо Ливи, мо метавонем вазифаи 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 тафтиш кунем - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Аз онҳо мо мебинем, ки ҳангоми занг задан ба Livy REST API дар контейнере бо номи "livy" шарораи ирсол иҷро карда мешавад, ки ба он чизе, ки мо дар боло истифода кардем (дар ин ҷо {livy-pod-name} номи pod-и сохташуда аст. бо сервери Livy). Маҷмӯа инчунин дархости дуюмро пешниҳод мекунад, ки ба шумо имкон медиҳад вазифаҳоеро иҷро кунед, ки бо истифода аз сервери Livy файли иҷрошавандаи Spark-ро аз фосилаи дур ҷойгир мекунанд.

Ҳолати истифодаи сеюм - Оператор Spark

Акнун, ки супориш санчида шуд, масъалаи мунтазам ичро кардани он ба миён меояд. Роҳи аслии иҷрои мунтазами вазифаҳо дар кластери Kubernetes ин объекти CronJob аст ва шумо метавонед онро истифода баред, аммо дар айни замон истифодаи операторҳо барои идоракунии барномаҳо дар Kubernetes хеле маъмул аст ва барои Spark як оператори хеле баркамол мавҷуд аст, ки инчунин дар ҳалли сатҳи Enterprise истифода мешавад (масалан, 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:
    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
    	

Агар оператор дуруст насб карда шуда бошад, дар лоиҳаи мувофиқ як паҳлӯи фаъол бо оператори 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 барои ҳамкорӣ бо API Kubernetes таъмин мекунанд (агар лозим бошад). Дар ҳолати мо, барнома барои эҷоди Pods ҳуқуқҳоро талаб мекунад. Биёед нақши ҳатмии заруриро эҷод кунем:

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

Инчунин бояд қайд кард, ки ин мушаххасоти манифест метавонад параметри "hadoopConfigMap" -ро дар бар гирад, ки ба шумо имкон медиҳад ConfigMap-ро бо конфигуратсияи Hadoop бидуни ҷойгир кардани файли мувофиқ дар тасвири Docker муайян кунед. Он инчунин барои иҷрои мунтазами вазифаҳо мувофиқ аст - бо истифода аз параметри "ҷадвал", ҷадвали иҷрои вазифаи додашударо метавон муайян кард.

Пас аз он, мо манифести худро дар файли spark-pi.yaml захира мекунем ва онро ба кластери Кубернетеси худ татбиқ мекунем:

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 ҷудо карда шудааст, талаб кунад. Ин нуқсонро тавассути истифодаи нармафзори махсус, ки ҷойгиршавии маълумотро дар Кубернетес таъмин мекунад (масалан, Alluxio) коҳиш додан мумкин аст, аммо ин воқеан маънои нигоҳ доштани нусхаи пурраи маълумотро дар гиреҳҳои кластери Kubernetes дорад.
  2. Камбудии дуюми муҳим ин амният аст. Бо нобаёнӣ, хусусиятҳои марбут ба амният дар иҷрои вазифаҳои Spark ғайрифаъол карда мешаванд, истифодаи Kerberos дар ҳуҷҷатҳои расмӣ фаро гирифта нашудааст (гарчанде ки имконоти мувофиқ дар версияи 3.0.0 ҷорӣ карда шудаанд, ки кори иловагиро талаб мекунад) ва ҳуҷҷатҳои амниятӣ барои бо истифода аз Spark (https://spark.apache.org/docs/2.4.5/security.html) танҳо YARN, Mesos ва Cluster Standalone ҳамчун мағозаҳои калидӣ пайдо мешаванд. Дар айни замон, корбареро, ки дар зери он вазифаҳои Spark оғоз мешаванд, мустақиман муайян кардан мумкин нест - мо танҳо ҳисоби хидматеро, ки зери он кор мекунад, муайян мекунем ва корбар дар асоси сиёсати танзимшудаи амният интихоб карда мешавад. Дар робита ба ин, ё корбари решавӣ истифода мешавад, ки дар муҳити истеҳсолӣ бехатар нест ё корбаре бо UID тасодуфӣ, ки ҳангоми тақсими ҳуқуқҳои дастрасӣ ба додаҳо нороҳат аст (инро тавассути эҷоди PodSecurityPolicies ва пайваст кардани онҳо ба ҳисобҳои дахлдори хидматрасонӣ). Дар айни замон, роҳи ҳалли он аст, ки ҳама файлҳои заруриро мустақиман ба тасвири Docker ҷойгир кунед ё скрипти оғозёбии Spark-ро тағир диҳед, то механизми нигоҳдорӣ ва дарёфти сирри дар ташкилоти шумо қабулшударо истифода барад.
  3. Иҷрои ҷойҳои Spark бо истифода аз Kubernetes расман ҳоло ҳам дар ҳолати таҷрибавӣ қарор дорад ва дар оянда метавонад дар артефактҳои истифодашуда (файлҳои конфигуратсия, тасвирҳои пойгоҳи Docker ва скриптҳои оғозёбӣ) тағйироти назаррас ба амал ояд. Ва дар ҳақиқат, ҳангоми омода кардани мавод, версияҳои 2.3.0 ва 2.4.5 санҷида шуданд, рафтор ба таври назаррас фарқ мекард.

Биёед мунтазири навсозиҳо - версияи нави Spark (3.0.0) ба наздикӣ бароварда шуд, ки ба кори Spark дар Kubernetes тағйироти назаррас овард, аммо мақоми таҷрибавии дастгирии ин менеҷери захираҳоро нигоҳ дошт. Шояд навсозиҳои навбатӣ воқеан имкон медиҳанд, ки комилан тавсия дода шавад, ки аз YARN ва иҷро кардани вазифаҳои Spark дар Kubernetes бидуни тарс аз амнияти системаи шумо ва бидуни тағир додани мустақилонаи ҷузъҳои функсионалӣ тавсия дода шавад.

Фин

Манбаъ: will.com

Илова Эзоҳ