Dans le monde moderne du Big Data, Apache Spark est le standard de facto pour le dĂ©veloppement de tĂąches de traitement par lots. En outre, il est Ă©galement utilisĂ© pour crĂ©er des applications de streaming fonctionnant dans le concept de micro-lots, traitant et envoyant des donnĂ©es par petites portions (Spark Structured Streaming). Et traditionnellement, il faisait partie de la pile globale Hadoop, utilisant YARN (ou, dans certains cas, Apache Mesos) comme gestionnaire de ressources. D'ici 2020, son utilisation traditionnelle pour la plupart des entreprises est sous une grande question en raison du manque de distributions Hadoop dĂ©centes - le dĂ©veloppement de HDP et CDH a cessĂ©, CDH est sous-dĂ©veloppĂ© et a un coĂ»t Ă©levĂ©, et le reste des fournisseurs Hadoop ont cessĂ© d'exister ou ont un avenir vague.Par consĂ©quent, l'intĂ©rĂȘt croissant de la communautĂ© et des grandes entreprises est le lancement d'Apache Spark utilisant Kubernetes - devenu la norme dans l'orchestration de conteneurs et la gestion des ressources dans les clouds privĂ©s et publics, il rĂ©sout le problĂšme de la planification des ressources incommode des tĂąches Spark sur YARN et fournit une plate-forme en dĂ©veloppement constant avec de nombreuses et des distributions open source pour les entreprises de toutes tailles et de toutes tailles. De plus, sur la vague de popularitĂ©, la plupart ont dĂ©jĂ rĂ©ussi Ă acquĂ©rir quelques-unes de leurs installations et Ă accroĂźtre leur expertise dans leur utilisation, ce qui simplifie le dĂ©mĂ©nagement.il rĂ©sout la planification dĂ©licate des tĂąches Spark sur YARN et fournit une plate-forme en constante Ă©volution avec de nombreuses distributions commerciales et open source pour les entreprises de toutes tailles et de toutes catĂ©gories. De plus, sur la vague de popularitĂ©, la plupart ont dĂ©jĂ rĂ©ussi Ă acquĂ©rir quelques-unes de leurs installations et Ă accroĂźtre leur expertise dans leur utilisation, ce qui simplifie le dĂ©mĂ©nagement.il rĂ©sout la planification dĂ©licate des tĂąches Spark sur YARN et fournit une plate-forme en constante Ă©volution avec de nombreuses distributions commerciales et open source pour les entreprises de toutes tailles et de toutes catĂ©gories. De plus, sur la vague de popularitĂ©, la plupart ont dĂ©jĂ rĂ©ussi Ă acquĂ©rir quelques-unes de leurs installations et Ă accroĂźtre leur expertise dans leur utilisation, ce qui simplifie le dĂ©mĂ©nagement.
Ă partir de la version 2.3.0, Apache Spark a acquis un support officiel pour l'exĂ©cution de tĂąches dans le cluster Kubernetes, et aujourd'hui, nous parlerons de la maturitĂ© actuelle de cette approche, des diffĂ©rents cas d'utilisation et des piĂšges qui seront rencontrĂ©s lors de la mise en Ćuvre.
Tout d'abord, nous examinerons le processus de dĂ©veloppement de tĂąches et d'applications basĂ©es sur Apache Spark et mettrons en Ă©vidence les cas typiques dans lesquels vous devez exĂ©cuter une tĂąche sur un cluster Kubernetes. Lors de la prĂ©paration de cet article, OpenShift est utilisĂ© comme un kit de distribution et les commandes pertinentes pour son utilitaire de ligne de commande (oc) seront donnĂ©es. Pour les autres distributions Kubernetes, les commandes correspondantes de l'utilitaire de ligne de commande Kubernetes standard (kubectl) ou leurs analogues (par exemple, pour oc adm policy) peuvent ĂȘtre utilisĂ©es.
Le premier cas d'utilisation est Spark-submit
Dans le processus de dĂ©veloppement de tĂąches et d'applications, le dĂ©veloppeur doit exĂ©cuter des tĂąches pour dĂ©boguer la transformation des donnĂ©es. ThĂ©oriquement, les stubs peuvent ĂȘtre utilisĂ©s Ă ces fins, mais le dĂ©veloppement avec la participation de copies rĂ©elles (bien que testĂ©es) de systĂšmes finis s'est montrĂ© plus rapide et mieux dans cette classe de tĂąches. Dans le cas oĂč l'on dĂ©bogue sur des instances rĂ©elles de systĂšmes d'extrĂ©mitĂ©, deux scĂ©narios sont possibles:
- le développeur exécute la tùche Spark localement en mode autonome;
- un développeur exécute une tùche Spark sur un cluster Kubernetes dans une boucle de test.
La premiÚre option a le droit d'exister, mais comporte un certain nombre d'inconvénients:
- pour chaque développeur, il est nécessaire de donner accÚs depuis le lieu de travail à toutes les copies des systÚmes terminaux dont il a besoin;
- la machine de travail nécessite des ressources suffisantes pour exécuter la tùche développée.
La deuxiÚme option est dépourvue de ces inconvénients, car l'utilisation d'un cluster Kubernetes vous permet d'allouer le pool de ressources nécessaire pour exécuter des tùches et de lui fournir l'accÚs nécessaire aux instances des systÚmes finaux, en lui fournissant de maniÚre flexible un accÚs à l'aide du modÚle de rÎle Kubernetes pour tous les membres de l'équipe de développement. Soulignons-le comme premier cas d'utilisation: exécuter des tùches Spark à partir d'une machine de développement locale sur un cluster Kubernetes dans une boucle de test.
Examinons de plus prÚs le processus de configuration de Spark pour qu'il s'exécute localement. Pour commencer à utiliser Spark, vous devez l'installer:
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
Nous collectons les packages nécessaires pour travailler avec Kubernetes:
cd spark-2.4.5/
./build/mvn -Pkubernetes -DskipTests clean package
La compilation complÚte prend beaucoup de temps, et pour créer des images Docker et les exécuter sur le cluster Kubernetes, en réalité, vous n'avez besoin que des fichiers jar du répertoire "assembly /", vous ne pouvez donc créer que ce sous-projet:
./build/mvn -f ./assembly/pom.xml -Pkubernetes -DskipTests clean package
Pour exécuter des tùches Spark dans Kubernetes, vous devez créer une image Docker qui servira d'image de base. 2 approches sont possibles ici:
- L'image Docker générée comprend le code exécutable de la tùche Spark;
- L'image créée comprend uniquement Spark et les dépendances nécessaires, le code exécutable est hébergé à distance (par exemple, en HDFS).
Tout d'abord, construisons une image Docker contenant un exemple de test d'une tĂąche Spark. Pour crĂ©er des images Docker, Spark dispose d'un utilitaire appelĂ© "docker-image-tool". Ătudions l'aide Ă ce sujet:
./bin/docker-image-tool.sh --help
Il peut ĂȘtre utilisĂ© pour crĂ©er des images Docker et les tĂ©lĂ©charger vers des registres distants, mais par dĂ©faut, il prĂ©sente plusieurs inconvĂ©nients:
- sans faute crée 3 images Docker à la fois - pour Spark, PySpark et R;
- ne vous permet pas de spécifier le nom de l'image.
Par conséquent, nous utiliserons une version modifiée de cet utilitaire, illustrée ci-dessous:
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
En l'utilisant, nous construisons une image Spark de base contenant une tùche de test pour calculer le nombre Pi à l'aide de Spark (ici {docker-registry-url} est l'URL de votre registre d'images Docker, {repo} est le nom du référentiel dans le registre, qui coïncide avec le projet dans OpenShift , {image-name} est le nom de l'image (si la séparation d'image à trois niveaux est utilisée, par exemple, comme dans le registre d'images intégré Red Hat OpenShift), {tag} est la balise de cette version de l'image):
./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
Connectez-vous au cluster OKD Ă l'aide de l'utilitaire de console (ici {OKD-API-URL} est l'URL de l'API du cluster OKD):
oc login {OKD-API-URL}
Récupérons le jeton de l'utilisateur actuel pour l'autorisation dans le registre Docker:
oc whoami -t
Connectez-vous au registre Docker interne du cluster OKD (utilisez le jeton obtenu avec la commande précédente comme mot de passe):
docker login {docker-registry-url}
Téléchargez l'image Docker intégrée dans le registre Docker OKD:
./bin/docker-image-tool-upd.sh -r {docker-registry-url}/{repo} -i {image-name} -t {tag} push
Vérifions que l'image construite est disponible dans OKD. Pour ce faire, ouvrez une URL avec une liste d'images du projet correspondant dans le navigateur (ici {project} est le nom du projet à l'intérieur du cluster OpenShift, {OKD-WEBUI-URL} est l'URL de la console Web OpenShift) - https: // {OKD-WEBUI-URL} / console / project / {projet} / parcourir / images / {image-name}.
Pour exĂ©cuter des tĂąches, un compte de service doit ĂȘtre crĂ©Ă© avec les privilĂšges d'exĂ©cuter des pods en tant que root (nous aborderons ce point plus tard):
oc create sa spark -n {project}
oc adm policy add-scc-to-user anyuid -z spark -n {project}
Exécutez la commande spark-submit pour publier la tùche Spark sur le cluster OKD, en spécifiant le compte de service créé et l'image 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
Ici:
--name est le nom de la tĂąche qui participera Ă la formation du nom des pods Kubernetes;
--class - la classe du fichier exécutable appelé lorsque la tùche démarre;
--conf - ParamĂštres de configuration Spark;
spark.executor.instances Le nombre d'exécuteurs Spark à exécuter.
spark.kubernetes.authenticate.driver.serviceAccountName - Le nom du compte de service Kubernetes utilisé lors du lancement des pods (pour définir le contexte de sécurité et les capacités lors de l'interaction avec l'API Kubernetes);
spark.kubernetes.namespace - Espace de noms Kubernetes dans lequel les pods pilote et exécuteur s'exécuteront;
spark.submit.deployMode - Méthode de lancement de Spark ("cluster" est utilisé pour la soumission standard de Spark, "client" pour Spark Operator et les versions ultérieures de Spark);
spark.kubernetes.container.image L'image Docker utilisée pour exécuter les pods.
spark.master - URL de l'API Kubernetes (l'externe est spécifié pour que l'appel se produise à partir de la machine locale);
local: // est le chemin d'accÚs à l'exécutable Spark dans l'image Docker.
Accédez au projet OKD correspondant et étudiez les pods créés - https: // {OKD-WEBUI-URL} / console / project / {project} / parcourir / pods.
Pour simplifier le processus de dĂ©veloppement, une autre option peut ĂȘtre utilisĂ©e, dans laquelle une image Spark de base commune est crĂ©Ă©e, qui est utilisĂ©e par toutes les tĂąches Ă exĂ©cuter, et des instantanĂ©s des fichiers exĂ©cutables sont publiĂ©s sur un stockage externe (par exemple, Hadoop) et spĂ©cifiĂ©s lors de l'appel de spark-submit en tant que lien. Dans ce cas, vous pouvez exĂ©cuter diffĂ©rentes versions de tĂąches Spark sans reconstruire les images Docker, en utilisant par exemple WebHDFS pour publier des images. Nous envoyons une requĂȘte pour crĂ©er un fichier (ici {host} est l'hĂŽte du service WebHDFS, {port} est le port du service WebHDFS, {path-to-file-on-hdfs} est le chemin souhaitĂ© vers le fichier sur HDFS):
curl -i -X PUT "http://{host}:{port}/webhdfs/v1/{path-to-file-on-hdfs}?op=CREATE
Celui-ci recevra une rĂ©ponse du formulaire (ici {location} est l'URL qui doit ĂȘtre utilisĂ©e pour tĂ©lĂ©charger le fichier):
HTTP/1.1 307 TEMPORARY_REDIRECT
Location: {location}
Content-Length: 0
Chargez le fichier exécutable Spark dans HDFS (ici {path-to-local-file} est le chemin d'accÚs à l'exécutable Spark sur l'hÎte actuel):
curl -i -X PUT -T {path-to-local-file} "{location}"
AprĂšs cela, nous pouvons effectuer une soumission spark Ă l'aide du fichier Spark tĂ©lĂ©chargĂ© sur HDFS (ici {class-name} est le nom de la classe qui doit ĂȘtre lancĂ©e pour terminer la tĂąche):
/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}
Dans le mĂȘme temps, il convient de noter que pour accĂ©der Ă HDFS et permettre Ă la tĂąche de fonctionner, vous devrez peut-ĂȘtre modifier le Dockerfile et le script entrypoint.sh - ajoutez une directive au Dockerfile pour copier les bibliothĂšques dĂ©pendantes dans le rĂ©pertoire / opt / spark / jars et inclure le fichier de configuration HDFS dans SPARK_CLASSPATH dans le point d'entrĂ©e. sh.
DeuxiĂšme cas d'utilisation - Apache Livy
De plus, lorsque la tĂąche est dĂ©veloppĂ©e et qu'il est nĂ©cessaire de tester le rĂ©sultat obtenu, la question se pose de la lancer dans le processus CI / CD et de suivre l'Ă©tat de son exĂ©cution. Bien sĂ»r, vous pouvez l'exĂ©cuter Ă l'aide d'un appel de soumission Spark local, mais cela complique l'infrastructure CI / CD car il nĂ©cessite l'installation et la configuration de Spark sur les agents / exĂ©cuteurs du serveur CI et la configuration de l'accĂšs Ă l'API Kubernetes. Dans ce cas, l'implĂ©mentation cible a choisi d'utiliser Apache Livy comme API REST pour exĂ©cuter des tĂąches Spark hĂ©bergĂ©es dans le cluster Kubernetes. Il peut ĂȘtre utilisĂ© pour lancer des tĂąches Spark sur le cluster Kubernetes Ă l'aide de requĂȘtes cURL rĂ©guliĂšres, qui sont facilement implĂ©mentĂ©es en fonction de n'importe quelle solution CI, et son placement dans le cluster Kubernetes rĂ©sout le problĂšme d'authentification lors de l'interaction avec l'API Kubernetes.
Soulignons-le comme deuxiÚme cas d'utilisation: exécuter des tùches Spark dans le cadre du processus CI / CD sur un cluster Kubernetes dans une boucle de test.
Un peu sur Apache Livy - il fonctionne comme un serveur HTTP qui fournit une interface Web et une API RESTful qui vous permet d'exĂ©cuter Ă distance spark-submit en passant les paramĂštres nĂ©cessaires. Traditionnellement, il Ă©tait livrĂ© dans le cadre de la distribution HDP, mais il peut Ă©galement ĂȘtre dĂ©ployĂ© sur OKD ou toute autre installation Kubernetes en utilisant le manifeste appropriĂ© et un ensemble d'images Docker, comme celui-ci - github.com/ttauveron/k8s-big-data-experiments/tree/master /livy-spark-2.3 . Dans notre cas, une image Docker similaire a Ă©tĂ© crĂ©Ă©e, y compris Spark version 2.4.5 Ă partir du Dockerfile suivant:
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"]
L'image gĂ©nĂ©rĂ©e peut ĂȘtre crĂ©Ă©e et tĂ©lĂ©chargĂ©e dans votre rĂ©fĂ©rentiel Docker existant, par exemple le rĂ©fĂ©rentiel OKD interne. Pour le dĂ©ployer, le manifeste suivant est utilisĂ© ({registry-url} est l'URL du registre d'images Docker, {image-name} est le nom de l'image Docker, {tag} est la balise de l'image Docker, {livy-url} est l'URL souhaitĂ©e oĂč le serveur sera accessible. Livy; le manifeste "Route" est utilisĂ© si Red Hat OpenShift est utilisĂ© comme distribution Kubernetes, sinon le manifeste Ingress ou Service correspondant de type NodePort est utilisĂ©):
---
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
AprĂšs son application et le lancement rĂ©ussi du pod, l'interface graphique de Livy est disponible sur le lien: http: // {livy-url} / ui. Avec Livy, nous pouvons publier notre tĂąche Spark Ă l'aide d'une requĂȘte REST, par exemple de Postman. Un exemple de collection avec des requĂȘtes est prĂ©sentĂ© ci-dessous (dans le tableau "args", les arguments de configuration avec les variables nĂ©cessaires au fonctionnement de la tĂąche en cours peuvent ĂȘtre passĂ©s):
{
"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": "{\n\t\"file\": \"local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.4.5.jar\", \n\t\"className\": \"org.apache.spark.examples.SparkPi\",\n\t\"numExecutors\":1,\n\t\"name\": \"spark-test-1\",\n\t\"conf\": {\n\t\t\"spark.jars.ivy\": \"/tmp/.ivy\",\n\t\t\"spark.kubernetes.authenticate.driver.serviceAccountName\": \"spark\",\n\t\t\"spark.kubernetes.namespace\": \"{project}\",\n\t\t\"spark.kubernetes.container.image\": \"{docker-registry-url}/{repo}/{image-name}:{tag}\"\n\t}\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": "{\n\t\"file\": \"hdfs://{host}:{port}/{path-to-file-on-hdfs}\", \n\t\"className\": \"{class-name}\",\n\t\"numExecutors\":1,\n\t\"name\": \"spark-test-2\",\n\t\"proxyUser\": \"0\",\n\t\"conf\": {\n\t\t\"spark.jars.ivy\": \"/tmp/.ivy\",\n\t\t\"spark.kubernetes.authenticate.driver.serviceAccountName\": \"spark\",\n\t\t\"spark.kubernetes.namespace\": \"{project}\",\n\t\t\"spark.kubernetes.container.image\": \"{docker-registry-url}/{repo}/{image-name}:{tag}\"\n\t},\n\t\"args\": [\n\t\t\"HADOOP_CONF_DIR=/opt/spark/hadoop-conf\",\n\t\t\"MASTER=k8s://https://kubernetes.default.svc:8443\"\n\t]\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": {}
}
ExĂ©cutons la premiĂšre requĂȘte de la collection, allons dans l'interface OKD et vĂ©rifions que la tĂąche a Ă©tĂ© lancĂ©e avec succĂšs - https: // {OKD-WEBUI-URL} / console / project / {project} / parcourir / pods. Dans ce cas, une session apparaĂźtra dans l'interface Livy (http: // {livy-url} / ui), dans laquelle, Ă l'aide de l'API Livy ou d'une interface graphique, vous pourrez suivre la progression de la tĂąche et Ă©tudier les journaux de session.
Voyons maintenant comment fonctionne Livy. Pour ce faire, examinons les journaux du conteneur Livy Ă l'intĂ©rieur du pod avec le serveur Livy - https: // {OKD-WEBUI-URL} / console / project / {projet} / parcourir / pods / {livy-pod-name}? Tab = logs. Ă partir d'eux, vous pouvez voir que lorsque vous appelez l'API Livy REST dans un conteneur nommĂ© "livy", un spark-submit est exĂ©cutĂ©, similaire Ă celui que nous avons utilisĂ© ci-dessus (ici {livy-pod-name} est le nom du pod crĂ©Ă© avec le serveur Livy). La collection fournit Ă©galement une deuxiĂšme requĂȘte qui vous permet d'exĂ©cuter des tĂąches avec l'hĂ©bergement Ă distance de l'exĂ©cutable Spark Ă l'aide du serveur Livy.
TroisiĂšme cas d'utilisation - Spark Operator
Maintenant que la tùche a été testée, la question se pose de l'exécuter réguliÚrement. Le moyen natif d'exécuter réguliÚrement des tùches dans le cluster Kubernetes est l'entité CronJob et vous pouvez l'utiliser, mais pour le moment, l'utilisation d'opérateurs pour contrÎler les applications dans Kubernetes est trÚs populaire, et pour Spark, il existe un opérateur assez mature, qui, entre autres, est utilisé dans les solutions de niveau entreprise. (par exemple, la plate-forme Lightbend FastData). Nous vous recommandons de l'utiliser - la version stable actuelle de Spark (2.4.5) a des options assez limitées pour configurer le lancement des tùches Spark dans Kubernetes, tandis que dans la prochaine version majeure (3.0.0), le support complet de Kubernetes est annoncé, mais sa date de sortie reste inconnue. Spark Operator compense cette lacune en ajoutant des paramÚtres de configuration importants (par exemple,montage de ConfigMap avec la configuration de l'accÚs à Hadoop dans les pods Spark) et la possibilité d'exécuter réguliÚrement la tùche selon un planning.
Soulignons-le comme troisiÚme cas d'utilisation: exécuter réguliÚrement des tùches Spark sur un cluster Kubernetes dans une boucle de production.
Spark Operator est open source et développé dans le cadre de Google Cloud Platform - github.com/GoogleCloudPlatform/spark-on-k8s-operator . Son installation peut se faire de 3 maniÚres:
- Dans le cadre de l'installation de Lightbend FastData Platform / Cloudflow;
- Avec Helm:
helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator helm install incubator/sparkoperator --namespace spark-operator
- (https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/tree/master/manifest). â Cloudflow API v1beta1. , Spark Git API, , «v1beta1-0.9.0-2.4.0». CRD, «versions»:
oc get crd sparkapplications.sparkoperator.k8s.io -o yaml
Si l'opérateur est correctement défini, un pod actif avec l'opérateur Spark (par exemple, cloudflow-fdp-sparkoperator dans l'espace Cloudflow pour l'installation de Cloudflow) apparaßtra dans le projet correspondant et le type de ressource Kubernetes correspondant nommé "sparkapplications" apparaßtra. Vous pouvez examiner les applications Spark disponibles avec la commande suivante:
oc get sparkapplications -n {project}
Pour exécuter des tùches avec Spark Operator, vous devez effectuer 3 choses:
- créer une image Docker qui comprend toutes les bibliothÚques requises, ainsi que les fichiers de configuration et exécutables. Dans l'image cible, il s'agit d'une image créée au stade CI / CD et testée sur un cluster de test;
- publier l'image Docker dans le registre accessible depuis le cluster Kubernetes;
- «SparkApplication» . (, github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/v1beta1-0.9.0-2.4.0/examples/spark-pi.yaml). :
- «apiVersion» API, ;
- «metadata.namespace» , ;
- «spec.image» Docker ;
- «spec.mainClass» Spark, ;
- «spec.mainApplicationFile» jar ;
- le dictionnaire "spec.sparkVersion" doit indiquer la version de Spark utilisée;
- le dictionnaire "spec.driver.serviceAccount" doit contenir un compte de service dans l'espace de noms Kubernetes approprié qui sera utilisé pour lancer l'application;
- le dictionnaire "spec.executor" doit indiquer la quantité de ressources allouées à l'application;
- le dictionnaire "spec.volumeMounts" doit spécifier le répertoire local dans lequel les fichiers de tùches Spark locaux seront créés.
Un exemple de génération d'un manifeste (ici {spark-service-account} est un compte de service dans le cluster Kubernetes pour exécuter des tùches 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"
Ce manifeste spécifie un compte de service pour lequel, avant de publier le manifeste, vous devez créer les liaisons de rÎle nécessaires qui fournissent les droits d'accÚs nécessaires à l'application Spark pour interagir avec l'API Kubernetes (si nécessaire). Dans notre cas, l'application a besoin des droits pour créer des pods. Créons la liaison de rÎle requise:
oc adm policy add-role-to-user edit system:serviceaccount:{project}:{spark-service-account} -n {project}
Il est Ă©galement intĂ©ressant de noter que la spĂ©cification de ce manifeste peut spĂ©cifier le paramĂštre hadoopConfigMap, qui vous permet de spĂ©cifier un ConfigMap avec une configuration Hadoop sans avoir Ă placer d'abord le fichier correspondant dans l'image Docker. Il convient Ă©galement au lancement rĂ©gulier de tĂąches - en utilisant le paramĂštre «planning», un calendrier pour le lancement de cette tĂąche peut ĂȘtre spĂ©cifiĂ©.
AprĂšs cela, nous enregistrons notre manifeste dans le fichier spark-pi.yaml et l'appliquons Ă notre cluster Kubernetes:
oc apply -f spark-pi.yaml
Cela créera un objet de type "sparkapplications":
oc get sparkapplications -n {project}
> NAME AGE
> spark-pi 22h
Cela crĂ©era un pod avec une application, dont le statut sera affichĂ© dans les "sparkapplications" crĂ©Ă©es. Il peut ĂȘtre visualisĂ© avec la commande suivante:
oc get sparkapplications spark-pi -o yaml -n {project}
Une fois la tĂąche terminĂ©e, le POD passera Ă l'Ă©tat "TerminĂ©", qui est Ă©galement mis Ă jour en "sparkapplications". Les journaux d'application peuvent ĂȘtre consultĂ©s dans un navigateur ou Ă l'aide de la commande suivante (ici {sparkapplications-pod-name} est le nom du pod de la tĂąche en cours):
oc logs {sparkapplications-pod-name} -n {project}
Les tĂąches Spark peuvent Ă©galement ĂȘtre gĂ©rĂ©es Ă l'aide de l'utilitaire sparkctl spĂ©cialisĂ©. Pour l'installer, nous clonons le rĂ©fĂ©rentiel avec son code source, installons Go et construisons cet utilitaire:
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
Examinons la liste des tùches Spark en cours d'exécution:
sparkctl list -n {project}
Créons une description pour la tùche 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"
Exécutons la tùche décrite en utilisant sparkctl:
sparkctl create spark-app.yaml -n {project}
Examinons la liste des tùches Spark en cours d'exécution:
sparkctl list -n {project}
Examinons la liste des événements de la tùche Spark démarrée:
sparkctl event spark-pi -n {project} -f
Examinons l'état de la tùche Spark en cours d'exécution:
sparkctl status spark-pi -n {project}
En conclusion, j'aimerais examiner les inconvénients découverts liés à l'exploitation de la version stable actuelle de Spark (2.4.5) dans Kubernetes:
- , , â Data Locality. YARN , , ( ). Spark , , , . Kubernetes , . , , , , Spark . , Kubernetes (, Alluxio), Kubernetes.
- â . , Spark , Kerberos ( 3.0.0, ), Spark (https://spark.apache.org/docs/2.4.5/security.html) YARN, Mesos Standalone Cluster. , Spark, â , , . root, , UID, ( PodSecurityPolicies ). Docker, Spark , .
- L'exĂ©cution de tĂąches Spark avec Kubernetes est toujours officiellement en mode expĂ©rimental, et des changements importants peuvent ĂȘtre apportĂ©s aux artefacts utilisĂ©s (fichiers de configuration, images de base Docker et scripts de dĂ©marrage) Ă l'avenir. En effet, lors de la prĂ©paration du matĂ©riel, les versions 2.3.0 et 2.4.5 ont Ă©tĂ© testĂ©es, le comportement Ă©tait significativement diffĂ©rent.
Nous attendrons les mises Ă jour - une nouvelle version de Spark (3.0.0) a rĂ©cemment Ă©tĂ© publiĂ©e, qui a apportĂ© des changements tangibles au travail de Spark sur Kubernetes, mais a conservĂ© le statut expĂ©rimental de prise en charge de ce gestionnaire de ressources. Peut-ĂȘtre que les prochaines mises Ă jour permettront vraiment de recommander totalement d'abandonner YARN et d'exĂ©cuter des tĂąches Spark sur Kubernetes, sans craindre pour la sĂ©curitĂ© de votre systĂšme et sans avoir besoin d'affiner indĂ©pendamment les composants fonctionnels.
Ailette.