Kubernetes жүйесінде Apache Spark іске қосу

Құрметті оқырмандар, қайырлы күн. Бүгін біз Apache Spark және оның даму перспективалары туралы аздап сөйлесетін боламыз.

Kubernetes жүйесінде Apache Spark іске қосу

Үлкен деректердің заманауи әлемінде Apache Spark пакеттік деректерді өңдеу тапсырмаларын әзірлеуге арналған іс жүзінде стандарт болып табылады. Бұған қоса, ол шағын бөліктерде деректерді өңдеу және тасымалдау (Spark Structured Streaming) микро топтама тұжырымдамасында жұмыс істейтін ағынды қолданбаларды жасау үшін де қолданылады. Дәстүрлі түрде ол YARN (немесе кейбір жағдайларда Apache Mesos) ресурс менеджері ретінде пайдаланатын жалпы Hadoop стекінің бөлігі болды. 2020 жылға қарай оны дәстүрлі түрде пайдалану көптеген компаниялар үшін лайықты Hadoop дистрибутивтерінің болмауына байланысты сұрақ туындайды - HDP және CDH дамуы тоқтатылды, CDH жақсы дамымаған және құны жоғары, ал қалған Hadoop жеткізушілері не өмір сүруін тоқтатты, не болашағы бұлыңғыр болды. Сондықтан Kubernetes көмегімен Apache Spark іске қосылуы қауымдастық пен ірі компаниялар арасында қызығушылықты арттырады - жеке және жалпы бұлттарда контейнерлерді басқару және ресурстарды басқаруда стандартқа айналады, ол YARN жүйесіндегі Spark тапсырмаларының ыңғайсыз ресурстарды жоспарлау мәселесін шешеді және қамтамасыз етеді. барлық өлшемдегі және жолақтардағы компаниялар үшін көптеген коммерциялық және ашық дистрибутивтері бар тұрақты дамып келе жатқан платформа. Сонымен қатар, танымал болғаннан кейін көпшілігі өздерінің бірнеше қондырғыларын сатып ала алды және оны пайдаланудағы тәжірибесін арттырды, бұл қозғалысты жеңілдетеді.

2.3.0 нұсқасынан бастап, Apache Spark Kubernetes кластеріндегі тапсырмаларды орындау үшін ресми қолдауға ие болды және бүгін біз осы тәсілдің қазіргі жетілгендігі, оны пайдаланудың әртүрлі нұсқалары және іске асыру кезінде кездесетін қателіктер туралы сөйлесетін боламыз.

Ең алдымен, Apache Spark негізіндегі тапсырмалар мен қолданбаларды әзірлеу процесін қарастырайық және Kubernetes кластерінде тапсырманы орындау қажет болатын типтік жағдайларды бөліп алайық. Бұл жазбаны дайындау кезінде OpenShift тарату ретінде пайдаланылады және оның пәрмен жолы утилитасына (oc) қатысты пәрмендер беріледі. Басқа Kubernetes дистрибутивтері үшін стандартты Kubernetes пәрмен жолы утилитасының (kubectl) сәйкес пәрмендерін немесе олардың аналогтарын (мысалы, oc adm саясаты үшін) пайдалануға болады.

Бірінші пайдалану жағдайы - spark-submit

Тапсырмалар мен қолданбаларды әзірлеу кезінде әзірлеушіге деректерді түрлендіруді жөндеу үшін тапсырмаларды орындау қажет. Теориялық тұрғыдан бұл мақсаттар үшін штубтарды қолдануға болады, бірақ соңғы жүйелердің нақты (тест болса да) даналарының қатысуымен әзірлеу осы тапсырмалар класында жылдамырақ және жақсырақ болып шықты. Соңғы жүйелердің нақты даналарында отладка жасаған жағдайда екі сценарий мүмкін:

  • әзірлеуші ​​Spark тапсырмасын оқшау режимде жергілікті түрде іске қосады;

    Kubernetes жүйесінде Apache Spark іске қосу

  • әзірлеуші ​​сынақ цикліндегі Kubernetes кластерінде Spark тапсырмасын іске қосады.

    Kubernetes жүйесінде Apache Spark іске қосу

Бірінші нұсқаның өмір сүруге құқығы бар, бірақ бірқатар кемшіліктерді тудырады:

  • Әрбір әзірлеушіге жұмыс орнынан өзіне қажетті соңғы жүйелердің барлық даналарына қолжетімділік қамтамасыз етілуі керек;
  • әзірленетін тапсырманы орындау үшін жұмыс машинасында ресурстардың жеткілікті мөлшері қажет.

Екінші нұсқада мұндай кемшіліктер жоқ, өйткені Kubernetes кластерін пайдалану орындалатын тапсырмалар үшін қажетті ресурс пулын бөлуге және оған соңғы жүйе даналарына қажетті қол жеткізуді қамтамасыз етуге мүмкіндік береді. әзірлеу тобының барлық мүшелері. Оны бірінші пайдалану жағдайы ретінде бөлектеп көрейік - сынақ цикліндегі Kubernetes кластеріндегі жергілікті әзірлеуші ​​машинасынан Spark тапсырмаларын іске қосу.

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 кластерінде іске қосу үшін сізге шын мәнінде тек «assembly/» каталогындағы jar файлдары қажет, сондықтан сіз тек осы ішкі жобаны құра аласыз:

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

Kubernetes жүйесінде Spark тапсырмаларын орындау үшін негізгі кескін ретінде пайдалану үшін Docker кескінін жасау керек. Мұнда 2 ықтимал тәсіл бар:

  • Жасалған Docker кескіні орындалатын Spark тапсырма кодын қамтиды;
  • Құрылған кескін тек Spark және қажетті тәуелділіктерді қамтиды, орындалатын код қашықтан орналастырылады (мысалы, HDFS-де).

Алдымен, Spark тапсырмасының сынақ үлгісін қамтитын Docker кескінін құрастырайық. 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} - Docker кескіндер тізілімінің URL мекенжайы, {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} OKD кластерінің API URL мекенжайы болып табылады):

oc login {OKD-API-URL}

Докер тізілімінде авторизациялау үшін ағымдағы пайдаланушының таңбалауышын алайық:

oc whoami -t

OKD кластерінің ішкі Docker тізіліміне кіріңіз (біз алдыңғы пәрменді пайдаланып алынған таңбалауышты құпия сөз ретінде қолданамыз):

docker login {docker-registry-url}

Жиналған Docker кескінін Docker Registry OKD жүйесіне жүктеп алайық:

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

Жиналған кескіннің OKD-де қол жетімді екенін тексерейік. Ол үшін браузерде URL мекенжайын сәйкес жобаның суреттерінің тізімімен ашыңыз (мұнда {project} — OpenShift кластеріндегі жобаның атауы, {OKD-WEBUI-URL} — OpenShift веб консолінің URL мекенжайы. ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Тапсырмаларды іске қосу үшін подкасттарды түбір ретінде іске қосу артықшылықтары бар қызмет тіркелгісі жасалуы керек (бұл мәселені кейінірек талқылаймыз):

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

Жасалған қызмет тіркелгісін және Docker кескінін көрсете отырып, OKD кластеріне Spark тапсырмасын жариялау үшін spark-submit пәрменін іске қосайық:

 /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 қызмет тіркелгінің атауы (Kubernetes API интерфейсімен әрекеттесу кезінде қауіпсіздік контекстін және мүмкіндіктерін анықтау үшін);

spark.kubernetes.namespace — драйвер мен орындаушы қосқыштары іске қосылатын Kubernetes аттар кеңістігі;

spark.submit.deployMode — Spark іске қосу әдісі (стандартты spark-submit «кластері» үшін пайдаланылады, Spark Operator және Spark «клиентінің» кейінгі нұсқалары үшін);

spark.kubernetes.container.image - подкасттарды іске қосу үшін пайдаланылатын Докер кескіні;

spark.master — Kubernetes API URL мекенжайы (сыртқы көрсетілген, сондықтан кіру жергілікті құрылғыдан болады);

local:// — Docker кескініндегі Spark орындалатын файлға апаратын жол.

Біз сәйкес OKD жобасына өтіп, жасалған подкасттарды зерттейміз - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Әзірлеу процесін жеңілдету үшін басқа опцияны пайдалануға болады, онда Spark жалпы негізгі кескіні жасалады, оны орындау үшін барлық тапсырмалар қолданады және орындалатын файлдардың суреттері сыртқы жадқа (мысалы, Hadoop) жарияланады және қоңырау шалу кезінде көрсетіледі. сілтеме ретінде spark-жіберу. Бұл жағдайда кескіндерді жариялау үшін, мысалы, WebHDFS көмегімен Docker кескіндерін қайта жасамай, Spark тапсырмаларының әртүрлі нұсқаларын іске қосуға болады. Біз файлды жасауға сұраныс жібереміз (мұнда {хост} — 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}"

Осыдан кейін HDFS жүйесіне жүктеп салынған Spark файлы арқылы spark-жіберу жасай аламыз (мұнда {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 сценарийін өзгерту қажет болуы мүмкін екенін атап өткен жөн - тәуелді кітапханаларды /opt/spark/jars каталогына көшіру және Dockerfile файлына директиваны қосыңыз. HDFS конфигурация файлын SPARK_CLASSPATH ішіндегі кіру нүктесінде қосыңыз.

Екінші қолдану жағдайы - Apache Livy

Әрі қарай, тапсырма әзірленіп, нәтижені тексеру қажет болғанда, оны CI/CD процесінің бөлігі ретінде іске қосу және оның орындалу күйін қадағалау мәселесі туындайды. Әрине, оны жергілікті spark-жіберу қоңырауы арқылы іске қосуға болады, бірақ бұл CI/CD инфрақұрылымын қиындатады, себебі ол CI сервер агенттерінде/жұмысшыларында Spark орнатуды және конфигурациялауды және Kubernetes API-ге кіруді орнатуды қажет етеді. Бұл жағдайда мақсатты енгізу Kubernetes кластерінде орналастырылған Spark тапсырмаларын орындау үшін REST API ретінде Apache Livy пайдалануды таңдады. Оның көмегімен Kubernetes кластерінде Spark тапсырмаларын кез келген CI шешімі негізінде оңай орындалатын тұрақты cURL сұрауларын пайдаланып іске қоса аласыз және оны Kubernetes кластерінің ішінде орналастыру Kubernetes API интерфейсімен әрекеттесу кезінде аутентификация мәселесін шешеді.

Kubernetes жүйесінде Apache Spark іске қосу

Оны екінші пайдалану жағдайы ретінде бөлектеп көрейік - сынақ цикліндегі Kubernetes кластеріндегі CI/CD процесінің бөлігі ретінде Spark тапсырмаларын іске қосу.

Apache Livy туралы аздап - ол веб-интерфейс пен RESTful API қамтамасыз ететін HTTP сервері ретінде жұмыс істейді, ол сізге қажетті параметрлерді беру арқылы spark-submit қызметін қашықтан іске қосуға мүмкіндік береді. Дәстүрлі түрде ол HDP дистрибутивінің бөлігі ретінде жіберіледі, бірақ сонымен бірге тиісті манифест пен Docker кескіндерінің жиынтығын пайдалана отырып, OKD немесе кез келген басқа Kubernetes қондырғысына орналастыруға болады, мысалы: github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Біздің жағдайда ұқсас Docker кескіні жасалды, оның ішінде келесі Docker файлынан Spark 2.4.5 нұсқасы бар:

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

Жасалған кескінді ішкі OKD репозиторийі сияқты бұрыннан бар Docker репозиторийіне салуға және жүктеп салуға болады. Оны қолдану үшін келесі манифестті пайдаланыңыз ({registry-url} - Docker кескін тізілімінің URL мекенжайы, {image-name} - Docker кескінінің аты, {tag} - Docker кескінінің тегі, {livy-url} - қалаған URL мекенжайы серверге Livy қол жетімді болады; Red Hat OpenShift Kubernetes дистрибутиві ретінде пайдаланылса, «Бағдар» манифесті пайдаланылады, әйтпесе NodePort түріндегі сәйкес кіріс немесе қызмет манифесті пайдаланылады):

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

Оны қолданып, подкастты сәтті іске қосқаннан кейін, Livy графикалық интерфейсі мына сілтемеде қолжетімді: http://{livy-url}/ui. Livy көмегімен біз, мысалы, Пошташыдан REST сұрауы арқылы Spark тапсырмамызды жариялай аламыз. Сұраныстары бар жинақтың мысалы төменде келтірілген (іске қосылған тапсырманың жұмысына қажетті айнымалылары бар конфигурация аргументтерін «args» массивінде беруге болады):

{
    "info": {
        "_postman_id": "be135198-d2ff-47b6-a33e-0d27b9dba4c8",
        "name": "Spark Livy",
        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
    },
    "item": [
        {
            "name": "1 Submit job with jar",
            "request": {
                "method": "POST",
                "header": [
                    {
                        "key": "Content-Type",
                        "value": "application/json"
                    }
                ],
                "body": {
                    "mode": "raw",
                    "raw": "{nt"file": "local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.4.5.jar", nt"className": "org.apache.spark.examples.SparkPi",nt"numExecutors":1,nt"name": "spark-test-1",nt"conf": {ntt"spark.jars.ivy": "/tmp/.ivy",ntt"spark.kubernetes.authenticate.driver.serviceAccountName": "spark",ntt"spark.kubernetes.namespace": "{project}",ntt"spark.kubernetes.container.image": "{docker-registry-url}/{repo}/{image-name}:{tag}"nt}n}"
                },
                "url": {
                    "raw": "http://{livy-url}/batches",
                    "protocol": "http",
                    "host": [
                        "{livy-url}"
                    ],
                    "path": [
                        "batches"
                    ]
                }
            },
            "response": []
        },
        {
            "name": "2 Submit job without jar",
            "request": {
                "method": "POST",
                "header": [
                    {
                        "key": "Content-Type",
                        "value": "application/json"
                    }
                ],
                "body": {
                    "mode": "raw",
                    "raw": "{nt"file": "hdfs://{host}:{port}/{path-to-file-on-hdfs}", nt"className": "{class-name}",nt"numExecutors":1,nt"name": "spark-test-2",nt"proxyUser": "0",nt"conf": {ntt"spark.jars.ivy": "/tmp/.ivy",ntt"spark.kubernetes.authenticate.driver.serviceAccountName": "spark",ntt"spark.kubernetes.namespace": "{project}",ntt"spark.kubernetes.container.image": "{docker-registry-url}/{repo}/{image-name}:{tag}"nt},nt"args": [ntt"HADOOP_CONF_DIR=/opt/spark/hadoop-conf",ntt"MASTER=k8s://https://kubernetes.default.svc:8443"nt]n}"
                },
                "url": {
                    "raw": "http://{livy-url}/batches",
                    "protocol": "http",
                    "host": [
                        "{livy-url}"
                    ],
                    "path": [
                        "batches"
                    ]
                }
            },
            "response": []
        }
    ],
    "event": [
        {
            "listen": "prerequest",
            "script": {
                "id": "41bea1d0-278c-40c9-ad42-bf2e6268897d",
                "type": "text/javascript",
                "exec": [
                    ""
                ]
            }
        },
        {
            "listen": "test",
            "script": {
                "id": "3cdd7736-a885-4a2d-9668-bd75798f4560",
                "type": "text/javascript",
                "exec": [
                    ""
                ]
            }
        }
    ],
    "protocolProfileBehavior": {}
}

Жинақтағы бірінші сұрауды орындап, OKD интерфейсіне өтіп, тапсырманың сәтті іске қосылғанын тексеріңіз - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Сонымен қатар Livy интерфейсінде (http://{livy-url}/ui) сеанс пайда болады, оның ішінде Livy API немесе графикалық интерфейсті пайдаланып тапсырманың орындалу барысын бақылауға және сеансты зерттеуге болады. журналдар.

Енді Ливидің қалай жұмыс істейтінін көрсетейік. Бұл әрекетті орындау үшін Livy сервері арқылы подвод ішіндегі Livy контейнерінің журналдарын қарастырайық - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Олардан «livy» деп аталатын контейнерде Livy REST API шақыру кезінде жоғарыда біз пайдаланғанға ұқсас spark-жіберу орындалатынын көреміз (мұнда {livy-pod-name} - жасалған подкасттың атауы. Livy серверімен). Жинақ сонымен қатар Livy сервері арқылы Spark орындалатын файлын қашықтан орналастыратын тапсырмаларды орындауға мүмкіндік беретін екінші сұрауды ұсынады.

Үшінші пайдалану жағдайы - Spark операторы

Енді тапсырма сынақтан өткеннен кейін оны жүйелі түрде орындау туралы сұрақ туындайды. Kubernetes кластеріндегі тапсырмаларды жүйелі түрде орындаудың негізгі әдісі - CronJob нысаны және сіз оны пайдалана аласыз, бірақ қазіргі уақытта Kubernetes қолданбаларын басқару үшін операторларды пайдалану өте танымал және Spark үшін жеткілікті жетілген оператор бар, ол да Кәсіпорын деңгейіндегі шешімдерде қолданылады (мысалы, Lightbend FastData платформасы). Біз оны пайдалануды ұсынамыз - Spark бағдарламасының (2.4.5) ағымдағы тұрақты нұсқасында Kubernetes жүйесінде Spark тапсырмаларын орындауға арналған конфигурация опциялары біршама шектеулі, ал келесі негізгі нұсқасы (3.0.0) Kubernetes үшін толық қолдауды жариялайды, бірақ оның шығу күні белгісіз болып қалады. . Spark операторы маңызды конфигурация опцияларын қосу (мысалы, Spark қосқыштарына Hadoop қатынас конфигурациясымен ConfigMap орнату) және жүйелі түрде жоспарланған тапсырманы орындау мүмкіндігі арқылы бұл кемшілікті өтейді.

Kubernetes жүйесінде Apache Spark іске қосу
Оны үшінші пайдалану жағдайы ретінде атап өтейік - Spark тапсырмаларын үнемі өндіріс циклінде Kubernetes кластерінде іске қосу.

Spark Operator ашық бастапқы коды болып табылады және Google Cloud Platform ішінде әзірленген - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Оны орнату үш жолмен жүзеге асырылуы мүмкін:

  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 қолданбасының манифест сипаттамалары сәйкес API нұсқасы бар Git ішіндегі мысал тегтеріне негізделуі керек, мысалы, "v1beta1-0.9.0-2.4.0". Оператор нұсқасын «нұсқалар» сөздігіндегі операторға енгізілген CRD сипаттамасынан табуға болады:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Оператор дұрыс орнатылған болса, сәйкес жобада Spark операторы бар белсенді қосқыш пайда болады (мысалы, Cloudflow орнатуына арналған Cloudflow кеңістігіндегі cloudflow-fdp-sparkoperator) және «sparkapplications» деп аталатын сәйкес Kubernetes ресурс түрі пайда болады. . Қол жетімді 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. “spec.mainApplicationFile” сөздігі орындалатын jar файлына жолды қамтуы керек;
    6. “spec.sparkVersion” сөздігі пайдаланылып жатқан Spark нұсқасын көрсетуі керек;
    7. “spec.driver.serviceAccount” сөздігі қолданбаны іске қосу үшін пайдаланылатын сәйкес Kubernetes аттар кеңістігіндегі қызмет тіркелгісін көрсетуі керек;
    8. «spec.executor» сөздігі қосымшаға бөлінген ресурстардың санын көрсетуі керек;
    9. "spec.volumeMounts" сөздігі жергілікті Spark тапсырма файлдары жасалатын жергілікті каталогты көрсетуі керек.

Манифест жасау мысалы (мұнда {spark-service-count} - Spark тапсырмаларын орындауға арналған Kubernetes кластеріндегі қызмет тіркелгісі):

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"

Бұл манифест манифестті жарияламас бұрын, Kubernetes API интерфейсімен (қажет болса) әрекеттесу үшін Spark қолданбасы үшін қажетті қатынас құқықтарын қамтамасыз ететін қажетті рөл байламдарын жасауыңыз қажет қызмет тіркелгісін көрсетеді. Біздің жағдайда қолданбаға Pod жасау құқығы қажет. Қажетті рөлді байланыстыруды жасайық:

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

Сондай-ақ, бұл манифест спецификациясы алдымен Docker кескініне сәйкес файлды орналастырмай-ақ Hadoop конфигурациясымен ConfigMap көрсетуге мүмкіндік беретін «hadoopConfigMap» параметрін қамтуы мүмкін екенін атап өткен жөн. Ол сондай-ақ тапсырмаларды үнемі орындау үшін қолайлы - «кесте» параметрін пайдаланып, берілген тапсырманы орындау кестесін көрсетуге болады.

Осыдан кейін біз манифестімізді spark-pi.yaml файлына сақтаймыз және оны Kubernetes кластеріне қолданамыз:

oc apply -f spark-pi.yaml

Бұл «sparkapplications» түріндегі нысанды жасайды:

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

Бұл жағдайда, күйі құрылған «ұшқын қолданбаларында» көрсетілетін қолданбасы бар подкладка жасалады. Сіз оны келесі пәрмен арқылы көре аласыз:

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

Тапсырманы орындағаннан кейін POD «Аяқталды» күйіне ауысады, ол «ұшқын қолданбаларында» да жаңартылады. Қолданба журналдарын шолғышта немесе келесі пәрменді пайдалану арқылы көруге болады (мұнда {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}

Қорытындылай келе, Kubernetes-те Spark (2.4.5) ағымдағы тұрақты нұсқасын пайдаланудың анықталған кемшіліктерін қарастырғым келеді:

  1. Бірінші және, мүмкін, негізгі кемшілігі - деректер локализациясының болмауы. YARN-тың барлық кемшіліктеріне қарамастан, оны пайдаланудың артықшылықтары да болды, мысалы, кодты деректерге жеткізу принципі (деректерден кодқа емес). Оның арқасында Spark тапсырмалары есептеулерге қатысатын деректер орналасқан түйіндерде орындалды және деректерді желі арқылы жеткізуге кететін уақыт айтарлықтай қысқарды. Kubernetes қолданбасын пайдаланған кезде біз тапсырмаға қатысты деректерді желі бойынша жылжыту қажеттілігіне тап боламыз. Егер олар жеткілікті үлкен болса, тапсырманы орындау уақыты айтарлықтай артуы мүмкін, сонымен қатар оларды уақытша сақтау үшін Spark тапсырма даналарына бөлінген жеткілікті үлкен дискілік кеңістікті қажет етеді. Бұл кемшілікті Kubernetes-те деректердің орналасуын қамтамасыз ететін арнайы бағдарламалық жасақтаманы пайдалану арқылы азайтуға болады (мысалы, Alluxio), бірақ бұл шын мәнінде деректердің толық көшірмесін Kubernetes кластерінің түйіндерінде сақтау қажеттілігін білдіреді.
  2. Екінші маңызды кемшілік - қауіпсіздік. Әдепкі бойынша, Spark тапсырмаларын орындауға қатысты қауіпсіздікке қатысты мүмкіндіктер өшірілген, Kerberos пайдалану ресми құжаттамада қарастырылмаған (бірақ сәйкес опциялар 3.0.0 нұсқасында енгізілген, ол қосымша жұмысты қажет етеді) және қауіпсіздік құжаттамасы Spark көмегімен (https://spark.apache.org/docs/2.4.5/security.html) тек YARN, Mesos және оқшау кластер негізгі қоймалар ретінде пайда болады. Сонымен қатар, Spark тапсырмалары іске қосылатын пайдаланушыны тікелей көрсету мүмкін емес - біз тек ол жұмыс істейтін қызмет тіркелгісін көрсетеміз және пайдаланушы конфигурацияланған қауіпсіздік саясаттары негізінде таңдалады. Осыған байланысты, өнімді ортада қауіпсіз емес түбірлік пайдаланушы немесе деректерге қол жеткізу құқықтарын тарату кезінде ыңғайсыз болатын кездейсоқ UID пайдаланушы пайдаланылады (бұл PodSecurityPolicies құру және оларды байланыстыру арқылы шешуге болады). сәйкес қызмет шоты). Қазіргі уақытта шешім барлық қажетті файлдарды тікелей Docker кескініне орналастыру немесе ұйымыңызда қабылданған құпияларды сақтау және шығарып алу механизмін пайдалану үшін Spark іске қосу сценарийін өзгерту болып табылады.
  3. Kubernetes көмегімен Spark тапсырмаларын іске қосу ресми түрде әлі де эксперименттік режимде және болашақта пайдаланылатын артефактілерде (конфигурация файлдары, Docker негізгі кескіндері және іске қосу сценарийлері) елеулі өзгерістер болуы мүмкін. Шынында да, материалды дайындау кезінде 2.3.0 және 2.4.5 нұсқалары сынақтан өтті, мінез-құлық айтарлықтай өзгеше болды.

Жаңартуларды күтейік – жақында Spark (3.0.0) жаңа нұсқасы шығарылды, ол Kubernetes-тегі Spark жұмысына елеулі өзгерістер әкелді, бірақ осы ресурс менеджеріне қолдау көрсетудің тәжірибелік күйін сақтап қалды. Мүмкін келесі жаңартулар шынымен YARN-дан бас тартуды және жүйеңіздің қауіпсіздігі үшін қорықпай және функционалдық құрамдастарды дербес өзгертуді қажет етпей Kubernetes жүйесінде Spark тапсырмаларын орындауды толық ұсынуға мүмкіндік береді.

Fin.

Ақпарат көзі: www.habr.com

пікір қалдыру