KÀra lÀsare, god dag. Idag ska vi prata lite om Apache Spark och dess utvecklingsutsikter.

I dagens Big Data-vĂ€rld Ă€r Apache Spark de facto-standarden för utveckling av uppgifter för batchbehandling av data. Utöver detta anvĂ€nds det ocksĂ„ för att skapa streamingapplikationer, som arbetar i konceptet micro batch och bearbetar och levererar data i smĂ„ portioner (Spark Structured Streaming). Traditionellt har det ocksĂ„ varit en del av den allmĂ€nna Hadoop-stacken och anvĂ€nt YARN som resurshanterare (eller, i vissa fall, Apache Mesos). Fram till 2020 stĂ„r dess anvĂ€ndning i traditionell form för de flesta företag under stort frĂ„getecken pĂ„ grund av bristen pĂ„ anstĂ€ndiga Hadoop-distributioner â utvecklingen av HDP och CDH har stoppats, CDH Ă€r otillrĂ€ckligt utvecklat och har hög kostnad, och andra Hadoop-leverantörer har antingen upphört att existera eller har en osĂ€ker framtid. DĂ€rför vĂ€cker lanseringen av Apache Spark via Kubernetes allt större intresse hos samhĂ€llet och stora företag â med att bli standard inom containerorkestrering och resursförvaltning i privata och offentliga moln löser det problemet med besvĂ€rlig resursplanering för Spark-uppgifter pĂ„ YARN och erbjuder en stabilt utvecklande plattform med mĂ„nga kommersiella och öppna distributioner för företag av alla storlekar och branscher. Dessutom har i takt med populariteten de flesta redan skaffat sig ett par installationer och ökat sin expertis i anvĂ€ndningen, vilket förenklar flytten.
FrÄn och med version 2.3.0 har Apache Spark officiellt stöd för att köra uppgifter i Kubernetes-kluster, och idag ska vi prata om den aktuella mognaden av detta tillvÀgagÄngssÀtt, olika anvÀndningsalternativ och fallgropar som kan uppstÄ vid implementering.
Först och frÀmst ska vi titta pÄ processen för utveckling av uppgifter och applikationer baserade pÄ Apache Spark och identifiera typiska fall dÀr det Àr nödvÀndigt att köra en uppgift i ett Kubernetes-kluster. Förberedelse av detta inlÀgg anvÀnder OpenShift som distribution, och kommandon som Àr aktuella för dess kommandoradsverktyg (oc) kommer att anges. För andra Kubernetes-distributioner kan motsvarande kommandon i det standardiserade kommandoradsverktyget Kubernetes (kubectl) eller deras motsvarigheter (som till exempel för oc adm policy) anvÀndas.
Det första anvÀndningsomrÄdet Àr spark-submit
Under utvecklingen av uppgifter och applikationer behöver utvecklaren köra uppgifter för att felsöka datatransformationer. Teoretiskt kan stubbar anvÀndas för dessa ÀndamÄl, men utveckling med verkliga (Àven om de Àr test) instanser av slutsystem har visat sig vara snabbare och mer effektiv i denna typ av uppgifter. NÀr vi felsöker pÄ verkliga instanser av slutsystemen, Àr det tvÄ möjliga arbetsscenarier:
- utvecklaren kör Spark-jobbet lokalt i standalone-lÀge;

- utvecklaren kör Spark-jobbet pÄ en Kubernetes-kluster i testmiljö.

Det första alternativet Àr giltigt, men medför ett antal nackdelar:
- varje utvecklare behöver sÀkerstÀlla Ätkomst frÄn sin arbetsstation till alla nödvÀndiga instanser av slutsystemen;
- den arbetsmaskin mÄste ha tillrÀckligt med resurser för att köra det utvecklade jobbet.
Det andra alternativet saknar dessa nackdelar, eftersom anvĂ€ndningen av Kubernetes-klustret tillĂ„ter att allokera nödvĂ€ndiga resurser för att köra uppgifterna och sĂ€kra nödvĂ€ndig Ă„tkomst till instanser av slutsystemen, vilket flexibelt ger tillgĂ„ng till dem genom Kubernetes rollmodell för alla medlemmar i utvecklingsteamet. LĂ„t oss lyfta fram det som det första anvĂ€ndningsomrĂ„det â körning av Spark-jobb frĂ„n utvecklarens lokala maskin pĂ„ en Kubernetes-kluster i testmiljö.
LÄt oss berÀtta mer om processen att konfigurera Spark för lokal körning. För att börja anvÀnda Spark behöver det installeras:
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
Vi samlar de nödvÀndiga paketen för att arbeta med Kubernetes:
cd spark-2.4.5/
./build/mvn -Pkubernetes -DskipTests clean package
Den kompletta bygget tar mycket tid, och för att skapa Docker-bilder och köra dem pÄ Kubernetes-klustret behövs egentligen bara jar-filer frÄn katalogen "assembly/", dÀrför kan bara detta delprojekt byggas:
./build/mvn -f ./assembly/pom.xml -Pkubernetes -DskipTests clean package
För att köra Spark-jobb i Kubernetes mÄste en Docker-bild skapas som ska anvÀndas som bas. HÀr finns tvÄ tillvÀgagÄngssÀtt:
- Den skapade Docker-bilden innehÄller den körbara koden för Spark-uppgiften;
- Den skapade bilden innehÄller endast Spark och nödvÀndiga beroenden, den exekverbara koden placeras pÄ en fjÀrrserver (t.ex. i HDFS).
För att börja, ska vi bygga en Docker-bild som innehĂ„ller ett testexempel pĂ„ en Spark-uppgift. För att skapa Docker-bilder har Spark ett verktyg som heter âdocker-image-toolâ. LĂ„t oss kolla hjĂ€lpmenyn för det:
./bin/docker-image-tool.sh --help
Med det kan man skapa Docker-bilder och ladda upp dem till fjÀrrregister, men som standard har den flera nackdelar:
- den skapar alltid 3 Docker-bilder â för Spark, PySpark och R;
- den tillÄter inte att ange bildens namn.
DÀrför kommer vi att anvÀnda en modifierad version av detta verktyg, som visas nedan:
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
Med det samlar vi en grundlĂ€ggande Spark-bild som innehĂ„ller ett testprojekt för att berĂ€kna vĂ€rdet av Pi med hjĂ€lp av Spark (hĂ€r {docker-registry-url} â URL till ditt Docker-register, {repo} â namnet pĂ„ förrĂ„det inom registret, som matchar projektet i OpenShift, {image-name} â namnet pĂ„ bilden (om det anvĂ€nds en tre-nivĂ„indelning av bilder, till exempel i det integrerade registret Red Hat OpenShift), {tag} â taggen för denna bildversion):
./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
Logga in i OKD-klustret med hjĂ€lp av kommandoradsverktyget (hĂ€r {OKD-API-URL} â URL till OKD-klustrets API):
oc login {OKD-API-URL}
Vi fÄr token för den aktuella anvÀndaren för att autentisera mot Docker Registry:
oc whoami -t
Logga in pÄ det interna Docker-registret för OKD-klustret (anvÀnd token som lösenord, som erhölls via föregÄende kommando):
docker login {docker-registry-url}
Ladda upp den skapade Docker-bilden till OKD Docker Registry:
./bin/docker-image-tool-upd.sh -r {docker-registry-url}/{repo} -i {image-name} -t {tag} push
Kontrollera att den skapade bilden Ă€r tillgĂ€nglig i OKD. För att göra detta, öppna URL:en i webblĂ€saren med en lista över bilder för det aktuella projektet (hĂ€r {project} â namnet pĂ„ projektet inom OpenShift-klustret, {OKD-WEBUI-URL} â URL till OpenShift-webbgrĂ€nssnittet) â https://{OKD-WEBUI-URL}/console/project/{project}/browse/images/{image-name}.
För att köra uppgift mÄste ett tjÀnstekonto med rÀttigheter att starta poddar som root skapas (mer om detta kommer vi att diskutera senare):
oc create sa spark -n {project}
oc adm policy add-scc-to-user anyuid -z spark -n {project}
Kör kommandot spark-submit för att publicera Spark-uppgiften i OKD-klustret, ange det skapade tjÀnstekontot och Docker-bilden:
/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
HĂ€r:
--name â uppgiftens namn som kommer att delta i formandet av podnamnen i Kubernetes;
âclass â klass av körbar fil som anropas vid uppstart av uppgiften;
âconf â konfigurationsparametrar för Spark;
spark.executor.instances â antal Spark-executorer som ska köras;
spark.kubernetes.authenticate.driver.serviceAccountName â namnet pĂ„ Kubernetes tjĂ€nstekonto som anvĂ€nds vid uppstart av pods (för att definiera sĂ€kerhetskontext och behörigheter vid interaktion med Kubernetes API);
spark.kubernetes.namespace â Kubernetes-namnrymden dĂ€r drivrutins- och executor-pods kommer att köras;
spark.submit.deployMode â sĂ€ttet att köra Spark (för standard spark-submit anvĂ€nds "cluster", för Spark Operator och senare versioner av Spark "client");
spark.kubernetes.container.image â Docker-image som anvĂ€nds för att köra pods;
spark.master â URL för Kubernetes API (anges externt sĂ„ att Ă„tkomst sker frĂ„n den lokala maskinen);
local:// â sökvĂ€g till Spark-körbar fil inuti Docker-imagen.
Vi gĂ„r till den aktuella OKD-projektet och undersöker de skapade pods â https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods.
För att förenkla utvecklingsprocessen kan en annan variant anvĂ€ndas dĂ€r en gemensam grundlĂ€ggande Spark-image skapas och anvĂ€nds av alla uppgifter för körning, och snapshots av körbara filer publiceras till extern lagring (t.ex. Hadoop) och anges vid anrop av spark-submit som en lĂ€nk. PĂ„ sĂ„ sĂ€tt kan olika versioner av Spark-uppgifter startas utan att behöva bygga om Docker-images, genom att exempelvis anvĂ€nda WebHDFS för publicering. Vi skickar en begĂ€ran om att skapa en fil (hĂ€r {host} â vĂ€rden för WebHDFS-tjĂ€nsten, {port} â porten för WebHDFS-tjĂ€nsten, {path-to-file-on-hdfs} â den önskade sökvĂ€gen till filen pĂ„ HDFS):
curl -i -X PUT "http://{host}:{port}/webhdfs/v1/{path-to-file-on-hdfs}?op=CREATE
Detta ger ett svar av följande slags (hĂ€r {location} â URL:en som ska anvĂ€ndas för att ladda upp filen):
HTTP/1.1 307 TEMPORARY_REDIRECT
Location: {location}
Content-Length: 0
Vi laddar upp den körbara Spark-filen till HDFS (hĂ€r {path-to-local-file} â sökvĂ€gen till den körbara Spark-filen pĂ„ den aktuella vĂ€rden):
curl -i -X PUT -T {path-to-local-file} "{location}"
Efter detta kan vi köra spark-submit med den Spark-fil som laddats upp pĂ„ HDFS (hĂ€r {class-name} â namnet pĂ„ klassen som ska köras för att utföra uppgiften):
/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}
Det bör noteras att för att fĂ„ Ă„tkomst till HDFS och sĂ€kerstĂ€lla att uppgiften fungerar kan det vara nödvĂ€ndigt att Ă€ndra Dockerfile och skriptet entrypoint.sh â lĂ€gga till en direktiv i Dockerfile för att kopiera beroende bibliotek till mappen /opt/spark/jars och inkludera HDFS konfigurationsfilen i SPARK_CLASSPATH i entrypoint.sh.
Det andra anvĂ€ndningsalternativet â Apache Livy
Vidare, nÀr uppgiften har utvecklats och resultatet behöver testas, uppstÄr frÄgan om hur den kan köras inom CI/CD-processen och hur statusen för dess utförande kan spÄras. SjÀlvklart kan den köras med hjÀlp av en lokal Äkallning av spark-submit, men det komplicerar CI/CD-infrastrukturen eftersom det krÀver installation och konfiguration av Spark pÄ CI-serverns agenter/runners och instÀllningar för Ätkomst till Kubernetes API. För detta fall valdes Apache Livy som en REST API-lösning för att köra Spark-jobb, placerad inom Kubernetes-klustret. Med hjÀlp av den kan man köra Spark-jobb i Kubernetes-klustret med vanliga cURL-förfrÄgningar, vilket Àr lÀtt att implementera i valfri CI-lösning, och dess placering inom Kubernetes-klustret löser problem med autentisering vid interaktion med Kubernetes API.

LĂ„t oss förstĂ€rka det som det andra anvĂ€ndningsalternativet â körning av Spark-jobb inom CI/CD-processen pĂ„ Kubernetes-klustret i testmiljön.
Lite om Apache Livy â det fungerar som en HTTP-server som tillhandahĂ„ller ett webbgrĂ€nssnitt och ett RESTful API som möjliggör fjĂ€rrkörning av spark-submit genom att överföra nödvĂ€ndiga parametrar. Traditionellt levererades det som en del av HDP-distributionen, men kan Ă€ven distribueras i OKD eller vilken annan Kubernetes-installation som helst med hjĂ€lp av motsvarande manifest och en uppsĂ€ttning Docker-bilder, till exempel den hĂ€r â . För vĂ„rt fall skapades en liknande Docker-bild som innehĂ„ller Spark version 2.4.5 frĂ„n följande 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"]
Den skapade bilden kan byggas och laddas upp till det Docker-repository du har, till exempel den interna OKD-repositoryn. För att implementera den anvĂ€nds följande manifest ({registry-url} â URL till Docker-bildernas register, {image-name} â namnet pĂ„ Docker-bilden, {tag} â taggen för Docker-bilden, {livy-url} â önskad URL dĂ€r Livy-servern kommer att vara tillgĂ€nglig; manifestet "Route" anvĂ€nds om Red Hat OpenShift anvĂ€nds som Kubernetes-distribution, annars anvĂ€nds motsvarande manifest för Ingress eller tjĂ€nst av typen 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
Efter att ha tillÀmpat det och framgÄngsrikt startat podden Àr Livys grafiska grÀnssnitt tillgÀngligt via lÀnken: http://{livy-url}/ui. Med hjÀlp av Livy kan vi publicera vÄr Spark-uppgift med ett REST-anrop, till exempel frÄn Postman. Exempel pÄ en samling med anrop presenteras nedan (i arrayen "args" kan konfigurationsargument med variabler som behövs för att köra den startade uppgiften skickas):
{
"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 Skicka jobb med 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 Skicka jobb utan 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": {}
}
Vi utför den första förfrĂ„gan frĂ„n samlingen, gĂ„r till OKD-grĂ€nssnittet och kontrollerar att uppgiften har startats framgĂ„ngsrikt â https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods. I Livy-grĂ€nssnittet (http://{livy-url}/ui) kommer en session att dyka upp, inom vilken vi kan övervaka framstegen för uppgiften och studera sessionens loggar antingen via Livy API eller det grafiska grĂ€nssnittet.
Nu ska vi visa hur Livy fungerar. För detta kommer vi att studera loggarna frĂ„n Livy-behĂ„llaren inuti podden med Livy-servern â https://{OKD-WEBUI-URL}/console/project/{project}/browse/pods/{livy-pod-name}?tab=logs. FrĂ„n dessa loggar kan vi se att nĂ€r REST API:et för Livy anropas, körs spark-submit i behĂ„llaren med namnet "livy", vilket liknar det vi anvĂ€nde ovan (hĂ€r Ă€r {livy-pod-name} namnet pĂ„ den skapade podden med Livy-servern). I samlingen finns ocksĂ„ en andra förfrĂ„gan som möjliggör att köra uppgifter med extern placering av Spark-körfilen via Livy-servern.
Tredje anvĂ€ndningsalternativet â Spark Operator
Nu nĂ€r uppgiften har testats, uppstĂ„r frĂ„gan om dess regelbundna körning. Den inbyggda metoden för att köra uppgifter regelbundet i en Kubernetes-kluster Ă€r CronJob-entiteten och vi kan anvĂ€nda den, men för tillfĂ€llet Ă€r det mer populĂ€rt att anvĂ€nda operatörer för att hantera applikationer i Kubernetes, och för Spark finns det en tillrĂ€ckligt mogen operator, som Ă€ven anvĂ€nds i företagslösningar (till exempel Lightbend FastData Platform). Vi rekommenderar att anvĂ€nda den â den nuvarande stabila versionen av Spark (2.4.5) har rĂ€tt begrĂ€nsade alternativ för att konfigurera körning av Spark-uppgifter i Kubernetes, medan nĂ€sta huvudversion (3.0.0) lovar fullt stöd för Kubernetes, men utgĂ„ngsdatumet för denna version Ă€r fortfarande okĂ€nt. Spark Operator kompenserar för denna brist genom att lĂ€gga till viktiga konfigurationsparametrar (till exempel montering av ConfigMap med konfiguration av Ă„tkomst till Hadoop i Spark-poddar) och möjligheten att köra uppgifter efter schema.

Vi framhĂ€ver detta som det tredje anvĂ€ndningsalternativet â regelbunden körning av Spark-uppgifter pĂ„ Kubernetes-klustret i produktionsmiljö.
Spark Operator Ă€r öppen kĂ€llkod och utvecklas inom ramen för Google Cloud Platform â . Det kan installeras pĂ„ tre sĂ€tt:
- Inom installationen av Lightbend FastData Platform/Cloudflow;
- Genom Helm:
helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator helm install incubator/sparkoperator --namespace spark-operator - Genom att anvĂ€nda manifest frĂ„n det officiella arkivet (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). Det Ă€r viktigt att notera följande â Cloudflow inkluderar en operator med API-version v1beta1. Om denna installationsmetod anvĂ€nds, bör beskrivningarna av Spark-applikationernas manifest baseras pĂ„ exempel frĂ„n taggar i Git med motsvarande API-version, till exempel "v1beta1-0.9.0-2.4.0". Versionen av operatorn kan ses i beskrivningen av CRD som ingĂ„r i operatorn i ordboken "versions":
oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
Om operatorn har installerats korrekt, kommer en aktiv pod med Spark-operatorn att dyka upp i den relevanta projektet (till exempel cloudflow-fdp-sparkoperator i Cloudflow-namnomrÄdet för Cloudflow-installationen) och en motsvarande typ av Kubernetes-resurs med namnet "sparkapplications" kommer att finnas. Du kan studera de befintliga Spark-applikationerna med följande kommando:
oc get sparkapplications -n {project}
För att köra uppgifter med hjÀlp av Spark-operatorn behöver man göra tre saker:
- skapa en Docker-bild som inkluderar alla nödvÀndiga bibliotek, samt konfigurations- och körbara filer. I det mÄlbilden handlar det om en bild som skapats under CI/CD-steget och testats pÄ en testkluster;
- publicera Docker-bilden i ett register som Àr tillgÀngligt frÄn Kubernetes-klustret;
- skapa ett manifest av typen "SparkApplication" och en beskrivning av den uppgift som ska köras. Exempel pÄ manifester finns i det officiella arkivet (till exempel, ). Det Àr viktigt att notera viktiga punkter angÄende manifestet:
- i ordboken "apiVersion" bör den API-version som motsvarar operatorns version anges;
- i ordboken "metadata.namespace" bör det anges vilket namnomrÄde som applikationen ska köras i;
- i ordboken "spec.image" ska adressen till den skapade Docker-bilden i det tillgÀngliga registret anges;
- i ordboken "spec.mainClass" ska namnet pÄ den Spark-klass som ska köras vid starten av processen anges;
- i ordboken "spec.mainApplicationFile" ska sökvÀgen till den körbara jar-filen anges;
- i ordboken "spec.sparkVersion" bör den anvÀnda versionen av Spark anges;
- i ordboken "spec.driver.serviceAccount" bör den tjÀnstekonto som anvÀnds för att köra applikationen inom det relevanta namnomrÄdet Kubernetes anges;
- i ordboken "spec.executor" bör antalet resurser som tilldelas applikationen anges;
- I ordboken «spec.volumeMounts» mÄste anges en lokal katalog dÀr de lokala filerna för Spark-uppgiften kommer att skapas.
Exempel pÄ hur man skapar en manifest (hÀr Àr {spark-service-account} servicekontot inom Kubernetes-klustret för att köra Spark-uppgifter):
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"
I det hÀr manifestet anges ett servicekonto för vilket det krÀvs att nödvÀndiga rollbindningar skapas innan manifestet publiceras, som ger de nödvÀndiga rÀttigheterna för att interagera med Kubernetes API (om det behövs). I vÄrt fall behöver applikationen rÀttigheter för att skapa Pods. LÄt oss skapa den nödvÀndiga rollbindningen:
oc adm policy add-role-to-user edit system:serviceaccount:{project}:{spark-service-account} -n {project}
Det bör ocksĂ„ noteras att parameter «hadoopConfigMap» kan anges i specifikationen för detta manifest, vilket tillĂ„ter att ange en ConfigMap med Hadoop-konfiguration utan att behöva lĂ€gga den motsvarande filen i Docker-avbilden i förvĂ€g. Den passar ocksĂ„ för att köra uppgifter regelbundet â med hjĂ€lp av parametern «schedule» kan körschema för denna uppgift anges.
Efter detta sparar vi vÄrt manifest i filen spark-pi.yaml och tillÀmpar det pÄ vÄrt Kubernetes-kluster:
oc apply -f spark-pi.yaml
DÄ skapas ett objekt av typen «sparkapplications»:
oc get sparkapplications -n {project}
> NAME AGE
> spark-pi 22h
DÄ skapas en pod med applikationen, vars status kommer att visas i den skapade «sparkapplications». Det kan ses med följande kommando:
oc get sparkapplications spark-pi -o yaml -n {project}
NÀr uppgiften Àr klar kommer POD att Àndra status till «Completed», vilket ocksÄ kommer att uppdateras i «sparkapplications». Loggar frÄn applikationen kan ses i webblÀsaren eller med följande kommando (hÀr Àr {sparkapplications-pod-name} namnet pÄ podden för den körda uppgiften):
oc logs {sparkapplications-pod-name} -n {project}
Förvaltning av Spark-uppgifter kan ocksÄ göras med hjÀlp av det specialiserade verktyget sparkctl. För att installera det klonar vi repot med dess kÀllkod, installerar Go och bygger detta verktyg:
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
LÄt oss granska listan över aktiva Spark-uppgifter:
sparkctl list -n {project}
Skapa en beskrivning för Spark-uppgiften:
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"
LÄt oss köra den beskrivna uppgiften med hjÀlp av sparkctl:
sparkctl create spark-app.yaml -n {project}
LÄt oss granska listan över aktiva Spark-uppgifter:
sparkctl list -n {project}
LÄt oss granska listan över hÀndelser för den aktiva Spark-uppgiften:
sparkctl event spark-pi -n {project} -f
LÄt oss granska statusen för den aktiva Spark-uppgiften:
sparkctl status spark-pi -n {project}
Avslutningsvis vill vi diskutera de nackdelar som identifierats vid anvÀndningen av den nuvarande stabila versionen av Spark (2.4.5) i Kubernetes:
- Den första och kanske största nackdelen Àr bristen pÄ data-lokalitet. Trots alla nackdelar med YARN fanns det ocksÄ fördelar med dess anvÀndning, till exempel principen att leverera kod till data (snarare Àn data till kod). Tack vare detta utfördes Spark-uppgifter pÄ noder dÀr datan som var involverad i berÀkningarna fanns, vilket tydligt minskade tiden för dataleverans över nÀtverket. NÀr vi anvÀnder Kubernetes möter vi behovet av att flytta data över nÀtverket som Àr involverat i uppgiften. Om de Àr tillrÀckligt stora kan tiden för att slutföra uppgiften öka avsevÀrt, och det kan ocksÄ krÀvas en betydande mÀngd lagringsutrymme av de Spark-instans som tilldelats för temporÀr lagring. Denna nackdel kan minskas genom anvÀndning av specialiserade programvaror som sÀkerstÀller datalokalitet i Kubernetes (till exempel Alluxio), men detta innebÀr faktiskt att en fullstÀndig kopia av datan behöver lagras pÄ noderna i Kubernetes-klustret.
- Den andra viktiga nackdelen Ă€r sĂ€kerheten. Som standard Ă€r funktionerna relaterade till sĂ€kerhet vid körning av Spark-uppgifter inaktiverade; anvĂ€ndningen av Kerberos nĂ€mns inte i den officiella dokumentationen (Ă€ven om motsvarande parametrar introducerades i version 3.0.0, vilket krĂ€ver ytterligare utredning), och i dokumentationen för sĂ€kerhet vid anvĂ€ndning av Spark (https://spark.apache.org/docs/2.4.5/security.html) nĂ€mns endast YARN, Mesos och Standalone Cluster som nyckel-lagringssysten. Dessutom kan anvĂ€ndaren som kör Spark-uppgifterna inte anges direkt â vi anger endast ett tjĂ€nstekonto under vilket podden kommer att köras, och anvĂ€ndaren vĂ€ljs utifrĂ„n de angivna sĂ€kerhetspolicyn. Detta innebĂ€r antingen att anvĂ€ndaren root anvĂ€nds, vilket inte Ă€r sĂ€kert i en produktionsmiljö, eller att en anvĂ€ndare med en slumpmĂ€ssig UID anvĂ€nds, vilket Ă€r besvĂ€rligt vid distribution av tillgĂ„ngsbehörigheter till data (detta kan lösas genom att skapa PodSecurityPolicies och koppla dem till motsvarande tjĂ€nstekonton). För nĂ€rvarande löses detta antingen genom att placera alla nödvĂ€ndiga filer direkt i Docker-bilden eller genom att modifiera Spark-startskriptet för att anvĂ€nda den mekanism för lagring och hĂ€mtning av hemligheter som accepteras i din organisation.
- Att köra Spark-jobb med Kubernetes Àr fortfarande officiellt i experimentellt lÀge och i framtiden kan det bli betydande förÀndringar i de anvÀnda artefakterna (konfigurationsfiler, grundlÀggande Docker-bilder och start-skript). Under förberedelserna testades versionerna 2.3.0 och 2.4.5, och beteendet skilde sig avsevÀrt.
Vi ser fram emot uppdateringar â nyligen slĂ€pptes en ny version av Spark (3.0.0), som medförde mĂ€rkbara förĂ€ndringar i hur Spark fungerar pĂ„ Kubernetes, men som fortfarande har ett experimentellt stöd för denna resursförvaltare. Kanske kommer de kommande uppdateringarna verkligen att möjliggöra att man pĂ„ fullt allvar kan avstĂ„ frĂ„n YARN och köra Spark-jobb pĂ„ Kubernetes utan att oroa sig för sĂ€kerheten i ditt system och utan behov av att göra egna modifieringar av funktionella komponenter.
Fin.
KĂ€lla: habr.com


