Ngajalankeun Apache Spark dina Kubernetes

Pembaca anu hormat, wilujeng siang. Dinten ieu kami bakal ngobrol sakedik ngeunaan Apache Spark sareng prospek pangembanganana.

Ngajalankeun Apache Spark dina Kubernetes

Di dunya modérn Big Data, Apache Spark mangrupikeun standar de facto pikeun ngembangkeun tugas ngolah data angkatan. Sajaba ti éta, ogé dipaké pikeun nyieun aplikasi streaming anu dianggo dina konsép micro-batch, ngolah jeung pengiriman barang data dina porsi leutik (Spark Structured Streaming). Sareng sacara tradisional éta mangrupikeun bagian tina tumpukan Hadoop sadayana, nganggo BENANG (atanapi dina sababaraha kasus Apache Mesos) salaku manajer sumberdaya. Nepi ka 2020, pamakeanna dina bentuk tradisional ditaroskeun pikeun sabagéan ageung perusahaan kusabab kurangna distribusi Hadoop anu santun - pamekaran HDP sareng CDH parantos lirén, CDH henteu saé dikembangkeun sareng gaduh biaya anu luhur, sareng sésa-sésa panyadia Hadoop ngagaduhan. boh teu aya atanapi gaduh masa depan anu surem. Ku alatan éta, peluncuran Apache Spark ngagunakeun Kubernetes téh beuki dipikaresep di kalangan masarakat jeung pausahaan badag - jadi standar dina orchestration wadahna sarta manajemén sumberdaya di awan swasta jeung publik, éta solves masalah kalawan scheduling sumberdaya merenah tina tugas Spark on BENANG jeung nyadiakeun. platform anu terus berkembang sareng seueur distribusi komérsial sareng kabuka pikeun perusahaan sadaya ukuran sareng belang. Sajaba ti éta, dina hudang popularitas, lolobana geus junun acquire sababaraha instalasi sorangan sarta geus ngaronjat kaahlian maranéhna dina pamakéan na, nu simplifies move.

Dimimitian ku vérsi 2.3.0, Apache Spark ngagaduhan dukungan resmi pikeun ngajalankeun tugas dina kluster Kubernetes sareng ayeuna, urang bakal ngobrol ngeunaan kematangan pendekatan ieu ayeuna, rupa-rupa pilihan pikeun dianggo sareng pitfalls anu bakal dipendakan nalika palaksanaan.

Anu mimiti, hayu urang tingali prosés ngembangkeun tugas sareng aplikasi dumasar kana Apache Spark sareng nyorot kasus-kasus khas dimana anjeun kedah ngajalankeun tugas dina klaster Kubernetes. Dina nyiapkeun tulisan ieu, OpenShift dianggo salaku distribusi sareng paréntah anu relevan sareng utilitas garis paréntah (oc) bakal dipasihkeun. Pikeun distribusi Kubernetes anu sanés, paréntah anu cocog tina utilitas garis paréntah Kubernetes standar (kubectl) atanapi analogna (contona, pikeun kawijakan oc adm) tiasa dianggo.

Kasus pamakéan munggaran - spark-kintunkeun

Salila pamekaran tugas sareng aplikasi, pamekar kedah ngajalankeun tugas pikeun debug transformasi data. Sacara téoritis, rintisan bisa dipaké pikeun tujuan ieu, tapi ngembangkeun jeung partisipasi nyata (sanajan test) instansi tina sistem tungtung geus kabuktian leuwih gancang sarta hadé dina kelas ieu tugas. Dina kasus nalika urang debug on instansi nyata tina sistem tungtung, dua skenario anu mungkin:

  • pamekar ngajalankeun tugas Spark lokal dina modeu mandiri;

    Ngajalankeun Apache Spark dina Kubernetes

  • pamekar ngajalankeun tugas Spark dina klaster Kubernetes dina loop test.

    Ngajalankeun Apache Spark dina Kubernetes

Pilihan kahiji boga hak eksis, tapi merlukeun sababaraha kalemahan:

  • Masing-masing pamekar kedah disayogikeun aksés ti tempat damel ka sadaya instansi sistem tungtung anu diperyogikeun;
  • jumlah cukup sumberdaya diperlukeun dina mesin digawé pikeun ngajalankeun tugas keur dimekarkeun.

Pilihan kadua teu boga kalemahan ieu, saprak pamakéan klaster Kubernetes ngidinan Anjeun pikeun allocate kolam renang sumberdaya diperlukeun pikeun ngajalankeun tugas jeung nyadiakeun eta kalawan aksés diperlukeun pikeun ngeureunkeun instansi sistem, flexibly nyadiakeun aksés ka dinya ngagunakeun panutan Kubernetes pikeun. sadaya anggota tim pamekaran. Hayu urang nyorotna salaku kasus pamakean anu munggaran - ngaluncurkeun tugas Spark tina mesin pamekar lokal dina klaster Kubernetes dina loop uji.

Hayu urang ngobrol langkung seueur ngeunaan prosés nyetél Spark pikeun dijalankeun sacara lokal. Pikeun ngamimitian nganggo Spark anjeun kedah pasang:

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

Kami ngumpulkeun bungkusan anu dipikabutuh pikeun damel sareng Kubernetes:

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

A ngawangun pinuh butuh loba waktu, sarta pikeun nyieun gambar Docker tur ngajalankeun aranjeunna dina klaster Kubernetes, anjeun bener ngan butuh file jar tina diréktori "assembly /", jadi Anjeun ngan bisa ngawangun subproject ieu:

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

Pikeun ngajalankeun padamelan Spark dina Kubernetes, anjeun kedah nyiptakeun gambar Docker pikeun dianggo salaku gambar dasar. Aya 2 pendekatan anu mungkin di dieu:

  • Gambar Docker anu dihasilkeun kalebet kode tugas Spark anu tiasa dieksekusi;
  • Gambar anu diciptakeun ngan ukur Spark sareng katergantungan anu diperyogikeun, kode anu tiasa dieksekusi di-host jarak jauh (contona, dina HDFS).

Mimiti, hayu urang ngawangun gambar Docker anu ngandung conto uji tugas Spark. Pikeun nyiptakeun gambar Docker, Spark ngagaduhan utilitas anu disebut "docker-image-tool". Hayu urang diajar pitulung ngeunaan eta:

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

Kalayan bantosanana, anjeun tiasa nyiptakeun gambar Docker sareng unggah kana pendaptaran jauh, tapi sacara standar ngagaduhan sababaraha kalemahan:

  • tanpa gagal nyiptakeun 3 gambar Docker sakaligus - pikeun Spark, PySpark sareng R;
  • teu ngidinan Anjeun pikeun nangtukeun hiji ngaran gambar.

Ku alatan éta, kami bakal ngagunakeun vérsi anu dirobih tina utilitas ieu di handap ieu:

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

Kalayan bantosanana, urang ngumpul gambar Spark dasar anu ngandung tugas tés pikeun ngitung Pi nganggo Spark (di dieu {docker-registry-url} mangrupikeun URL pendaptaran gambar Docker anjeun, {repo} mangrupikeun nami gudang di jero pendaptaran, anu cocog sareng proyék di OpenShift , {image-name} - nami gambar (upami separation tilu tingkat gambar dianggo, contona, sapertos dina pendaptaran terpadu gambar Red Hat OpenShift), {tag} - tag ieu versi gambar):

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

Asup ka klaster OKD nganggo utilitas konsol (di dieu {OKD-API-URL} nyaéta URL API klaster OKD):

oc login {OKD-API-URL}

Hayu urang kéngingkeun token pangguna ayeuna pikeun otorisasi dina Docker Registry:

oc whoami -t

Asup ka Docker Registry internal tina klaster OKD (urang nganggo token anu dicandak nganggo paréntah sateuacana salaku sandi):

docker login {docker-registry-url}

Hayu urang unggah gambar Docker anu dirakit ka Docker Registry OKD:

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

Hayu urang pariksa yen gambar dirakit sadia dina OKD. Jang ngalampahkeun ieu, buka URL dina browser kalayan daptar gambar tina proyék nu saluyu (di dieu {proyek} nyaéta ngaran proyék di jero OpenShift klaster, {OKD-WEBUI-URL} nyaéta URL tina OpenShift Web konsol. ) - https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Pikeun ngajalankeun tugas, hiji akun jasa kudu dijieun kalawan hak husus pikeun ngajalankeun pods salaku root (urang bakal ngabahas titik ieu engké):

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

Hayu urang ngajalankeun paréntah spark-submit pikeun nyebarkeun tugas Spark kana klaster OKD, nangtukeun akun jasa anu dijieun sareng gambar 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

Kieu:

—name — ngaran tugas anu bakal ilubiung dina formasi ngaran pods Kubernetes;

—class — kelas file laksana, disebut nalika tugas dimimitian;

—conf — Parameter konfigurasi Spark;

spark.executor.instances - jumlah pelaksana Spark pikeun diluncurkeun;

spark.kubernetes.authenticate.driver.serviceAccountName - nami akun jasa Kubernetes anu dianggo nalika ngaluncurkeun pods (pikeun nangtukeun kontéks kaamanan sareng kamampuan nalika berinteraksi sareng API Kubernetes);

spark.kubernetes.namespace - Kubernetes namespace dimana supir sareng eksekutor pod bakal diluncurkeun;

spark.submit.deployMode - métode launching Spark (pikeun standar spark-submit "cluster" dipaké, pikeun Spark Operator jeung versi engké tina Spark "klien");

spark.kubernetes.container.image - Gambar Docker dipaké pikeun ngajalankeun pods;

spark.master - URL API Kubernetes (éksternal dieusian ku kituna aksés lumangsung ti mesin lokal);

lokal: // nyaeta jalur ka Spark laksana di jero gambar Docker.

Urang buka proyek OKD pakait jeung diajar pods dijieun - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Pikeun nyederhanakeun prosés pamekaran, pilihan anu sanés tiasa dianggo, dimana gambar dasar umum Spark didamel, dianggo ku sadaya tugas pikeun dijalankeun, sareng snapshot file anu tiasa dieksekusi dipedarkeun ka panyimpenan éksternal (contona, Hadoop) sareng ditunjuk nalika nelepon. spark-kintunkeun salaku tumbu. Dina hal ieu, anjeun tiasa ngajalankeun versi béda tina tugas Spark tanpa ngawangun deui gambar Docker, ngagunakeun, contona, WebHDFS pikeun nyebarkeun gambar. Kami ngirim pamenta pikeun nyiptakeun file (di dieu {host} mangrupikeun host jasa WebHDFS, {port} mangrupikeun palabuhan jasa WebHDFS, {path-to-file-on-hdfs} mangrupikeun jalur anu dipikahoyong kana file. dina HDFS):

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

Anjeun bakal nampi réspon sapertos kieu (di dieu {lokasi} mangrupikeun URL anu kedah dianggo pikeun ngaunduh file):

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

Muatkeun file Spark executable kana HDFS (di dieu {path-to-local-file} nyaéta jalur ka file executable Spark dina host ayeuna):

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

Saatos ieu, urang tiasa ngalakukeun spark-submit nganggo file Spark anu diunggah ka HDFS (di dieu {class-name} mangrupikeun nami kelas anu kedah diluncurkeun pikeun ngarengsekeun tugas):

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

Perlu dicatet yén pikeun ngaksés HDFS sareng mastikeun tugasna jalan, anjeun panginten kedah ngarobih Dockerfile sareng skrip entrypoint.sh - tambahkeun diréktif ka Dockerfile pikeun nyalin perpustakaan gumantung kana diréktori /opt/spark/jars sareng kaasup file konfigurasi HDFS di SPARK_CLASSPATH di entrypoint. sh.

Kasus pamakéan kadua - Apache Livy

Salajengna, nalika tugas dikembangkeun sareng hasilna kedah diuji, timbul patarosan ngeunaan peluncuran éta salaku bagian tina prosés CI / CD sareng ngalacak status palaksanaanna. Tangtosna, anjeun tiasa ngajalankeun éta nganggo telepon ngirimkeun spark lokal, tapi ieu nyusahkeun infrastruktur CI / CD sabab peryogi masang sareng ngonpigurasikeun Spark dina agén server / pelari CI sareng nyetél aksés ka API Kubernetes. Pikeun hal ieu, palaksanaan targét geus milih ngagunakeun Apache Livy salaku REST API pikeun ngajalankeun tugas Spark hosted jero hiji klaster Kubernetes. Kalayan bantosanana, anjeun tiasa ngajalankeun tugas Spark dina kluster Kubernetes nganggo pamundut cURL biasa, anu gampang dilaksanakeun dumasar kana solusi CI, sareng panempatanna di jero kluster Kubernetes ngarengsekeun masalah auténtikasi nalika berinteraksi sareng API Kubernetes.

Ngajalankeun Apache Spark dina Kubernetes

Hayu urang nyorot salaku kasus pamakéan kadua - ngajalankeun tugas Spark salaku bagian tina prosés CI / CD dina klaster Kubernetes dina loop test.

Sakedik ngeunaan Apache Livy - éta tiasa dianggo salaku server HTTP anu nyayogikeun antarmuka Wéb sareng API RESTful anu ngamungkinkeun anjeun ngaluncurkeun spark-submit jarak jauh ku ngalangkungan parameter anu diperyogikeun. Sacara tradisional, éta dikirimkeun salaku bagian tina distribusi HDP, tapi ogé tiasa disebarkeun ka OKD atanapi pamasangan Kubernetes anu sanés nganggo manifes anu pas sareng sakumpulan gambar Docker, sapertos kieu - github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Pikeun kasus urang, gambar Docker anu sami diwangun, kalebet versi Spark 2.4.5 tina Dockerfile ieu:

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

Gambar anu dihasilkeun tiasa diwangun sareng diunggah kana gudang Docker anjeun anu tos aya, sapertos gudang OKD internal. Pikeun nyebarkeun éta, paké manifest di handap ieu ({registry-url} - URL pendaptaran gambar Docker, {image-name} - Docker image name, {tag} - Docker image tag, {livy-url} - URL anu dipikahoyong dimana server bakal diaksés Livy; nu "Rute" manifest dipaké lamun Red Hat OpenShift dipaké salaku distribusi Kubernetes, disebutkeun dina pakait Ingress atanapi Service manifest tipe NodePort dipaké):

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

Saatos nerapkeun éta sareng suksés ngaluncurkeun pod, antarmuka grafis Livy sayogi dina tautan: http://{livy-url}/ui. Kalayan Livy, urang tiasa nyebarkeun tugas Spark kami nganggo pamundut REST ti, contona, Tukang Pos. Conto koleksi sareng pamenta dipidangkeun di handap (argumen konfigurasi sareng variabel anu dipikabutuh pikeun operasi tugas anu diluncurkeun tiasa disalurkeun dina susunan "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": {}
}

Hayu urang ngaéksekusi pamundut munggaran ti koleksi, buka panganteur OKD jeung pariksa yen tugas geus suksés diluncurkeun - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Dina waktos anu sami, sési bakal muncul dina antarmuka Livy (http://{livy-url}/ui), dimana, nganggo API Livy atanapi antarmuka grafis, anjeun tiasa ngalacak kamajuan tugas sareng diajar sési. log.

Ayeuna hayu urang nunjukkeun kumaha Livy jalan. Jang ngalampahkeun ieu, hayu urang pariksa log wadahna Livy di jero pod sareng server Livy - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=log. Ti aranjeunna urang tiasa ningali yén nalika nyauran Livy REST API dina wadah anu namina "livy", spark-submit dieksekusi, sami sareng anu kami dianggo di luhur (di dieu {livy-pod-name} mangrupikeun nami pod anu diciptakeun. kalawan server Livy). Koléksi ogé ngenalkeun pamundut kadua anu ngamungkinkeun anjeun ngajalankeun tugas anu jarak jauh host Spark executable nganggo server Livy.

Kasus pamakéan katilu - Spark Operator

Ayeuna tugasna parantos diuji, patarosan ngeunaan ngajalankeun éta rutin timbul. Cara asli pikeun rutin ngajalankeun tugas dina klaster Kubernetes nyaéta éntitas CronJob sareng anjeun tiasa nganggo éta, tapi dina waktos ayeuna panggunaan operator pikeun ngatur aplikasi dina Kubernetes populer pisan sareng pikeun Spark aya operator anu cukup dewasa, anu ogé dipaké dina solusi tingkat Perusahaan (contona, Lightbend FastData Platform). Kami ngarékoméndasikeun ngagunakeun éta - versi stabil ayeuna Spark (2.4.5) boga pilihan konfigurasi rada kawates pikeun ngajalankeun tugas Spark di Kubernetes, sedengkeun versi utama salajengna (3.0.0) nyatakeun rojongan pinuh pikeun Kubernetes, tapi tanggal release na tetep kanyahoan. . Spark Operator ngimbangan kakurangan ieu ku nambihan pilihan konfigurasi anu penting (contona, masang ConfigMap sareng konfigurasi aksés Hadoop ka Spark pods) sareng kamampuan pikeun ngajalankeun tugas anu dijadwalkeun rutin.

Ngajalankeun Apache Spark dina Kubernetes
Hayu urang nyorot salaku kasus pamakéan katilu - rutin ngajalankeun tugas Spark dina klaster Kubernetes dina loop produksi.

Operator Spark mangrupikeun sumber terbuka sareng dikembangkeun dina Google Cloud Platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator. Pamasanganna tiasa dilakukeun ku 3 cara:

  1. Salaku bagian tina instalasi Lightbend FastData Platform / Cloudflow;
  2. Ngagunakeun Helm:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Ngagunakeun manifests ti Repository resmi (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Perhatoskeun di handap ieu - Cloudflow kalebet operator kalayan versi API v1beta1. Upami jinis pamasangan ieu dianggo, pedaran manifest aplikasi Spark kedah dumasar kana conto tag dina Git kalayan versi API anu cocog, contona, "v1beta1-0.9.0-2.4.0". Versi operator tiasa dipendakan dina pedaran CRD anu kalebet dina operator dina kamus "versi":
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Upami operator dipasang leres, pod aktif sareng operator Spark bakal muncul dina proyék anu aya (contona, cloudflow-fdp-sparkoperator dina rohangan Cloudflow pikeun pamasangan Cloudflow) sareng jinis sumber daya Kubernetes anu saluyu anu namina "sparkapplications" bakal muncul. . Anjeun tiasa ngajalajah aplikasi Spark anu sayogi nganggo paréntah ieu:

oc get sparkapplications -n {project}

Pikeun ngajalankeun tugas nganggo Spark Operator anjeun kedah ngalakukeun 3 hal:

  • nyiptakeun gambar Docker anu kalebet sadaya perpustakaan anu diperyogikeun, ogé konfigurasi sareng file anu tiasa dieksekusi. Dina gambar udagan, ieu gambar dijieun dina tahap CI / CD sarta diuji dina klaster test;
  • nyebarkeun gambar Docker ka pendaptaran diaksés tina klaster Kubernetes;
  • ngahasilkeun manifest kalayan jenis "SparkApplication" sarta pedaran tugas bakal dibuka. Conto manifes sayogi dina gudang resmi (misalna. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Aya titik penting pikeun dicatet ngeunaan manifesto:
    1. kamus "apiVersion" kedah nunjukkeun versi API anu cocog sareng versi operator;
    2. kamus "metadata.namespace" kedah nunjukkeun nami rohangan dimana aplikasi bakal diluncurkeun;
    3. kamus "spec.image" kedah ngandung alamat gambar Docker anu diciptakeun dina pendaptaran anu tiasa diaksés;
    4. kamus "spec.mainClass" kedah ngandung kelas tugas Spark anu kedah dijalankeun nalika prosésna dimimitian;
    5. kamus "spec.mainApplicationFile" kedah ngandung jalur ka file jar anu tiasa dieksekusi;
    6. kamus "spec.sparkVersion" kedah nunjukkeun versi Spark anu dianggo;
    7. kamus "spec.driver.serviceAccount" kudu nangtukeun akun layanan dina spasi ngaran Kubernetes pakait anu bakal dipaké pikeun ngajalankeun aplikasi;
    8. kamus "spec.executor" kedah nunjukkeun jumlah sumber anu dialokasikeun ka aplikasi;
    9. kamus "spec.volumeMounts" kudu nangtukeun diréktori lokal nu file tugas Spark lokal bakal dijieun.

Conto ngahasilkeun manifes (di dieu {spark-service-account} nyaéta akun layanan dina klaster Kubernetes pikeun ngajalankeun tugas 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"

Manifes ieu netepkeun akun jasa anu, sateuacan nyebarkeun manifest, anjeun kedah nyiptakeun beungkeutan peran anu dipikabutuh anu nyayogikeun hak aksés anu dipikabutuh pikeun aplikasi Spark pikeun berinteraksi sareng API Kubernetes (upami diperyogikeun). Dina kasus urang, aplikasi butuh hak pikeun nyieun Pods. Hayu urang nyieun peran mengikat diperlukeun:

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

Éta ogé sia ​​noting yén spésifikasi manifest ieu bisa ngawengku parameter "hadoopConfigMap", nu ngidinan Anjeun pikeun nangtukeun hiji ConfigMap kalawan konfigurasi Hadoop tanpa kudu nempatkeun heula file pakait dina gambar Docker. Éta ogé cocog pikeun ngajalankeun tugas sacara teratur - nganggo parameter "jadwal", jadwal pikeun ngajalankeun tugas anu dipasihkeun tiasa ditetepkeun.

Sanggeus éta, urang nyimpen manifest urang kana file spark-pi.yaml jeung nerapkeun ka klaster Kubernetes kami:

oc apply -f spark-pi.yaml

Ieu bakal nyiptakeun objék tina jinis "sparkapplications":

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

Dina hal ieu, pod sareng aplikasi bakal didamel, statusna bakal ditingalikeun dina "sparkapplications" anu diciptakeun. Anjeun tiasa ningali éta kalayan paréntah di handap ieu:

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

Saatos parantosan tugas, POD bakal ngalih ka status "Rengse", anu ogé bakal ngamutahirkeun dina "sparkapplications". Log aplikasi tiasa ditingali dina browser atanapi nganggo paréntah di handap ieu (di dieu {sparkapplications-pod-name} mangrupikeun nami pod tina tugas anu dijalankeun):

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

Tugas Spark ogé tiasa diurus nganggo utilitas sparkctl khusus. Pikeun masang éta, clone gudang sareng kode sumberna, pasang Go sareng ngawangun utilitas ieu:

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

Hayu urang pariksa daptar ngajalankeun tugas Spark:

sparkctl list -n {project}

Hayu urang ngadamel pedaran pikeun tugas 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"

Hayu urang ngajalankeun tugas anu dijelaskeun nganggo sparkctl:

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

Hayu urang pariksa daptar ngajalankeun tugas Spark:

sparkctl list -n {project}

Hayu urang pariksa daptar acara tugas Spark dibuka:

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

Hayu urang nalungtik status ngajalankeun tugas Spark:

sparkctl status spark-pi -n {project}

Dina kacindekan, abdi hoyong mertimbangkeun kalemahan kapanggih tina ngagunakeun versi stabil ayeuna Spark (2.4.5) dina Kubernetes:

  1. Kahiji jeung, meureun, disadvantage utama nyaéta kurangna Data Lokalitas. Sanaos sadaya kakurangan BENANG, aya ogé kauntungan pikeun ngagunakeunana, contona, prinsip ngirimkeun kode kana data (tinimbang data kana kode). Hatur nuhun kana éta, tugas Spark dieksekusi dina titik dimana data aub dina itungan ieu lokasina, sarta waktu nu diperlukeun pikeun nganteurkeun data ngaliwatan jaringan ieu nyata ngurangan. Nalika nganggo Kubernetes, urang kedah ngalihkeun data anu aub dina tugas dina jaringan. Upami aranjeunna cukup ageung, waktos palaksanaan tugas tiasa ningkat sacara signifikan, sareng ogé peryogi jumlah rohangan disk anu lumayan ageung pikeun instansi tugas Spark pikeun neundeun samentawisna. disadvantage ieu bisa mitigated ku ngagunakeun software husus nu ensures lokalitas data di Kubernetes (Contona, Alluxio), tapi ieu sabenerna hartina kudu nyimpen salinan lengkep data dina titik tina klaster Kubernetes.
  2. Karugian penting kadua nyaéta kaamanan. Sacara standar, fitur patali kaamanan ngeunaan ngajalankeun tugas Spark ditumpurkeun, pamakéan Kerberos teu katutupan dina dokuméntasi resmi (sanajan pilihan pakait diwanohkeun dina versi 3.0.0, nu merlukeun gawé tambahan), sarta dokuméntasi kaamanan pikeun. ngagunakeun Spark (https://spark.apache.org/docs/2.4.5/security.html) ngan BENANG, Mesos jeung Mandiri Kluster némbongan salaku toko konci. Dina waktos anu sami, pangguna anu ngaluncurkeun tugas Spark henteu tiasa langsung ditetepkeun - kami ngan ukur netepkeun akun jasa anu bakal dianggo, sareng pangguna dipilih dumasar kana kawijakan kaamanan anu dikonpigurasikeun. Dina hal ieu, boh pamaké root dipaké, nu teu aman di lingkungan produktif, atawa pamaké kalawan UID acak, nu teu merenah nalika ngadistribusikaeun hak aksés ka data (ieu bisa direngsekeun ku nyieun PodSecurityPolicies jeung linking aranjeunna ka akun jasa anu cocog). Ayeuna, solusina nyaéta nempatkeun sadaya file anu diperyogikeun langsung kana gambar Docker, atanapi ngarobih skrip peluncuran Spark pikeun ngagunakeun mékanisme pikeun nyimpen sareng nyandak rusiah anu diadopsi dina organisasi anjeun.
  3. Ngajalankeun padamelan Spark nganggo Kubernetes sacara resmi masih dina modeu ékspérimén sareng tiasa aya parobihan anu signifikan dina artefak anu dianggo (file konfigurasi, gambar dasar Docker, sareng skrip peluncuran) ka hareup. Sareng memang, nalika nyiapkeun bahan, versi 2.3.0 sareng 2.4.5 diuji, paripolahna béda sacara signifikan.

Hayu urang ngadagoan apdet - versi anyar Spark (3.0.0) nembe dirilis, nu mawa parobahan signifikan kana karya Spark on Kubernetes, tapi nahan status ékspérimén pangrojong pikeun manajer sumberdaya ieu. Panginten apdet salajengna bakal ngamungkinkeun anjeun pinuh nyarankeun pikeun ngantunkeun BENANG sareng ngajalankeun tugas Spark dina Kubernetes tanpa sieun pikeun kaamanan sistem anjeun sareng tanpa kedah ngarobih komponén fungsional sacara mandiri.

Tungtungna.

sumber: www.habr.com

Tambahkeun komentar