Apache Spark käynnissä Kubernetesissa

Hyvät lukijat, hyvää iltapäivää. Tänään puhumme hieman Apache Sparkista ja sen kehitysnäkymistä.

Apache Spark käynnissä Kubernetesissa

Modernissa Big Datan maailmassa Apache Spark on de facto standardi erätietojen käsittelytehtävien kehittämiseen. Lisäksi sitä käytetään myös suoratoistosovellusten luomiseen, jotka toimivat mikroeräkonseptissa, prosessoivat ja lähettävät tietoja pieninä erinä (Spark Structured Streaming). Ja perinteisesti se on ollut osa yleistä Hadoop-pinoa, jossa on käytetty YARN:ia (tai joissain tapauksissa Apache Mesosia) resurssienhallintana. Vuoteen 2020 mennessä sen käyttö perinteisessä muodossa on kyseenalainen useimmille yrityksille kunnollisten Hadoop-jakelujen puutteen vuoksi - HDP:n ja CDH:n kehitys on pysähtynyt, CDH ei ole hyvin kehittynyt ja sen hinta on korkea, ja loput Hadoop-toimittajat ovat joko lakannut olemasta tai niillä on hämärä tulevaisuus. Siksi Apache Sparkin lanseeraus Kubernetesia käyttämällä on yhä kiinnostavampaa yhteisön ja suurten yritysten keskuudessa - siitä tulee standardi konttien orkestroinnissa ja resurssienhallinnassa yksityisissä ja julkisissa pilvissä, se ratkaisee ongelman, joka liittyy Spark-tehtävien epämukavaan resurssien ajoitukseen YARN:ssa ja tarjoaa tasaisesti kehittyvä alusta, jossa on monia kaupallisia ja avoimia jakeluja kaikenkokoisille ja -maisille yrityksille. Lisäksi useimmat ovat suosion perässä jo onnistuneet hankkimaan pari omaa asennusta ja lisänneet asiantuntemustaan ​​sen käytössä, mikä helpottaa muuttoa.

Versiosta 2.3.0 alkaen Apache Spark hankki virallisen tuen tehtävien suorittamiseen Kubernetes-klusterissa, ja tänään puhumme tämän lähestymistavan kypsyydestä, sen erilaisista käyttövaihtoehdoista ja käyttöönoton aikana kohdattavista sudenkuomista.

Ensinnäkin tarkastellaan Apache Sparkiin perustuvien tehtävien ja sovellusten kehittämisprosessia ja korostetaan tyypillisiä tapauksia, joissa sinun on suoritettava tehtävä Kubernetes-klusterissa. Tätä viestiä valmisteltaessa käytetään OpenShiftiä jakeluna ja sen komentorivityökaluun (oc) liittyvät komennot annetaan. Muissa Kubernetes-jakeluissa voidaan käyttää vastaavia komentoja tavallisesta Kubernetes-komentorivityökalusta (kubectl) tai niiden analogeista (esimerkiksi oc adm -käytäntöä varten).

Ensimmäinen käyttötapaus - kipinä-submit

Tehtävien ja sovellusten kehittämisen aikana kehittäjän on suoritettava tehtäviä tietojen muuntamisen virheenkorjausta varten. Teoriassa tyngejä voidaan käyttää näihin tarkoituksiin, mutta kehitys todellisten (vaikkakin testi) loppujärjestelmien esiintymien kanssa on osoittautunut nopeammaksi ja paremmaksi tässä tehtäväluokassa. Jos suoritamme virheenkorjauksen loppujärjestelmien todellisissa esiintymissä, kaksi skenaariota on mahdollista:

  • kehittäjä suorittaa Spark-tehtävän paikallisesti itsenäisessä tilassa;

    Apache Spark käynnissä Kubernetesissa

  • kehittäjä suorittaa Spark-tehtävän Kubernetes-klusterissa testisilmukassa.

    Apache Spark käynnissä Kubernetesissa

Ensimmäisellä vaihtoehdolla on oikeus olemassaoloon, mutta siihen liittyy useita haittoja:

  • Jokaiselle kehittäjälle on annettava pääsy työpaikalta kaikkiin hänen tarvitsemiinsa loppujärjestelmiin.
  • Työkoneelta vaaditaan riittävä määrä resursseja kehitettävän tehtävän suorittamiseen.

Toisella vaihtoehdolla ei ole näitä haittoja, koska Kubernetes-klusterin käytön avulla voit varata tarvittavan resurssivarannon suoritettaviin tehtäviin ja antaa sille tarvittavan pääsyn loppujärjestelmän ilmentymiin, mikä tarjoaa joustavasti pääsyn siihen Kubernetes-roolimallin avulla. kaikki kehitystiimin jäsenet. Korostetaan se ensimmäisenä käyttötapauksena - Spark-tehtävien käynnistäminen paikalliselta kehittäjäkoneelta Kubernetes-klusterissa testipiirissä.

Puhutaanpa lisää prosessista, jolla Spark määritetään toimimaan paikallisesti. Aloita Sparkin käyttö asentamalla se:

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

Keräämme Kuberneteksen kanssa työskentelyyn tarvittavat paketit:

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

Täysi rakentaminen vie paljon aikaa, ja Docker-kuvien luomiseen ja niiden suorittamiseen Kubernetes-klusterissa tarvitset vain jar-tiedostoja "assembly/"-hakemistosta, joten voit rakentaa vain tämän aliprojektin:

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

Jotta voit suorittaa Spark-töitä Kubernetesissa, sinun on luotava Docker-kuva, jota käytetään peruskuvana. Tässä on 2 mahdollista lähestymistapaa:

  • Luotu Docker-kuva sisältää suoritettavan Spark-tehtäväkoodin;
  • Luotu kuva sisältää vain Sparkin ja tarvittavat riippuvuudet, suoritettavaa koodia isännöidään etänä (esimerkiksi HDFS:ssä).

Rakennataan ensin Docker-kuva, joka sisältää testiesimerkin Spark-tehtävästä. Docker-kuvien luomiseen Sparkilla on apuohjelma nimeltä "docker-image-tool". Tutustutaanpa siihen liittyvään ohjeeseen:

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

Sen avulla voit luoda Docker-kuvia ja ladata ne etärekistereihin, mutta oletuksena sillä on useita haittoja:

  • luo epäonnistumatta 3 Docker-kuvaa kerralla - Sparkille, PySparkille ja R:lle;
  • ei salli kuvan nimen määrittämistä.

Siksi käytämme tämän apuohjelman muokattua versiota, joka on annettu alla:

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

Sen avulla kokoamme Spark-peruskuvan, joka sisältää testitehtävän Pi:n laskemiseen Sparkilla (tässä {docker-registry-url} on Docker-kuvarekisterisi URL-osoite, {repo} on rekisterin sisällä olevan arkiston nimi, joka vastaa projektia OpenShiftissä , {image-name} - kuvan nimi (jos käytetään kuvien kolmitasoista erottelua, kuten esimerkiksi Red Hat OpenShift -kuvien integroidussa rekisterissä), {tag} - tämän tunniste versio kuvasta):

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

Kirjaudu OKD-klusteriin konsoliapuohjelmalla (tässä {OKD-API-URL} on OKD-klusterin API-URL-osoite):

oc login {OKD-API-URL}

Haetaan nykyisen käyttäjän tunnus valtuutusta varten Docker-rekisterissä:

oc whoami -t

Kirjaudu sisään OKD-klusterin sisäiseen Docker-rekisteriin (käytämme salasanana edellisellä komennolla saatua merkkiä):

docker login {docker-registry-url}

Ladataan koottu Docker-kuva Dockerin rekisteriin OKD:

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

Tarkistetaan, että koottu kuva on saatavilla OKD:ssä. Voit tehdä tämän avaamalla selaimessa URL-osoitteen, jossa on luettelo vastaavan projektin kuvista (tässä {projekti} on projektin nimi OpenShift-klusterin sisällä, {OKD-WEBUI-URL} on OpenShift-verkkokonsolin URL-osoite ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Tehtävien suorittamiseksi on luotava palvelutili, jolla on oikeudet suorittaa podeja pääkäyttäjänä (käsittelemme tätä kohtaa myöhemmin):

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

Suoritetaan spark-submit-komento julkaistaksesi Spark-tehtävä OKD-klusteriin ja määritetään luotu palvelutili ja Docker-kuva:

 /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

tässä:

—nimi — sen tehtävän nimi, joka osallistuu Kubernetes-palojen nimen muodostukseen;

—class — suoritettavan tiedoston luokka, joka kutsutaan tehtävän alkaessa;

—conf — Spark-kokoonpanoparametrit;

spark.executor.instances — käynnistettävien Spark-executoreiden määrä;

spark.kubernetes.authenticate.driver.serviceAccountName - Kubernetes-palvelutilin nimi, jota käytetään podeja käynnistettäessä (suojauskontekstin ja -ominaisuuksien määrittäminen Kubernetes API:n kanssa vuorovaikutuksessa);

spark.kubernetes.namespace — Kubernetes-nimiavaruus, jossa ohjain- ja suoritinkotelot käynnistetään;

spark.submit.deployMode — tapa käynnistää Spark (tavalliseen spark-submit -ryhmään käytetään "klusteria", Spark Operatorille ja uudemmille Spark "asiakasversioille");

spark.kubernetes.container.kuva Docker-kuva, jota käytetään koteloiden käynnistämiseen;

spark.master - Kubernetes API URL (ulkoinen on määritetty, jotta pääsy tapahtuu paikalliselta koneelta);

local:// on polku Spark-suoritettavaan tiedostoon Docker-kuvan sisällä.

Siirrymme vastaavaan OKD-projektiin ja tutkimme luotuja podeja - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Kehitysprosessin yksinkertaistamiseksi voidaan käyttää toista vaihtoehtoa, jossa luodaan yhteinen peruskuva Sparkista, jota kaikki tehtävät käyttävät suorittaessaan, ja suoritettavat tiedostot julkaistaan ​​ulkoiseen tallennustilaan (esimerkiksi Hadoop) ja määritetään kutsuttaessa. spark-lähetä linkkinä. Tässä tapauksessa voit suorittaa eri versioita Spark-tehtävistä rakentamatta uudelleen Docker-kuvia, käyttämällä esimerkiksi WebHDFS:ää kuvien julkaisemiseen. Lähetämme tiedoston luomispyynnön (tässä {host} on WebHDFS-palvelun isäntä, {port} on WebHDFS-palvelun portti, {polku-tiedostoon-hdfs} on haluttu polku tiedostoon HDFS:llä):

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

Saat seuraavanlaisen vastauksen (tässä {location} on URL-osoite, jota käytetään tiedoston lataamiseen):

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

Lataa Spark-suoritettava tiedosto HDFS:ään (tässä {path-to-local-file} on polku Spark-suoritettavaan tiedostoon nykyisessä isännässä):

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

Tämän jälkeen voimme tehdä spark-submitin käyttämällä HDFS:ään ladattua Spark-tiedostoa (tässä {class-name} on luokan nimi, joka on käynnistettävä tehtävän suorittamiseksi):

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

On huomattava, että HDFS:n käyttämiseksi ja tehtävän toimivuuden varmistamiseksi sinun on ehkä muutettava Dockerfile-tiedostoa ja enterpoint.sh-skriptiä - lisää Dockerfile-tiedostoon käsky kopioida riippuvaiset kirjastot /opt/spark/jars-hakemistoon ja sisällytä HDFS-määritystiedosto aloituspisteen SPARK_CLASSPATH:iin. sh.

Toinen käyttötapaus - Apache Livy

Lisäksi kun tehtävää kehitetään ja tulos on testattava, herää kysymys sen käynnistämisestä osana CI/CD-prosessia ja sen suorituksen tilan seurantaa. Tietysti voit käyttää sitä paikallisella spark-submit-kutsulla, mutta tämä monimutkaistaa CI-/CD-infrastruktuuria, koska se vaatii Sparkin asentamista ja määrittämistä CI-palvelinagenteille/-agenteille ja Kubernetes API:n käyttöoikeuden määrittämistä. Tässä tapauksessa kohdetoteutus on päättänyt käyttää Apache Livyä REST-sovellusliittymänä Kubernetes-klusterin sisällä olevien Spark-tehtävien suorittamiseen. Sen avulla voit suorittaa Spark-tehtäviä Kubernetes-klusterissa tavallisilla cURL-pyynnöillä, mikä on helppo toteuttaa minkä tahansa CI-ratkaisun perusteella, ja sen sijoittaminen Kubernetes-klusterin sisälle ratkaisee todennusongelman vuorovaikutuksessa Kubernetes API:n kanssa.

Apache Spark käynnissä Kubernetesissa

Korostetaan se toisena käyttötapauksena - Spark-tehtävien suorittaminen osana CI/CD-prosessia Kubernetes-klusterissa testisilmukassa.

Hieman Apache Livystä - se toimii HTTP-palvelimena, joka tarjoaa web-rajapinnan ja RESTful API:n, jonka avulla voit etäkäynnistää spark-submitin välittämällä tarvittavat parametrit. Perinteisesti se on toimitettu osana HDP-jakelua, mutta se voidaan ottaa käyttöön myös OKD:hen tai mihin tahansa muuhun Kubernetes-asennukseen käyttämällä asianmukaista luetteloa ja Docker-otoksia, kuten tämä - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Meidän tapauksessamme rakennettiin samanlainen Docker-kuva, mukaan lukien Spark-versio 2.4.5 seuraavasta Docker-tiedostosta:

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

Luotu kuva voidaan rakentaa ja ladata olemassa olevaan Docker-tietovarastoon, kuten sisäiseen OKD-tietovarastoon. Ota se käyttöön käyttämällä seuraavaa luetteloa ({registry-url} - Docker-kuvarekisterin URL-osoite, {image-name} - Docker-kuvan nimi, {tag} - Docker-kuvatunniste, {livy-url} - haluttu URL-osoite, jossa palvelin on käytettävissä Livy; "Reitti"-luetteloa käytetään, jos Red Hat OpenShiftiä käytetään Kubernetes-jakeluna, muussa tapauksessa käytetään vastaavaa NodePort-tyyppistä Ingress- tai Service-luetteloa):

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

Kun se on otettu käyttöön ja podi on käynnistetty onnistuneesti, Livy-graafinen käyttöliittymä on saatavilla linkistä: http://{livy-url}/ui. Livyllä voimme julkaista Spark-tehtävämme esimerkiksi Postimiehen REST-pyynnöllä. Alla on esimerkki kokoelmasta, jossa on pyyntöjä (konfiguraatioargumentit muuttujineen, jotka ovat välttämättömiä käynnistetyn tehtävän toiminnalle, voidaan välittää "args"-taulukossa):

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

Suoritetaan ensimmäinen pyyntö kokoelmasta, siirrytään OKD-käyttöliittymään ja tarkistetaan, että tehtävä on käynnistetty onnistuneesti - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Samalla Livy-käyttöliittymään (http://{livy-url}/ui) ilmestyy istunto, jossa Livy API:n tai graafisen käyttöliittymän avulla voit seurata tehtävän etenemistä ja tutkia istuntoa. lokit.

Näytämme nyt kuinka Livy toimii. Tätä varten tutkitaan podin sisällä olevan Livy-säilön lokit Livy-palvelimella - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Niistä voimme nähdä, että kutsuttaessa Livy REST API:ta "livy"-nimisessä säilössä suoritetaan spark-submit, joka on samanlainen kuin yllä käytimme (tässä {livy-pod-name} on luodun podin nimi Livy-palvelimen kanssa). Kokoelmassa on myös toinen kysely, jonka avulla voit suorittaa tehtäviä, jotka isännöivät etänä Spark-suoritettavaa tiedostoa Livy-palvelimen avulla.

Kolmas käyttötapaus - Spark Operator

Nyt kun tehtävä on testattu, herää kysymys sen suorittamisesta säännöllisesti. Alkuperäinen tapa suorittaa säännöllisesti tehtäviä Kubernetes-klusterissa on CronJob-kokonaisuus ja voit käyttää sitä, mutta tällä hetkellä operaattoreiden käyttö sovellusten hallintaan Kubernetesissa on erittäin suosittua ja Sparkille on olemassa melko kypsä operaattori, joka on myös käytetään yritystason ratkaisuissa (esimerkiksi Lightbend FastData Platform). Suosittelemme sen käyttöä - Sparkin nykyisessä vakaassa versiossa (2.4.5) on melko rajalliset konfigurointimahdollisuudet Spark-tehtävien suorittamiseen Kubernetesissa, kun taas seuraava suuri versio (3.0.0) ilmoittaa täyden tuen Kubernetesille, mutta sen julkaisupäivä ei ole tiedossa. . Spark Operator kompensoi tämän puutteen lisäämällä tärkeitä konfigurointivaihtoehtoja (esimerkiksi asentamalla ConfigMapin Hadoop-käyttökonfiguraatiolla Spark-podeihin) ja mahdollisuuden suorittaa säännöllisesti ajoitettuja tehtäviä.

Apache Spark käynnissä Kubernetesissa
Korostetaan se kolmantena käyttötapauksena - Spark-tehtävien säännöllinen suorittaminen Kubernetes-klusterissa tuotantosilmukassa.

Spark Operator on avoimen lähdekoodin, ja se on kehitetty Google Cloud Platformissa - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Sen asennus voidaan tehdä kolmella tavalla:

  1. osana Lightbend FastData Platform/Cloudflow -asennusta;
  2. Helmin käyttö:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Käytä virallisen arkiston luetteloita (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). On syytä huomata seuraava - Cloudflow sisältää operaattorin, jonka API-versio on v1beta1. Jos käytetään tämäntyyppistä asennusta, Spark-sovellusluettelokuvausten tulee perustua Gitin esimerkkitageihin sopivalla API-versiolla, esimerkiksi "v1beta1-0.9.0-2.4.0". Operaattorin versio löytyy "versiot"-sanakirjan operaattoriin sisältyvästä CRD:n kuvauksesta:
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Jos operaattori on asennettu oikein, vastaavaan projektiin ilmestyy aktiivinen pod Spark-operaattorilla (esimerkiksi cloudflow-fdp-sparkoperator Cloudflow-asennuksen Cloudflow-tilassa) ja vastaava Kubernetes-resurssityyppi nimeltä "sparkapplications" tulee näkyviin. . Voit tutkia saatavilla olevia Spark-sovelluksia seuraavalla komennolla:

oc get sparkapplications -n {project}

Jotta voit suorittaa tehtäviä Spark Operatorilla, sinun on tehtävä kolme asiaa:

  • Luo Docker-kuva, joka sisältää kaikki tarvittavat kirjastot sekä kokoonpano- ja suoritettavat tiedostot. Kohdekuvassa tämä on CI/CD-vaiheessa luotu ja testiklusterissa testattu kuva;
  • julkaista Docker-kuva Kubernetes-klusterista saatavilla olevaan rekisteriin;
  • luo luettelo, jossa on "SparkApplication"-tyyppi ja kuvaus käynnistettävästä tehtävästä. Esimerkkiluettelot ovat saatavilla virallisessa arkistossa (esim. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Manifestissa on tärkeitä huomioitavia seikkoja:
    1. "apiVersion"-sanakirjassa on ilmoitettava operaattoriversiota vastaava API-versio;
    2. "metadata.namespace" -sanakirjassa on ilmoitettava nimiavaruus, jossa sovellus käynnistetään;
    3. "spec.image"-sanakirjan on sisällettävä luodun Docker-kuvan osoite käytettävissä olevassa rekisterissä;
    4. "spec.mainClass"-sanakirjan on sisällettävä Spark-tehtäväluokka, joka on suoritettava prosessin alkaessa;
    5. "spec.mainApplicationFile"-sanakirjan tulee sisältää polku suoritettavaan jar-tiedostoon;
    6. "spec.sparkVersion"-sanakirjassa on ilmoitettava käytetyn Spark-version versio;
    7. "spec.driver.serviceAccount"-sanakirjassa on määritettävä palvelutili vastaavassa Kubernetes-nimitilassa, jota käytetään sovelluksen suorittamiseen;
    8. "spec.executor"-sanakirjassa on ilmoitettava sovellukselle allokoitujen resurssien määrä;
    9. "spec.volumeMounts"-sanakirjassa on määritettävä paikallinen hakemisto, johon paikalliset Spark-tehtävätiedostot luodaan.

Esimerkki luettelon luomisesta (tässä {spark-service-account} on palvelutili Kubernetes-klusterin sisällä Spark-tehtävien suorittamista varten):

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"

Tämä luettelo määrittää palvelutilin, jolle sinun on ennen luettelon julkaisemista luotava tarvittavat roolisidokset, jotka tarjoavat tarvittavat käyttöoikeudet, jotta Spark-sovellus voi olla vuorovaikutuksessa Kubernetes API:n kanssa (tarvittaessa). Meidän tapauksessamme sovellus tarvitsee oikeudet podien luomiseen. Luodaan tarvittava roolisidonta:

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

On myös syytä huomata, että tämä luettelon määrittely voi sisältää "hadoopConfigMap"-parametrin, jonka avulla voit määrittää ConfigMapin Hadoop-määrityksellä ilman, että sinun tarvitsee ensin sijoittaa vastaava tiedosto Docker-kuvaan. Se sopii myös tehtävien suorittamiseen säännöllisesti - "schedule"-parametrilla voidaan määrittää aikataulu tietyn tehtävän suorittamiselle.

Tämän jälkeen tallennamme manifestimme spark-pi.yaml-tiedostoon ja käytämme sitä Kubernetes-klusteriimme:

oc apply -f spark-pi.yaml

Tämä luo objektin tyyppiä "sparkapplications":

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

Tässä tapauksessa luodaan pod, jossa on sovellus, jonka tila näytetään luoduissa "kipinäsovelluksissa". Voit tarkastella sitä seuraavalla komennolla:

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

Tehtävän suorittamisen jälkeen POD siirtyy "Completed" -tilaan, joka päivittyy myös "sparkapplikaatioissa". Sovelluslokeja voi tarkastella selaimessa tai käyttämällä seuraavaa komentoa (tässä {sparkapplications-pod-name} on käynnissä olevan tehtävän podin nimi):

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

Spark-tehtäviä voidaan hallita myös erikoistuneen sparkctl-apuohjelman avulla. Asenna se kloonaamalla arkisto ja sen lähdekoodi, asentamalla Go ja rakentamalla tämä apuohjelma:

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

Tarkastellaan käynnissä olevien Spark-tehtävien luetteloa:

sparkctl list -n {project}

Luodaan kuvaus Spark-tehtävälle:

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"

Suoritetaan kuvattu tehtävä sparkctl:llä:

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

Tarkastellaan käynnissä olevien Spark-tehtävien luetteloa:

sparkctl list -n {project}

Tarkastellaan käynnistetyn Spark-tehtävän tapahtumien luetteloa:

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

Tarkastellaan käynnissä olevan Spark-tehtävän tilaa:

sparkctl status spark-pi -n {project}

Lopuksi haluaisin tarkastella havaittuja haittoja Sparkin nykyisen vakaan version (2.4.5) käytöstä Kubernetesissa:

  1. Ensimmäinen ja ehkä suurin haittapuoli on Data Localityn puute. Kaikista YARNin puutteista huolimatta sen käytössä oli myös etuja, esimerkiksi periaate toimittaa koodia dataan (eikä datasta koodiksi). Sen ansiosta Spark-tehtävät suoritettiin solmuissa, joissa laskelmiin osallistuvat tiedot sijaitsivat, ja tiedon toimittamiseen kulunut aika verkon yli lyheni merkittävästi. Kun käytät Kubernetesia, kohtaamme tarpeen siirtää tehtävään liittyviä tietoja verkon yli. Jos ne ovat riittävän suuria, tehtävien suoritusaika voi pidentää merkittävästi ja vaatia myös melko paljon levytilaa, joka on varattu Spark-tehtäväesiintymille niiden väliaikaista tallennusta varten. Tätä haittaa voidaan lieventää käyttämällä erikoisohjelmistoa, joka varmistaa tietojen sijainnin Kubernetesissa (esimerkiksi Alluxio), mutta tämä tarkoittaa itse asiassa tarvetta tallentaa täydellinen kopio tiedoista Kubernetes-klusterin solmuihin.
  2. Toinen tärkeä haittapuoli on turvallisuus. Oletusarvoisesti Spark-tehtävien suorittamiseen liittyvät tietoturvaominaisuudet ovat poissa käytöstä, Kerberosin käyttöä ei käsitellä virallisessa dokumentaatiossa (vaikka vastaavat vaihtoehdot esiteltiin versiossa 3.0.0, mikä vaatii lisätyötä) ja tietoturvadokumentaatio käyttämällä Sparkia (https://spark.apache.org/docs/2.4.5/security.html) vain YARN, Mesos ja Standalone Cluster näkyvät avainsäilöinä. Samanaikaisesti käyttäjää, jolle Spark-tehtävät käynnistetään, ei voida määrittää suoraan - määritämme vain palvelutilin, jolla se toimii, ja käyttäjä valitaan määritettyjen suojauskäytäntöjen perusteella. Tässä yhteydessä käytetään joko pääkäyttäjää, joka ei ole turvallista tuottavassa ympäristössä, tai käyttäjää, jolla on satunnainen UID, mikä on hankalaa tietojen käyttöoikeuksien jakamisessa (tämä voidaan ratkaista luomalla PodSecurityPolicies ja linkittämällä ne vastaavat palvelutilit). Tällä hetkellä ratkaisuna on joko sijoittaa kaikki tarvittavat tiedostot suoraan Docker-kuvaan tai muokata Spark-käynnistyskomentosarjaa käyttämään organisaatiossasi käyttöön otettua salaisuuksien tallennus- ja hakumekanismia.
  3. Spark-töiden suorittaminen Kubernetesilla on virallisesti vielä kokeellisessa tilassa, ja käytettäviin artefakteihin (määritystiedostoihin, Dockerin peruskuviin ja käynnistysskripteihin) saattaa tulla merkittäviä muutoksia tulevaisuudessa. Ja todellakin, kun materiaalia valmisteltiin, versiot 2.3.0 ja 2.4.5 testattiin, käyttäytyminen oli merkittävästi erilaista.

Odotetaan päivityksiä - äskettäin julkaistiin uusi Spark-versio (3.0.0), joka toi merkittäviä muutoksia Sparkin työhön Kubernetesissa, mutta säilytti tämän resurssienhallinnan tuen kokeellisen tilan. Ehkä seuraavat päivitykset todella mahdollistavat täysin YARNin luopumisen ja Spark-tehtävien suorittamisen Kubernetesissa ilman pelkoa järjestelmäsi turvallisuudesta ja ilman tarvetta muokata toiminnallisia komponentteja itsenäisesti.

Loppu.

Lähde: will.com

Lisää kommentti