pysparkをnotebookで書けるspark clusterをかんたんに構築できるdockerfile作った



# はじめに
学部3年、現在就活中の大木です。
現在インターン先でレコメンドシステムをsparkをもちいて構築しているのですが、
EMRでsparkを動かしてゴニョゴニョする前にローカルで色々試してみたく、
またどうせならspark clusterを構築してワチャワチャやりたいと思いそのためにdockerfileを作ったので共有します。

こちら
[2357gi/apache-spark-cluster: localでspark clusterを立ち上げて遊ぶ用　pyspark用のnotebookもあるヨ！](https://github.com/2357gi/apache-spark-cluster)

# 🐰使い方
cloneして

```shell
$ git clone https://github.com/2357gi/apache-spark-cluster
```

jupyter notebookを使いたい場合はDockerfile内の以下の環境変数を有効化して

`make run &&make notebook`で立ち上がります。

```Dockerfile:Dockerfile
	PYSPARK_DRIVER_PYTHON=/usr/local/bin/jupyter \
	PYSPARK_DRIVER_PYTHON_OPTS="notebook --no-browser --port 8888 --ip=0.0.0.0 --allow-root"
```
`\`で環境変数を複数行に渡り書き連ねているので、その辺を気をつけてください。


pythonfileをsubmitしたい場合は上記の環境変数に関する記述を消して`make run &&make kick-py <pythonfile>`でどうぞ。

UI用のポートも開放しているので
[Spark Master at spark://master:7077](http://localhost:8080/)
や
[Spark Jobs](http://localhost:4040/jobs/)(spark taskが実行されている瞬間のみ)
も使えるようになっています。



# 解説
## Dockerfile

```Dockerfile
FROM ubuntu:18.04
```

ベースとなるイメージはUbuntu18.04を使用しています。
採用理由は個人的に使い慣れているだけです。

python等のイメージを使用しても良かったのですが、理解しながら作業を進めたかったのであえてこちらを使用しました。

```Dockerfile
# Environment variables
ENV SPARK_VERSION=2.4.0 \
	HADOOP_VERSION=2.7 \
	SPARK_HOME=/spark \
	PATH=$SPARK_HOME/bin:$PATH \
	PYSPARK_PYTHON=/usr/bin/python3 \
	PYSPARK_DRIVER_PYTHON=/usr/local/bin/jupyter \
	PYSPARK_DRIVER_PYTHON_OPTS="notebook --no-browser --port 8888 --ip=0.0.0.0 --allow-root"
```
環境変数を設定します。
SPARK_VERSIONやHADOOP_VERSIONを変数として登録し、後のsparkをダウンロードする際のダウンロードUrlに変数を使用しています。
これによりバージョン変更に強くなります。

ミソとなるのが`PYSPARK_PYTHON`, `PYSPARK_DRIVER_PYTHON`, `PYSPARK_DRIVER_PYTHON_OPTS`です。
`PYSPARK_PYTHON`ここでpysparkのpythonのバージョン指定を行なっています。
`PYSPARK_DRIVER_PYTHON`, `PYSPARK_DRIVER_PYTHON_OPTS`こちらでpysparkがjupyter notebookで立ち上がるように設定しています。

**githubに上がっているDockerfileにはこの２種の変数の記述はないので、
もしnotebookを使用したいのであれば適宜追加してください。**


notebookの引数諸々はdocker内で起動したjupyter notebookをホスト側から接続するための諸々です。
この辺はすこしセキュアではないのですが、ローカルで遊ぶだけなので許容範囲としました。

```Dockerfile
# Install required apt package and ensure that the packages were installed
RUN apt-get update && apt-get install -y \
	bc \
	curl \
	default-jdk \
	git \
	python3 \
	python3-pip \
	scala
```
(説明略)

```Dockerfile
# Install spark
RUN mkdir spark &&\
	curl -sL \
	http://ftp.tsukuba.wide.ad.jp/software/apache/spark/spark-${SPARK_VERSION}/spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz \
	| tar zx -C spark --strip-components 1

```
sparkを引っ張ってきて展開し、/sparkに設置しています。
近さ的に筑波のミラーから引っ張ってきていますがココらへんはあんまりよくないかもしれません。

展開の方法ですがcurlで引っ張ってきたものをtarにパイプで渡すことによって`curl &&tar &&mv && rm `などといったナンセンスなコマンドの羅列にならないように工夫しましたがココらへんは悔しさが残ります。（[詳細](https://qiita.com/2357gi/items/098c28e0ab6608439f79)）

```Dockerfile
RUN pip3 install jupyter ipyparallel \
	&& ipcluster nbextension enable
```
jupyterをpipでinstallします。
pipでjupyterを導入するとpython3のkernelを読み込んでくれない？ここがすこしうまく行かなかったので
別途ipyparallelをpipでインストールしてnbextensiionをenableします。
これでjupyter notebook上でpython3がつかえるようになります。

## Docker-compose
みんな大好きdocker-compose

```docker-compose.yml
version: '3'

services:
  master:
    image: 2357gi/apache-spark
    hostname: master
    ports:
      - 4040:4040
      - 6066:6066
      - 8080:8080
      - 7077:7077
    environment:
        MASTER: spark://master:7077
        SPARK_CONF_DIR: /conf
        SPARK_PUBLIC_DNS: localhost
    volumes:
      - ./work:/work
      - ./data:/data
    command: /spark/bin/spark-class org.apache.spark.deploy.master.Master -h master

  worker1:
    image: 2357gi/apache-spark
    hostname: worker1
    depends_on:
     - master
    ports:
        - 8081:8081
    environment:
        SPARK_CONF_DIR: /conf
        SPARK_WORKER_CORES:
        SPARK_WORKER_MEMORY: 2g
        SPARK_WORKER_PORT: 8081
        SPARK_WORKER_WEBUI_PORT: 8081
        SPARK_PUBLIC_DNS: localhost
    volumes:
      - ./work:/work
      - ./data:/data
    command: /spark/bin/spark-class org.apache.spark.deploy.worker.Worker spark://master:7077

  worker2:
    image: 2357gi/apache-spark
    hostname: worker1
    depends_on:
     - master
    ports:
        - 8082:8082
    environment:
        SPARK_CONF_DIR: /conf
        SPARK_WORKER_CORES:
        SPARK_WORKER_MEMORY: 2g
        SPARK_WORKER_PORT: 8082
        SPARK_WORKER_WEBUI_PORT: 8082
        SPARK_PUBLIC_DNS: localhost
    volumes:
      - ./work:/work
      - ./data:/data
    command: /spark/bin/spark-class org.apache.spark.deploy.worker.Worker spark://master:7077
```

`$ docker-compose up --scale worker=n`でスケールするようにしたかったのですが、
ポートの変更と`SPARK_WORKER_PORT`の変数を連動させるのが難しかったのでこの形に落ち着きました。

誰か氏〜〜〜！w


## Makefile

```makefile

WORKER=1

build_docker: Dockerfile
	@echo "\n📦 build 2357gi/apache-spark"
	@docker build ./ -t 2357gi/apache-spark

build_compose:
	@echo "\n🏗  Pile up containers"
	@docker-compose up -d --scale worker=$(WORKER)

run: docker-compose.yml
	@echo "\n✨ apache-spark cluster setup is start!"
	@make build_docker
	@make build_compose
	@echo "\n🍾 set up is finished! \n you wanna access ⚡spaek shell, hit it! \n$$ make  s-shell"
	@echo "or you wanna start 📔 pyspark notebook, hit it! \n$$ make notebook"
	@echo "... if u wanna open 🐚 shell? do this. \n$$ make shell"

s-shell:
	@echo "\n⚡ start spark-shell...."
	@docker-compose exec master /spark/bin/spark-shell --master spark://localhost:7077

notebook:
	@echo "\n📔 start pyspark notebook...."
	@docker-compose exec master /spark/bin/pyspark
	@docker-compose exec

shell:
	@echo "\n🐚 start master shell...."
	@docker-compose exec master /bin/bash

```
WORKERを変数にすることによって`$ make run WORKER=<任意のworker node数`を指定できるようにしました。
あとは普通な感じです。


これは蛇足ですが、
makefileに絵文字を入れると可愛くていいですね。