Spuštění Apache Spark na Kubernetes

Vážení čtenáři, dobrý den. Dnes si povíme něco málo o Apache Spark a jeho perspektivách vývoje.

Spuštění Apache Spark na Kubernetes

V moderním světě velkých dat je Apache Spark de facto standardem pro vývoj úloh dávkového zpracování dat. Kromě toho se také používá k vytváření streamovacích aplikací, které pracují v mikrodávkovém konceptu, zpracovávají a nahrávají data po malých částech (Spark Structured Streaming). A tradičně to bylo součástí celkového Hadoop stacku s použitím YARN (nebo v některých případech Apache Mesos) jako správce zdrojů. Do roku 2020 je jeho používání ve své tradiční podobě pro většinu společností velkou otázkou kvůli nedostatku slušných distribucí Hadoop - vývoj HDP a CDH se zastavil, CDH je málo rozvinuté a má vysoké náklady a zbytek Hadoop poskytovatelé buď přestali existovat, nebo mají nejasnou budoucnost. Proto se komunita a velké společnosti stále více zajímají o provozování Apache Spark pomocí Kubernetes – protože se stalo standardem v orchestraci kontejnerů a správě zdrojů v soukromých a veřejných cloudech, řeší problém nepohodlného plánování zdrojů pro úlohy Spark na YARN a poskytuje stabilní vývojová platforma s mnoha komerčními a open source distribucemi pro společnosti všech velikostí. Navíc na vlně popularity se většině z nich již podařilo získat pár vlastních instalací a vybudovat si odborné znalosti v jeho používání, což zjednodušuje přesun.

Počínaje verzí 2.3.0 získal Apache Spark oficiální podporu pro spouštění úloh v clusteru Kubernetes a dnes si povíme něco o aktuální vyspělosti tohoto přístupu, různých možnostech jeho použití a úskalích, na které při implementaci narazí.

Nejprve se podíváme na proces vývoje úloh a aplikací založených na Apache Spark a upozorníme na typické případy, kdy chcete spustit úlohu na clusteru Kubernetes. Při přípravě tohoto příspěvku se jako distribuce používá OpenShift a budou uvedeny příkazy relevantní pro jeho nástroj příkazového řádku (oc). Pro ostatní distribuce Kubernetes lze použít příslušné příkazy standardního nástroje příkazového řádku Kubernetes (kubectl) nebo jejich ekvivalenty (například pro zásady oc adm).

První případ použití je spark-submit

Během vývoje úloh a aplikací musí vývojář spouštět úlohy pro ladění transformace dat. Teoreticky lze pro tyto účely použít pahýly, ale vývoj zahrnující skutečné (i když testovací) instance koncových systémů se v této třídě úloh ukázal jako rychlejší a lepší. V případě, že ladíme na skutečných instancích koncových systémů, jsou možné dva pracovní scénáře:

  • vývojář spustí úlohu Spark lokálně v samostatném režimu;

    Spuštění Apache Spark na Kubernetes

  • vývojář spustí úlohu Spark na clusteru Kubernetes v testovací smyčce.

    Spuštění Apache Spark na Kubernetes

První možnost má právo existovat, ale má řadu nevýhod:

  • pro každého vývojáře je požadován přístup z pracoviště ke všem instancím koncových systémů, které potřebuje;
  • Pro spuštění vyvíjené úlohy jsou na produkčním stroji vyžadovány dostatečné zdroje.

Druhá možnost postrádá tyto nedostatky, protože použití clusteru Kubernetes vám umožňuje alokovat potřebný fond zdrojů pro spouštění úloh a poskytnout mu potřebný přístup k instancím koncových systémů, flexibilně k němu poskytovat přístup pomocí modelu role Kubernetes pro všichni členové vývojového týmu. Vyzdvihněme to jako první případ použití – spouštění úloh Spark z místního vývojářského stroje na clusteru Kubernetes v testovacím okruhu.

Podívejme se blíže na proces nastavení Sparku pro místní spuštění. Chcete-li začít používat Spark, musíte jej nainstalovat:

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

Shromažďujeme potřebné balíčky pro práci s Kubernetes:

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

Úplné sestavení zabere spoustu času a ve skutečnosti jsou k vytvoření obrazů Docker a jejich spuštění v clusteru Kubernetes potřeba pouze jary z adresáře „assembly/“, takže lze sestavit pouze tento dílčí projekt:

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

Spouštění úloh Spark na Kubernetes vyžaduje, abyste vytvořili image Dockeru, který se použije jako základní image. Zde jsou 2 možné přístupy:

  • Vygenerovaný obraz Dockeru obsahuje spustitelný kód úlohy Spark;
  • Vytvořený image obsahuje pouze Spark a potřebné závislosti, spustitelný kód je hostován vzdáleně (například v HDFS).

Nejprve vytvořte obraz Dockeru obsahující testovací případ úlohy Spark. Pro vytváření obrazů Docker má Spark odpovídající nástroj s názvem „docker-image-tool“. Pojďme si to pro pomoc prostudovat:

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

Lze jej použít k vytváření obrázků Docker a jejich nahrávání do vzdálených registrů, ale ve výchozím nastavení má řadu nevýhod:

  • bez selhání vytvoří 3 Docker obrazy najednou - pro Spark, PySpark a R;
  • neumožňuje zadat název obrázku.

Proto použijeme upravenou verzi tohoto nástroje, která je uvedena níže:

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

S jeho pomocí vytvoříme základní obrázek Spark, který obsahuje testovací úlohu pro výpočet čísla Pi pomocí Spark (zde {docker-registry-url} je adresa URL vašeho registru obrázků Docker, {repo} je název úložiště uvnitř registr, který odpovídá projektu v OpenShift , {image-name} — název obrázku (pokud je použito tříúrovňové oddělení obrázků, například jako v integrovaném registru obrázků Red Hat OpenShift), {tag} — tag tohoto obrázku verze):

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

Autorizujte v clusteru OKD pomocí konzolového nástroje (zde {OKD-API-URL} je URL API clusteru OKD):

oc login {OKD-API-URL}

Pojďme získat token aktuálního uživatele pro autorizaci v registru Docker:

oc whoami -t

Autorizovat v interním registru Docker clusteru OKD (jako heslo používáme token získaný předchozím příkazem):

docker login {docker-registry-url}

Nahrajte vytvořený obraz Dockeru do registru Docker OKD:

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

Zkontrolujeme, zda je vytvořený obraz dostupný v OKD. Chcete-li to provést, otevřete v prohlížeči adresu URL se seznamem obrázků odpovídajícího projektu (zde {project} je název projektu v clusteru OpenShift, {OKD-WEBUI-URL} je adresa URL webové konzole OpenShift ) — https://{OKD-WEBUI-URL}/console /project/{project}/browse/images/{image-name}.

Chcete-li spouštět úlohy, musí být vytvořen servisní účet s oprávněními ke spouštění modulů jako root (tento bod probereme později):

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

Spusťte příkaz spark-submit pro publikování úlohy Spark do clusteru OKD s uvedením vytvořeného účtu služby a obrazu 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

Zde:

--name - název úkolu, který se bude podílet na tvorbě názvu podů Kubernetes;

--class — třída spustitelného souboru volaného při spuštění úlohy;

--conf - konfigurační parametry Spark;

spark.executor.instances - počet spouštěcích programů Spark, které se mají spustit

spark.kubernetes.authenticate.driver.serviceAccountName – Název účtu služby Kubernetes používaného při spouštění Pods (k definování kontextu zabezpečení a možností při interakci s Kubernetes API)

spark.kubernetes.namespace – jmenný prostor Kubernetes, ve kterém poběží moduly ovladače a spouštěče;

spark.submit.deployMode - jak spustit Spark (pro standardní spark-submit použijte "cluster", pro Spark Operator a novější verze Spark "client");

spark.kubernetes.container.image je obraz Dockeru používaný ke spuštění modulů Pods.

spark.master – URL rozhraní Kubernetes API (je určeno externí, takže volání probíhá z místního počítače);

local:// je cesta ke spustitelnému souboru Spark uvnitř obrazu Dockeru.

Přejdeme do odpovídajícího projektu OKD a prostudujeme vytvořené pody - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.

Pro zjednodušení procesu vývoje lze použít další možnost, ve které se vytvoří společný základní obraz Spark, který používají všechny úlohy ke spuštění, a snímky spustitelných souborů jsou publikovány na externí úložiště (například Hadoop) a specifikovány při volání sparku. - odeslat jako odkaz. V tomto případě můžete spouštět různé verze úloh Spark, aniž byste museli znovu sestavovat obrazy Dockeru, například pomocí WebHDFS k publikování obrazů. Odešleme požadavek na vytvoření souboru (zde {host} je hostitel služby WebHDFS, {port} je port služby WebHDFS, {cesta-k-souboru-na-hdfs} je požadovaná cesta k souboru na HDFS):

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

Tím se vrátí odpověď formuláře (zde {location} je adresa URL, která se má použít ke stažení souboru):

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

Nahrajte spustitelný soubor Spark na HDFS (kde {cesta-k-lokálnímu-souboru} je cesta ke spustitelnému souboru Spark na aktuálním hostiteli):

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

Poté můžeme spark-submit pomocí souboru Spark nahraného na HDFS (zde {class-name} je název třídy, kterou je třeba spustit, aby byla úloha dokončena):

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

Zároveň je třeba poznamenat, že pro přístup k HDFS a zprovoznění úlohy může být nutné změnit Dockerfile a skript entrypoint.sh – přidat do Dockerfile direktivu pro kopírování závislých knihoven do /opt /spark/jars a zahrňte konfigurační soubor HDFS do SPARK_CLASSPATH ve vstupním bodu.

Druhým případem použití je Apache Livy

Dále, když je úloha vyvinuta a je nutné otestovat získaný výsledek, vyvstává otázka jejího spuštění jako součásti procesu CI/CD a sledování stavu jejího provádění. Samozřejmě jej můžete spustit pomocí místního volání pro spark-submit, ale to komplikuje infrastrukturu CI / CD, protože vyžaduje instalaci a konfiguraci Spark na agentech / běžcích serveru CI a konfiguraci přístupu k Kubernetes API. V tomto případě se cílová implementace rozhodla použít Apache Livy jako REST API pro spouštění úloh Spark hostovaných v clusteru Kubernetes. S ním můžete spouštět úlohy Spark na clusteru Kubernetes pomocí běžných požadavků cURL, což je snadno implementovatelné na základě libovolného řešení CI a jeho umístění uvnitř clusteru Kubernetes řeší problém autentizace při interakci s Kubernetes API.

Spuštění Apache Spark na Kubernetes

Vyzdvihněme to jako druhý případ použití – spouštění úloh Spark jako součást procesu CI / CD na clusteru Kubernetes v testovacím okruhu.

Něco málo o Apache Livy – funguje jako HTTP server, který poskytuje webové rozhraní a RESTful API, které vám umožní vzdáleně spustit spark-submit předáním potřebných parametrů. Tradičně byl dodáván jako součást distribuce HDP, ale lze jej nasadit také do OKD nebo jakékoli jiné instalace Kubernetes pomocí příslušného manifestu a sady obrazů Docker, jako je tento − github.com/ttauveron/k8s-big-data-experiments/tree/master/livy-spark-2.3. Pro náš případ byl vytvořen podobný obraz Docker, včetně Spark verze 2.4.5 z následujícího Dockerfile:

FROM java:8-alpine

ENV SPARK_HOME=/opt/spark
ENV LIVY_HOME=/opt/livy
ENV HADOOP_CONF_DIR=/etc/hadoop/conf
ENV SPARK_USER=spark

WORKDIR /opt

RUN apk add --update openssl wget bash && 
    wget -P /opt https://downloads.apache.org/spark/spark-2.4.5/spark-2.4.5-bin-hadoop2.7.tgz && 
    tar xvzf spark-2.4.5-bin-hadoop2.7.tgz && 
    rm spark-2.4.5-bin-hadoop2.7.tgz && 
    ln -s /opt/spark-2.4.5-bin-hadoop2.7 /opt/spark

RUN wget http://mirror.its.dal.ca/apache/incubator/livy/0.7.0-incubating/apache-livy-0.7.0-incubating-bin.zip && 
    unzip apache-livy-0.7.0-incubating-bin.zip && 
    rm apache-livy-0.7.0-incubating-bin.zip && 
    ln -s /opt/apache-livy-0.7.0-incubating-bin /opt/livy && 
    mkdir /var/log/livy && 
    ln -s /var/log/livy /opt/livy/logs && 
    cp /opt/livy/conf/log4j.properties.template /opt/livy/conf/log4j.properties

ADD livy.conf /opt/livy/conf
ADD spark-defaults.conf /opt/spark/conf/spark-defaults.conf
ADD entrypoint.sh /entrypoint.sh

ENV PATH="/opt/livy/bin:${PATH}"

EXPOSE 8998

ENTRYPOINT ["/entrypoint.sh"]
CMD ["livy-server"]

Vygenerovaný obrázek lze sestavit a nahrát do úložiště Docker, které máte, jako je například interní úložiště OKD. Chcete-li jej nasadit, použijte následující manifest ({registry-url} – adresa URL registru obrázků Docker, {image-name} – název obrázku Docker, {tag} – značka obrázku Docker, {livy-url} – požadovaná adresa URL, kam bude server být k dispozici Livy; manifest "Route" se použije, pokud je Red Hat OpenShift použit jako distribuce Kubernetes, jinak se použije odpovídající manifest Ingress nebo Service typu NodePort):

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

Po jeho použití a úspěšném spuštění modulu je Livy GUI dostupné na: http://{livy-url}/ui. S Livy můžeme publikovat naši úlohu Spark pomocí požadavku REST od Postmana, například. Níže je uveden příklad kolekce s požadavky (argumenty konfigurace s proměnnými nezbytnými pro práci spouštěné úlohy lze předat v poli "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": {}
}

Proveďme první požadavek z kolekce, přejděte do rozhraní OKD a zkontrolujte, zda byla úloha úspěšně spuštěna - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. Zároveň se v rozhraní Livy (http://{livy-url}/ui) objeví relace, v rámci které můžete pomocí Livy API nebo grafického rozhraní sledovat průběh úkolu a studovat protokoly relací.

Nyní si ukážeme, jak Livy funguje. Chcete-li to provést, prozkoumejte protokoly kontejneru Livy uvnitř pod se serverem Livy - https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name }?tab=logs. Z nich můžete vidět, že při volání Livy REST API v kontejneru s názvem „livy“ se provede spark-submit, podobný tomu, který jsme použili výše (zde {livy-pod-name} je název vytvořeného podu se serverem Livy). Kolekce také poskytuje druhý dotaz, který vám umožňuje spouštět úlohy se vzdáleným hostováním spustitelného souboru Spark pomocí serveru Livy.

Třetí případ použití - Spark Operator

Nyní, když je úloha otestována, vyvstává otázka jejího pravidelného spouštění. Nativním způsobem, jak pravidelně spouštět úlohy v clusteru Kubernetes, je entita CronJob a můžete ji používat, ale v současné době je použití operátorů pro správu aplikací v Kubernetes velmi populární a pro Spark existuje poměrně vyzrálý operátor, který mezi jiné věci, se používá v řešeních na podnikové úrovni (například Lightbend FastData Platform). Doporučujeme ji používat - aktuální stabilní verze Spark (2.4.5) má poněkud omezené možnosti konfigurace spouštění úloh Spark v Kubernetes, zatímco další hlavní verze (3.0.0) si nárokuje plnou podporu pro Kubernetes, ale její datum vydání zůstává neznámý. Spark Operator to vynahrazuje přidáním důležitých konfiguračních možností (jako je připojení ConfigMap s konfigurací přístupu Hadoop do Spark Pods) a možností pravidelného spouštění naplánované úlohy.

Spuštění Apache Spark na Kubernetes
Vyjmenujme to jako třetí případ použití – pravidelné spouštění úloh Spark na clusteru Kubernetes v produkční smyčce.

Spark Operator je open source a je vyvinutý v rámci Google Cloud Platform − github.com/GoogleCloudPlatform/spark-on-k8s-operator. Lze jej nainstalovat 3 způsoby:

  1. Jako součást instalace Lightbend FastData Platform/Cloudflow;
  2. S přilbami:
    helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
    helm install incubator/sparkoperator --namespace spark-operator
    	

  3. Pomocí manifestů z oficiálního úložiště (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Zároveň stojí za zmínku následující – Cloudflow zahrnuje operátora s verzí API v1beta1. Pokud se použije tento typ instalace, pak by popisy manifestu aplikace Spark měly být založeny na příkladech ze značek v Gitu s příslušnou verzí API, například "v1beta1-0.9.0-2.4.0". Verzi operátora si můžete prohlédnout v popisu CRD, který je součástí operátoru ve slovníku "verze":
    oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
    	

Pokud je operátor správně nainstalován, pak bude mít odpovídající projekt aktivní Pod s operátorem Spark (například cloudflow-fdp-sparkoperator v prostoru Cloudflow pro instalaci Cloudflow) a objeví se odpovídající typ prostředku Kubernetes s názvem „sparkapplications“. Dostupné aplikace Spark můžete prozkoumat pomocí následujícího příkazu:

oc get sparkapplications -n {project}

Chcete-li spouštět úlohy pomocí operátora Spark, musíte udělat 3 věci:

  • vytvořte obraz Dockeru, který obsahuje všechny potřebné knihovny a také konfigurační a spustitelné soubory. V cílovém obrázku se jedná o obrázek vytvořený ve fázi CI / CD a testovaný na testovacím clusteru;
  • publikovat obraz Dockeru do registru přístupného z clusteru Kubernetes;
  • vygenerujte manifest s typem „SparkApplication“ a popisem úlohy, která má být spuštěna. Ukázkové manifesty jsou k dispozici v oficiálním úložišti (např. github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). Stojí za zmínku důležité body týkající se manifestu:
    1. slovník "apiVersion" musí obsahovat verzi API odpovídající verzi operátora;
    2. slovník "metadata.namespace" musí obsahovat jmenný prostor, ve kterém bude aplikace spuštěna;
    3. slovník "spec.image" by měl obsahovat adresu vytvořeného obrazu Docker v dostupném registru;
    4. slovník "spec.mainClass" musí obsahovat třídu úlohy Spark, kterou chcete spustit při spuštění procesu;
    5. slovník "spec.mainApplicationFile" musí obsahovat cestu ke spustitelnému souboru jar;
    6. slovník "spec.sparkVersion" by měl být použitou verzí Spark;
    7. slovník "spec.driver.serviceAccount" musí obsahovat účet služby v odpovídajícím oboru názvů Kubernetes, který bude použit ke spuštění aplikace;
    8. slovník "spec.executor" by měl udávat množství zdrojů přidělených aplikaci;
    9. slovník "spec.volumeMounts" musí být nastaven na místní adresář, kde budou vytvořeny místní soubory úloh Spark.

Příklad generování manifestu (zde {spark-service-account} je servisní účet v clusteru Kubernetes pro spouštění úloh 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"

Tento manifest specifikuje účet služby, pro který musíte před publikováním manifestu vytvořit nezbytné vazby rolí a poskytnout potřebná přístupová práva pro aplikaci Spark pro interakci s Kubernetes API (v případě potřeby). V našem případě aplikace potřebuje práva k vytvoření Podů. Vytvořme potřebnou vazbu role:

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

Za zmínku také stojí, že parametr „hadoopConfigMap“ může být specifikován ve specifikaci tohoto manifestu, což vám umožňuje zadat ConfigMap s konfigurací Hadoop, aniž byste museli nejprve vkládat odpovídající soubor do obrazu Docker. Je vhodný i pro pravidelné spouštění úloh – pomocí parametru „plán“ lze určit plán spouštění této úlohy.

Poté uložíme náš manifest do souboru spark-pi.yaml a aplikujeme jej na náš cluster Kubernetes:

oc apply -f spark-pi.yaml

Tím se vytvoří objekt typu "sparkapplications":

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

Tím se vytvoří pod s aplikací, jejíž stav se bude zobrazovat ve vytvořených „sparkapplications“. Lze jej zobrazit pomocí následujícího příkazu:

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

Po dokončení úkolu přejde POD do stavu „Completed“, který bude také aktualizován v „sparkapplications“. Protokoly aplikací lze zobrazit v prohlížeči nebo pomocí následujícího příkazu (zde {sparkapplications-pod-name} je název spuštěné úlohy pod):

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

Úlohy Spark lze také spravovat pomocí specializovaného nástroje sparkctl. Chcete-li jej nainstalovat, naklonujeme úložiště s jeho zdrojovým kódem, nainstalujeme Go a vytvoříme tento nástroj:

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

Podívejme se na seznam spuštěných úloh Spark:

sparkctl list -n {project}

Vytvořme popis úlohy 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"

Spusťte popsanou úlohu pomocí sparkctl:

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

Podívejme se na seznam spuštěných úloh Spark:

sparkctl list -n {project}

Podívejme se na seznam událostí spuštěné úlohy Spark:

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

Podívejme se na stav spuštěné úlohy Spark:

sparkctl status spark-pi -n {project}

Na závěr bych rád zvážil objevené nevýhody provozování aktuální stabilní verze Spark (2.4.5) v Kubernetes:

  1. První a možná hlavní nevýhodou je nedostatek datové lokality. Přes všechny nedostatky YARN byly v jeho použití plusy, například princip doručování kódu do dat (spíše než data do kódu). Díky němu byly úkoly Sparku prováděny na uzlech, kde se nacházela data zúčastněná na výpočtech, a čas na doručení dat po síti se znatelně zkrátil. Při používání Kubernetes se potýkáme s potřebou přesouvat po síti data, která se podílejí na práci na úkolu. Jsou-li dostatečně velké, může se doba provádění úlohy výrazně prodloužit a je třeba alokovat dostatečně velké množství místa na disku instancím úloh Spark pro jejich dočasné úložiště. Tuto nevýhodu lze snížit použitím specializovaných softwarových nástrojů, které poskytují datovou lokalitu v Kubernetes (například Alluxio), ale ve skutečnosti to znamená nutnost ukládat kompletní kopii dat na uzly clusteru Kubernetes.
  2. Druhou velkou nevýhodou je bezpečnost. Ve výchozím nastavení jsou funkce související se zabezpečením týkající se spouštění úloh Spark zakázány, použití Kerberos není zahrnuto v oficiální dokumentaci (ačkoli odpovídající možnosti se objevily ve verzi 3.0.0, která bude vyžadovat další práci) a v dokumentaci zabezpečení při použití Spark (https://spark.apache.org/docs/2.4.5/security.html) se jako úložiště klíčů objeví pouze YARN, Mesos a Standalone Cluster. Zároveň nelze přímo specifikovat uživatele, pod kterým jsou úlohy Sparku spouštěny – nastavujeme pouze servisní účet, pod kterým bude pracovat, a uživatel je vybírán na základě nakonfigurovaných bezpečnostních politik. V tomto ohledu je použit buď uživatel root, který není v produktivním prostředí bezpečný, nebo uživatel s náhodným UID, což je nepohodlné při distribuci přístupových práv k datům (rozhoduje se vytvořením PodSecurityPolicies a jejich propojením s odpovídajícími servisními účty) . V tuto chvíli je řešením buď vložení všech potřebných souborů přímo do obrazu Dockeru, nebo úprava spouštěcího skriptu Spark tak, aby používal mechanismus pro ukládání a získávání tajných informací akceptovaných ve vaší organizaci.
  3. Spouštění úloh Spark s Kubernetes je stále oficiálně v experimentálním režimu a v budoucnu může dojít k významným změnám v použitých artefaktech (konfigurační soubory, základní obrázky Dockeru a spouštěcí skripty). A skutečně – při přípravě materiálu se testovaly verze 2.3.0 a 2.4.5, chování bylo výrazně odlišné.

Počkejme si na aktualizace – nedávno byla vydána čerstvá verze Spark (3.0.0), která přinesla hmatatelné změny do práce Sparku na Kubernetes, ale zachovala si experimentální status podpory tohoto správce zdrojů. Snad příští aktualizace skutečně umožní plně doporučit opustit YARN a spouštět úlohy Spark na Kubernetes bez obav o bezpečnost vašeho systému a bez nutnosti sami dolaďovat funkční komponenty.

Ploutev.

Zdroj: www.habr.com

Přidat komentář