condaで手軽にpysparkをインストールする

# 1. 背景・対象

ローカル環境でcondaを使ってpysparkを動かすためのメモ。
他の一般的なPythonライブラリと同じような感じでpysparkをインストール・実行する。

想定する主な対象：

- 細かい設定は置いておいて、少ない手順でとにかく動く環境を作りたい
    - ネット記事や参考書のサンプルコードを動かしたり、小規模なテストデータを使ったコーディング・機能開発をとりあえずで出来れば良い
    - [公式サイト](https://spark.apache.org/downloads.html)や[ミラー](https://www.apache.org/dyn/closer.lua/spark/)からダウンロードしてきて`PATH`や`PYTHONPATH`を通して、更にJavaもインストールして、、、などといちいちやるのが面倒
        - **bashrcなどの類を書いたり管理したくない**
- SparkやJavaのバージョンを仮想環境毎に分けて管理したい
    - PC本体で使うJavaとSparkで使うJavaを区別したい
    - Spark2.4とSpark3.0を使い分けたい（あるいはプロジェクト毎に区別してSparkをインストールしたい）
    - でもDockerや仮想マシンなどは使いたくない

といった状況を考えています。

# 2. condaでSparkとJavaをインストール

対象とするconda仮想環境に入って、

- Apache Spark3.0を使う場合

```bash
conda install -c conda-forge pyspark=3.0 openjdk=8
```

- Apache Spark2.4を使う場合

```bash
# 注意：Python3.8は未対応なのでPython3.7.xなどの環境を使う
conda install -c conda-forge pyspark=2.4 openjdk=8
```

とすると、pysparkライブラリだけでなくApache Spark本体も仮想環境下にインストールされます。（ちなみにpandasや、pandasとSpark間のデータ連携を担うpyarrowなども一緒に入ります）

**ここまででとりあえず`pyspark`が使える状態になっているはずです。**

ちなみに、上の例のようにcondaでopenjdkを入れると、`conda activate`で仮想環境に入ったときに`JAVA_HOME`をcondaで入れたものに合わせて自動で良い感じに設定してくれます。（conda-forgeチャンネルから入れる場合、2020-08-14現在でバージョンは1.8.0_192 (Azul Systems, Inc.)になります。）

## 実行

`conda activate <仮想環境名>`してからCLI上で

- Spark3

```:shell（conda環境）
$ pyspark                                           
Python 3.8.5 (default, Aug  5 2020, 08:36:46) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
20/08/14 22:00:15 WARN Utils: Your hostname, <***> resolves to a loopback address: 127.0.1.1; using 192.168.3.17 instead (on interface wlp3s0)
20/08/14 22:00:15 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
20/08/14 22:00:15 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /__ / .__/\_,_/_/ /_/\_\   version 3.0.0
      /_/

Using Python version 3.8.5 (default, Aug  5 2020 08:36:46)
SparkSession available as 'spark'.
>>> 
```

- Spark2

```:shell（conda環境）
$ pyspark                      
Python 3.7.7 (default, May  7 2020, 21:25:33) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
20/08/14 22:16:09 WARN Utils: Your hostname, <***> resolves to a loopback address: 127.0.1.1; using 192.168.3.17 instead (on interface wlp3s0)
20/08/14 22:16:09 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
20/08/14 22:16:09 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /__ / .__/\_,_/_/ /_/\_\   version 2.4.6
      /_/

Using Python version 3.7.7 (default, May  7 2020 21:25:33)
SparkSession available as 'spark'.
>>> 
```

のようにしてそれぞれで`pyspark`が使えることを確認出来ます。

- condaで仮想環境を用意して`conda install`しただけなので、他の普通のPythonライブラリと同じ感覚でpysparkをインストール・実行出来ています。

### 補足（Java）

なお、Spark3からはJava11も対応しているのですが、簡単に試してみたところメモリ関係のエラーが出るとかで満足に動かせていないです。。。
[ここ](https://spark.apache.org/docs/3.0.0/#downloading)などを見てもJava11を使う場合は追加で設定が必要そうですし（上述のエラーとは違いそうな気もしますが）、タイトルの通りに「**手軽にとりあえず**」で動かす場合はSpark3でもJavaのバージョンは8が無難だと思います。（なお、Spark2系ではJava8でないと動かないです。）

### 補足（Windows）

そこそこの機能は上の通りで動きますが、`spark.sql`のデータベース・テーブル操作をするときなどにデフォルトでは権限周りのエラーが起きます。
[ここ](https://qiita.com/tomotagwork/items/1431f692387242f4a636#apache-spark%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)などを参考にして、

- hadoop2.7の`winutils.exe`をダウンロード（例えば[ここ](https://github.com/steveloughran/winutils/blob/master/hadoop-2.7.1/bin/winutils.exe)や、[ここ](https://github.com/cdarlint/winutils)のリポジトリから入手可能）
    - `PATH`を通しておく
- 上記のダウンロード場所を環境変数`HADOOP_HOME`に設定

を追加でやっておく必要があります。

# 3. 追加で設定が必要な場合

ここまでで簡易的に（**デフォルトの設定で**）pysparkを実行出来るようになっているはずですが、ときにはconfigの設定や調整をしなくてはならないときがあります。

本格的にカスタマイズをしていくとタイトルの「手軽に」の範囲を超えてしまいますが、最低限のところだけ補足します。
（一般的な環境変数の設定など、condaで入れた場合に特有でない共通した話は省きます。）

### `SPARK_HOME`の設定

環境変数`JAVA_HOME`（Sparkの実行に必要）は勝手にconda側で設定してくれると書きましたが、Apache Sparkを使う際によく設定する環境変数`SPARK_HOME`は実は設定されていません。（未設定でも割と動きますが、たまに困るときがある）

仮想環境上でのインストール場所を指定すれば良いのですが、若干場所が分かりづらいです。
やり方は色々あると思いますが、個人的に使う調べ方として、

1. condaでpysparkをインストールすると`scala`のSparkシェルである`spark-shell`も実行可能（PATHも通っているはず）なので、CLI上で`spark-shell`を実行する
2. `sc.getConf.get("spark.home")`と打ってEnterを押すと出てくる文字列を取得して環境変数`SPARK_HOME`に設定する

例えば、以下のような感じになります：

```:shell
$ spark-shell                
20/08/16 12:32:18 WARN Utils: Your hostname, <***> resolves to a loopback address: 127.0.1.1; using 192.168.3.17 instead (on interface wlp3s0)
20/08/16 12:32:18 WARN Utils: Set SPARK_LOCAL_IP if you need to bind to another address
20/08/16 12:32:19 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Spark context Web UI available at http://192.168.3.17:4040
Spark context available as 'sc' (master = local[*], app id = local-1597548749526).
Spark session available as 'spark'.
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 3.0.0
      /_/
         
Using Scala version 2.12.10 (OpenJDK 64-Bit Server VM, Java 1.8.0_192)
Type in expressions to have them evaluated.
Type :help for more information.

scala> sc.getConf.get("spark.home")
res0: String = <仮想環境のPATH>/lib/python3.8/site-packages/pyspark

# ↑ `String = `の部分にSparkのインストール場所の絶対パスが表示される
# `Ctrl-C`で抜けて、以下のように環境変数を設定

$ export SPARK_HOME=<仮想環境のPATH>/lib/python3.8/site-packages/pyspark
```

 または、

1. 同様に`spark-shell`を実行する
2. ローカルで動かす想定なので、1.の後で http://localhost:4040 にアクセスしてSpark UIを開く
    * （sshなどでリモートで動かしている場合はlocalhost部分を適宜置き換えるなどする ）
3. Environmentタブの`spark.home`にあるパスを控えて、環境変数`SPARK_HOME`に設定する

などとやります。（[ここ](https://stackoverflow.com/questions/33806450/how-to-find-sparks-installation-directory)を参考にした方法です。）
例えば`SPARK_HOME=/path/to/miniconda3-latest/envs/<仮想環境名>/lib/python3.7/site-packages/pyspark`みたいな感じになります。

要するにscalaのspark-shellではSparkSession内においてのみ自動で適切な`spark.home`を設定してくれているのですが、何故かpysparkではやってくれないのでspark-shellを使って調べる、といった感じです。

### 設定ファイルの場所

公式などからダウンロードしてきたSparkには`conf`ディレクトリが存在しますが、condaで自動インストールしたものには`conf`ディレクトリが存在しないようです。。。
が、自分で然るべき場所に`conf`ディレクトリを作って設定ファイルを置けば読み込んでくれるようです。（`spark-defaults.conf`で検証）

設定ファイルの置き場所は先程調べた`SPARK_HOME`のパスを使って、`$SPARK_HOME/conf/`下になります。
そこで、例えば

```
$SPARK_HOME/conf/spark-defaults.conf
```
を作成・記入していくことでconfigの設定が可能です。

※`SPARK_HOME`未設定でも読み込んでくれたので、上記読み込みに環境変数`SPARK_HOME`は必須でないようです。

他の設定ファイル（例えば`conf/spark-env.sh`など）は試していませんが、おそらく同じような感じで作成・記入すれば動くのではないかと思います。（違っていたらすみません。）


condaで入れた個別のパッケージに手を加えるのは可搬性が落ちて設定が汚くなる（タイトルの「手軽に」の要素が薄れてくる）ので、個人的にはそこまで好きなやり方では無いです。
（必要だったら出来る、という話です。）

ただ、それでも**仮想環境毎に設定を独立させて保持出来る**メリットは残ると思います。

### 3. （補足）

stackoverflowにある程度近いケースでの環境設定の話があったので、参考リンクとして記載

 - https://stackoverflow.com/questions/33814005/how-to-import-pyspark-in-anaconda
  

# まとめ

condaでpysparkを手軽にインストール・管理出来ることと、その気になれば設定ファイルのカスタマイズなども可能なところまで確認した。