Inaendesha Apache Spark kwenye Kubernetes

Wasomaji wapendwa, habari za mchana. Leo tutazungumza kidogo juu ya Apache Spark na matarajio yake ya maendeleo.

Inaendesha Apache Spark kwenye Kubernetes

Katika ulimwengu wa kisasa wa Data Kubwa, Apache Spark ndicho kiwango halisi cha kutengeneza kazi za usindikaji wa data batch. Kwa kuongeza, pia hutumiwa kuunda programu za utiririshaji zinazofanya kazi katika dhana ya bechi ndogo, usindikaji na usafirishaji wa data katika sehemu ndogo (Spark Structured Streaming). Na jadi imekuwa sehemu ya mkusanyiko wa jumla wa Hadoop, kwa kutumia YARN (au katika hali zingine Apache Mesos) kama msimamizi wa rasilimali. Kufikia 2020, matumizi yake katika hali yake ya kitamaduni yanahojiwa kwa kampuni nyingi kwa sababu ya ukosefu wa usambazaji mzuri wa Hadoop - ukuzaji wa HDP na CDH umesimama, CDH haijatengenezwa vizuri na ina gharama kubwa, na wauzaji wa Hadoop waliobaki wana. ama ilikoma kuwepo au kuwa na mustakabali mbaya. Kwa hivyo, kuzinduliwa kwa Apache Spark kwa kutumia Kubernetes ni ya kuongezeka kwa riba kati ya jamii na kampuni kubwa - kuwa kiwango katika upangaji wa vyombo na usimamizi wa rasilimali katika mawingu ya kibinafsi na ya umma, inasuluhisha shida na upangaji wa rasilimali usiofaa wa kazi za Spark kwenye YARN na hutoa. jukwaa linalokua kwa kasi na usambazaji mwingi wa kibiashara na wazi kwa kampuni za ukubwa na mistari. Kwa kuongezea, kutokana na umaarufu huo, wengi tayari wameweza kupata mitambo kadhaa yao wenyewe na wameongeza utaalam wao katika matumizi yake, ambayo hurahisisha harakati.

Kuanzia na toleo la 2.3.0, Apache Spark ilipata usaidizi rasmi wa kuendesha kazi katika nguzo ya Kubernetes na leo, tutazungumza juu ya ukomavu wa sasa wa mbinu hii, chaguzi mbalimbali za matumizi yake na mitego ambayo itakutana wakati wa utekelezaji.

Kwanza kabisa, hebu tuangalie mchakato wa kukuza kazi na programu kulingana na Apache Spark na tuangazie kesi za kawaida ambazo unahitaji kuendesha kazi kwenye nguzo ya Kubernetes. Katika kuandaa chapisho hili, OpenShift inatumika kama usambazaji na amri zinazofaa kwa matumizi yake ya mstari wa amri (oc) zitatolewa. Kwa usambazaji mwingine wa Kubernetes, amri zinazolingana kutoka kwa matumizi ya kawaida ya mstari wa amri ya Kubernetes (kubectl) au analogi zake (kwa mfano, kwa sera ya oc adm) zinaweza kutumika.

Kesi ya matumizi ya kwanza - cheche-wasilisha

Wakati wa uundaji wa kazi na programu, msanidi anahitaji kutekeleza majukumu ili kutatua mabadiliko ya data. Kinadharia, vijiti vinaweza kutumika kwa madhumuni haya, lakini ukuzaji kwa ushiriki wa mifano halisi (ingawa mtihani) wa mifumo ya mwisho imethibitishwa kuwa ya haraka na bora zaidi katika darasa hili la kazi. Katika kesi tunapotatua matukio halisi ya mifumo ya mwisho, matukio mawili yanawezekana:

  • msanidi huendesha kazi ya Spark ndani ya nchi katika hali ya pekee;

    Inaendesha Apache Spark kwenye Kubernetes

  • msanidi huendesha kazi ya Spark kwenye nguzo ya Kubernetes katika kitanzi cha majaribio.

    Inaendesha Apache Spark kwenye Kubernetes

Chaguo la kwanza lina haki ya kuwepo, lakini linajumuisha idadi ya hasara:

  • Kila msanidi lazima apewe ufikiaji kutoka mahali pa kazi hadi kwa hali zote za mifumo ya mwisho anayohitaji;
  • kiasi cha kutosha cha rasilimali kinahitajika kwenye mashine ya kufanya kazi ili kuendesha kazi inayotengenezwa.

Chaguo la pili halina ubaya huu, kwani utumiaji wa nguzo ya Kubernetes hukuruhusu kutenga rasilimali muhimu ya kufanya kazi na kuipatia ufikiaji unaohitajika wa hali za mfumo wa mwisho, kutoa ufikiaji wake kwa urahisi kwa kutumia mfano wa Kubernetes. wanachama wote wa timu ya maendeleo. Wacha tuiangazie kama kisa cha kwanza cha utumiaji - kuzindua kazi za Spark kutoka kwa mashine ya msanidi wa ndani kwenye nguzo ya Kubernetes kwenye saketi ya majaribio.

Wacha tuzungumze zaidi juu ya mchakato wa kusanidi Spark kuendeshwa ndani ya nchi. Ili kuanza kutumia Spark unahitaji kusakinisha:

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

Tunakusanya vifurushi muhimu vya kufanya kazi na Kubernetes:

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

Uundaji kamili huchukua muda mwingi, na kuunda picha za Docker na kuziendesha kwenye nguzo ya Kubernetes, unahitaji tu faili za jar kutoka kwa saraka ya "mkusanyiko/", kwa hivyo unaweza kuunda mradi huu mdogo tu:

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

Ili kuendesha kazi za Spark kwenye Kubernetes, unahitaji kuunda picha ya Docker ili kutumia kama picha ya msingi. Kuna njia 2 zinazowezekana hapa:

  • Picha ya Docker iliyotengenezwa inajumuisha nambari ya kazi ya Spark inayoweza kutekelezwa;
  • Picha iliyoundwa inajumuisha tu Spark na tegemezi muhimu, nambari inayoweza kutekelezwa inashikiliwa kwa mbali (kwa mfano, katika HDFS).

Kwanza, wacha tujenge picha ya Docker iliyo na mfano wa jaribio la kazi ya Spark. Ili kuunda picha za Docker, Spark ina huduma inayoitwa "docker-image-tool". Wacha tujifunze msaada juu yake:

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

Kwa msaada wake, unaweza kuunda picha za Docker na kuzipakia kwenye sajili za mbali, lakini kwa chaguo-msingi ina idadi ya hasara:

  • bila kushindwa huunda picha 3 za Docker mara moja - kwa Spark, PySpark na R;
  • haikuruhusu kubainisha jina la picha.

Kwa hivyo, tutatumia toleo lililobadilishwa la shirika hili lililopewa hapa chini:

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

Kwa usaidizi wake, tunakusanya picha ya msingi ya Spark iliyo na kazi ya majaribio ya kukokotoa Pi kwa kutumia Spark (hapa {docker-registry-url} ni URL ya sajili yako ya picha ya Docker, {repo} ndilo jina la hazina ndani ya sajili, ambayo inalingana na mradi katika OpenShift , {image-name} β€” jina la picha (ikiwa mgawanyo wa ngazi tatu wa picha unatumika, kwa mfano, kama katika sajili iliyojumuishwa ya picha za Red Hat OpenShift), {tag} - lebo ya hii toleo la picha):

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

Ingia kwenye nguzo ya OKD ukitumia matumizi ya dashibodi (hapa {OKD-API-URL} ni URL ya API ya nguzo ya OKD):

oc login {OKD-API-URL}

Wacha tupate ishara ya mtumiaji wa sasa kwa idhini katika Usajili wa Docker:

oc whoami -t

Ingia kwenye Usajili wa Docker wa ndani wa nguzo ya OKD (tunatumia ishara iliyopatikana kwa kutumia amri ya awali kama nenosiri):

docker login {docker-registry-url}

Wacha tupakie picha ya Docker iliyokusanyika kwenye Usajili wa Docker OKD:

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

Wacha tuangalie ikiwa picha iliyokusanywa inapatikana katika OKD. Ili kufanya hivyo, fungua URL kwenye kivinjari na orodha ya picha za mradi unaolingana (hapa {project} ni jina la mradi ndani ya nguzo ya OpenShift, {OKD-WEBUI-URL} ni URL ya dashibodi ya OpenShift Web. ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Ili kutekeleza majukumu, akaunti ya huduma lazima iundwe ikiwa na mapendeleo ya kuendesha ganda kama mzizi (tutajadili jambo hili baadaye):

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

Wacha tuendeshe amri ya kuwasilisha cheche ili kuchapisha kazi ya Spark kwenye nguzo ya OKD, tukibainisha akaunti ya huduma iliyoundwa na picha ya 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

Hapa:

-jina - jina la kazi ambayo itashiriki katika malezi ya jina la maganda ya Kubernetes;

-darasa - darasa la faili inayoweza kutekelezwa, inayoitwa wakati kazi inapoanza;

-conf - Vigezo vya usanidi wa Spark;

spark.executor.instances - idadi ya watekelezaji wa Spark wa kuzindua;

spark.kubernetes.authenticate.driver.serviceAccountName - jina la akaunti ya huduma ya Kubernetes inayotumiwa wakati wa kuzindua maganda (kufafanua muktadha wa usalama na uwezo wakati wa kuingiliana na API ya Kubernetes);

spark.kubernetes.namespace - Nafasi ya majina ya Kubernetes ambamo maganda ya madereva na watekelezaji yatazinduliwa;

spark.submit.deployMode β€” njia ya kurusha Spark (kwa kiwango cha kawaida cha spark-submit "cluster" hutumiwa, kwa Spark Operator na matoleo ya baadaye ya Spark "mteja");

spark.kubernetes.container.image - Picha ya Docker iliyotumiwa kuzindua maganda;

spark.master - Kubernetes API URL (ya nje imebainishwa ili ufikiaji utokee kutoka kwa mashine ya ndani);

local:// ndio njia ya Spark inayoweza kutekelezwa ndani ya picha ya Docker.

Tunaenda kwa mradi unaolingana wa OKD na kusoma maganda yaliyoundwa - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Ili kurahisisha mchakato wa maendeleo, chaguo jingine linaweza kutumika, ambalo picha ya kawaida ya Spark imeundwa, inayotumiwa na kazi zote za kukimbia, na snapshots za faili zinazoweza kutekelezwa huchapishwa kwenye hifadhi ya nje (kwa mfano, Hadoop) na maalum wakati wa kupiga simu. cheche-wasilisha kama kiungo. Katika kesi hii, unaweza kuendesha matoleo tofauti ya kazi za Spark bila kujenga upya picha za Docker, kwa kutumia, kwa mfano, WebHDFS kuchapisha picha. Tunatuma ombi la kuunda faili (hapa {host} ni seva pangishi ya huduma ya WebHDFS, {port} ni lango la huduma ya WebHDFS, {path-to-file-on-hdfs} ndiyo njia inayotakiwa ya faili. kwenye HDFS):

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

Utapokea jibu kama hili (hapa {location} ndio URL inayohitaji kutumiwa kupakua faili):

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

Pakia faili inayoweza kutekelezwa ya Spark kwenye HDFS (hapa {path-to-local-file} ndio njia ya faili inayoweza kutekelezwa ya Spark kwenye seva pangishi ya sasa):

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

Baada ya hayo, tunaweza kuwasilisha cheche kwa kutumia faili ya Spark iliyopakiwa kwa HDFS (hapa {class-name} ni jina la darasa linalohitaji kuzinduliwa ili kukamilisha kazi):

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

Ikumbukwe kwamba ili kupata HDFS na kuhakikisha kazi inafanya kazi, unaweza kuhitaji kubadilisha Dockerfile na hati ya entrypoint.sh - ongeza maagizo kwa Dockerfile kunakili maktaba tegemezi kwenye saraka ya /opt/spark/jars na jumuisha faili ya usanidi ya HDFS katika SPARK_CLASSPATH katika kiingilio.

Kesi ya pili ya matumizi - Apache Livy

Zaidi ya hayo, kazi inapotengenezwa na matokeo yanahitaji kujaribiwa, swali linatokea la kuizindua kama sehemu ya mchakato wa CI/CD na kufuatilia hali ya utekelezaji wake. Bila shaka, unaweza kuiendesha kwa kutumia simu ya ndani ya kuwasilisha cheche, lakini hii inatatiza miundombinu ya CI/CD kwani inahitaji kusakinisha na kusanidi Spark kwenye mawakala/wakimbiaji wa seva ya CI na kusanidi ufikiaji wa Kubernetes API. Kwa kesi hii, utekelezaji lengwa umechagua kutumia Apache Livy kama API REST kwa ajili ya kuendesha kazi za Spark zinazopangishwa ndani ya nguzo ya Kubernetes. Kwa usaidizi wake, unaweza kuendesha kazi za Spark kwenye kundi la Kubernetes kwa kutumia maombi ya kawaida ya cURL, ambayo hutekelezwa kwa urahisi kulingana na suluhisho lolote la CI, na uwekaji wake ndani ya nguzo ya Kubernetes hutatua suala la uthibitishaji wakati wa kuingiliana na API ya Kubernetes.

Inaendesha Apache Spark kwenye Kubernetes

Wacha tuiangazie kama kisa cha pili cha utumiaji - kuendesha kazi za Spark kama sehemu ya mchakato wa CI/CD kwenye nguzo ya Kubernetes kwenye kitanzi cha majaribio.

Kidogo kuhusu Apache Livy - inafanya kazi kama seva ya HTTP ambayo hutoa kiolesura cha Wavuti na API RESTful ambayo hukuruhusu kuzindua uwasilishaji wa cheche kwa mbali kwa kupitisha vigezo muhimu. Kijadi imesafirishwa kama sehemu ya usambazaji wa HDP, lakini pia inaweza kutumwa kwa OKD au usakinishaji mwingine wowote wa Kubernetes kwa kutumia faili ya maelezo inayofaa na seti ya picha za Docker, kama hii - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Kwa upande wetu, picha sawa ya Docker ilijengwa, pamoja na toleo la Spark 2.4.5 kutoka kwa Dockerfile ifuatayo:

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

Picha iliyotolewa inaweza kujengwa na kupakiwa kwenye hazina yako iliyopo ya Docker, kama vile hazina ya ndani ya OKD. Ili kuitumia, tumia faili ifuatayo ya maelezo ({registry-url} - URL ya sajili ya picha ya Docker, {image-name} - Jina la picha ya Docker, {tag} - Lebo ya picha ya Docker, {livy-url} - URL inayotakiwa ambapo seva itafikiwa Livy; faili ya maelezo ya "Njia" itatumika ikiwa Red Hat OpenShift inatumika kama usambazaji wa Kubernetes, vinginevyo Ingress au maelezo ya Huduma yanayolingana ya aina ya NodePort inatumika):

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

Baada ya kuitumia na kuzindua ganda kwa mafanikio, kiolesura cha picha cha Livy kinapatikana kwenye kiungo: http://{livy-url}/ui. Tukiwa na Livy, tunaweza kuchapisha kazi yetu ya Spark kwa kutumia ombi la REST kutoka, kwa mfano, Postman. Mfano wa mkusanyiko ulio na maombi umewasilishwa hapa chini (hoja za usanidi zilizo na vigezo muhimu kwa ajili ya uendeshaji wa kazi iliyozinduliwa zinaweza kupitishwa katika safu ya "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": {}
}

Hebu tutekeleze ombi la kwanza kutoka kwa mkusanyiko, nenda kwenye kiolesura cha OKD na uhakikishe kuwa kazi imezinduliwa kwa mafanikio - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Wakati huo huo, kipindi kitatokea katika kiolesura cha Livy (http://{livy-url}/ui), ambapo, kwa kutumia Livy API au kiolesura cha picha, unaweza kufuatilia maendeleo ya kazi na kusoma kipindi. magogo.

Sasa hebu tuonyeshe jinsi Livy anavyofanya kazi. Ili kufanya hivyo, hebu tuchunguze kumbukumbu za chombo cha Livy ndani ya ganda na seva ya Livy - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tabo=magogo. Kutoka kwao tunaweza kuona kwamba wakati wa kuita API ya Livy REST kwenye chombo kinachoitwa "livy", uwasilishaji wa cheche hutekelezwa, sawa na ule tuliotumia hapo juu (hapa {livy-pod-name} ni jina la pod iliyoundwa. na seva ya Livy). Mkusanyiko pia unatanguliza swali la pili ambalo hukuruhusu kutekeleza majukumu ambayo yanapangisha Spark inayoweza kutekelezeka kwa mbali kwa kutumia seva ya Livy.

Kesi ya tatu ya matumizi - Spark Operator

Sasa kwa kuwa kazi imejaribiwa, swali la kuendesha mara kwa mara hutokea. Njia asili ya kuendesha kazi mara kwa mara kwenye nguzo ya Kubernetes ni chombo cha CronJob na unaweza kuitumia, lakini kwa sasa utumiaji wa waendeshaji kusimamia programu katika Kubernetes ni maarufu sana na kwa Spark kuna mwendeshaji aliyekomaa, ambaye pia kutumika katika ufumbuzi wa ngazi ya Enterprise (kwa mfano, Lightbend FastData Platform). Tunapendekeza uitumie - toleo thabiti la sasa la Spark (2.4.5) lina chaguo chache za usanidi za kuendesha kazi za Spark huko Kubernetes, wakati toleo kuu linalofuata (3.0.0) linatangaza msaada kamili kwa Kubernetes, lakini tarehe yake ya kutolewa bado haijulikani. . Spark Operator hufidia upungufu huu kwa kuongeza chaguo muhimu za usanidi (kwa mfano, kuweka ConfigMap yenye usanidi wa ufikiaji wa Hadoop kwenye maganda ya Spark) na uwezo wa kuendesha kazi iliyoratibiwa mara kwa mara.

Inaendesha Apache Spark kwenye Kubernetes
Hebu tuiangazie kama kisa cha tatu cha matumizi - kuendesha kazi za Spark mara kwa mara kwenye nguzo ya Kubernetes katika kitanzi cha uzalishaji.

Spark Operator ni chanzo wazi na kimetengenezwa ndani ya Google Cloud Platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Ufungaji wake unaweza kufanywa kwa njia 3:

  1. Kama sehemu ya usakinishaji wa Lightbend FastData Platform/Cloudflow;
  2. Kutumia Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Kutumia maonyesho kutoka hazina rasmi ( https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Inafaa kuzingatia yafuatayo - Cloudflow inajumuisha opereta na toleo la API v1beta1. Ikiwa aina hii ya usakinishaji itatumiwa, maelezo ya faili ya maelezo ya programu ya Spark yanapaswa kutegemea tagi za mfano katika Git zilizo na toleo linalofaa la API, kwa mfano, "v1beta1-0.9.0-2.4.0". Toleo la opereta linaweza kupatikana katika maelezo ya CRD iliyojumuishwa katika opereta katika kamusi ya "matoleo":
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Ikiwa opereta itasakinishwa kwa usahihi, ganda amilifu lenye kiendesha Spark litaonekana katika mradi unaolingana (kwa mfano, cloudflow-fdp-sparkoperator katika nafasi ya Cloudflow kwa usakinishaji wa Cloudflow) na aina ya nyenzo inayolingana ya Kubernetes inayoitwa "sparkapplications" itaonekana. . Unaweza kuchunguza programu zinazopatikana za Spark kwa amri ifuatayo:

oc get sparkapplications -n {project}

Ili kuendesha kazi kwa kutumia Spark Operator unahitaji kufanya mambo 3:

  • unda picha ya Docker ambayo inajumuisha maktaba zote muhimu, pamoja na usanidi na faili zinazoweza kutekelezwa. Katika picha inayolengwa, hii ni picha iliyoundwa katika hatua ya CI/CD na kujaribiwa kwenye kundi la majaribio;
  • kuchapisha picha ya Docker kwa sajili inayopatikana kutoka kwa nguzo ya Kubernetes;
  • toa faili ya maelezo yenye aina ya "SparkApplication" na maelezo ya kazi itakayozinduliwa. Mifano ya maonyesho yanapatikana katika hazina rasmi (k.m. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml) Kuna mambo muhimu ya kuzingatia kuhusu manifesto:
    1. kamusi ya "apiVersion" lazima ionyeshe toleo la API linalolingana na toleo la opereta;
    2. kamusi ya "metadata.namespace" lazima ionyeshe nafasi ya majina ambayo programu itazinduliwa;
    3. kamusi ya "spec.image" lazima iwe na anwani ya picha ya Docker iliyoundwa katika sajili inayoweza kufikiwa;
    4. kamusi ya "spec.mainClass" lazima iwe na darasa la kazi la Spark ambalo linahitaji kuendeshwa mchakato unapoanza;
    5. njia ya faili ya jar inayoweza kutekelezwa lazima ibainishwe katika kamusi ya "spec.mainApplicationFile";
    6. lazima kamusi ya "spec.sparkVersion" ionyeshe toleo la Spark linalotumika;
    7. lazima kamusi ya "spec.driver.serviceAccount" ibainishe akaunti ya huduma ndani ya nafasi ya jina inayolingana ya Kubernetes ambayo itatumika kuendesha programu;
    8. kamusi ya "spec.executor" lazima ionyeshe idadi ya rasilimali zilizotolewa kwa programu;
    9. kamusi ya "spec.volumeMounts" lazima ibainishe saraka ya ndani ambayo faili za kazi za ndani za Spark zitaundwa.

Mfano wa kutengeneza faili ya maelezo (hapa {spark-service-account} ni akaunti ya huduma ndani ya kundi la Kubernetes kwa ajili ya kuendesha kazi za 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"

Faili hii ya maelezo inabainisha akaunti ya huduma ambayo, kabla ya kuchapisha faili ya maelezo, ni lazima uunde vifungo vya jukumu muhimu vinavyotoa haki muhimu za ufikiaji kwa programu ya Spark ili kuingiliana na API ya Kubernetes (ikihitajika). Kwa upande wetu, programu inahitaji haki ili kuunda Pods. Wacha tuunde jukumu muhimu la kumfunga:

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

Inafaa pia kuzingatia kwamba maelezo haya ya maelezo yanaweza kujumuisha kigezo cha "hadoopConfigMap", ambacho hukuruhusu kubainisha ConfigMap na usanidi wa Hadoop bila kulazimika kuweka kwanza faili inayolingana kwenye picha ya Docker. Inafaa pia kwa kufanya kazi mara kwa mara - kwa kutumia parameta ya "ratiba", ratiba ya kuendesha kazi fulani inaweza kubainishwa.

Baada ya hapo, tunahifadhi faili yetu ya maelezo kwenye faili ya spark-pi.yaml na kuitumia kwenye nguzo yetu ya Kubernetes:

oc apply -f spark-pi.yaml

Hii itaunda kitu cha aina ya "cheche":

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

Katika kesi hii, pod iliyo na programu itaundwa, hali ambayo itaonyeshwa kwenye "programu za cheche" zilizoundwa. Unaweza kuiona kwa amri ifuatayo:

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

Baada ya kukamilisha kazi, POD itahamia kwenye hali ya "Imekamilika", ambayo pia itasasisha katika "maombi ya cheche". Kumbukumbu za programu zinaweza kutazamwa kwenye kivinjari au kwa kutumia amri ifuatayo (hapa {sparkapplications-pod-name} ni jina la ganda la kazi inayoendeshwa):

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

Kazi za Spark pia zinaweza kusimamiwa kwa kutumia matumizi maalum ya sparkctl. Ili kuisakinisha, unganisha hazina na msimbo wake wa chanzo, sasisha Go na ujenge shirika hili:

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

Wacha tuchunguze orodha ya kazi za Spark:

sparkctl list -n {project}

Wacha tuunde maelezo ya kazi ya 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"

Wacha tuendeshe kazi iliyoelezewa kwa kutumia sparkctl:

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

Wacha tuchunguze orodha ya kazi za Spark:

sparkctl list -n {project}

Wacha tuchunguze orodha ya matukio ya kazi iliyozinduliwa ya Spark:

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

Wacha tuchunguze hali ya kazi ya Spark inayoendesha:

sparkctl status spark-pi -n {project}

Kwa kumalizia, ningependa kuzingatia ubaya uliogunduliwa wa kutumia toleo thabiti la sasa la Spark (2.4.5) katika Kubernetes:

  1. Ya kwanza na, labda, hasara kuu ni ukosefu wa Eneo la Data. Licha ya mapungufu yote ya YARN, pia kulikuwa na faida za kuitumia, kwa mfano, kanuni ya kutoa msimbo kwa data (badala ya data kwa kanuni). Shukrani kwa hilo, kazi za Spark zilitekelezwa kwenye nodes ambapo data iliyohusika katika mahesabu ilikuwa iko, na muda uliochukua ili kutoa data kwenye mtandao ulipunguzwa kwa kiasi kikubwa. Tunapotumia Kubernetes, tunakabiliwa na haja ya kuhamisha data inayohusika katika kazi kwenye mtandao. Ikiwa ni kubwa ya kutosha, muda wa utekelezaji wa kazi unaweza kuongezeka kwa kiasi kikubwa, na pia kuhitaji kiasi kikubwa cha nafasi ya disk iliyotengwa kwa matukio ya kazi ya Spark kwa hifadhi yao ya muda. Upungufu huu unaweza kupunguzwa kwa kutumia programu maalum inayohakikisha eneo la data katika Kubernetes (kwa mfano, Alluxio), lakini hii inamaanisha hitaji la kuhifadhi nakala kamili ya data kwenye nodi za nguzo ya Kubernetes.
  2. Hasara ya pili muhimu ni usalama. Kwa chaguo-msingi, vipengele vinavyohusiana na usalama kuhusu kuendesha kazi za Spark vimezimwa, matumizi ya Kerberos hayajaangaziwa katika nyaraka rasmi (ingawa chaguo sambamba zilianzishwa katika toleo la 3.0.0, ambalo litahitaji kazi ya ziada), na nyaraka za usalama za kwa kutumia Spark (https://spark.apache.org/docs/2.4.5/security.html) YARN, Mesos na Nguzo Iliyojitegemea pekee ndizo huonekana kama duka kuu. Wakati huo huo, mtumiaji ambaye kazi za Spark zimezinduliwa haziwezi kutajwa moja kwa moja - tunabainisha tu akaunti ya huduma ambayo itafanya kazi, na mtumiaji anachaguliwa kulingana na sera za usalama zilizowekwa. Katika suala hili, mtumiaji wa mizizi hutumiwa, ambayo si salama katika mazingira ya uzalishaji, au mtumiaji aliye na UID isiyo ya kawaida, ambayo ni ngumu wakati wa kusambaza haki za upatikanaji wa data (hii inaweza kutatuliwa kwa kuunda PodSecurityPolicies na kuziunganisha na akaunti za huduma zinazolingana). Kwa sasa, suluhu ni ama kuweka faili zote muhimu moja kwa moja kwenye picha ya Docker, au kurekebisha hati ya uzinduzi wa Spark ili kutumia utaratibu wa kuhifadhi na kurejesha siri zilizopitishwa katika shirika lako.
  3. Uendeshaji wa kazi za Spark kwa kutumia Kubernetes bado uko katika hali ya majaribio na kunaweza kuwa na mabadiliko makubwa katika vizalia vya programu vinavyotumika (faili za usanidi, picha za msingi za Docker, na hati za uzinduzi) katika siku zijazo. Na kwa kweli, wakati wa kuandaa nyenzo, matoleo 2.3.0 na 2.4.5 yalijaribiwa, tabia ilikuwa tofauti sana.

Wacha tusubiri masasisho - toleo jipya la Spark (3.0.0) lilitolewa hivi karibuni, ambalo lilileta mabadiliko makubwa katika kazi ya Spark kwenye Kubernetes, lakini lilihifadhi hali ya majaribio ya usaidizi kwa meneja huyu wa rasilimali. Labda sasisho zinazofuata zitafanya iwezekanavyo kupendekeza kabisa kuacha UZI na kuendesha kazi za Spark kwenye Kubernetes bila kuogopa usalama wa mfumo wako na bila hitaji la kurekebisha vipengee vya kazi kwa uhuru.

Mwisho.

Chanzo: mapenzi.com

Kuongeza maoni