Mac
Spark
kubernetes

Apache Spark on Kubernetes(Docker for Mac)を動かしてみる

Spark2.3から、KubernetesクラスタでのSparkの実行がネイティブにサポートされたようなので、手元の環境で動かしてみたのを記録。

参考にした情報

使用環境

前提条件

JDK8がインストールされていることを確認

$ java -version
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

環境変数JAVA_HOMEを設定

$ echo $JAVA_HOME
$

設定されてないので
~/.bash_profileを作成(無かったので)して以下を記載

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/
$ source ~/.bash_profile
$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/

その他用意しておくもの
- Docker Hubアカウント
- Gitクライアント

実施手順

特に説明無い場合、コマンドはMacのターミナルで実行している。

Sparkのリポジトリをclone
ブランチはbranch-2.3を指定する

$ git clone -b branch-2.3 https://github.com/apache/spark.git

Sparkをビルド
-P kubernetesは、初め付けずにビルドしたらkubernetes関連のパッケージが作られず、後続のspark-submitでエラーになったので追加した

$ cd ./spark  # 以降特に説明ない場合ここを作業ディレクトリとしている
$ build/mvn -DskipTests -P kubernetes clean package

SUCCESSになると、spark/assembly/target/scala-2.11/jars/の下に各種jarが作られている。

Sparkノードのdockerイメージを作成する
spark/bin/docker-image-tool.shというdockerコマンドのラッパーが用意されているので、それを実行してdockerイメージを作る
使い方を見るには引数無しで実行

$ ./bin/docker-image-tool.sh
Usage: ./bin/docker-image-tool.sh [options] [command]
Builds or pushes the built-in Spark Docker image.
...
...

sparkをソースからビルドしている場合、以下にあるDockerfileがイメージのビルドに使われるようだ

./resource-managers/kubernetes/docker/src/main/dockerfiles/spark/Dockerfile

今回は特にDockerfileの内容を変更せずにビルドした。

dockerイメージをビルド

$ bin/docker-image-tool.sh -r sample-user -t v2.3.0-r2 build

-rは自分のDocker Hubアカウント名, -tは任意のタグ名を指定する。

ビルドしたイメージをDocker Hubにpush

$ bin/docker-image-tool.sh -r sample-user -t v2.3.0-r2 push

docker imagesで作成したイメージを確認。「Docker Hubアカウント名/spark」という名称で作成されている。
Docker Hubにプッシュされたリポジトリを https://hub.docker.com/ で確認。パブリックリポジトリとして登録されている。

kubectlでKubernetesクラスター(今回はMacローカルで実行)に接続できることを確認

$ kubectl cluster-info
Kubernetes master is running at https://localhost:6443
KubeDNS is running at https://localhost:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

KubernetesでSparkを実行するサービスアカウントを作成

$ kubectl create serviceaccount spark
$ kubectl create clusterrolebinding spark-role --clusterrole=edit  --serviceaccount=default:spark --namespace=default

spark-submitでKubernetesクラスターにSparkを実行させる

$ bin/spark-submit  \
    --master k8s://https://localhost:6443  \
    --deploy-mode cluster  \
    --conf spark.executor.instances=3  \
    --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark  \
    --conf spark.kubernetes.container.image=sample-user/spark:v2.3.0-r2  \
    --class org.apache.spark.examples.SparkPi  \
    --name spark-pi  \
    local:///opt/spark/examples/target/scala-2.11/jars/spark-examples_2.11-2.3.2-SNAPSHOT.jar

主なオプション
- --master: Kubernetes masterのアドレスを指定。 k8s://で始める
- --conf spark.kubernetes.authenticate.driver.serviceAccountName: spark driverポッドに割当てるサービスアカウント。上の手順で作成したものを指定した
- --conf spark.kubernetes.container.image: sparkコンテナに使用するdockerイメージを指定。
- --class: Sparkアプリケーションクラス。今回はSpark公式サンプルのSparkPiを使用。

引数
最後のパラメタで、実行アプリケーションが含まれるjarファイルを指定。local://で始まるパスには、実行されるdockerイメージ(コンテナ?)内のファイルパスを記載する。
公式によると、現時点ではspark-submitを実行するクライアントのローカルにあるjarを指定するのは不可。

Note that using application dependencies from the submission client’s local file system is currently not yet supported.

spark-submit実行後の標準出力

2018-06-08 13:58:00 INFO  LoggingPodStatusWatcherImpl:54 - State changed, new state: 
pod name: spark-pi-242772353cf23541875248af103f9abc-driver
namespace: default
labels: spark-app-selector -> spark-6f40d1e7b8b44a3aa4251e31b751fdf9, spark-role -> driver
pod uid: 84b85fc5-6ad8-11e8-9016-025000000001
creation time: 2018-06-08T04:58:00Z
service account name: spark
volumes: spark-token-sgkxf
node name: N/A
start time: N/A
container images: N/A
phase: Pending
status: []
...
...

spark driverのポッドが起動されていることがわかる

正常終了すると最終的にターミナル上は以下のような出力で終わる

...
...
2018-06-08 13:58:37 INFO  LoggingPodStatusWatcherImpl:54 - Container final statuses:
Container name: spark-kubernetes-driver
Container image: sample-user/spark:v2.3.0-r2
Container state: Terminated
Exit code: 0
2018-06-08 13:58:37 INFO  Client:54 - Application spark-pi finished.
2018-06-08 13:58:37 INFO  ShutdownHookManager:54 - Shutdown hook called
2018-06-08 13:58:37 INFO  ShutdownHookManager:54 - Deleting directory /private/var/folders/3z/znrnkbgj6t3b6__5r_8kbp540000gp/T/spark-c72e7308-fcaa-4744-b5f9-1f69f35ff1c4

アプリの実行結果はspark driverポッドのログで確認

$ kubectl logs spark-pi-242772353cf23541875248af103f9abc-driver
...
...
2018-06-08 04:58:36 INFO  DAGScheduler:54 - Job 0 finished: reduce at SparkPi.scala:38, took 0.919081 s
Pi is roughly 3.1422957114785572
2018-06-08 04:58:36 INFO  AbstractConnector:318 - Stopped Spark@297ea53a{HTTP/1.1,[http/1.1]}{0.0.0.0:4040}
2018-06-08 04:58:36 INFO  SparkUI:54 - Stopped Spark web UI at http://spark-pi-242772353cf23541875248af103f9abc-driver-svc.default.svc:4040
2018-06-08 04:58:36 INFO  KubernetesClusterSchedulerBackend:54 - Shutting down all executors
...
...

ここまで確認できたのでとりあえず成功。

最後に、Docker Hubにpushしたイメージを削除しておく
ブラウザでDocker Hubにログインし、
https://hub.docker.com/r/<自分のアカウント>/spark/ のページから、"Settings"タブに移動し、"Delete Repository"-> "Delete"

トラブル等

参考にしたページでも触れられているのと同じエラーを踏んだ。

今回の検証中に、Spark ExecutorのPodが、Pendingのまま実行されないという現象が発生しました。

その時のdriverポッドのログ

2018-06-06 17:19:55 WARN  TaskSchedulerImpl:66 - Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources
2018-06-06 17:20:10 WARN  TaskSchedulerImpl:66 - Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources
2018-06-06 17:20:25 WARN  TaskSchedulerImpl:66 - Initial job has not accepted any resources; check your cluster UI to ensure that workers are registered and have sufficient resources

pod一覧を確認するとexecutorポッドのステータスがPendingのまま

$ kubectl get pods
NAME                                               READY     STATUS    RESTARTS   AGE
spark-pi-d36103b95b5639028b709b4a284d53a9-driver   1/1       Running   0          15m
spark-pi-d36103b95b5639028b709b4a284d53a9-exec-1   0/1       Pending   0          14m
spark-pi-d36103b95b5639028b709b4a284d53a9-exec-2   0/1       Pending   0          14m
spark-pi-d36103b95b5639028b709b4a284d53a9-exec-3   0/1       Pending   0          14m

Pendingになっているexecutorポッドのログ

$ kubectl describe pods spark-pi-d36103b95b5639028b709b4a284d53a9-exec-1 | tail -n 4
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  1m (x72 over 21m)  default-scheduler  0/1 nodes are available: 1 Insufficient cpu, 1 Insufficient memory.

CPUとメモリが不足しているのが原因とのこと。

このままpodを起動していても進まないので削除してやる

$ kubectl delete pod spark-pi-d36103b95b5639028b709b4a284d53a9-driver
pod "spark-pi-4c5ab219d15238828c0e6770ed21799b-driver" deleted

driverポッドをdeleteしたら、pendingだったexecutorポッドも消えたみたい。

Docker for Macの設定画面で、dockerが使えるCPU, メモリのリソースを増やして以下のようにした
image
"Apply & Restart"を押すとdocker, kubernetesともに再起動される

docker, kubernetesとも起動したことを確認後、再度spark-submitを実行したら無事executorポッドも起動し、正常終了した。

感想とか

当初Docker for Windows環境で動かそうとしたのだけどハマりにハマった挙句結局Podが正常に起動するまで至らず...。そしてUnix系OSのMacの方が動かしやすいんじゃないかと思い至ってやってみたのが本記事の内容。
実際Macで動作確認するのは割とスムーズにいけたけど、Windowsで動かそうと試行錯誤するのにだいぶ時間をロスしてしまっていたので、内部の仕組みの理解はまだこれから。
普段はWindowsをメインで使っているので、早くWindowsでも動かせるようにしたい。